Update chef_zero provisioner to use real chef-zero/local-mode to support cookbook version constraint

This commit is contained in:
Michael Lopez 2015-02-14 22:32:16 +01:00
parent 74ffcb27d6
commit 8adc10f295
3 changed files with 312 additions and 19 deletions

View File

@ -3,26 +3,104 @@ require_relative "chef_solo"
module VagrantPlugins
module Chef
module Config
class ChefZero < ChefSolo
attr_accessor :nodes_path
class ChefZero < BaseRunner
# The path on disk where Chef cookbooks are stored.
# Default is "cookbooks".
# @return [String]
attr_accessor :cookbooks_path
# The path where data bags are stored on disk.
# @return [String]
attr_accessor :data_bags_path
# The path where environments are stored on disk.
# @return [String]
attr_accessor :environments_path
# The path where roles are stored on disk.
# @return [String]
attr_accessor :roles_path
# The type of synced folders to use.
# @return [String]
attr_accessor :synced_folder_type
def initialize
super
@nodes_path = UNSET_VALUE
@cookbooks_path = UNSET_VALUE
@data_bags_path = UNSET_VALUE
@environments_path = UNSET_VALUE
@roles_path = UNSET_VALUE
@synced_folder_type = UNSET_VALUE
end
def finalize!
super
@nodes_path = [] if @nodes_path == UNSET_VALUE
@synced_folder_type = nil if @synced_folder_type == UNSET_VALUE
if @cookbooks_path == UNSET_VALUE
@cookbooks_path = []
@cookbooks_path << [:host, "cookbooks"] if !@recipe_url
@cookbooks_path << [:vm, "cookbooks"]
end
@data_bags_path = [] if @data_bags_path == UNSET_VALUE
@roles_path = [] if @roles_path == UNSET_VALUE
@environments_path = [] if @environments_path == UNSET_VALUE
@environments_path = [@environments_path].flatten
# Make sure the path is an array.
@nodes_path = prepare_folders_config(@nodes_path)
@cookbooks_path = prepare_folders_config(@cookbooks_path)
@data_bags_path = prepare_folders_config(@data_bags_path)
@roles_path = prepare_folders_config(@roles_path)
@environments_path = prepare_folders_config(@environments_path)
end
def validate(machine)
{ "chef zero provisioner" => super["chef solo provisioner"] }
errors = validate_base(machine)
if [cookbooks_path].flatten.compact.empty?
errors << I18n.t("vagrant.config.chef.cookbooks_path_empty")
end
if environment && environments_path.empty?
errors << I18n.t("vagrant.config.chef.environment_path_required")
end
environments_path.each do |type, raw_path|
next if type != :host
path = Pathname.new(raw_path).expand_path(machine.env.root_path)
if !path.directory?
errors << I18n.t("vagrant.config.chef.environment_path_missing",
path: raw_path.to_s
)
end
end
{ "chef zero provisioner" => errors }
end
protected
# This takes any of the configurations that take a path or
# array of paths and turns it into the proper format.
#
# @return [Array]
def prepare_folders_config(config)
# Make sure the path is an array
config = [config] if !config.is_a?(Array) || config.first.is_a?(Symbol)
return [] if config.flatten.compact.empty?
# Make sure all the paths are in the proper format
config.map do |path|
path = [:host, path] if !path.is_a?(Array)
path
end
end
end
end

View File

@ -1,36 +1,207 @@
require "digest/md5"
require "securerandom"
require "set"
require "log4r"
require_relative "chef_solo"
require "vagrant/util/counter"
require_relative "base"
module VagrantPlugins
module Chef
module Provisioner
# This class implements provisioning via chef-zero.
class ChefZero < ChefSolo
attr_reader :node_folders
class ChefZero < Base
extend Vagrant::Util::Counter
include Vagrant::Util::Counter
include Vagrant::Action::Builtin::MixinSyncedFolders
attr_reader :environments_folders
attr_reader :cookbook_folders
attr_reader :role_folders
attr_reader :data_bags_folders
def initialize(machine, config)
super
@logger = Log4r::Logger.new("vagrant::provisioners::chef_zero")
@shared_folders = []
end
def configure(root_config)
super
@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")
@environments_folders = expanded_folders(@config.environments_path, "environments")
@node_folders = expanded_folders(@config.nodes_path, "nodes")
share_folders(root_config, "csn", @node_folders)
existing = synced_folders(@machine, cached: true)
share_folders(root_config, "csc", @cookbook_folders, existing)
share_folders(root_config, "csr", @role_folders, existing)
share_folders(root_config, "csdb", @data_bags_folders, existing)
share_folders(root_config, "cse", @environments_folders, existing)
end
def provision
super(:zero)
def provision(mode = :client)
install_chef
# Verify that the proper shared folders exist.
check = []
@shared_folders.each do |type, local_path, remote_path|
# We only care about checking folders that have a local path, meaning
# they were shared from the local machine, rather than assumed to
# exist on the VM.
check << remote_path if local_path
end
chown_provisioning_folder
verify_shared_folders(check)
verify_binary(chef_binary_path("chef-client"))
upload_encrypted_data_bag_secret
setup_json
setup_zero_config
run_chef(mode)
delete_encrypted_data_bag_secret
end
def solo_config
super.merge(
# Converts paths to a list of properly expanded paths with types.
def expanded_folders(paths, appended_folder=nil)
# Convert the path to an array if it is a string or just a single
# path element which contains the folder location (:host or :vm)
paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
results = []
paths.each do |type, path|
# Create the local/remote path based on whether this is a host
# or VM path.
local_path = nil
remote_path = nil
if type == :host
# Get the expanded path that the host path points to
local_path = File.expand_path(path, @machine.env.root_path)
if File.exist?(local_path)
# Path exists on the host, setup the remote path. We use
# the MD5 of the local path so that it is predictable.
key = Digest::MD5.hexdigest(local_path)
remote_path = "#{@config.provisioning_path}/#{key}"
else
@machine.ui.warn(I18n.t("vagrant.provisioners.chef.cookbook_folder_not_found_warning",
path: local_path.to_s))
next
end
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)
# 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
# to achieve this. Otherwise, Vagrant attempts to share at some crazy
# path like /home/vagrant/c:/foo/bar
remote_path = remote_path.gsub(/^[a-zA-Z]:/, "")
end
# If we have specified a folder name to append then append it
remote_path += "/#{appended_folder}" if appended_folder
# Append the result
results << [type, local_path, remote_path]
end
results
end
# Shares the given folders with the given prefix. The folders should
# be of the structure resulting from the `expanded_folders` function.
def share_folders(root_config, prefix, folders, existing=nil)
existing_set = Set.new
(existing || []).each do |_, fs|
fs.each do |id, data|
existing_set.add(data[:guestpath])
end
end
folders.each do |type, local_path, remote_path|
next if type != :host
# If this folder already exists, then we don't share it, it means
# it was already put down on disk.
if existing_set.include?(remote_path)
@logger.debug("Not sharing #{local_path}, exists as #{remote_path}")
next
end
opts = {}
opts[:id] = "v-#{prefix}-#{self.class.get_and_update_counter(:shared_folder)}"
opts[:type] = @config.synced_folder_type if @config.synced_folder_type
root_config.vm.synced_folder(local_path, remote_path, opts)
end
@shared_folders += folders
end
def setup_zero_config
setup_config("provisioners/chef_zero/zero", "client.rb", {
local_mode: true,
node_path: guest_paths(@node_folders).first
)
enable_reporting: false,
cookbooks_path: guest_paths(@cookbook_folders),
roles_path: guest_paths(@role_folders),
data_bags_path: guest_paths(@data_bags_folders).first,
environments_path: guest_paths(@environments_folders).first,
})
end
def run_chef(mode)
if @config.run_list && @config.run_list.empty?
@machine.ui.warn(I18n.t("vagrant.chef_run_list_empty"))
end
if @machine.guest.capability?(:wait_for_reboot)
@machine.guest.capability(:wait_for_reboot)
end
command = build_command(:client)
@config.attempts.times do |attempt|
if attempt == 0
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_#{mode}")
else
@machine.ui.info I18n.t("vagrant.provisioners.chef.running_#{mode}_again")
end
opts = { error_check: false, elevated: true }
exit_status = @machine.communicate.sudo(command, opts) do |type, data|
# Output the data with the proper color based on the stream.
color = type == :stdout ? :green : :red
data = data.chomp
next if data.empty?
@machine.ui.info(data, color: color)
end
# There is no need to run Chef again if it converges
return if exit_status == 0
end
# If we reached this point then Chef never converged! Error.
raise ChefError, :no_convergence
end
def verify_shared_folders(folders)
folders.each do |folder|
@logger.debug("Checking for shared folder: #{folder}")
if !@machine.communicate.test("test -d #{folder}", sudo: true)
raise ChefError, :missing_shared_folders
end
end
end
protected
# Extracts only the remote paths from a list of folders
def guest_paths(folders)
folders.map { |parts| parts[2] }
end
end
end

View File

@ -0,0 +1,44 @@
<% if node_name %>
node_name "<%= node_name %>"
<% end %>
file_cache_path "<%= file_cache_path %>"
file_backup_path "<%= file_backup_path %>"
cookbook_path <%= cookbooks_path.inspect %>
<% if roles_path %>
role_path <%= roles_path.size == 1 ? roles_path.first.inspect : roles_path.inspect %>
<% end %>
log_level <%= log_level.inspect %>
verbose_logging <%= verbose_logging.inspect %>
<% if ! enable_reporting %>
enable_reporting <%= enable_reporting.inspect %>
<% end %>
encrypted_data_bag_secret <%= encrypted_data_bag_secret.inspect %>
<% if data_bags_path -%>
data_bag_path <%= data_bags_path.inspect %>
<% end %>
<% if environments_path %>
environment_path <%= environments_path.inspect %>
<% end -%>
<% if environment %>
environment "<%= environment %>"
<% end -%>
<% if local_mode -%>
chef_zero.enabled true
local_mode true
<% end -%>
<% if node_path -%>
node_path <%= node_path.inspect %>
<% end -%>
<% if formatter %>
add_formatter "<%= formatter %>"
<% end %>
<% if custom_configuration -%>
Chef::Config.from_file "<%= custom_configuration %>"
<% end -%>