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')))