From e66c5066e4b25f55fc57fcf2b03ed4324a4036d7 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 12:38:17 -0800 Subject: [PATCH 01/11] Plugin configuration can have scopes now, ex. provider --- lib/vagrant/plugin/v2/components.rb | 11 +++++++-- lib/vagrant/plugin/v2/manager.rb | 4 ++-- lib/vagrant/plugin/v2/plugin.rb | 23 ++++--------------- test/unit/vagrant/environment_test.rb | 2 +- .../unit/vagrant/plugin/v2/components_test.rb | 12 ++++++++-- test/unit/vagrant/plugin/v2/manager_test.rb | 4 ++-- test/unit/vagrant/plugin/v2/plugin_test.rb | 8 +++---- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/lib/vagrant/plugin/v2/components.rb b/lib/vagrant/plugin/v2/components.rb index b982b8c0a..2301656da 100644 --- a/lib/vagrant/plugin/v2/components.rb +++ b/lib/vagrant/plugin/v2/components.rb @@ -2,11 +2,18 @@ module Vagrant module Plugin module V2 # This is the container class for the components of a single plugin. + # This allows us to separate the plugin class which defines the + # components, and the actual container of those components. This + # removes a bit of state overhead from the plugin class itself. class Components - attr_reader :provider_configs + # This contains all the configuration plugins by scope. + # + # @return [Hash] + attr_reader :configs def initialize - @provider_configs = Registry.new + # Create the configs hash which defaults to a registry + @configs = Hash.new { |h, k| h[k] = Registry.new } end end end diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index 18ec2e013..8fdfa6097 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -47,7 +47,7 @@ module Vagrant result = {} @registered.each do |plugin| - plugin.config.each do |key, klass| + plugin.components.configs[:top].each do |key, klass| result[key] = klass end end @@ -101,7 +101,7 @@ module Vagrant configs = {} @registered.each do |plugin| - configs.merge!(plugin.components.provider_configs.to_hash) + configs.merge!(plugin.components.configs[:provider].to_hash) end configs diff --git a/lib/vagrant/plugin/v2/plugin.rb b/lib/vagrant/plugin/v2/plugin.rb index 28a27a026..611835626 100644 --- a/lib/vagrant/plugin/v2/plugin.rb +++ b/lib/vagrant/plugin/v2/plugin.rb @@ -128,25 +128,10 @@ module Vagrant # # @param [String] name Configuration key. # XXX: Document options hash - def self.config(name=UNSET_VALUE, options=nil, &block) - data[:config] ||= Registry.new - - # Register a new config class only if a name was given. - if name != UNSET_VALUE - options ||= {} - - if options[:provider] - # This config is for a specific provider. Register it as - # a provider config component. - components.provider_configs.register(name.to_sym, &block) - else - # This is a generic configuration plugin, register it as such. - data[:config].register(name.to_sym, &block) - end - end - - # Return the registry - data[:config] + def self.config(name, scope=nil, &block) + scope ||= :top + components.configs[scope].register(name.to_sym, &block) + nil end # Defines an "easy hook," which gives an easier interface to hook diff --git a/test/unit/vagrant/environment_test.rb b/test/unit/vagrant/environment_test.rb index de6a66623..7361c9039 100644 --- a/test/unit/vagrant/environment_test.rb +++ b/test/unit/vagrant/environment_test.rb @@ -297,7 +297,7 @@ VF p.provider(name) { provider_cls } if config_class - p.config(name, :provider => name) { config_class } + p.config(name, :provider) { config_class } end end diff --git a/test/unit/vagrant/plugin/v2/components_test.rb b/test/unit/vagrant/plugin/v2/components_test.rb index 30c2df403..bf98ca71b 100644 --- a/test/unit/vagrant/plugin/v2/components_test.rb +++ b/test/unit/vagrant/plugin/v2/components_test.rb @@ -1,9 +1,17 @@ require File.expand_path("../../../../base", __FILE__) +require "vagrant/registry" + describe Vagrant::Plugin::V2::Components do let(:instance) { described_class.new } - it "should have provider configs" do - instance.provider_configs.should be_kind_of(Vagrant::Registry) + describe "configs" do + it "should have configs" do + instance.configs.should be_kind_of(Hash) + end + + it "should default the values to registries" do + instance.configs[:i_probably_dont_exist].should be_kind_of(Vagrant::Registry) + end end end diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb index 46d044ece..c2d3f811b 100644 --- a/test/unit/vagrant/plugin/v2/manager_test.rb +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -98,11 +98,11 @@ describe Vagrant::Plugin::V2::Manager do it "provides the collection of registered provider configs" do pA = plugin do |p| - p.config("foo", :provider => true) { "foo" } + p.config("foo", :provider) { "foo" } end pB = plugin do |p| - p.config("bar", :provider => true) { "bar" } + p.config("bar", :provider) { "bar" } p.config("baz") { "baz" } end diff --git a/test/unit/vagrant/plugin/v2/plugin_test.rb b/test/unit/vagrant/plugin/v2/plugin_test.rb index d6acb0128..6248f0c9c 100644 --- a/test/unit/vagrant/plugin/v2/plugin_test.rb +++ b/test/unit/vagrant/plugin/v2/plugin_test.rb @@ -106,7 +106,7 @@ describe Vagrant::Plugin::V2::Plugin do config("foo") { "bar" } end - plugin.config[:foo].should == "bar" + plugin.components.configs[:top][:foo].should == "bar" end it "should lazily register configuration classes" do @@ -123,16 +123,16 @@ describe Vagrant::Plugin::V2::Plugin do # Now verify when we actually get the configuration key that # a proper error is raised. expect { - plugin.config[:foo] + plugin.components.configs[:top][:foo] }.to raise_error(StandardError) end it "should register configuration classes for providers" do plugin = Class.new(described_class) do - config("foo", :provider => true) { "bar" } + config("foo", :provider) { "bar" } end - plugin.components.provider_configs[:foo].should == "bar" + plugin.components.configs[:provider][:foo].should == "bar" end end From f1f4f276a08e09e737f89581be6cbde07ab5c3b2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 12:38:51 -0800 Subject: [PATCH 02/11] Set the virtualbox config scope --- plugins/providers/virtualbox/plugin.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 5d8bcd6f1..d313ab4ba 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -14,7 +14,7 @@ module VagrantPlugins Provider end - config(:virtualbox, :provider => :virtualbox) do + config(:virtualbox, :provider) do require File.expand_path("../config", __FILE__) Config end From 45879132a3f5ddafcc22509b2580bb00c179dcea Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 12:58:48 -0800 Subject: [PATCH 03/11] Registries support merging --- lib/vagrant/registry.rb | 23 ++++++++++++++++ test/unit/vagrant/registry_test.rb | 43 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/lib/vagrant/registry.rb b/lib/vagrant/registry.rb index 7fe02d096..df8147c9e 100644 --- a/lib/vagrant/registry.rb +++ b/lib/vagrant/registry.rb @@ -42,6 +42,22 @@ module Vagrant end end + # Merge one registry with another and return a completely new + # registry. Note that the result cache is completely busted, so + # any gets on the new registry will result in a cache miss. + def merge(other) + self.class.new.tap do |result| + result.merge!(self) + result.merge!(other) + end + end + + # Like #{merge} but merges into self. + def merge!(other) + @items.merge!(other.__internal_state[:items]) + self + end + # Converts this registry to a hash def to_hash result = {} @@ -51,5 +67,12 @@ module Vagrant result end + + def __internal_state + { + :items => @items, + :results_cache => @results_cache + } + end end end diff --git a/test/unit/vagrant/registry_test.rb b/test/unit/vagrant/registry_test.rb index 1c82117f6..2c1c00128 100644 --- a/test/unit/vagrant/registry_test.rb +++ b/test/unit/vagrant/registry_test.rb @@ -82,4 +82,47 @@ describe Vagrant::Registry do result["foo"].should == "foovalue" result["bar"].should == "barvalue" end + + describe "merging" do + it "should merge in another registry" do + one = described_class.new + two = described_class.new + + one.register("foo") { raise "BOOM!" } + two.register("bar") { raise "BAM!" } + + three = one.merge(two) + expect { three["foo"] }.to raise_error("BOOM!") + expect { three["bar"] }.to raise_error("BAM!") + end + + it "should NOT merge in the cache" do + one = described_class.new + two = described_class.new + + one.register("foo") { [] } + one["foo"] << 1 + + two.register("bar") { [] } + two["bar"] << 2 + + three = one.merge(two) + three["foo"].should == [] + three["bar"].should == [] + end + end + + describe "merge!" do + it "should merge into self" do + one = described_class.new + two = described_class.new + + one.register("foo") { "foo" } + two.register("bar") { "bar" } + + one.merge!(two) + one["foo"].should == "foo" + one["bar"].should == "bar" + end + end end From f3b340aae03fe69394eca78d588503f60752abff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 13:00:06 -0800 Subject: [PATCH 04/11] Use registry merging for provider configs --- lib/vagrant/plugin/v2/manager.rb | 4 ++-- test/unit/vagrant/plugin/v2/manager_test.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index 8fdfa6097..a073561fc 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -98,10 +98,10 @@ module Vagrant # # @return [Hash] def provider_configs - configs = {} + configs = Registry.new @registered.each do |plugin| - configs.merge!(plugin.components.configs[:provider].to_hash) + configs.merge!(plugin.components.configs[:provider]) end configs diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb index c2d3f811b..4fe3a0c76 100644 --- a/test/unit/vagrant/plugin/v2/manager_test.rb +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -109,7 +109,7 @@ describe Vagrant::Plugin::V2::Manager do instance.register(pA) instance.register(pB) - instance.provider_configs.length.should == 2 + instance.provider_configs.to_hash.length.should == 2 instance.provider_configs[:foo].should == "foo" instance.provider_configs[:bar].should == "bar" end From 25fcb59e38bd150bdaa7b562b354b589f9a7f445 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 13:05:31 -0800 Subject: [PATCH 05/11] Use registries for the V2 plugin manager --- lib/vagrant/environment.rb | 2 +- lib/vagrant/plugin/v2/manager.rb | 80 ++++++++------------- test/unit/vagrant/plugin/v2/manager_test.rb | 10 +-- 3 files changed, 37 insertions(+), 55 deletions(-) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index f6680b6d3..9f9276795 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -329,7 +329,7 @@ module Vagrant # will return nil, and we don't want to trigger a detect load. host_klass = config_global.vagrant.host if host_klass.nil? || host_klass == :detect - hosts = Vagrant.plugin("2").manager.hosts + hosts = Vagrant.plugin("2").manager.hosts.to_hash # Get the flattened list of available hosts host_klass = Hosts.detect(hosts) diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index a073561fc..0490d533b 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -18,106 +18,88 @@ module Vagrant # # @return [Hash] def commands - result = {} - - @registered.each do |plugin| - result.merge!(plugin.command.to_hash) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.command) + end end - - result end # This returns all the registered communicators. # # @return [Hash] def communicators - result = {} - - @registered.each do |plugin| - result.merge!(plugin.communicator.to_hash) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.communicator) + end end - - result end # This returns all the registered configuration classes. # # @return [Hash] def config - result = {} - - @registered.each do |plugin| - plugin.components.configs[:top].each do |key, klass| - result[key] = klass + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.components.configs[:top]) end end - - result end # This returns all the registered guests. # # @return [Hash] def guests - result = {} - - @registered.each do |plugin| - result.merge!(plugin.guest.to_hash) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.guest) + end end - - result end # This returns all registered host classes. # # @return [Hash] def hosts - hosts = {} - - @registered.each do |plugin| - hosts.merge!(plugin.host.to_hash) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.host) + end end - - hosts end # This returns all registered providers. # # @return [Hash] def providers - providers = {} - - @registered.each do |plugin| - providers.merge!(plugin.provider.to_hash) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.provider) + end end - - providers end # This returns all the config classes for the various providers. # # @return [Hash] def provider_configs - configs = Registry.new - - @registered.each do |plugin| - configs.merge!(plugin.components.configs[:provider]) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.components.configs[:provider]) + end end - - configs end # This returns all registered provisioners. # # @return [Hash] def provisioners - results = {} - - @registered.each do |plugin| - results.merge!(plugin.provisioner.to_hash) + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.provisioner) + end end - - results end # This registers a plugin. This should _NEVER_ be called by the public diff --git a/test/unit/vagrant/plugin/v2/manager_test.rb b/test/unit/vagrant/plugin/v2/manager_test.rb index 4fe3a0c76..85431c337 100644 --- a/test/unit/vagrant/plugin/v2/manager_test.rb +++ b/test/unit/vagrant/plugin/v2/manager_test.rb @@ -23,7 +23,7 @@ describe Vagrant::Plugin::V2::Manager do instance.register(pA) instance.register(pB) - instance.communicators.length.should == 2 + instance.communicators.to_hash.length.should == 2 instance.communicators[:foo].should == "bar" instance.communicators[:bar].should == "baz" end @@ -40,7 +40,7 @@ describe Vagrant::Plugin::V2::Manager do instance.register(pA) instance.register(pB) - instance.config.length.should == 2 + instance.config.to_hash.length.should == 2 instance.config[:foo].should == "bar" instance.config[:bar].should == "baz" end @@ -57,7 +57,7 @@ describe Vagrant::Plugin::V2::Manager do instance.register(pA) instance.register(pB) - instance.guests.length.should == 2 + instance.guests.to_hash.length.should == 2 instance.guests[:foo].should == "bar" instance.guests[:bar].should == "baz" end @@ -74,7 +74,7 @@ describe Vagrant::Plugin::V2::Manager do instance.register(pA) instance.register(pB) - instance.hosts.length.should == 2 + instance.hosts.to_hash.length.should == 2 instance.hosts[:foo].should == "bar" instance.hosts[:bar].should == "baz" end @@ -91,7 +91,7 @@ describe Vagrant::Plugin::V2::Manager do instance.register(pA) instance.register(pB) - instance.providers.length.should == 2 + instance.providers.to_hash.length.should == 2 instance.providers[:foo].should == "bar" instance.providers[:bar].should == "baz" end From c8053c00a477ed9f4d3ccb8129954f8dada07049 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 15:48:52 -0800 Subject: [PATCH 06/11] New provisioner API. Shell provisioner adheres to it. --- lib/vagrant/plugin/v2/provisioner.rb | 57 ++++++++-------- plugins/provisioners/shell/config.rb | 42 ++++++++++++ plugins/provisioners/shell/plugin.rb | 7 +- plugins/provisioners/shell/provisioner.rb | 80 ++++++----------------- 4 files changed, 95 insertions(+), 91 deletions(-) create mode 100644 plugins/provisioners/shell/config.rb diff --git a/lib/vagrant/plugin/v2/provisioner.rb b/lib/vagrant/plugin/v2/provisioner.rb index af4975ce9..131c78880 100644 --- a/lib/vagrant/plugin/v2/provisioner.rb +++ b/lib/vagrant/plugin/v2/provisioner.rb @@ -4,45 +4,42 @@ module Vagrant # This is the base class for a provisioner for the V2 API. A provisioner # is primarily responsible for installing software on a Vagrant guest. class Provisioner - # The environment which provisioner is running in. This is the - # action environment, not a Vagrant::Environment. - attr_reader :env - - # The configuration for this provisioner. This will be an instance of - # the `Config` class which is part of the provisioner. + attr_reader :machine attr_reader :config - def initialize(env, config) - @env = env - @config = config - end - - # This method is expected to return a class that is used for - # configuring the provisioner. This return value is expected to be - # a subclass of {Config}. + # Initializes the provisioner with the machine that it will be + # provisioning along with the provisioner configuration (if there + # is any). # - # @return [Config] - def self.config_class + # The provisioner should _not_ do anything at this point except + # initialize internal state. + # + # @param [Machine] machine The machine that this will be provisioning. + # @param [Object] config Provisioner configuration, if one was set. + def initialize(machine, config) + @machine = machine + @config = config end - # This is the method called to "prepare" the provisioner. This is called - # before any actions are run by the action runner (see {Vagrant::Actions::Runner}). - # This can be used to setup shared folders, forward ports, etc. Whatever is - # necessary on a "meta" level. + # Called with the root configuration of the machine so the provisioner + # can add some configuration on top of the machine. + # + # During this step, and this step only, the provisioner should modify + # the root machine configuration to add any additional features it + # may need. Examples include sharing folders, networking, and so on. + # This step is guaranteed to be called before any of those steps are + # done so the provisioner may do that. # # No return value is expected. - def prepare + def configure(root_config) end - # This is the method called to provision the system. This method - # is expected to do whatever necessary to provision the system (create files, - # SSH, etc.) - def provision! - end - - # This is the method called to when the system is being destroyed - # and allows the provisioners to engage in any cleanup tasks necessary. - def cleanup + # This is the method called when the actual provisioning should be + # done. The communicator is guaranteed to be ready at this point, + # and any shared folders or networks are already setup. + # + # No return value is expected. + def provision end end end diff --git a/plugins/provisioners/shell/config.rb b/plugins/provisioners/shell/config.rb new file mode 100644 index 000000000..ef5d10a3e --- /dev/null +++ b/plugins/provisioners/shell/config.rb @@ -0,0 +1,42 @@ +module VagrantPlugins + module Shell + class Config < Vagrant.plugin("2", :config) + attr_accessor :inline + attr_accessor :path + attr_accessor :upload_path + attr_accessor :args + + def initialize + @upload_path = "/tmp/vagrant-shell" + end + + def validate(env, errors) + # Validate that the parameters are properly set + if path && inline + errors.add(I18n.t("vagrant.provisioners.shell.path_and_inline_set")) + elsif !path && !inline + errors.add(I18n.t("vagrant.provisioners.shell.no_path_or_inline")) + end + + # Validate the existence of a script to upload + if path + expanded_path = Pathname.new(path).expand_path(env.root_path) + if !expanded_path.file? + errors.add(I18n.t("vagrant.provisioners.shell.path_invalid", + :path => expanded_path)) + end + end + + # There needs to be a path to upload the script to + if !upload_path + errors.add(I18n.t("vagrant.provisioners.shell.upload_path_not_set")) + end + + # If there are args and its not a string, that is a problem + if args && !args.is_a?(String) + errors.add(I18n.t("vagrant.provisioners.shell.args_not_string")) + end + end + end + end +end diff --git a/plugins/provisioners/shell/plugin.rb b/plugins/provisioners/shell/plugin.rb index b88ec5630..2da0e5320 100644 --- a/plugins/provisioners/shell/plugin.rb +++ b/plugins/provisioners/shell/plugin.rb @@ -9,7 +9,12 @@ module VagrantPlugins shell scripts. DESC - provisioner("shell") do + config(:shell, :provisioner) do + require File.expand_path("../config", __FILE__) + Config + end + + provisioner(:shell) do require File.expand_path("../provisioner", __FILE__) Provisioner end diff --git a/plugins/provisioners/shell/provisioner.rb b/plugins/provisioners/shell/provisioner.rb index c29ac1da6..a1ef9aa04 100644 --- a/plugins/provisioners/shell/provisioner.rb +++ b/plugins/provisioners/shell/provisioner.rb @@ -4,48 +4,32 @@ require "tempfile" module VagrantPlugins module Shell class Provisioner < Vagrant.plugin("2", :provisioner) - class Config < Vagrant.plugin("2", :config) - attr_accessor :inline - attr_accessor :path - attr_accessor :upload_path - attr_accessor :args + def provision + args = "" + args = " #{config.args}" if config.args + command = "chmod +x #{config.upload_path} && #{config.upload_path}#{args}" - def initialize - @upload_path = "/tmp/vagrant-shell" - end + with_script_file do |path| + # Upload the script to the machine + @machine.communicate.tap do |comm| + comm.upload(path.to_s, config.upload_path) - def validate(env, errors) - # Validate that the parameters are properly set - if path && inline - errors.add(I18n.t("vagrant.provisioners.shell.path_and_inline_set")) - elsif !path && !inline - errors.add(I18n.t("vagrant.provisioners.shell.no_path_or_inline")) - end + # Execute it with sudo + comm.sudo(command) do |type, data| + if [:stderr, :stdout].include?(type) + # Output the data with the proper color based on the stream. + color = type == :stdout ? :green : :red - # Validate the existence of a script to upload - if path - expanded_path = Pathname.new(path).expand_path(env.root_path) - if !expanded_path.file? - errors.add(I18n.t("vagrant.provisioners.shell.path_invalid", - :path => expanded_path)) + # Note: Be sure to chomp the data to avoid the newlines that the + # Chef outputs. + @machine.env.ui.info(data.chomp, :color => color, :prefix => false) + end end end - - # There needs to be a path to upload the script to - if !upload_path - errors.add(I18n.t("vagrant.provisioners.shell.upload_path_not_set")) - end - - # If there are args and its not a string, that is a problem - if args && !args.is_a?(String) - errors.add(I18n.t("vagrant.provisioners.shell.args_not_string")) - end end end - def self.config_class - Config - end + protected # This method yields the path to a script to upload and execute # on the remote server. This method will properly clean up the @@ -53,7 +37,8 @@ module VagrantPlugins def with_script_file if config.path # Just yield the path to that file... - yield Pathname.new(config.path).expand_path(env[:root_path]) + root_path = @machine.env.root_path + yield Pathname.new(config.path).expand_path(root_path) return end @@ -75,31 +60,6 @@ module VagrantPlugins file.unlink end end - - def provision! - args = "" - args = " #{config.args}" if config.args - command = "chmod +x #{config.upload_path} && #{config.upload_path}#{args}" - - with_script_file do |path| - # Upload the script to the machine - env[:machine].communicate.tap do |comm| - comm.upload(path.to_s, config.upload_path) - - # Execute it with sudo - comm.sudo(command) do |type, data| - if [:stderr, :stdout].include?(type) - # Output the data with the proper color based on the stream. - color = type == :stdout ? :green : :red - - # Note: Be sure to chomp the data to avoid the newlines that the - # Chef outputs. - env[:ui].info(data.chomp, :color => color, :prefix => false) - end - end - end - end - end end end end From cf2cca3b7cf95d625a133c397ab96d38399e72dc Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 16:02:48 -0800 Subject: [PATCH 07/11] Revamp the configuration internal state for defining provisioners --- lib/vagrant/plugin/v2/manager.rb | 11 ++++ plugins/kernel_v2/config/vm.rb | 5 -- plugins/kernel_v2/config/vm_provisioner.rb | 59 ++++++++-------------- 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/lib/vagrant/plugin/v2/manager.rb b/lib/vagrant/plugin/v2/manager.rb index 0490d533b..2a743c59d 100644 --- a/lib/vagrant/plugin/v2/manager.rb +++ b/lib/vagrant/plugin/v2/manager.rb @@ -91,6 +91,17 @@ module Vagrant end end + # This returns all the config classes for the various provisioners. + # + # @return [Registry] + def provisioner_configs + Registry.new.tap do |result| + @registered.each do |plugin| + result.merge!(plugin.components.configs[:provisioner]) + end + end + end + # This returns all registered provisioners. # # @return [Hash] diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 24d356332..803a892d5 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -142,11 +142,6 @@ module VagrantPlugins :name => name)) end end - - # Each provisioner can validate itself - provisioners.each do |prov| - prov.validate(env, errors) - end end end end diff --git a/plugins/kernel_v2/config/vm_provisioner.rb b/plugins/kernel_v2/config/vm_provisioner.rb index 2ef202754..ede80bc43 100644 --- a/plugins/kernel_v2/config/vm_provisioner.rb +++ b/plugins/kernel_v2/config/vm_provisioner.rb @@ -4,51 +4,36 @@ module VagrantPlugins module Kernel_V2 # Represents a single configured provisioner for a VM. class VagrantConfigProvisioner - attr_reader :shortcut - attr_reader :provisioner + # The name of the provisioner that should be registered + # as a plugin. + # + # @return [Symbol] + attr_reader :name + + # The configuration associated with the provisioner, if there is any. + # + # @return [Object] attr_reader :config - def initialize(shortcut, options=nil, &block) + def initialize(name, options=nil, &block) @logger = Log4r::Logger.new("vagrant::config::vm::provisioner") - @logger.debug("Provisioner config: #{shortcut}") - @shortcut = shortcut - @provisioner = shortcut + @logger.debug("Provisioner defined: #{name}") + @config = nil + @name = name - # If the shorcut is a symbol, we look through the registered - # plugins to see if any define a provisioner we want. - if shortcut.is_a?(Symbol) - @provisioner = Vagrant.plugin("2").manager.provisioners[shortcut] - end - - @logger.info("Provisioner class: #{provisioner}") - configure(options, &block) if @provisioner - end - - # Configures the provisioner if it can (if it is valid). - def configure(options=nil, &block) - config_class = @provisioner.config_class - return if !config_class - - @logger.debug("Configuring provisioner with: #{config_class}") - @config = config_class.new - @config.set_options(options) if options - block.call(@config) if block - end - - def validate(env, errors) - if !provisioner - # If we don't have a provisioner then the whole thing is invalid. - errors.add(I18n.t("vagrant.config.vm.provisioner_not_found", :shortcut => shortcut)) + # Attempt to find the configuration class for this provider + # if it exists and load the configuration. + config_class = Vagrant.plugin("2").manager.provisioner_configs[@name] + if !config_class + @logger.info("Provisioner config for '#{@name}' not found. Ignoring config.") return end - if !(provisioner <= Vagrant.plugin("2", :provisioner)) - errors.add(I18n.t("vagrant.config.vm.provisioner_invalid_class", :shortcut => shortcut)) - end - - # Pass on validation to the provisioner config - config.validate(env, errors) if config + @config = config_class.new + @config.set_options(options) if options + block.call(@config) if block + @config.finalize! end end end From 84c45a854c37dc7e760b4bc71f5ca44f8d1fd586 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 16:03:34 -0800 Subject: [PATCH 08/11] Remove the ProvisionerCLeanup action from VirtualBox --- plugins/providers/virtualbox/action.rb | 2 -- .../virtualbox/action/provisioner_cleanup.rb | 25 ------------------- 2 files changed, 27 deletions(-) delete mode 100644 plugins/providers/virtualbox/action/provisioner_cleanup.rb diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 06595942a..85c31a450 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -37,7 +37,6 @@ module VagrantPlugins autoload :NFS, File.expand_path("../action/nfs", __FILE__) autoload :Package, File.expand_path("../action/package", __FILE__) autoload :PackageVagrantfile, File.expand_path("../action/package_vagrantfile", __FILE__) - autoload :ProvisionerCleanup, File.expand_path("../action/provisioner_cleanup", __FILE__) autoload :PruneNFSExports, File.expand_path("../action/prune_nfs_exports", __FILE__) autoload :Resume, File.expand_path("../action/resume", __FILE__) autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__) @@ -90,7 +89,6 @@ module VagrantPlugins b3.use CheckAccessible b3.use EnvSet, :force => true b3.use action_halt - b3.use ProvisionerCleanup b3.use PruneNFSExports b3.use Destroy b3.use CleanMachineFolder diff --git a/plugins/providers/virtualbox/action/provisioner_cleanup.rb b/plugins/providers/virtualbox/action/provisioner_cleanup.rb deleted file mode 100644 index c97e3334e..000000000 --- a/plugins/providers/virtualbox/action/provisioner_cleanup.rb +++ /dev/null @@ -1,25 +0,0 @@ -module VagrantPlugins - module ProviderVirtualBox - module Action - class ProvisionerCleanup - def initialize(app, env) - @app = app - end - - def call(env) - # Instantiate all the enabled provisioners - provisioners = env[:machine].config.vm.provisioners.map do |provisioner| - provisioner.provisioner.new(env, provisioner.config) - end - - # Call cleanup on each - provisioners.each do |instance| - instance.cleanup - end - - @app.call(env) - end - end - end - end -end From 5c9f27626ce9030b1638401318104f23b6db42ec Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 16:09:32 -0800 Subject: [PATCH 09/11] Provisioner built-in uses new API --- lib/vagrant/action/builtin/provision.rb | 11 +++++++---- plugins/providers/virtualbox/action.rb | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/action/builtin/provision.rb b/lib/vagrant/action/builtin/provision.rb index 9d003c5f4..4f593f748 100644 --- a/lib/vagrant/action/builtin/provision.rb +++ b/lib/vagrant/action/builtin/provision.rb @@ -18,11 +18,14 @@ module Vagrant def call(env) # Get all the configured provisioners provisioners = env[:machine].config.vm.provisioners.map do |provisioner| - provisioner.provisioner.new(env, provisioner.config) + klass = Vagrant.plugin("2").manager.provisioners[provisioner.name] + klass.new(env[:machine], provisioner.config) end - # Instantiate and prepare them. - provisioners.map { |p| p.prepare } + # Ask the provisioners to modify the configuration if needed + provisioners.each do |p| + p.configure(env[:machine].config) + end # Continue, we need the VM to be booted. @app.call(env) @@ -32,7 +35,7 @@ module Vagrant env[:ui].info(I18n.t("vagrant.actions.vm.provision.beginning", :provisioner => p.class)) - p.provision! + p.provision end end end diff --git a/plugins/providers/virtualbox/action.rb b/plugins/providers/virtualbox/action.rb index 85c31a450..45bdb1e95 100644 --- a/plugins/providers/virtualbox/action.rb +++ b/plugins/providers/virtualbox/action.rb @@ -56,8 +56,8 @@ module VagrantPlugins b.use CleanMachineFolder b.use ClearForwardedPorts b.use EnvSet, :port_collision_handler => :correct - b.use CheckPortCollisions b.use Provision + b.use CheckPortCollisions b.use PruneNFSExports b.use NFS b.use ClearSharedFolders From 51a227ae7e0ea3861f96dae3bf59e886d1f2f0dd Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 16:22:47 -0800 Subject: [PATCH 10/11] Puppet uses the new provisioner API --- plugins/provisioners/puppet/config/puppet.rb | 65 +++++++++ .../puppet/config/puppet_server.rb | 16 ++ plugins/provisioners/puppet/plugin.rb | 14 +- .../provisioners/puppet/provisioner/puppet.rb | 138 +++++------------- .../puppet/provisioner/puppet_server.rb | 36 ++--- 5 files changed, 139 insertions(+), 130 deletions(-) create mode 100644 plugins/provisioners/puppet/config/puppet.rb create mode 100644 plugins/provisioners/puppet/config/puppet_server.rb diff --git a/plugins/provisioners/puppet/config/puppet.rb b/plugins/provisioners/puppet/config/puppet.rb new file mode 100644 index 000000000..b4d710802 --- /dev/null +++ b/plugins/provisioners/puppet/config/puppet.rb @@ -0,0 +1,65 @@ +module VagrantPlugins + module Puppet + module Config + class Puppet < Vagrant.plugin("2", :config) + attr_accessor :manifest_file + attr_accessor :manifests_path + attr_accessor :module_path + attr_accessor :pp_path + attr_accessor :options + attr_accessor :facter + + def manifest_file; @manifest_file || "default.pp"; end + def manifests_path; @manifests_path || "manifests"; end + def pp_path; @pp_path || "/tmp/vagrant-puppet"; end + def options; @options ||= []; end + def facter; @facter ||= {}; end + + # Returns the manifests path expanded relative to the root path of the + # environment. + def expanded_manifests_path(root_path) + Pathname.new(manifests_path).expand_path(root_path) + end + + # Returns the module paths as an array of paths expanded relative to the + # root path. + def expanded_module_paths(root_path) + return [] if !module_path + + # Get all the paths and expand them relative to the root path, returning + # the array of expanded paths + paths = module_path + paths = [paths] if !paths.is_a?(Array) + paths.map do |path| + Pathname.new(path).expand_path(root_path) + end + end + + def validate(env, errors) + # Calculate the manifests and module paths based on env + this_expanded_manifests_path = expanded_manifests_path(env.root_path) + this_expanded_module_paths = expanded_module_paths(env.root_path) + + # Manifests path/file validation + if !this_expanded_manifests_path.directory? + errors.add(I18n.t("vagrant.provisioners.puppet.manifests_path_missing", + :path => this_expanded_manifests_path)) + else + expanded_manifest_file = this_expanded_manifests_path.join(manifest_file) + if !expanded_manifest_file.file? + errors.add(I18n.t("vagrant.provisioners.puppet.manifest_missing", + :manifest => expanded_manifest_file.to_s)) + end + end + + # Module paths validation + this_expanded_module_paths.each do |path| + if !path.directory? + errors.add(I18n.t("vagrant.provisioners.puppet.module_path_missing", :path => path)) + end + end + end + end + end + end +end diff --git a/plugins/provisioners/puppet/config/puppet_server.rb b/plugins/provisioners/puppet/config/puppet_server.rb new file mode 100644 index 000000000..967bf4c4d --- /dev/null +++ b/plugins/provisioners/puppet/config/puppet_server.rb @@ -0,0 +1,16 @@ +module VagrantPlugins + module Puppet + module Config + class PuppetServer < Vagrant.plugin("2", :config) + attr_accessor :puppet_server + attr_accessor :puppet_node + attr_accessor :options + attr_accessor :facter + + def facter; @facter ||= {}; end + def puppet_server; @puppet_server || "puppet"; end + def options; @options ||= []; end + end + end + end +end diff --git a/plugins/provisioners/puppet/plugin.rb b/plugins/provisioners/puppet/plugin.rb index 036ff5e00..362a1c421 100644 --- a/plugins/provisioners/puppet/plugin.rb +++ b/plugins/provisioners/puppet/plugin.rb @@ -9,12 +9,22 @@ module VagrantPlugins Puppet either using `puppet apply` or a Puppet server. DESC - provisioner("puppet") do + config(:puppet, :provisioner) do + require File.expand_path("../config/puppet", __FILE__) + Config::Puppet + end + + config(:puppet_server, :provisioner) do + require File.expand_path("../config/puppet_server", __FILE__) + Config::PuppetServer + end + + provisioner(:puppet) do require File.expand_path("../provisioner/puppet", __FILE__) Provisioner::Puppet end - provisioner("puppet_server") do + provisioner(:puppet_server) do require File.expand_path("../provisioner/puppet_server", __FILE__) Provisioner::PuppetServer end diff --git a/plugins/provisioners/puppet/provisioner/puppet.rb b/plugins/provisioners/puppet/provisioner/puppet.rb index 78c7ca519..d105a854b 100644 --- a/plugins/provisioners/puppet/provisioner/puppet.rb +++ b/plugins/provisioners/puppet/provisioner/puppet.rb @@ -8,88 +8,40 @@ module VagrantPlugins end class Puppet < Vagrant.plugin("2", :provisioner) - class Config < Vagrant.plugin("2", :config) - attr_accessor :manifest_file - attr_accessor :manifests_path - attr_accessor :module_path - attr_accessor :pp_path - attr_accessor :options - attr_accessor :facter - - def manifest_file; @manifest_file || "default.pp"; end - def manifests_path; @manifests_path || "manifests"; end - def pp_path; @pp_path || "/tmp/vagrant-puppet"; end - def options; @options ||= []; end - def facter; @facter ||= {}; end - - # Returns the manifests path expanded relative to the root path of the - # environment. - def expanded_manifests_path(root_path) - Pathname.new(manifests_path).expand_path(root_path) - end - - # Returns the module paths as an array of paths expanded relative to the - # root path. - def expanded_module_paths(root_path) - return [] if !module_path - - # Get all the paths and expand them relative to the root path, returning - # the array of expanded paths - paths = module_path - paths = [paths] if !paths.is_a?(Array) - paths.map do |path| - Pathname.new(path).expand_path(root_path) - end - end - - def validate(env, errors) - # Calculate the manifests and module paths based on env - this_expanded_manifests_path = expanded_manifests_path(env.root_path) - this_expanded_module_paths = expanded_module_paths(env.root_path) - - # Manifests path/file validation - if !this_expanded_manifests_path.directory? - errors.add(I18n.t("vagrant.provisioners.puppet.manifests_path_missing", - :path => this_expanded_manifests_path)) - else - expanded_manifest_file = this_expanded_manifests_path.join(manifest_file) - if !expanded_manifest_file.file? - errors.add(I18n.t("vagrant.provisioners.puppet.manifest_missing", - :manifest => expanded_manifest_file.to_s)) - end - end - - # Module paths validation - this_expanded_module_paths.each do |path| - if !path.directory? - errors.add(I18n.t("vagrant.provisioners.puppet.module_path_missing", :path => path)) - end - end - end - end - - def self.config_class - Config - end - - def initialize(env, config) + def initialize(machine, config) super @logger = Log4r::Logger.new("vagrant::provisioners::puppet") end - def prepare + def configure(root_config) # Calculate the paths we're going to use based on the environment - @expanded_manifests_path = config.expanded_manifests_path(env[:root_path]) - @expanded_module_paths = config.expanded_module_paths(env[:root_path]) - @manifest_file = File.join(manifests_guest_path, config.manifest_file) + root_path = @machine.env.root_path + @expanded_manifests_path = @config.expanded_manifests_path(root_path) + @expanded_module_paths = @config.expanded_module_paths(root_path) + @manifest_file = File.join(manifests_guest_path, @config.manifest_file) - set_module_paths - share_manifests - share_module_paths + # Setup the module paths + @module_paths = [] + @expanded_module_paths.each_with_index do |path, i| + @module_paths << [path, File.join(config.pp_path, "modules-#{i}")] + end + + # Share the manifests directory with the guest + root_config.vm.share_folder( + "manifests", manifests_guest_path, @expanded_manifests_path) + + # Share the module paths + count = 0 + @module_paths.each do |from, to| + # Sorry for the cryptic key here, but VirtualBox has a strange limit on + # maximum size for it and its something small (around 10) + root_config.vm.share_folder("v-pp-m#{count}", to, from) + count += 1 + end end - def provision! + def provision # Check that the shared folders are properly shared check = [manifests_guest_path] @module_paths.each do |host_path, guest_path| @@ -103,36 +55,16 @@ module VagrantPlugins run_puppet_apply end - def share_manifests - env[:machine].config.vm.share_folder("manifests", manifests_guest_path, @expanded_manifests_path) - end - - def share_module_paths - count = 0 - @module_paths.each do |from, to| - # Sorry for the cryptic key here, but VirtualBox has a strange limit on - # maximum size for it and its something small (around 10) - env[:machine].config.vm.share_folder("v-pp-m#{count}", to, from) - count += 1 - end - end - - def set_module_paths - @module_paths = [] - @expanded_module_paths.each_with_index do |path, i| - @module_paths << [path, File.join(config.pp_path, "modules-#{i}")] - end - end - def manifests_guest_path File.join(config.pp_path, "manifests") end def verify_binary(binary) - env[:machine].communicate.sudo("which #{binary}", - :error_class => PuppetError, - :error_key => :not_detected, - :binary => binary) + @machine.communicate.sudo( + "which #{binary}", + :error_class => PuppetError, + :error_key => :not_detected, + :binary => binary) end def run_puppet_apply @@ -155,19 +87,19 @@ module VagrantPlugins command = "cd #{manifests_guest_path} && #{facter}puppet apply #{options} --detailed-exitcodes || [ $? -eq 2 ]" - env[:ui].info I18n.t("vagrant.provisioners.puppet.running_puppet", - :manifest => @manifest_file) + @machine.env.ui.info I18n.t("vagrant.provisioners.puppet.running_puppet", + :manifest => @manifest_file) - env[:machine].communicate.sudo(command) do |type, data| + @machine.communicate.sudo(command) do |type, data| data.chomp! - env[:ui].info(data, :prefix => false) if !data.empty? + @machine.env.ui.info(data, :prefix => false) if !data.empty? end end def verify_shared_folders(folders) folders.each do |folder| @logger.debug("Checking for shared folder: #{folder}") - if !env[:machine].communicate.test("test -d #{folder}") + if !@machine.communicate.test("test -d #{folder}") raise PuppetError, :missing_shared_folders end end diff --git a/plugins/provisioners/puppet/provisioner/puppet_server.rb b/plugins/provisioners/puppet/provisioner/puppet_server.rb index 3383b88f1..617332934 100644 --- a/plugins/provisioners/puppet/provisioner/puppet_server.rb +++ b/plugins/provisioners/puppet/provisioner/puppet_server.rb @@ -6,31 +6,17 @@ module VagrantPlugins end class PuppetServer < Vagrant.plugin("2", :provisioner) - class Config < Vagrant.plugin("2", :config) - attr_accessor :puppet_server - attr_accessor :puppet_node - attr_accessor :options - attr_accessor :facter - - def facter; @facter ||= {}; end - def puppet_server; @puppet_server || "puppet"; end - def options; @options ||= []; end - end - - def self.config_class - Config - end - - def provision! + def provision verify_binary("puppet") run_puppet_agent end def verify_binary(binary) - env[:vm].channel.sudo("which #{binary}", - :error_class => PuppetServerError, - :error_key => :not_detected, - :binary => binary) + @machine.communicate.sudo( + "which #{binary}", + :error_class => PuppetServerError, + :error_key => :not_detected, + :binary => binary) end def run_puppet_agent @@ -43,13 +29,13 @@ module VagrantPlugins if config.puppet_node # If a node name is given, we use that directly for the certname cn = config.puppet_node - elsif env[:vm].config.vm.host_name + elsif @machine.config.vm.host_name # If a host name is given, we explicitly set the certname to # nil so that the hostname becomes the cert name. cn = nil else # Otherwise, we default to the name of the box. - cn = env[:vm].config.vm.box + cn = @machine.config.vm.box end # Add the certname option if there is one @@ -69,10 +55,10 @@ module VagrantPlugins command = "#{facter}puppet agent #{options} --server #{config.puppet_server} --detailed-exitcodes || [ $? -eq 2 ]" - env[:ui].info I18n.t("vagrant.provisioners.puppet_server.running_puppetd") - env[:vm].channel.sudo(command) do |type, data| + @machine.env.ui.info I18n.t("vagrant.provisioners.puppet_server.running_puppetd") + @machine.communicate.sudo(command) do |type, data| data.chomp! - env[:ui].info(data, :prefix => false) if !data.empty? + @machine.env.ui.info(data, :prefix => false) if !data.empty? end end end From 0c8391aedd9eb991bd16bdf5250becdd1044f3e8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 13 Jan 2013 16:41:32 -0800 Subject: [PATCH 11/11] Chef uses the new provisioner API --- plugins/provisioners/chef/config/base.rb | 71 ++++++++++ .../provisioners/chef/config/chef_client.rb | 35 +++++ plugins/provisioners/chef/config/chef_solo.rb | 49 +++++++ plugins/provisioners/chef/plugin.rb | 14 +- plugins/provisioners/chef/provisioner/base.rb | 121 ++++-------------- .../chef/provisioner/chef_client.rb | 94 +++++--------- .../chef/provisioner/chef_solo.rb | 116 ++++++----------- 7 files changed, 262 insertions(+), 238 deletions(-) create mode 100644 plugins/provisioners/chef/config/base.rb create mode 100644 plugins/provisioners/chef/config/chef_client.rb create mode 100644 plugins/provisioners/chef/config/chef_solo.rb diff --git a/plugins/provisioners/chef/config/base.rb b/plugins/provisioners/chef/config/base.rb new file mode 100644 index 000000000..67bc2283a --- /dev/null +++ b/plugins/provisioners/chef/config/base.rb @@ -0,0 +1,71 @@ +module VagrantPlugins + module Chef + module Config + class Base < Vagrant.plugin("2", :config) + # Shared config + attr_accessor :node_name + attr_accessor :provisioning_path + attr_accessor :log_level + attr_accessor :json + attr_accessor :http_proxy + attr_accessor :http_proxy_user + attr_accessor :http_proxy_pass + attr_accessor :https_proxy + attr_accessor :https_proxy_user + attr_accessor :https_proxy_pass + attr_accessor :no_proxy + attr_accessor :binary_path + attr_accessor :binary_env + attr_accessor :attempts + attr_accessor :arguments + attr_writer :run_list + + # Provide defaults in such a way that they won't override the instance + # variable. This is so merging continues to work properly. + def attempts; @attempts || 1; end + def json; @json ||= {}; end + def log_level; @log_level || :info; end + + # This returns the json that is merged with the defaults and the + # user set data. + def merged_json + original = { :instance_role => "vagrant" } + original[:run_list] = @run_list if @run_list + original.merge(json || {}) + end + + # Returns the run list, but also sets it up to be empty if it + # hasn't been defined already. + def run_list + @run_list ||= [] + end + + # Adds a recipe to the run list + def add_recipe(name) + name = "recipe[#{name}]" unless name =~ /^recipe\[(.+?)\]$/ + run_list << name + end + + # Adds a role to the run list + def add_role(name) + name = "role[#{name}]" unless name =~ /^role\[(.+?)\]$/ + run_list << name + end + + def validate(env, errors) + super + + errors.add(I18n.t("vagrant.config.chef.vagrant_as_json_key")) if json.has_key?(:vagrant) + end + + def instance_variables_hash + # Overridden so that the 'json' key could be removed, since its just + # merged into the config anyways + result = super + result.delete("json") + result + end + end + end + end +end diff --git a/plugins/provisioners/chef/config/chef_client.rb b/plugins/provisioners/chef/config/chef_client.rb new file mode 100644 index 000000000..ea734cc86 --- /dev/null +++ b/plugins/provisioners/chef/config/chef_client.rb @@ -0,0 +1,35 @@ +require File.expand_path("../base", __FILE__) + +module VagrantPlugins + module Chef + module Config + class ChefClient < Base + attr_accessor :chef_server_url + attr_accessor :validation_key_path + attr_accessor :validation_client_name + attr_accessor :client_key_path + attr_accessor :file_cache_path + attr_accessor :file_backup_path + attr_accessor :environment + attr_accessor :encrypted_data_bag_secret_key_path + attr_accessor :encrypted_data_bag_secret + + # Provide defaults in such a way that they won't override the instance + # variable. This is so merging continues to work properly. + def validation_client_name; @validation_client_name || "chef-validator"; end + def client_key_path; @client_key_path || "/etc/chef/client.pem"; end + def file_cache_path; @file_cache_path || "/srv/chef/file_store"; end + def file_backup_path; @file_backup_path || "/srv/chef/cache"; end + def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end + + def validate(env, errors) + super + + errors.add(I18n.t("vagrant.config.chef.server_url_empty")) if !chef_server_url || chef_server_url.strip == "" + errors.add(I18n.t("vagrant.config.chef.validation_key_path")) if !validation_key_path + errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if @run_list && @run_list.empty? + end + end + end + end +end diff --git a/plugins/provisioners/chef/config/chef_solo.rb b/plugins/provisioners/chef/config/chef_solo.rb new file mode 100644 index 000000000..351e10fef --- /dev/null +++ b/plugins/provisioners/chef/config/chef_solo.rb @@ -0,0 +1,49 @@ +require File.expand_path("../base", __FILE__) + +module VagrantPlugins + module Chef + module Config + class ChefSolo < Base + attr_accessor :cookbooks_path + attr_accessor :roles_path + attr_accessor :data_bags_path + attr_accessor :recipe_url + attr_accessor :nfs + attr_accessor :encrypted_data_bag_secret_key_path + attr_accessor :encrypted_data_bag_secret + + def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end + + def initialize + super + + @__default = ["cookbooks", [:vm, "cookbooks"]] + end + + # Provide defaults in such a way that they won't override the instance + # variable. This is so merging continues to work properly. + def cookbooks_path + @cookbooks_path || _default_cookbook_path + end + + # This stores a reference to the default cookbook path which is used + # later. Do not use this publicly. I apologize for not making it + # "protected" but it has to be called by Vagrant internals later. + def _default_cookbook_path + @__default + end + + def nfs + @nfs || false + end + + def validate(env, errors) + super + + errors.add(I18n.t("vagrant.config.chef.cookbooks_path_empty")) if !cookbooks_path || [cookbooks_path].flatten.empty? + errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if !run_list || run_list.empty? + end + end + end + end +end diff --git a/plugins/provisioners/chef/plugin.rb b/plugins/provisioners/chef/plugin.rb index 8e08af5d2..5ca53a0ae 100644 --- a/plugins/provisioners/chef/plugin.rb +++ b/plugins/provisioners/chef/plugin.rb @@ -9,12 +9,22 @@ module VagrantPlugins Chef via `chef-solo` or `chef-client`. DESC - provisioner("chef_solo") do + config(:chef_solo, :provisioner) do + require File.expand_path("../config/chef_solo", __FILE__) + Config::ChefSolo + end + + config(:chef_client, :provisioner) do + require File.expand_path("../config/chef_client", __FILE__) + Config::ChefClient + end + + provisioner(:chef_solo) do require File.expand_path("../provisioner/chef_solo", __FILE__) Provisioner::ChefSolo end - provisioner("chef_client") do + provisioner(:chef_client) do require File.expand_path("../provisioner/chef_client", __FILE__) Provisioner::ChefClient end diff --git a/plugins/provisioners/chef/provisioner/base.rb b/plugins/provisioners/chef/provisioner/base.rb index c30a82ff4..80abb4a4a 100644 --- a/plugins/provisioners/chef/provisioner/base.rb +++ b/plugins/provisioners/chef/provisioner/base.rb @@ -1,5 +1,6 @@ require 'tempfile' +require "vagrant/util/counter" require "vagrant/util/template_renderer" module VagrantPlugins @@ -9,9 +10,13 @@ module VagrantPlugins # chef-solo and chef-client provisioning are stored. This is **not an actual # provisioner**. Instead, {ChefSolo} or {ChefServer} should be used. class Base < Vagrant.plugin("2", :provisioner) + class ChefError < Vagrant::Errors::VagrantError + error_namespace("vagrant.provisioners.chef") + end + include Vagrant::Util::Counter - def initialize(env, config) + def initialize(machine, config) super config.provisioning_path ||= "/tmp/vagrant-chef-#{get_and_update_counter(:provisioning_path)}" @@ -20,36 +25,37 @@ module VagrantPlugins def verify_binary(binary) # Checks for the existence of chef binary and error if it # doesn't exist. - env[:machine].communicate.sudo("which #{binary}", - :error_class => ChefError, - :error_key => :chef_not_detected, - :binary => binary) + @machine.communicate.sudo( + "which #{binary}", + :error_class => ChefError, + :error_key => :chef_not_detected, + :binary => binary) end # Returns the path to the Chef binary, taking into account the # `binary_path` configuration option. def chef_binary_path(binary) - return binary if !config.binary_path - return File.join(config.binary_path, binary) + return binary if !@config.binary_path + return File.join(@config.binary_path, binary) end def chown_provisioning_folder - env[:machine].communicate.tap do |comm| - comm.sudo("mkdir -p #{config.provisioning_path}") - comm.sudo("chown #{env[:machine].config.ssh.username} #{config.provisioning_path}") + @machine.communicate.tap do |comm| + comm.sudo("mkdir -p #{@config.provisioning_path}") + comm.sudo("chown #{@machine.config.ssh.username} #{@config.provisioning_path}") end end def setup_config(template, filename, template_vars) config_file = Vagrant::Util::TemplateRenderer.render(template, { - :log_level => config.log_level.to_sym, - :http_proxy => config.http_proxy, - :http_proxy_user => config.http_proxy_user, - :http_proxy_pass => config.http_proxy_pass, - :https_proxy => config.https_proxy, - :https_proxy_user => config.https_proxy_user, - :https_proxy_pass => config.https_proxy_pass, - :no_proxy => config.no_proxy + :log_level => @config.log_level.to_sym, + :http_proxy => @config.http_proxy, + :http_proxy_user => @config.http_proxy_user, + :http_proxy_pass => @config.http_proxy_pass, + :https_proxy => @config.https_proxy, + :https_proxy_user => @config.https_proxy_user, + :https_proxy_pass => @config.https_proxy_pass, + :no_proxy => @config.no_proxy }.merge(template_vars)) # Create a temporary file to store the data so we @@ -59,17 +65,17 @@ module VagrantPlugins temp.close remote_file = File.join(config.provisioning_path, filename) - env[:machine].communicate.tap do |comm| + @machine.communicate.tap do |comm| comm.sudo("rm #{remote_file}", :error_check => false) comm.upload(temp.path, remote_file) end end def setup_json - env[:ui].info I18n.t("vagrant.provisioners.chef.json") + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.json") # Get the JSON that we're going to expose to Chef - json = JSON.pretty_generate(config.merged_json) + json = JSON.pretty_generate(@config.merged_json) # Create a temporary file to store the data so we # can upload it @@ -77,78 +83,7 @@ module VagrantPlugins temp.write(json) temp.close - env[:machine].communicate.upload(temp.path, File.join(config.provisioning_path, "dna.json")) - end - - class ChefError < Vagrant::Errors::VagrantError - error_namespace("vagrant.provisioners.chef") - end - - # This is the configuration which is available through `config.chef` - class Config < Vagrant.plugin("2", :config) - # Shared config - attr_accessor :node_name - attr_accessor :provisioning_path - attr_accessor :log_level - attr_accessor :json - attr_accessor :http_proxy - attr_accessor :http_proxy_user - attr_accessor :http_proxy_pass - attr_accessor :https_proxy - attr_accessor :https_proxy_user - attr_accessor :https_proxy_pass - attr_accessor :no_proxy - attr_accessor :binary_path - attr_accessor :binary_env - attr_accessor :attempts - attr_accessor :arguments - attr_writer :run_list - - # Provide defaults in such a way that they won't override the instance - # variable. This is so merging continues to work properly. - def attempts; @attempts || 1; end - def json; @json ||= {}; end - def log_level; @log_level || :info; end - - # This returns the json that is merged with the defaults and the - # user set data. - def merged_json - original = { :instance_role => "vagrant" } - original[:run_list] = @run_list if @run_list - original.merge(json || {}) - end - - # Returns the run list, but also sets it up to be empty if it - # hasn't been defined already. - def run_list - @run_list ||= [] - end - - # Adds a recipe to the run list - def add_recipe(name) - name = "recipe[#{name}]" unless name =~ /^recipe\[(.+?)\]$/ - run_list << name - end - - # Adds a role to the run list - def add_role(name) - name = "role[#{name}]" unless name =~ /^role\[(.+?)\]$/ - run_list << name - end - - def validate(env, errors) - super - - errors.add(I18n.t("vagrant.config.chef.vagrant_as_json_key")) if json.has_key?(:vagrant) - end - - def instance_variables_hash - # Overridden so that the 'json' key could be removed, since its just - # merged into the config anyways - result = super - result.delete("json") - result - end + @machine.communicate.upload(temp.path, File.join(@config.provisioning_path, "dna.json")) end end end diff --git a/plugins/provisioners/chef/provisioner/chef_client.rb b/plugins/provisioners/chef/provisioner/chef_client.rb index ebbf673f1..3b97068af 100644 --- a/plugins/provisioners/chef/provisioner/chef_client.rb +++ b/plugins/provisioners/chef/provisioner/chef_client.rb @@ -8,42 +8,10 @@ module VagrantPlugins # This class implements provisioning via chef-client, allowing provisioning # with a chef server. class ChefClient < Base - class Config < Base::Config - attr_accessor :chef_server_url - attr_accessor :validation_key_path - attr_accessor :validation_client_name - attr_accessor :client_key_path - attr_accessor :file_cache_path - attr_accessor :file_backup_path - attr_accessor :environment - attr_accessor :encrypted_data_bag_secret_key_path - attr_accessor :encrypted_data_bag_secret - - # Provide defaults in such a way that they won't override the instance - # variable. This is so merging continues to work properly. - def validation_client_name; @validation_client_name || "chef-validator"; end - def client_key_path; @client_key_path || "/etc/chef/client.pem"; end - def file_cache_path; @file_cache_path || "/srv/chef/file_store"; end - def file_backup_path; @file_backup_path || "/srv/chef/cache"; end - def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end - - def validate(env, errors) - super - - errors.add(I18n.t("vagrant.config.chef.server_url_empty")) if !chef_server_url || chef_server_url.strip == "" - errors.add(I18n.t("vagrant.config.chef.validation_key_path")) if !validation_key_path - errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if @run_list && @run_list.empty? - end - end - - def self.config_class - Config - end - - def prepare - raise ChefError, :server_validation_key_required if config.validation_key_path.nil? + def configure(root_config) + raise ChefError, :server_validation_key_required if @config.validation_key_path.nil? raise ChefError, :server_validation_key_doesnt_exist if !File.file?(validation_key_path) - raise ChefError, :server_url_required if config.chef_server_url.nil? + raise ChefError, :server_url_required if @config.chef_server_url.nil? end def provision! @@ -51,63 +19,63 @@ module VagrantPlugins chown_provisioning_folder create_client_key_folder upload_validation_key - upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path + upload_encrypted_data_bag_secret if @config.encrypted_data_bag_secret_key_path setup_json setup_server_config run_chef_client end def create_client_key_folder - env[:ui].info I18n.t("vagrant.provisioners.chef.client_key_folder") - path = Pathname.new(config.client_key_path) + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.client_key_folder") + path = Pathname.new(@config.client_key_path) - env[:machine].communicate.sudo("mkdir -p #{path.dirname}") + @machine.communicate.sudo("mkdir -p #{path.dirname}") end def upload_validation_key - env[:ui].info I18n.t("vagrant.provisioners.chef.upload_validation_key") - env[:machine].communicate.upload(validation_key_path, guest_validation_key_path) + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.upload_validation_key") + @machine.communicate.upload(validation_key_path, guest_validation_key_path) end def upload_encrypted_data_bag_secret - env[:ui].info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key") - env[:machine].communicate.upload(encrypted_data_bag_secret_key_path, - config.encrypted_data_bag_secret) + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key") + @machine.communicate.upload(encrypted_data_bag_secret_key_path, + @config.encrypted_data_bag_secret) end def setup_server_config setup_config("provisioners/chef_client/client", "client.rb", { - :node_name => config.node_name, - :chef_server_url => config.chef_server_url, - :validation_client_name => config.validation_client_name, + :node_name => @config.node_name, + :chef_server_url => @config.chef_server_url, + :validation_client_name => @config.validation_client_name, :validation_key => guest_validation_key_path, - :client_key => config.client_key_path, - :file_cache_path => config.file_cache_path, - :file_backup_path => config.file_backup_path, - :environment => config.environment, - :encrypted_data_bag_secret => config.encrypted_data_bag_secret + :client_key => @config.client_key_path, + :file_cache_path => @config.file_cache_path, + :file_backup_path => @config.file_backup_path, + :environment => @config.environment, + :encrypted_data_bag_secret => @config.encrypted_data_bag_secret }) end def run_chef_client - command_env = config.binary_env ? "#{config.binary_env} " : "" - command_args = config.arguments ? " #{config.arguments}" : "" - command = "#{command_env}#{chef_binary_path("chef-client")} -c #{config.provisioning_path}/client.rb -j #{config.provisioning_path}/dna.json #{command_args}" + command_env = @config.binary_env ? "#{@config.binary_env} " : "" + command_args = @config.arguments ? " #{@config.arguments}" : "" + command = "#{command_env}#{chef_binary_path("chef-client")} -c #{@config.provisioning_path}/client.rb -j #{@config.provisioning_path}/dna.json #{command_args}" - config.attempts.times do |attempt| + @config.attempts.times do |attempt| if attempt == 0 - env[:ui].info I18n.t("vagrant.provisioners.chef.running_client") + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.running_client") else - env[:ui].info I18n.t("vagrant.provisioners.chef.running_client_again") + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.running_client_again") end - exit_status = env[:machine].communicate.sudo(command, :error_check => false) do |type, data| + exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data| # Output the data with the proper color based on the stream. color = type == :stdout ? :green : :red # Note: Be sure to chomp the data to avoid the newlines that the # Chef outputs. - env[:ui].info(data.chomp, :color => color, :prefix => false) + @machine.env.ui.info(data.chomp, :color => color, :prefix => false) end # There is no need to run Chef again if it converges @@ -119,15 +87,15 @@ module VagrantPlugins end def validation_key_path - File.expand_path(config.validation_key_path, env[:root_path]) + File.expand_path(@config.validation_key_path, @machine.env.root_path) end def encrypted_data_bag_secret_key_path - File.expand_path(config.encrypted_data_bag_secret_key_path, env[:root_path]) + File.expand_path(@config.encrypted_data_bag_secret_key_path, @machine.env.root_path) end def guest_validation_key_path - File.join(config.provisioning_path, "validation.pem") + File.join(@config.provisioning_path, "validation.pem") end end end diff --git a/plugins/provisioners/chef/provisioner/chef_solo.rb b/plugins/provisioners/chef/provisioner/chef_solo.rb index fbd640539..adcadb4a1 100644 --- a/plugins/provisioners/chef/provisioner/chef_solo.rb +++ b/plugins/provisioners/chef/provisioner/chef_solo.rb @@ -1,5 +1,7 @@ require "log4r" +require "vagrant/util/counter" + require File.expand_path("../base", __FILE__) module VagrantPlugins @@ -10,72 +12,26 @@ module VagrantPlugins extend Vagrant::Util::Counter include Vagrant::Util::Counter - class Config < Base::Config - attr_accessor :cookbooks_path - attr_accessor :roles_path - attr_accessor :data_bags_path - attr_accessor :recipe_url - attr_accessor :nfs - attr_accessor :encrypted_data_bag_secret_key_path - attr_accessor :encrypted_data_bag_secret - - def encrypted_data_bag_secret; @encrypted_data_bag_secret || "/tmp/encrypted_data_bag_secret"; end - - def initialize - super - - @__default = ["cookbooks", [:vm, "cookbooks"]] - end - - # Provide defaults in such a way that they won't override the instance - # variable. This is so merging continues to work properly. - def cookbooks_path - @cookbooks_path || _default_cookbook_path - end - - # This stores a reference to the default cookbook path which is used - # later. Do not use this publicly. I apologize for not making it - # "protected" but it has to be called by Vagrant internals later. - def _default_cookbook_path - @__default - end - - def nfs - @nfs || false - end - - def validate(env, errors) - super - - errors.add(I18n.t("vagrant.config.chef.cookbooks_path_empty")) if !cookbooks_path || [cookbooks_path].flatten.empty? - errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if !run_list || run_list.empty? - end - end - attr_reader :cookbook_folders attr_reader :role_folders attr_reader :data_bags_folders - def self.config_class - Config - end - - def initialize(env, config) + def initialize(machine, config) super @logger = Log4r::Logger.new("vagrant::provisioners::chef_solo") end - def prepare - @cookbook_folders = expanded_folders(config.cookbooks_path, "cookbooks") - @role_folders = expanded_folders(config.roles_path, "roles") - @data_bags_folders = expanded_folders(config.data_bags_path, "data_bags") + def configure(root_config) + @cookbook_folders = expanded_folders(@config.cookbooks_path, "cookbooks") + @role_folders = expanded_folders(@config.roles_path, "roles") + @data_bags_folders = expanded_folders(@config.data_bags_path, "data_bags") - share_folders("csc", @cookbook_folders) - share_folders("csr", @role_folders) - share_folders("csdb", @data_bags_folders) + share_folders(root_config, "csc", @cookbook_folders) + share_folders(root_config, "csr", @role_folders) + share_folders(root_config, "csdb", @data_bags_folders) end - def provision! + def provision # Verify that the proper shared folders exist. check = [] [@cookbook_folders, @role_folders, @data_bags_folders].each do |folders| @@ -91,7 +47,7 @@ module VagrantPlugins verify_binary(chef_binary_path("chef-solo")) chown_provisioning_folder - upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path + upload_encrypted_data_bag_secret if @config.encrypted_data_bag_secret_key_path setup_json setup_solo_config run_chef_solo @@ -116,22 +72,22 @@ module VagrantPlugins remote_path = nil if type == :host # Get the expanded path that the host path points to - local_path = File.expand_path(path, env[:root_path]) + local_path = File.expand_path(path, @machine.env.root_path) # Super hacky but if we're expanded the default cookbook paths, # and one of the host paths doesn't exist, then just ignore it, # because that is fine. - if paths.equal?(config._default_cookbook_path) && !File.directory?(local_path) + if paths.equal?(@config._default_cookbook_path) && !File.directory?(local_path) @logger.info("'cookbooks' folder doesn't exist on defaults. Ignoring.") next end # Path exists on the host, setup the remote path - remote_path = "#{config.provisioning_path}/chef-solo-#{get_and_update_counter(:cookbooks_path)}" + remote_path = "#{@config.provisioning_path}/chef-solo-#{get_and_update_counter(:cookbooks_path)}" else # Path already exists on the virtual machine. Expand it # relative to where we're provisioning. - remote_path = File.expand_path(path, config.provisioning_path) + remote_path = File.expand_path(path, @config.provisioning_path) # Remove drive letter if running on a windows host. This is a bit # of a hack but is the most portable way I can think of at the moment @@ -152,20 +108,20 @@ module VagrantPlugins # Shares the given folders with the given prefix. The folders should # be of the structure resulting from the `expanded_folders` function. - def share_folders(prefix, folders) + def share_folders(root_config, prefix, folders) folders.each do |type, local_path, remote_path| if type == :host - env[:machine].config.vm.share_folder( + root_config.vm.share_folder( "v-#{prefix}-#{self.class.get_and_update_counter(:shared_folder)}", - remote_path, local_path, :nfs => config.nfs) + remote_path, local_path, :nfs => @config.nfs) end end end def upload_encrypted_data_bag_secret - env[:ui].info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key") - env[:machine].communicate.upload(encrypted_data_bag_secret_key_path, - config.encrypted_data_bag_secret) + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key") + @machine.communicate.upload(encrypted_data_bag_secret_key_path, + @config.encrypted_data_bag_secret) end def setup_solo_config @@ -174,35 +130,35 @@ module VagrantPlugins data_bags_path = guest_paths(@data_bags_folders).first setup_config("provisioners/chef_solo/solo", "solo.rb", { - :node_name => config.node_name, - :provisioning_path => config.provisioning_path, + :node_name => @config.node_name, + :provisioning_path => @config.provisioning_path, :cookbooks_path => cookbooks_path, - :recipe_url => config.recipe_url, + :recipe_url => @config.recipe_url, :roles_path => roles_path, :data_bags_path => data_bags_path, - :encrypted_data_bag_secret => config.encrypted_data_bag_secret, + :encrypted_data_bag_secret => @config.encrypted_data_bag_secret, }) end def run_chef_solo - command_env = config.binary_env ? "#{config.binary_env} " : "" - command_args = config.arguments ? " #{config.arguments}" : "" - command = "#{command_env}#{chef_binary_path("chef-solo")} -c #{config.provisioning_path}/solo.rb -j #{config.provisioning_path}/dna.json #{command_args}" + command_env = @config.binary_env ? "#{@config.binary_env} " : "" + command_args = @config.arguments ? " #{@config.arguments}" : "" + command = "#{command_env}#{chef_binary_path("chef-solo")} -c #{@config.provisioning_path}/solo.rb -j #{@config.provisioning_path}/dna.json #{command_args}" - config.attempts.times do |attempt| + @config.attempts.times do |attempt| if attempt == 0 - env[:ui].info I18n.t("vagrant.provisioners.chef.running_solo") + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.running_solo") else - env[:ui].info I18n.t("vagrant.provisioners.chef.running_solo_again") + @machine.env.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again") end - exit_status = env[:machine].communicate.sudo(command, :error_check => false) do |type, data| + exit_status = @machine.communicate.sudo(command, :error_check => false) do |type, data| # Output the data with the proper color based on the stream. color = type == :stdout ? :green : :red # Note: Be sure to chomp the data to avoid the newlines that the # Chef outputs. - env[:ui].info(data.chomp, :color => color, :prefix => false) + @machine.env.ui.info(data.chomp, :color => color, :prefix => false) end # There is no need to run Chef again if it converges @@ -216,14 +172,14 @@ module VagrantPlugins def verify_shared_folders(folders) folders.each do |folder| @logger.debug("Checking for shared folder: #{folder}") - if !env[:machine].communicate.test("test -d #{folder}") + if !@machine.communicate.test("test -d #{folder}") raise ChefError, :missing_shared_folders end end end def encrypted_data_bag_secret_key_path - File.expand_path(config.encrypted_data_bag_secret_key_path, env[:root_path]) + File.expand_path(@config.encrypted_data_bag_secret_key_path, @machine.env.root_path) end protected