Merge branch 'sethvargo/chef_apply'

This commit is contained in:
Seth Vargo 2014-10-30 16:53:11 -04:00
commit 5483cadb87
8 changed files with 363 additions and 17 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

@ -16,23 +16,23 @@ module VagrantPlugins
def initialize
super
@chef_server_url = UNSET_VALUE
@client_key_path = UNSET_VALUE
@delete_client = UNSET_VALUE
@delete_node = UNSET_VALUE
@validation_key_path = UNSET_VALUE
@validation_client_name = UNSET_VALUE
@chef_server_url = UNSET_VALUE
@client_key_path = UNSET_VALUE
@delete_client = UNSET_VALUE
@delete_node = UNSET_VALUE
@validation_key_path = UNSET_VALUE
@validation_client_name = UNSET_VALUE
end
def finalize!
super
@chef_server_url = nil if @chef_server_url == UNSET_VALUE
@chef_server_url = nil if @chef_server_url == UNSET_VALUE
@client_key_path = "/etc/chef/client.pem" if @client_key_path == UNSET_VALUE
@delete_client = false if @delete_client == UNSET_VALUE
@delete_node = false if @delete_node == UNSET_VALUE
@delete_client = false if @delete_client == UNSET_VALUE
@delete_node = false if @delete_node == UNSET_VALUE
@validation_client_name = "chef-validator" if @validation_client_name == UNSET_VALUE
@validation_key_path = nil if @validation_key_path == UNSET_VALUE
@validation_key_path = nil if @validation_key_path == UNSET_VALUE
end
def validate(machine)

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

View File

@ -174,6 +174,7 @@
<li<%= sidebar_current("provisioning-cfengine") %>><a href="/v2/provisioning/cfengine.html">CFEngine</a></li>
<li<%= sidebar_current("provisioning-chefsolo") %>><a href="/v2/provisioning/chef_solo.html">Chef Solo</a></li>
<li<%= sidebar_current("provisioning-chefclient") %>><a href="/v2/provisioning/chef_client.html">Chef Client</a></li>
<li<%= sidebar_current("provisioning-chefapply") %>><a href="/v2/provisioning/chef_apply.html">Chef Apply</a></li>
<li<%= sidebar_current("provisioning-docker") %>><a href="/v2/provisioning/docker.html">Docker</a></li>
<li<%= sidebar_current("provisioning-puppetapply") %>><a href="/v2/provisioning/puppet_apply.html">Puppet Apply</a></li>
<li<%= sidebar_current("provisioning-puppetagent") %>><a href="/v2/provisioning/puppet_agent.html">Puppet Agent</a></li>

View File

@ -0,0 +1,102 @@
---
page_title: "Chef Apply - Provisioning"
sidebar_current: "provisioning-chefapply"
---
# Chef Apply Provisioner
**Provisioner name: `chef_apply`**
The Chef Apply provisioner allows you to provision the guest using
[Chef](https://www.getchef.com/), specifically with
[Chef Apply](https://docs.getchef.com/ctl_chef_apply.html).
Chef Apply is ideal for people who are already experienced with Chef and the
Chef ecosystem. Specifically, this documentation page does not cover how use
Chef or how to write Chef recipes.
<div class="alert alert-warn">
<p>
<strong>Warning:</strong> If you are not familiar with Chef and Vagrant already,
we recommend starting with the <a href="/v2/provisioning/shell.html">shell
provisioner</a>.
</p>
</div>
## Options
This section lists the complete set of available options for the Chef Apply
provisioner. More detailed examples of how to use the provisioner are
available below this section.
Due to the unqiue nature of Chef Apply, the Chef Apply provisioner does not
inherit the [common options for other Chef provisioners](/v2/provisioning/chef_common.html).
* `recipe` (string) - The raw recipe contents to execute using Chef Apply on
the guest.
* `log_level` (string) - The log level to use while executing `chef-apply`. The
default value is "info".
* `upload_path` (string) - **Advanced!** The location on the guest where the
generated recipe file should be stored. For most use cases, it is unlikely you
will need to customize this value. The default value is
`/tmp/vagrant-chef-apply-#` where `#` is a unique counter generated by
Vagrant to prevent collisions.
## Specifying a Recipe
The easiest way to get started with the Chef Apply provisioner is to just
specify an inline
[Chef recipe](https://docs.getchef.com/essentials_cookbook_recipes.html). For
example:
```ruby
Vagrant.configure("2") do |config|
config.vm.provision "chef_apply" do |chef|
chef.recipe = "package[apache2]"
end
end
```
This causes Vagrant to run Chef Apply with the given recipe contents. If you are
familiar with Chef, you know this will install the apache2 package from the
system package provider.
Since single-line Chef recipes are rare, you can also specify the recipe using a
"heredoc":
```ruby
Vagrant.configure("2") do |config|
config.vm.provision "chef_apply" do |chef|
chef.recipe = <<-RECIPE
package "apache2"
template "/etc/apache2/my.config" do
# ...
end
RECIPE
end
end
```
Finally, if you would prefer to store the recipe as plain-text, you can set the
recipe to the contents of a file:
```ruby
Vagrant.configure("2") do |config|
config.vm.provision "chef_apply" do |chef|
chef.recipe = File.read("/path/to/my/recipe.rb")
end
end
```
## Roles
The Vagrant Chef Apply provisioner does not support roles. Please use the a
different Vagrant Chef provisioner if you need support for roles.
## Data Bags
The Vagrant Chef Apply provisioner does not support data_bags. Please use the a
different Vagrant Chef provisioner if you need support for data_bags.