lib/capistrano-chef-solo.rb in yyuu-capistrano-chef-solo-0.1.3 vs lib/capistrano-chef-solo.rb in yyuu-capistrano-chef-solo-0.1.4
- old
+ new
@@ -13,10 +13,15 @@
desc("Setup chef-solo. (an alias of chef_solo:setup)")
task(:setup, :except => { :no_release => true }) {
find_and_execute_task("chef_solo:setup")
}
+ desc("Uninstall chef-solo. (an alias of chef_solo:purge)")
+ task(:purge, :except => { :no_release => true }) {
+ find_and_execute_task("chef_solo:purge")
+ }
+
desc("Run chef-solo. (an alias of chef_solo)")
task(:default, :except => { :no_release => true }) {
find_and_execute_task("chef_solo:default")
}
@@ -30,15 +35,17 @@
find_and_execute_task("chef_solo:attributes")
}
}
namespace(:chef_solo) {
+ _cset(:chef_solo_use_bundler, true)
_cset(:chef_solo_version, "11.4.0")
_cset(:chef_solo_path) { capture("echo $HOME/chef").strip }
_cset(:chef_solo_cache_path) { File.join(chef_solo_path, "cache") }
_cset(:chef_solo_config_path) { File.join(chef_solo_path, "config") }
_cset(:chef_solo_cookbooks_path) { File.join(chef_solo_path, "cookbooks") }
+ _cset(:chef_solo_data_bags_path) { File.join(chef_solo_path, "data_bags") }
_cset(:chef_solo_config_file) { File.join(chef_solo_config_path, "solo.rb") }
_cset(:chef_solo_attributes_file) { File.join(chef_solo_config_path, "solo.json") }
_cset(:chef_solo_bootstrap_user) {
if variables.key?(:chef_solo_user)
@@ -128,21 +135,34 @@
# FIXME:
# Some variables (such like :default_environment set by capistrano-rbenv) may be
# initialized without bootstrap settings during `on :load`.
# Is there any way to avoid this without setting `:rbenv_setup_default_environment`
# as false?
- set(:rbenv_setup_default_environment, false)
+ on(:load) do
+ before("rbenv:setup_default_environment") do
+ set(:rbenv_setup_default_environment, false) if chef_solo_bootstrap
+ end
+ end
desc("Setup chef-solo.")
task(:setup, :except => { :no_release => true }) {
connect_with_settings do
transaction do
install
end
end
}
+ desc("Uninstall chef-solo.")
+ task(:purge, :except => { :no_release => true }) {
+ connect_with_settings do
+ transaction do
+ uninstall
+ end
+ end
+ }
+
desc("Run chef-solo.")
task(:default, :except => { :no_release => true }) {
connect_with_settings do
setup
transaction do
@@ -155,22 +175,22 @@
# Acts like `default`, but will apply specified recipes only.
def run_list(*recipes)
connect_with_settings do
setup
transaction do
- update(:run_list => recipes)
- invoke
+ update
+ invoke(:run_list => recipes)
end
end
end
_cset(:chef_solo_cmd, "chef-solo")
desc("Show chef-solo version.")
task(:version, :except => { :no_release => true }) {
connect_with_settings do
- run("cd #{chef_solo_path.dump} && #{bundle_cmd} exec #{chef_solo_cmd} --version")
+ execute("--version")
end
}
desc("Show chef-solo attributes.")
task(:attributes, :except => { :no_release => true }) {
@@ -179,110 +199,186 @@
roles += hosts.map { |host| role_names_for_host(ServerDefinition.new(host)) }
attributes = _generate_attributes(:hosts => hosts, :roles => roles)
STDOUT.puts(_json_attributes(attributes))
}
- task(:install, :except => { :no_release => true }) {
- install_ruby
- install_chef
- }
-
- task(:install_ruby, :except => { :no_release => true }) {
- set(:rbenv_install_bundler, true)
- find_and_execute_task("rbenv:setup")
- }
-
+ _cset(:chef_solo_gem_dependencies) {{
+ fetch(:chef_solo_gem, "chef") => chef_solo_version,
+ }}
_cset(:chef_solo_gemfile) {
- (<<-EOS).gsub(/^\s*/, "")
- source "https://rubygems.org"
- gem "chef", #{chef_solo_version.to_s.dump}
- EOS
+ gemfile = []
+ gemfile << %{source "https://rubygems.org"}
+ chef_solo_gem_dependencies.each do |name, options|
+ if options.nil?
+ gemfile << %{gem #{name.dump}}
+ else
+ gemfile << %{gem #{name.dump}, #{options.inspect}}
+ end
+ end
+ gemfile.join("\n")
}
- task(:install_chef, :except => { :no_release => true }) {
+ task(:install, :except => { :no_release => true }) {
+ set(:rbenv_install_bundler, true) if chef_solo_use_bundler
+ find_and_execute_task("rbenv:setup")
begin
- version = capture("cd #{chef_solo_path.dump} && #{bundle_cmd} exec #{chef_solo_cmd} --version")
+ version = execute("--version", :via => :capture)
installed = Regexp.new(Regexp.escape(chef_solo_version)) =~ version
rescue
installed = false
end
unless installed
- dirs = [ chef_solo_path, chef_solo_cache_path, chef_solo_config_path, chef_solo_cookbooks_path ].uniq
+ dirs = [ chef_solo_path, chef_solo_cache_path, chef_solo_config_path ].uniq
run("mkdir -p #{dirs.map { |x| x.dump }.join(" ")}")
- top.put(chef_solo_gemfile, File.join(chef_solo_path, "Gemfile"))
- args = fetch(:chef_solo_bundle_options, [])
- args << "--path=#{File.join(chef_solo_path, "bundle").dump}"
- args << "--quiet"
- run("cd #{chef_solo_path.dump} && #{bundle_cmd} install #{args.join(" ")}")
+ if chef_solo_use_bundler
+ top.put(chef_solo_gemfile, File.join(chef_solo_path, "Gemfile"))
+ args = fetch(:chef_solo_bundle_options, [])
+ args << "--path=#{File.join(chef_solo_path, "bundle").dump}"
+ args << "--quiet"
+ run("cd #{chef_solo_path.dump} && #{bundle_cmd} install #{args.join(" ")}")
+ else
+ chef_solo_gem_dependencies.each do |name, options|
+ args = String === options ? "-v #{options.dump}" : "" # options must be a version string
+ rbenv.exec("gem install #{args} #{name.dump}", :path => chef_solo_path)
+ end
+ rbenv.rehash
+ end
end
}
+ task(:uninstall, :except => { :no_release => true }) {
+ if chef_solo_use_bundler
+ run("rm -f #{File.join(chef_solo_path, "Gemfile").dump} #{File.join(chef_solo_path, "Gemfile.lock").dump}")
+ run("rm -rf #{File.join(chef_solo_path, "bundle").dump}")
+ else
+ chef_solo_gem_dependencies.each do |name, options|
+ args = String === options ? "-v #{options.dump}" : "" # options must be a version string
+ rbenv.exec("gem uninstall -I -x #{args} #{name.dump}", :path => chef_solo_path)
+ end
+ end
+ }
+
def update(options={})
update_cookbooks(options)
+ update_data_bags(options)
update_attributes(options)
update_config(options)
end
def update_cookbooks(options={})
- _normalize_cookbooks(chef_solo_cookbooks).each do |name, variables|
+ repos = _normalize_cookbooks(chef_solo_cookbooks)
+ _install_repos(:cookbooks, repos, chef_solo_cookbooks_path, options) do |name, tmpdir, variables|
+ deploy_cookbooks(name, tmpdir, variables, options)
+ end
+ end
+
+ def update_data_bags(options={})
+ repos = _normalize_data_bags(chef_solo_data_bags)
+ _install_repos(:data_bags, repos, chef_solo_data_bags_path, options) do |name, tmpdir, variables|
+ deploy_data_bags(name, tmpdir, variables, options)
+ end
+ end
+
+ def _install_repos(t, repos, destination, options={}, &block)
+ # (0) remove existing old data
+ run("rm -rf #{destination.dump} && mkdir -p #{destination.dump}", options)
+ repos.each do |name, variables|
begin
- tmpdir = capture("mktemp -d /tmp/cookbooks.XXXXXXXXXX", options).strip
+ tmpdir = capture("mktemp -d #{File.join("/tmp", "#{t}.XXXXXXXXXX").dump}", options).strip
run("rm -rf #{tmpdir.dump} && mkdir -p #{tmpdir.dump}", options)
- deploy_cookbooks(name, tmpdir, variables, options)
- install_cookbooks(name, tmpdir, chef_solo_cookbooks_path, options)
+ # (1) caller deploys the repository to tmpdir
+ yield name, tmpdir, variables
+ # (2) then deploy it to actual destination
+ logger.debug("installing #{t} `#{name}' from #{tmpdir} to #{destination}.")
+ run("rsync -lrpt #{(tmpdir + "/").dump} #{destination.dump}", options)
ensure
run("rm -rf #{tmpdir.dump}", options)
end
end
end
- #
- # The definition of cookbooks.
- # By default, load cookbooks from local path of "config/cookbooks".
- #
- _cset(:chef_solo_cookbooks_exclude, %w(.hg .git .svn))
- _cset(:chef_solo_cookbooks_default_variables) {{
+ _cset(:chef_solo_repository_cache) { File.expand_path("tmp") }
+ _cset(:chef_solo_repository_exclude, %w(.hg .git .svn))
+ _cset(:chef_solo_repository_variables) {{
:scm => :none,
:deploy_via => :copy_subdir,
:deploy_subdir => nil,
:repository => ".",
- :cookbooks_exclude => chef_solo_cookbooks_exclude,
:copy_cache => nil,
}}
- _cset(:chef_solo_cookbooks) {
- variables = chef_solo_cookbooks_default_variables.dup
- variables[:scm] = fetch(:chef_solo_cookbooks_scm) if exists?(:chef_solo_cookbooks_scm)
- variables[:deploy_subdir] = fetch(:chef_solo_cookbooks_subdir, "config/cookbooks")
- variables[:repository] = fetch(:chef_solo_cookbooks_repository) if exists?("chef_solo_cookbooks_repository")
- variables[:revision] = fetch(:chef_solo_cookbooks_revision) if exists?(:chef_solo_cookbooks_revision)
- if exists?(:chef_solo_cookbook_name)
- # deploy as single cookbook
- name = fetch(:chef_solo_cookbook_name)
- { name => variables.merge(:cookbook_name => name) }
+
+ #
+ # The definition of cookbooks.
+ # By default, load cookbooks from local path of "config/cookbooks".
+ #
+ _cset(:chef_solo_cookbooks_exclude) { chef_solo_repository_exclude }
+ _cset(:chef_solo_cookbooks_variables) { chef_solo_repository_variables.merge(:copy_exclude => chef_solo_cookbooks_exclude) }
+ _cset(:chef_solo_cookbooks) { _default_repos(:cookbook, chef_solo_cookbooks_variables) }
+ _cset(:chef_solo_cookbooks_cache) { File.join(chef_solo_repository_cache, "cookbooks-cache") }
+ def _normalize_cookbooks(repos)
+ _normalize_repos(repos, chef_solo_cookbooks_cache, chef_solo_cookbooks_variables) { |name, variables|
+ variables[:deploy_subdir] ||= variables[:cookbooks] # use :cookbooks as :deploy_subdir for backward compatibility with prior than 0.1.2
+ variables[:copy_exclude] ||= variables[:cookbooks_exclude]
+ }
+ end
+
+ #
+ # The definition of data_bags.
+ # By default, load data_bags from local path of "config/data_bags".
+ #
+ _cset(:chef_solo_data_bags_exclude) { chef_solo_repository_exclude }
+ _cset(:chef_solo_data_bags_variables) { chef_solo_repository_variables.merge(:copy_exclude => chef_solo_data_bags_exclude) }
+ _cset(:chef_solo_data_bags) { _default_repos(:data_bag, chef_solo_data_bags_variables) }
+ _cset(:chef_solo_data_bags_cache) { File.join(chef_solo_repository_cache, "data_bags-cache") }
+ def _normalize_data_bags(repos)
+ _normalize_repos(repos, chef_solo_data_bags_cache, chef_solo_data_bags_variables) { |name, variables|
+ variables[:deploy_subdir] ||= variables[:data_bags] # use :data_bags as :deploy_subdir for backward compatibility with prior than 0.1.2
+ variables[:copy_exclude] ||= variables[:data_bags_exclude]
+ }
+ end
+
+ def _default_repos(singular, variables={}, &block)
+ plural = "#{singular}s"
+ variables = variables.dup
+ variables[:scm] = fetch("chef_solo_#{plural}_scm".to_sym) if exists?("chef_solo_#{plural}_scm".to_sym)
+ variables[:deploy_subdir] = fetch("chef_solo_#{plural}_subdir".to_sym, File.join("config", plural))
+ variables[:repository] = fetch("chef_solo_#{plural}_repository".to_sym) if exists?("chef_solo_#{plural}_repository".to_sym)
+ variables[:revision] = fetch("chef_solo_#{plural}_revision".to_sym) if exists?("chef_solo_#{plural}_revision".to_sym)
+ if exists?("chef_solo_#{singular}_name".to_sym)
+ name = fetch("chef_solo_#{singular}_name".to_sym) # deploy as single cookbook
+ variables["#{singular}_name".to_sym] = name
else
- # deploy as multiple cookbooks
- name = fetch(:chef_solo_cookbooks_name, application)
- { name => variables }
+ name = fetch("chef_solo_#{plural}_name".to_sym, application) # deploy as multiple cookbooks
end
- }
+ { name => variables }
+ end
- _cset(:chef_solo_repository_cache) { File.expand_path("tmp/cookbooks-cache") }
- def _normalize_cookbooks(cookbooks)
- xs = cookbooks.map { |name, variables|
- variables = chef_solo_cookbooks_default_variables.merge(variables)
+ def _normalize_repos(repos, cache_path, default_variables={}, &block)
+ normalized = repos.map { |name, variables|
+ variables = default_variables.merge(variables)
variables[:application] ||= name
- # use :cookbooks as :deploy_subdir for backward compatibility with prior than 0.1.2
- variables[:deploy_subdir] ||= variables[:cookbooks]
if variables[:scm] != :none
- variables[:copy_cache] ||= File.expand_path(name, chef_solo_repository_cache)
+ variables[:copy_cache] ||= File.expand_path(name, cache_path)
end
+ yield name, variables
[name, variables]
}
- Hash[xs]
+ Hash[normalized]
end
def deploy_cookbooks(name, destination, variables={}, options={})
- logger.debug("retrieving cookbooks `#{name}' from #{variables[:repository]} via #{variables[:deploy_via]}.")
+ # deploy as single cookbook, or deploy as multiple cookbooks
+ final_destination = variables.key?(:cookbook_name) ? File.join(destination, variables[:cookbook_name]) : destination
+ _deploy_repo(:cookbooks, name, final_destination, variables, options)
+ end
+
+ def deploy_data_bags(name, destination, variables={}, options={})
+ # deploy as single data_bag, or deploy as multiple data_bags
+ final_destination = variables.key?(:data_bag_name) ? File.join(destination, variables[:data_bag_name]) : destination
+ _deploy_repo(:data_bags, name, final_destination, variables, options)
+ end
+
+ def _deploy_repo(t, name, destination, variables={}, options={})
begin
releases_path = capture("mktemp -d /tmp/releases.XXXXXXXXXX", options).strip
release_path = File.join(releases_path, release_name)
run("rm -rf #{releases_path.dump} && mkdir -p #{releases_path.dump}", options)
c = _middle_copy(top) # create new configuration with separated @variables
@@ -293,22 +389,22 @@
set(:revision) { source.head }
set(:source) { ::Capistrano::Deploy::SCM.new(scm, self) }
set(:real_revision) { source.local.query_revision(revision) { |cmd| with_env("LC_ALL", "C") { run_locally(cmd) } } }
set(:strategy) { ::Capistrano::Deploy::Strategy.new(deploy_via, self) }
variables.each do |key, val|
- set(key, val)
+ if val.nil?
+ unset(key)
+ else
+ set(key, val)
+ end
end
+ from = File.join(repository, fetch(:deploy_subdir, "/"))
+ to = destination
+ logger.debug("retrieving #{t} `#{name}' from #{from} (scm=#{scm}, via=#{deploy_via}) to #{to}.")
strategy.deploy!
end
- if variables.key?(:cookbook_name)
- # deploy as single cookbook
- final_destination = File.join(destination, variables[:cookbook_name])
- else
- # deploy as multiple cookbooks
- final_destination = destination
- end
- run("rsync -lrpt #{(release_path + "/").dump} #{final_destination.dump}", options)
+ run("rsync -lrpt #{(release_path + "/").dump} #{destination.dump}", options)
ensure
run("rm -rf #{releases_path.dump}", options)
end
end
@@ -319,20 +415,15 @@
o.instance_variable_set(k, v ? v.clone : v)
end
o
end
- def install_cookbooks(name, source, destination, options={})
- logger.debug("installing cookbooks `#{name}' to #{destination}.")
- run("mkdir -p #{source.dump} #{destination.dump}", options)
- run("rsync -lrpt #{(source + "/").dump} #{destination.dump}", options)
- end
-
_cset(:chef_solo_config) {
(<<-EOS).gsub(/^\s*/, "")
file_cache_path #{chef_solo_cache_path.dump}
cookbook_path #{chef_solo_cookbooks_path.dump}
+ data_bag_path #{chef_solo_data_bags_path.dump}
EOS
}
def update_config(options={})
top.put(chef_solo_config, chef_solo_config_file, options)
end
@@ -384,11 +475,10 @@
_cset(:chef_solo_role_run_list, {})
def _generate_attributes(options={})
hosts = [ options.delete(:hosts) ].flatten.compact.uniq
roles = [ options.delete(:roles) ].flatten.compact.uniq
- run_list = [ options.delete(:run_list) ].flatten.compact.uniq
#
# By default, the Chef attributes will be generated by following order.
#
# 1. Use _non-lazy_ variables of Capistrano.
# 2. Use attributes defined in `:chef_solo_attributes`.
@@ -402,46 +492,49 @@
end
hosts.each do |host|
_merge_attributes!(attributes, chef_solo_host_attributes.fetch(host, {}))
end
#
- # The Chef `run_list` will be generated by following rules.
+ # The Chef `run_list` will be generated from `:chef_solo_role_run_list` and
+ # `:chef_solo_host_run_list`.
#
- # * If `:run_list` was given as argument, just use it.
- # * Otherwise, generate it from `:chef_solo_role_run_list`, `:chef_solo_role_run_list`
- # and `:chef_solo_host_run_list`.
- #
- if run_list.empty?
- _merge_attributes!(attributes, {"run_list" => chef_solo_run_list})
- roles.each do |role|
- _merge_attributes!(attributes, {"run_list" => chef_solo_role_run_list.fetch(role, [])})
- end
- hosts.each do |host|
- _merge_attributes!(attributes, {"run_list" => chef_solo_host_run_list.fetch(host, [])})
- end
- else
- attributes["run_list"] = [] # ignore run_list not from argument
- _merge_attributes!(attributes, {"run_list" => run_list})
+ _merge_attributes!(attributes, {"run_list" => chef_solo_run_list})
+ roles.each do |role|
+ _merge_attributes!(attributes, {"run_list" => chef_solo_role_run_list.fetch(role, [])})
end
+ hosts.each do |host|
+ _merge_attributes!(attributes, {"run_list" => chef_solo_host_run_list.fetch(host, [])})
+ end
attributes
end
def update_attributes(options={})
- run_list = options.delete(:run_list)
servers = find_servers_for_task(current_task)
servers.each do |server|
logger.debug("updating chef-solo attributes for #{server.host}.")
- attributes = _generate_attributes(:hosts => server.host, :roles => role_names_for_host(server), :run_list => run_list)
+ attributes = _generate_attributes(:hosts => server.host, :roles => role_names_for_host(server))
top.put(_json_attributes(attributes), chef_solo_attributes_file, options.merge(:hosts => server.host))
end
end
def invoke(options={})
+ options = options.dup
+ run_list = [ options.delete(:run_list) ].flatten.compact
logger.debug("invoking chef-solo.")
args = fetch(:chef_solo_options, [])
- args << "-c #{chef_solo_config_file.dump}"
- args << "-j #{chef_solo_attributes_file.dump}"
- run("cd #{chef_solo_path.dump} && #{sudo} #{bundle_cmd} exec #{chef_solo_cmd} #{args.join(" ")}", options)
+ args += ["-c", chef_solo_config_file]
+ args += ["-j", chef_solo_attributes_file]
+ args += ["-o", run_list.join(",")] unless run_list.empty?
+ execute(args, options.merge(:via => :sudo))
+ end
+
+ def execute(args, options={})
+ if chef_solo_use_bundler
+ command = "bundle exec #{chef_solo_cmd}"
+ else
+ command = chef_solo_cmd
+ end
+ rbenv.exec("#{command} #{[ args ].flatten.compact.map { |x| x.dump }.join(" ")}", options.merge(:path => chef_solo_path))
end
}
}
end
end