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"
description <<-DESC
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
config(:chef_solo, :provisioner) do
require_relative "config/chef_solo"
Config::ChefSolo
config(:chef_apply, :provisioner) do
require_relative "config/chef_apply"
Config::ChefApply
end
config(:chef_client, :provisioner) do
@ -23,14 +23,19 @@ module VagrantPlugins
Config::ChefClient
end
config(:chef_solo, :provisioner) do
require_relative "config/chef_solo"
Config::ChefSolo
end
config(:chef_zero, :provisioner) do
require_relative "config/chef_zero"
Config::ChefZero
end
provisioner(:chef_solo) do
require_relative "provisioner/chef_solo"
Provisioner::ChefSolo
provisioner(:chef_apply) do
require_relative "provisioner/chef_apply"
Provisioner::ChefApply
end
provisioner(:chef_client) do
@ -38,6 +43,11 @@ module VagrantPlugins
Provisioner::ChefClient
end
provisioner(:chef_solo) do
require_relative "provisioner/chef_solo"
Provisioner::ChefSolo
end
provisioner(:chef_zero) do
require_relative "provisioner/chef_zero"
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..."
json: "Generating chef JSON and uploading..."
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_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_again: "Running chef-client again (failed to converge)..."
running_client_apply: "Running chef-apply..."
running_solo: "Running chef-solo..."
running_solo_again: "Running chef-solo again (failed to converge)..."
missing_shared_folders: |-
@ -1784,6 +1792,9 @@ en:
server_validation_key_doesnt_exist: |-
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.
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..."
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