lib/vagabond/internal_configuration.rb in vagabond-0.2.8 vs lib/vagabond/internal_configuration.rb in vagabond-0.2.10
- old
+ new
@@ -1,69 +1,123 @@
-require 'digest/sha2'
+#encoding: utf-8
require 'json'
+require 'digest/sha2'
+
+require 'chef'
+require 'chef/mixin/deep_merge'
+
require 'vagabond/helpers'
require 'vagabond/constants'
-require 'chef/mixin/deep_merge'
+require 'vagabond/version'
+require 'vagabond/notify_mash'
module Vagabond
class InternalConfiguration
+ DEFAULT_ERCHEF_VERSION = '11.0.8'
+
+ class << self
+ attr_accessor :host_provisioned
+
+ def host_provisioned?
+ !!@host_provisioned
+ end
+ end
+
include Helpers
attr_reader :config
attr_reader :ui
attr_reader :options
attr_accessor :force_bases
def initialize(vagabondfile, ui, options, args={})
@vagabondfile = vagabondfile
@checksums = Mash.new
- @ui = ui
+ if(ui)
+ @ui = ui
+ else
+ @ui = Logger.new('/dev/null')
+ @ui.instance_eval do
+ def color(*args)
+ end
+ end
+ end
@options = options
create_store
load_existing
- @config = Mash.new(
- :mappings => Mash.new,
- :template_mappings => Mash.new,
- :test_mappings => Mash.new,
- :spec_mappings => Mash.new,
- :spec_clusters => Mash.new
- ).merge(config)
+ @config = storage_hash(
+ Mash.new(
+ :mappings => {},
+ :template_mappings => {},
+ :test_mappings => {},
+ :spec_mappings => {},
+ :spec_clusters => {}
+ ).merge(config)
+ )
@force_bases = args[:force_bases] || []
ensure_state
make_knife_config_if_required
end
def ensure_state
+ unless(self.class.host_provisioned?)
+ install_cookbooks
+ end
check_bases_and_customs!
+ set_templates
+ store_checksums
+ write_dna_json
+ write_solo_rb
if(solo_needed?)
- store_checksums
- write_dna_json
- write_solo_rb
- run_solo if solo_needed?
+ run_solo
+ else
+ self.class.host_provisioned = true
end
end
+ def set_templates
+ unless(Vagabond.const_defined?(:BASE_TEMPLATES))
+ Vagabond.const_set(
+ :BASE_TEMPLATES, cookbook_attributes(:vagabond).bases.keys
+ )
+ end
+ end
+
def check_bases_and_customs!
if(File.exists?(dna_path))
dna = Mash.new(JSON.load(File.read(dna_path)))
%w(bases customs).each do |key|
if(dna[:vagabond][key])
dna[:vagabond][key].each do |n, opts|
options[:force_solo] = true unless Lxc.new(n).exists?
end
end
end
+ if(dna[:vagabond][:server])
+ srv_name = [
+ cookbook_attributes(:vagabond).server.prefix,
+ dna[:vagabond][:server][:erchefs].first.to_s.gsub('.', '_')
+ ].join
+ options[:force_solo] = true unless Lxc.new(srv_name).exists?
+ end
+ unless(Lxc.new(cookbook_attributes(:vagabond).server.zero_lxc_name).exists?)
+ options[:force_solo] = true
+ end
end
end
def [](k)
@config[k]
end
def []=(k,v)
+ if(v.is_a?(Hash))
+ v = storage_hash(v)
+ end
@config[k] = v
+ save
end
def create_store
FileUtils.mkdir_p(store_path)
end
@@ -81,13 +135,13 @@
else
config = Mash.new(
JSON.load(content)
)
end
- @config = Chef::Mixin::DeepMerge.merge(config, @config)
+ @config = storage_hash(Chef::Mixin::DeepMerge.merge(config, @config))
else
- @config = Mash.new
+ @config = storage_hash
end
end
def store_path
path = File.join(File.dirname(@vagabondfile.store_path), '.vagabond')
@@ -106,14 +160,14 @@
end
def write_dna_json
conf = Mash.new(:bases => Mash.new, :customs => Mash.new)
(Array(@vagabondfile[:nodes]).map(&:last).map{|i| i[:template]}.compact + Array(force_bases)).uniq.each do |t|
- conf[:bases][t] = Mash.new(:enabled => true) if BASE_TEMPLATES.include?(t.to_s)
+ conf[:bases][t] = Mash.new(:enabled => true) if Vagabond::BASE_TEMPLATES.include?(t.to_s)
end
Array(@vagabondfile[:templates]).each do |t_name, opts|
- if(BASE_TEMPLATES.include?(opts[:base].to_s))
+ if(Vagabond::BASE_TEMPLATES.include?(opts[:base].to_s))
conf[:bases][opts[:base]] = Mash.new(:enabled => true)
if(opts.has_key?(:memory) && !opts[:memory].is_a?(Hash))
opts[:memory][:ram] = opts[:memory].to_s
end
conf[:customs][generated_name(t_name)] = opts
@@ -122,10 +176,17 @@
ui.fatal "Invalid base template encountered: #{t_name}"
ui.info ui.color(" -> Valid base templates: #{BASE_TEMPLATES.sort.join(', ')}", :red)
raise VagabondError::InvalidBaseTemplate.new(t_name)
end
end
+ if(@vagabondfile.local_chef_server? && !@vagabondfile[:local_chef_server][:zero])
+ version = cookbook_attributes(:vagabond).server.erchefs.dup || []
+ version.push @vagabondfile[:local_chef_server][:version] || DEFAULT_ERCHEF_VERSION
+ conf[:server] = Mash.new
+ conf[:server][:erchefs] = [version].flatten.uniq
+ end
+ conf[:host_cookbook_store] = cookbook_path
File.open(dna_path, 'w') do |file|
file.write(
JSON.dump(
:vagabond => conf,
:run_list => %w(recipe[vagabond])
@@ -175,34 +236,92 @@
end
@cache_path
end
def cookbook_path
+ unless(@cookbook_path)
+ FileUtils.mkdir_p(@cookbook_path = File.join(store_path, 'cookbooks'))
+ end
+ @cookbook_path
+ end
+
+ def cheffile_path
File.expand_path(
File.join(
- File.dirname(__FILE__), 'cookbooks'
+ File.dirname(__FILE__),
+ running_development_vagabond? ? 'Cheffile.dev' : 'Cheffile'
)
)
end
+
+ def vendor_cheffile_path
+ File.expand_path(
+ File.join(File.dirname(cookbook_path), 'Cheffile')
+ )
+ end
- def run_solo
+ def cookbook_vendor_required?
+ need_vendor = !File.exists?(vendor_cheffile_path)
+ need_vendor ||= get_checksum(vendor_cheffile_path) != get_checksum(cheffile_path)
+ spec = Gem::Specification.find_by_name('vagabond', ::Vagabond::VERSION.version)
+ if(running_development_vagabond?)
+ if(self[:dev_mode] && self[:dev_mode][:vendor_cookbook_check])
+ elapsed_time = Time.now.to_i - self[:dev_mode][:vendor_cookbook_check].to_i
+ need_vendor = elapsed_time > (ENV['VAGABOND_DEV_VENDOR_EVERY'] || 3600).to_i
+ end
+ self[:dev_mode] ||= Mash.new
+ self[:dev_mode][:vendor_cookbook_check] = Time.now.to_i if need_vendor
+ end
+ unless(ENV['VAGABOND_FORCE_VENDOR'].to_s == 'false')
+ ENV['VAGABOND_FORCE_VENDOR'] || need_vendor
+ end
+ end
+
+ def running_development_vagabond?
+ spec = Gem::Specification.find_by_name('vagabond', ::Vagabond::VERSION.version)
+ ::Vagabond::VERSION.segments.last.odd?
+ end
+
+ def install_cookbooks
begin
- ui.info ui.color('Ensuring expected system state (creating required base containers)', :yellow)
- ui.info ui.color(' - This can take a while on first run or new templates...', :yellow)
- com = "#{options[:sudo]}chef-solo -j #{File.join(store_path, 'dna.json')} -c #{File.join(store_path, 'solo.rb')}"
- debug(com)
- cmd = Mixlib::ShellOut.new(com, :timeout => 12000, :live_stream => options[:debug])
- cmd.run_command
- cmd.error!
- ui.info ui.color(' -> COMPLETE!', :yellow)
+ if(cookbook_vendor_required?)
+ FileUtils.copy(cheffile_path, vendor_cheffile_path)
+ ui.info ui.color('Fetching required cookbooks...', :yellow)
+ cmd = build_command('librarian-chef update', :shellout => {:cwd => File.dirname(cookbook_path)})
+ cmd.run_command
+ cmd.error!
+ ui.info ui.color(' -> COMPLETE!', :yellow)
+ else
+ cmd = build_command('librarian-chef install', :shellout => {:cwd => File.dirname(cookbook_path)})
+ cmd.run_command
+ cmd.error!
+ end
rescue => e
ui.info e.to_s
- FileUtils.rm(solo_path)
ui.info ui.color(' -> FAILED!', :red, :bold)
- raise VagabondError::HostProvisionFailed.new(e)
+ raise VagabondError::LibrarianHostInstallFailed.new(e)
end
end
+
+ def run_solo
+ unless(self.class.host_provisioned?)
+ begin
+ ui.info ui.color('Ensuring expected system state (creating required base containers)', :yellow)
+ ui.info ui.color(' - This can take a while on first run or new templates...', :yellow)
+ cmd = build_command("chef-solo -j #{File.join(store_path, 'dna.json')} -c #{File.join(store_path, 'solo.rb')}", :sudo => true)
+ cmd.run_command
+ cmd.error!
+ ui.info ui.color(' -> COMPLETE!', :yellow)
+ self.class.host_provisioned = true
+ rescue => e
+ ui.info e.to_s
+ FileUtils.rm(solo_path)
+ ui.info ui.color(' -> FAILED!', :red, :bold)
+ raise VagabondError::HostProvisionFailed.new(e)
+ end
+ end
+ end
def config_path
File.join(store_path, 'vagabond.json')
end
@@ -236,13 +355,48 @@
end
def file_changed?(path)
checksum = get_checksum(path)
checksum unless @checksums[path] == checksum
- end
+ end
+
+ def storage_hash(hsh={})
+ new_hash = {}
+ hsh.each_pair do |k,v|
+ if(v.is_a?(Hash))
+ new_hash[k] = storage_hash(v)
+ else
+ new_hash[k] = v
+ end
+ end
+ n = NotifyMash.new(new_hash)
+ n.add_notification do
+ save
+ end
+ n
+ end
+ def cookbook_attributes(cookbook, namespace=true)
+ @_attr_cache ||= Mash.new
+ unless(@_attr_cache[cookbook])
+ node = Chef::Node.new
+ %w(rb json js).each do |ext|
+ Dir.glob(File.join(cookbook_path, cookbook.to_s, 'attributes', "*.#{ext}")).each do |attr_file|
+ node.from_file(attr_file)
+ end
+ end
+ if(namespace)
+ key = namespace.is_a?(String) || namespace.is_a?(Symbol) ? namespace : cookbook
+ @_attr_cache[cookbook] = node.attributes.send(key)
+ else
+ @_attr_cache[cookbook] = node.attributes
+ end
+ end
+ @_attr_cache[cookbook]
+ end
+
def make_knife_config_if_required(force=false)
- if((@vagabondfile[:local_chef_server] && @vagabondfile[:local_chef_server][:enabled]) || force)
+ if(@vagabondfile.local_chef_server? || force)
unless(knife_config_available?)
store_dir = File.dirname(store_path)
k_dir = File.join(store_dir, '.chef')
FileUtils.mkdir_p(k_dir)
unless(File.exists?(knife = File.join(k_dir, 'knife.rb')))