Add Chef Apply provisioner

This commit is contained in:
Seth Vargo 2014-10-30 15:32:15 -04:00
parent 514101816b
commit 59eb0ad2e8
5 changed files with 250 additions and 7 deletions

View File

@ -0,0 +1,68 @@
module VagrantPlugins
module Chef
module Config
class ChefApply < Vagrant.plugin("2", :config)
extend Vagrant::Util::Counter
# The raw recipe text (as a string) to execute via chef-apply.
# @return [String]
attr_accessor :recipe
# The path (on the guest) where the uploaded apply recipe should be
# written (/tmp/vagrant-chef-apply-#.rb).
# @return [String]
attr_accessor :upload_path
# The Chef log level.
# @return [String]
attr_accessor :log_level
def initialize
@recipe = UNSET_VALUE
@log_level = UNSET_VALUE
@upload_path = UNSET_VALUE
end
def finalize!
@recipe = nil if @recipe == UNSET_VALUE
if @log_level == UNSET_VALUE
@log_level = :info
else
@log_level = @log_level.to_sym
end
if @upload_path == UNSET_VALUE
counter = self.class.get_and_update_counter(:chef_apply)
@upload_path = "/tmp/vagrant-chef-apply-#{counter}"
end
end
def validate(machine)
errors = _detected_errors
if missing(recipe)
errors << I18n.t("vagrant.provisioners.chef.recipe_empty")
end
if missing(log_level)
errors << I18n.t("vagrant.provisioners.chef.log_level_empty")
end
if missing(upload_path)
errors << I18n.t("vagrant.provisioners.chef.upload_path_empty")
end
{ "chef apply provisioner" => errors }
end
# Determine if the given string is "missing" (blank)
# @return [true, false]
def missing(obj)
obj.to_s.strip.empty?
end
end
end
end
end

View File

@ -10,12 +10,12 @@ module VagrantPlugins
name "chef" name "chef"
description <<-DESC description <<-DESC
Provides support for provisioning your virtual machines with Provides support for provisioning your virtual machines with
Chef via `chef-solo` or `chef-client`. Chef via `chef-solo`, `chef-client`, or `chef-apply`.
DESC DESC
config(:chef_solo, :provisioner) do config(:chef_apply, :provisioner) do
require_relative "config/chef_solo" require_relative "config/chef_apply"
Config::ChefSolo Config::ChefApply
end end
config(:chef_client, :provisioner) do config(:chef_client, :provisioner) do
@ -23,14 +23,19 @@ module VagrantPlugins
Config::ChefClient Config::ChefClient
end end
config(:chef_solo, :provisioner) do
require_relative "config/chef_solo"
Config::ChefSolo
end
config(:chef_zero, :provisioner) do config(:chef_zero, :provisioner) do
require_relative "config/chef_zero" require_relative "config/chef_zero"
Config::ChefZero Config::ChefZero
end end
provisioner(:chef_solo) do provisioner(:chef_apply) do
require_relative "provisioner/chef_solo" require_relative "provisioner/chef_apply"
Provisioner::ChefSolo Provisioner::ChefApply
end end
provisioner(:chef_client) do provisioner(:chef_client) do
@ -38,6 +43,11 @@ module VagrantPlugins
Provisioner::ChefClient Provisioner::ChefClient
end end
provisioner(:chef_solo) do
require_relative "provisioner/chef_solo"
Provisioner::ChefSolo
end
provisioner(:chef_zero) do provisioner(:chef_zero) do
require_relative "provisioner/chef_zero" require_relative "provisioner/chef_zero"
Provisioner::ChefZero Provisioner::ChefZero

View File

@ -0,0 +1,56 @@
require "tempfile"
module VagrantPlugins
module Chef
module Provisioner
class ChefApply < Vagrant.plugin("2", :provisioner)
def provision
command = "chef-apply"
command << " --log-level #{config.log_level}"
command << " #{config.upload_path}"
user = @machine.ssh_info[:username]
# Reset upload path permissions for the current ssh user
@machine.communicate.sudo("mkdir -p #{config.upload_path}")
@machine.communicate.sudo("chown -R #{user} #{config.upload_path}")
# Upload the recipe
upload_recipe
@machine.ui.info(I18n.t("vagrant.provisioners.chef.running_chef_apply",
script: config.path)
)
# Execute it with sudo
@machine.communicate.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
# Chomp the data to avoid the newlines that the Chef outputs
@machine.env.ui.info(data.chomp, color: color, prefix: false)
end
end
end
# Write the raw recipe contents to a tempfile and upload that to the
# machine.
def upload_recipe
# Write the raw recipe contents to a tempfile
file = Tempfile.new(["vagrant-chef-apply", ".rb"])
file.write(config.recipe)
file.rewind
# Upload the tempfile to the guest
destination = File.join(config.upload_path, "recipe.rb")
@machine.communicate.upload(file.path, destination)
ensure
# Delete our template
file.close
file.unlink
end
end
end
end
end

View File

@ -1754,10 +1754,18 @@ en:
"The cookbook path '%{path}' doesn't exist. Ignoring..." "The cookbook path '%{path}' doesn't exist. Ignoring..."
json: "Generating chef JSON and uploading..." json: "Generating chef JSON and uploading..."
client_key_folder: "Creating folder to hold client key..." client_key_folder: "Creating folder to hold client key..."
log_level_empty: |-
The Chef provisioner requires a log level. If you did not set a
log level, this is probably a bug and should be reported.
upload_validation_key: "Uploading chef client validation key..." upload_validation_key: "Uploading chef client validation key..."
upload_encrypted_data_bag_secret_key: "Uploading chef encrypted data bag secret key..." upload_encrypted_data_bag_secret_key: "Uploading chef encrypted data bag secret key..."
recipe_empty: |-
Chef Apply provisioning requires that the `config.chef.recipe` be set
to a string containing the recipe contents you want to execute on the
guest.
running_client: "Running chef-client..." running_client: "Running chef-client..."
running_client_again: "Running chef-client again (failed to converge)..." running_client_again: "Running chef-client again (failed to converge)..."
running_client_apply: "Running chef-apply..."
running_solo: "Running chef-solo..." running_solo: "Running chef-solo..."
running_solo_again: "Running chef-solo again (failed to converge)..." running_solo_again: "Running chef-solo again (failed to converge)..."
missing_shared_folders: |- missing_shared_folders: |-
@ -1784,6 +1792,9 @@ en:
server_validation_key_doesnt_exist: |- server_validation_key_doesnt_exist: |-
The validation key set for `config.chef.validation_key_path` does not exist! This The validation key set for `config.chef.validation_key_path` does not exist! This
file needs to exist so it can be uploaded to the virtual machine. file needs to exist so it can be uploaded to the virtual machine.
upload_path_empty: |-
The Chef Apply provisioner requires that the `config.chef.upload_path`
be set to a non-nil, non-empty value.
deleting_from_server: "Deleting %{deletable} \"%{name}\" from Chef server..." deleting_from_server: "Deleting %{deletable} \"%{name}\" from Chef server..."
file: file:

View File

@ -0,0 +1,98 @@
require_relative "../../../../base"
require Vagrant.source_root.join("plugins/provisioners/chef/config/chef_apply")
describe VagrantPlugins::Chef::Config::ChefApply do
include_context "unit"
subject { described_class.new }
let(:machine) { double("machine") }
def chef_error(key, options = {})
I18n.t("vagrant.provisioners.chef.#{key}", options)
end
describe "#recipe" do
it "defaults to nil" do
subject.finalize!
expect(subject.recipe).to be(nil)
end
end
describe "#log_level" do
it "defaults to :info" do
subject.finalize!
expect(subject.log_level).to be(:info)
end
it "is converted to a symbol" do
subject.log_level = "foo"
subject.finalize!
expect(subject.log_level).to eq(:foo)
end
end
describe "#upload_path" do
it "defaults to /tmp/vagrant-chef-apply.rb" do
subject.finalize!
expect(subject.upload_path).to match(%r{/tmp/vagrant-chef-apply-\d+})
end
end
describe "#validate" do
before do
allow(machine).to receive(:env)
.and_return(double("env",
root_path: "",
))
subject.recipe = <<-EOH
package "foo"
EOH
end
let(:result) { subject.validate(machine) }
let(:errors) { result["chef apply provisioner"] }
context "when the recipe is nil" do
it "returns an error" do
subject.recipe = nil
subject.finalize!
expect(errors).to include chef_error("recipe_empty")
end
end
context "when the recipe is empty" do
it "returns an error" do
subject.recipe = " "
subject.finalize!
expect(errors).to include chef_error("recipe_empty")
end
end
context "when the log_level is an empty array" do
it "returns an error" do
subject.log_level = " "
subject.finalize!
expect(errors).to include chef_error("log_level_empty")
end
end
context "when the upload_path is nil" do
it "returns an error" do
subject.upload_path = nil
subject.finalize!
expect(errors).to include chef_error("upload_path_empty")
end
end
context "when the upload_path is an empty array" do
it "returns an error" do
subject.upload_path = " "
subject.finalize!
expect(errors).to include chef_error("upload_path_empty")
end
end
end
end