chake-0.12/0000755000004100000410000000000012643206100012531 5ustar www-datawww-datachake-0.12/Rakefile0000644000004100000410000000431712643206100014203 0ustar www-datawww-datanamespace :bundler do require "bundler/gem_tasks" end task :test do sh 'rspec', '--color' end pkg = Gem::Specification.load('chake.gemspec') task 'build:tarball' => 'bundler:build' do chdir 'pkg' do sh 'gem2tgz', "#{pkg.name}-#{pkg.version}.gem" end end desc 'Create Debian source package' task 'build:debsrc' => ['build:tarball'] do dirname = "#{pkg.name}-#{pkg.version}" chdir 'pkg' do sh 'gem2deb', '--no-wnpp-check', '-s', '-p', pkg.name, "#{dirname}.tar.gz" chdir dirname do ln 'man/Rakefile', 'debian/dh_ruby.rake' sh "sed -i -e 's/-1/-1~0/ ; s/\*.*/* Development snapshot/' debian/changelog" sh 'dpkg-buildpackage', '-S', '-us', '-uc' end end end desc 'Builds and installs Debian package' task 'deb:install' => 'build:debsrc'do chdir "pkg/#{pkg.name}-#{pkg.version}" do sh 'fakeroot debian/rules binary' end sh 'sudo', 'dpkg', '-i', "pkg/#{pkg.name}_#{pkg.version}-1~0_all.deb" end desc 'Create source RPM package' task 'build:rpmsrc' => ['build:tarball', 'pkg/chake.spec'] do chdir 'pkg' do sh 'rpmbuild --define "_topdir .rpmbuild" --define "_sourcedir $(pwd)" --define "_srcrpmdir $(pwd)" -bs chake.spec --nodeps' end end file 'pkg/chake.spec' => ['chake.spec.erb', 'lib/chake/version.rb'] do |t| require 'erb' pkg = Gem::Specification.load('chake.gemspec') template = ERB.new(File.read('chake.spec.erb')) File.open(t.name, 'w') do |f| f.puts(template.result(binding)) end puts "Generated #{t.name}" end task 'build:all' => ['build:debsrc', 'build:rpmsrc'] desc 'lists changes since last release' task :changelog do last_tag = `git tag | sort -V`.split.last sh 'git', 'shortlog', last_tag + '..' end task :check_tag do last_tag = `git tag | sort -V`.split.last if last_tag == "v#{pkg.version}" fail "Version #{pkg.version} was already released!" end end desc 'checks if the latest release is properly documented in ChangeLog.md' task :check_changelog do begin sh 'grep', '^#\s*' + pkg.version.to_s, 'ChangeLog.md' rescue puts "Version #{pkg.version} not documented in ChangeLog.md!" raise end end desc 'Makes a release' task :release => [:check_tag, :check_changelog, :test, 'bundler:release'] task :default => :test chake-0.12/bin/0000755000004100000410000000000012643206100013301 5ustar www-datawww-datachake-0.12/bin/chake0000755000004100000410000000103712643206100014303 0ustar www-datawww-data#!/usr/bin/env ruby require 'rake' rakefiles = %w[rakefile Rakefile rakefile.rb Rakefile.rb] if (!rakefiles.any? { |f| File.exist?(f) }) && !ARGV.include?('-f') && !ARGV.include?('--rakefile') require 'tmpdir' require 'fileutils' # syntethize a Rakefile tmpdir = Dir.mktmpdir rakefile = File.join(tmpdir, 'Rakefile') File.open(rakefile, 'w') do |f| f.puts 'require "chake"' end ARGV.unshift << '--rakefile' << rakefile # clenup after finishing at_exit do FileUtils.rm_rf tmpdir end end Rake.application.run chake-0.12/Gemfile0000644000004100000410000000013212643206100014020 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in chake.gemspec gemspec chake-0.12/examples/0000755000004100000410000000000012643206100014347 5ustar www-datawww-datachake-0.12/examples/test/0000755000004100000410000000000012643206100015326 5ustar www-datawww-datachake-0.12/examples/test/Rakefile0000644000004100000410000000065312643206100016777 0ustar www-datawww-data$:.unshift '../../lib' # this shouldn't be needed when you have chake installed require 'chake' manifest = %w[ Rakefile cookbooks cookbooks/example cookbooks/example/recipes cookbooks/example/recipes/default.rb cookbooks/example/files cookbooks/example/files/host-homer cookbooks/example/files/host-homer/test.asc config.rb ] desc 'removes everything' task :clean do rm_rf Dir.glob('**/*') - manifest end chake-0.12/examples/test/config.rb0000644000004100000410000000024112643206100017115 0ustar www-datawww-dataroot = File.expand_path(File.dirname(__FILE__)) file_cache_path root + '/cache' cookbook_path root + '/cookbooks' role_path root + '/config/roles' chake-0.12/examples/test/cookbooks/0000755000004100000410000000000012643206100017317 5ustar www-datawww-datachake-0.12/examples/test/cookbooks/example/0000755000004100000410000000000012643206100020752 5ustar www-datawww-datachake-0.12/examples/test/cookbooks/example/files/0000755000004100000410000000000012643206100022054 5ustar www-datawww-datachake-0.12/examples/test/cookbooks/example/files/host-homer/0000755000004100000410000000000012643206100024141 5ustar www-datawww-datachake-0.12/examples/test/cookbooks/example/files/host-homer/test.asc0000644000004100000410000000156412643206100025616 0ustar www-datawww-data-----BEGIN PGP MESSAGE----- Version: GnuPG v1 hQIMA5A8ZkAWdYz7AQ//dgQhKXuGS0dY04TXa1cXEtOHYC64LLnoo+UZT/KgnkzI /IAgLFbAV0Fd9DGLK857qv9fWf/FB5b6loYLPOVEys0mb5aQrqPPFeKwTTXxTJdk Ts2NvSVfNZx95Igotm/vkKW6/dbGlDfQSOVQMzhvmKOMMpRP+ixqn+G/nwE/0wQG QXc3UPAe9gBFlI9GWLTafhftwrxXiYeNF8N03W9BUk1OiQqJUmDRK9ZjVe/vbEiZ iQ689MnF5l+/6gptU0j77QIqk5vEItkl7RISxOS8PDFI+926NV3fZUrRAlu2eDIy HNMGkcKTjGoPNrj/UzzmqjP3uNQtKoK559cul7uY44QmsEINoFP3HoAOHeVxm1pG 6IG5EY6znwVEYTIVPK21NAPFpfk9yAB53sv6GrtOaYp8FFp+lLHJlPQcOOP51UV9 GqiAuec7Lgr3iy1yaXIBHFlLG0lmZ1OI41zwqh+Z8EF1NC0gFPhGuOJqBGmBbOxy Wt1IFx1JUHi0f277us9pFbQlcUb8tgFZ0J69epBrod+xWaZQKFwLWsCTN66fKJfp rqBzRBcNDJwsKOd54v/Cmrws8bwnfB8iNZYuEQdEt9u5TGIZFl4R9k+/pydAQouP Z95g4vh2ST2ZEeblnbCc2TFY/7j2O+aXyBzX/4+/bM0kZbFjxCNuefU/v3ZnDwXS QAH+NPQ31IiqMmoETUqHzo1UI+hV9em81Llt2bsbgWULp84LEmzczbjAcMi2EWNt SzPyJzOLrqEvEYg2O/+bGho= =Y2iZ -----END PGP MESSAGE----- chake-0.12/examples/test/cookbooks/example/recipes/0000755000004100000410000000000012643206100022404 5ustar www-datawww-datachake-0.12/examples/test/cookbooks/example/recipes/default.rb0000644000004100000410000000040312643206100024352 0ustar www-datawww-datafile '/tmp/chake.test' do content "It works on #{node[:fqdn]}!\n" mode '0644' owner 'root' group 'root' end cookbook_file '/tmp/chake.test.unencrypted' do source 'test' mode '0600' owner 'root' group 'root' end chake-0.12/ChangeLog.md0000644000004100000410000001005212643206100014700 0ustar www-datawww-data# 0.12 * Switch manpage build from ronn to asciidoctor * Add ability to override the Chef configuration file by setting `$CHAKE_CHEF_CONFIG` (default: `config.rb`) * bootstrap: ensure short hostname is in /etc/hosts # 0.11 * bootstrap: make sure FQDN matches hostname * Add `rake check` task to check SSH connectivity and sudo setup * Add tasks to apply a single recipe to nodes: `rake apply[recipe]` and `rake apply:$NODE[recipe]`. If `[recipe]` is not passed in the command line, the user is prompted for the recipe name. * run task changed to have the same interface and behavior as the new apply task: `rake run[command]`, or `rake run:$NODE[command]`. If `[command]` is not passed in the command line, the user is prompted for the command. # 0.10.2 * Fix check for modified files at the upload phase. Now chake will properly avoiding rsync calls when there is no changed files since the latest upload. * Fix generated RPM spec file. Will now properly build, install, and work under both CentOS 7 and Fedora 22+. * Collect test coverage statistics when running tests. * Added dependency on simplecov # 0.10.1 * actually implement support for custom ports in Node URL's. Despite being documented, that didn't actually work until now. # 0.10 * Add hook functionality. See README/manpage for documentation. * README.md: a few reviews # 0.9.1 * fix manpage installation path # 0.9 * fix build step for obs uploads * add infrastructure to build and install a manpage * Add support for a nodes.d/ directory; very useful when dealing with a larger amount of nodes. # 0.8 * gemspec: minor improvements in the long description * LICENSE.txt: fixed license name * run: print small message before prompting * Add history support for the `run` tasks * Abort `run` tasks if no command is provided # 0.7 * gemspec: improve summary and description * Also for encrypted files under $cookbook/files/, and not only under $cookbook/files/\*/. * Allow overriding tmpdir with `$CHAKE_TMPDIR` * Stop cloud-init from resetting the hostname # 0.6 * Support a ssh prefix command by setting `$CHAKE_SSH_PREFIX` in the environment. For example, `CHAKE_SSH_PREFIX=tsocks` will make all ssh invocations as `tocks ssh ...` instead of just `ssh ...`. # 0.5 * Add a task login:$host that you can use to easily log in to any of your hosts. # 0.4.3 * When running remote commands as root, run `sudo COMMAND` directly instead of `sudo sh -c "COMMAND"`. Under over-restrictive sudo setups (i.e. one in which you cannot run a shell as root), `sudo sh -c "FOO"` will not be allowed. # 0.4.2 * tmp/chake: create only when actually needed * Control nodes files with `$CHAKE_NODES` # 0.4.1 * Don't always assume the local username as the remote username for SSH connections: * `user@host`: connect with `user@host` * `host`: connect with `host` (username will be obtained by SSH itself from either its configuration files or the current username) # 0.4.0 * Redesign build of RPM package * Output of command run on nodes is now aligned * Change storage of temporary files from .tmp to tmp/chake * The JSON node attributes files generated in tmp/chake are not readable * SSH config file can now be controlled with the `$CHAKE_SSH_CONFIG` environment variable * Extra options for rsync can now be passed in the `$CHAKE_RSYNC_OPTIONS` environment variable * Chake::VERSION is now available in Rakefiles * update test suite to use new rspec syntax instead the old one which is obsolete in rspec 3. * Thanks to Athos Ribeiro. # 0.3.3 * rsync: exclude cache/ to work with the version of rsync in OSX # 0.3.2 * Now finally, hopefully, really fix RPM builds * chake init: rename 'myhost' → 'basics' * The official home is on gitlab * Completed basic documentation # 0.3.1 * Fix setting hostname when bootstrapping * Rakefile: do not allow releases without a changelog entry * Now *really* fix RPM builds, hopefully # 0.3 * Fix RPM build * bootstrap: set hostname # 0.2.3 * No functional changes * Small changes to make chake compatible with Debian 7, and most of the RPM-based distributions chake-0.12/LICENSE.txt0000644000004100000410000000210612643206100014353 0ustar www-datawww-dataCopyright (c) 2014 Antonio Terceiro The Expat License (a.k.a. "MIT") Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. chake-0.12/spec/0000755000004100000410000000000012643206100013463 5ustar www-datawww-datachake-0.12/spec/spec_helper.rb0000644000004100000410000000222212643206100016277 0ustar www-datawww-databegin require 'simplecov' SimpleCov.start rescue LoadError puts "W: simplecov not installed, we won't have a coverage report" end require 'chake/node' require 'chake/backend' require 'rspec/version' if RSpec::Version::STRING < '2.14' puts "Skipping tests, need RSpec >= 2.14" exit end shared_examples "Chake::Backend" do |backend_class| let(:backend) { backend_class.new(node) } it('runs commands') do io = StringIO.new("line 1\nline 2\n") expect(IO).to receive(:popen).with(backend.command_runner + ['something']).and_return(io) expect(backend).to receive(:printf).with(anything, "myhost", "something") expect(backend).to receive(:printf).with(anything, "myhost", "line 1") expect(backend).to receive(:printf).with(anything, "myhost", "line 2") backend.run('something') end it('runs as root') do expect(backend).to receive(:run).with('sudo something') backend.run_as_root('something') end it('does not use sudo if already root') do allow(backend.node).to receive(:remote_username).and_return('root') expect(backend).to receive(:run).with('something') backend.run_as_root('something') end end chake-0.12/spec/chake/0000755000004100000410000000000012643206100014536 5ustar www-datawww-datachake-0.12/spec/chake/backend_spec.rb0000644000004100000410000000003112643206100017456 0ustar www-datawww-datarequire 'chake/backend' chake-0.12/spec/chake/node_spec.rb0000644000004100000410000000375312643206100017032 0ustar www-datawww-datarequire 'chake/node' describe Chake::Node do before do ent = double allow(ent).to receive(:name).and_return('jonhdoe') allow(Etc).to receive(:getpwuid).and_return(ent) end let(:simple) { Chake::Node.new('hostname') } it('has a name') { expect(simple.hostname).to eq('hostname') } it('uses ssh by default') { expect(simple.backend).to be_an_instance_of(Chake::Backend::Ssh) } it('user current username by default') { expect(simple.username).to eq('jonhdoe') } it('writes to /var/tmp/chef.$username') { expect(simple.path).to eq('/var/tmp/chef.jonhdoe') } let(:with_username) { Chake::Node.new('username@hostname') } it('accepts username') { expect(with_username.username).to eq('username') } it('uses ssh') { expect(with_username.backend).to be_an_instance_of(Chake::Backend::Ssh) } let(:with_backend) { Chake::Node.new('local://hostname')} it('accepts backend as URI scheme') { expect(with_backend.backend).to be_an_instance_of(Chake::Backend::Local) } it('wont accept any backend') do expect { Chake::Node.new('foobar://bazqux').backend }.to raise_error(ArgumentError) end let(:with_data) { Chake::Node.new('local://localhost', 'run_list' => ['recipe[common]']) } it('takes data') do expect(with_data.data).to be_a(Hash) end let(:with_port) { Chake::Node.new('ssh://foo.bar.com:2222') } it('accepts a port specification') do expect(with_port.port).to eq(2222) end let(:with_port_but_no_scheme) { Chake::Node.new('foo.bar.com:2222') } it('accepts a port specification without a scheme') do expect(with_port_but_no_scheme.port).to eq(2222) expect(with_port_but_no_scheme.backend.to_s).to eq('ssh') end [:run, :run_as_root, :rsync_dest].each do |method| it("delegates #{method} to backend") do node = simple backend = double args = Object.new allow(node).to receive(:backend).and_return(backend) expect(backend).to receive(method).with(args) node.send(method, args) end end end chake-0.12/spec/chake/backend/0000755000004100000410000000000012643206100016125 5ustar www-datawww-datachake-0.12/spec/chake/backend/ssh_spec.rb0000644000004100000410000000213212643206100020257 0ustar www-datawww-datarequire 'spec_helper' describe Chake::Backend::Ssh do include_examples "Chake::Backend", Chake::Backend::Ssh let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chef') } it('runs commands with ssh') { expect(backend.command_runner).to eq(['ssh', 'myuser@myhost']) } it('rsyncs over ssh') { expect(backend.rsync_dest).to eq('myuser@myhost:/srv/chef/') } it 'uses no remote username if none was passed' do node = Chake::Node.new('theserver') expect(node.username).to eq(Etc.getpwuid.name) expect(node.remote_username).to be_nil end it 'uses username is passwd' do expect(node.username).to eq('myuser') expect(node.remote_username).to eq('myuser') end context 'with a custom port' do let(:node) { Chake::Node.new('ssh://myhost:2222') } it 'uses port with ssh' do expect(backend.command_runner).to eq(['ssh', '-p', '2222', 'myhost']) end it 'uses port with scp' do expect(backend.scp).to eq(['scp', '-P', '2222']) end it 'uses port with rsync' do expect(backend.send(:rsync_ssh)).to eq(['-e', 'ssh -p 2222']) end end end chake-0.12/spec/chake/backend/local_spec.rb0000644000004100000410000000101112643206100020547 0ustar www-datawww-datarequire 'spec_helper' describe Chake::Backend::Local do include_examples "Chake::Backend", Chake::Backend::Local let(:node) { Chake::Node.new('local://myusername@myhost/srv/chef') } it('runs commands with sh -c') { expect(backend.command_runner).to eq(['sh', '-c']) } it('rsyncs locally') { expect(backend.rsync_dest).to eq('/srv/chef/') } it('skips if hostname is not the local hostname') do allow(Socket).to receive(:gethostname).and_return('otherhost') expect(node.skip?).to eq(true) end end chake-0.12/lib/0000755000004100000410000000000012643206100013277 5ustar www-datawww-datachake-0.12/lib/chake.rb0000644000004100000410000002105612643206100014703 0ustar www-datawww-data# encoding: UTF-8 require 'yaml' require 'json' require 'tmpdir' require 'chake/version' require 'chake/node' require 'chake/readline' require 'chake/tmpdir' chef_config = ENV['CHAKE_CHEF_CONFIG'] || 'config.rb' nodes_file = ENV['CHAKE_NODES'] || 'nodes.yaml' nodes_directory = ENV['CHAKE_NODES_D'] || 'nodes.d' node_data = File.exists?(nodes_file) && YAML.load_file(nodes_file) || {} Dir.glob(File.join(nodes_directory, '*.yaml')).sort.each do |f| node_data.merge!(YAML.load_file(f)) end $nodes = node_data.map { |node,data| Chake::Node.new(node, data) }.reject(&:skip?).uniq(&:hostname) $chake_tmpdir = Chake.tmpdir desc "Initializes current directory with sample structure" task :init do if File.exists?('nodes.yaml') puts '[exists] nodes.yaml' else File.open('nodes.yaml', 'w') do |f| sample_nodes = < bootstrap_steps do |t| mkdir_p(File.dirname(bootstrap_script)) File.open(t.name, 'w') do |f| f.puts '#!/bin/sh' f.puts 'set -eu' bootstrap_steps.each do |platform| f.puts(File.read(platform)) end end chmod 0755, t.name end desc "bootstrap #{hostname}" task "bootstrap:#{hostname}" => [:bootstrap_common, bootstrap_script] do config = File.join($chake_tmpdir, hostname + '.json') if File.exists?(config) # already bootstrapped, just overwrite write_json_file(config, node.data) else # copy bootstrap script over scp = node.scp target = "/tmp/.chake-bootstrap.#{Etc.getpwuid.name}" sh *scp, bootstrap_script, node.scp_dest + target # run bootstrap script node.run_as_root("#{target} #{hostname}") # overwrite config with current contents mkdir_p File.dirname(config) write_json_file(config, node.data) end end desc "upload data to #{hostname}" task "upload:#{hostname}" => :upload_common do encrypted = encrypted_for(hostname) rsync_excludes = (encrypted.values + encrypted.keys).map { |f| ["--exclude", f] }.flatten rsync_excludes << "--exclude" << ".git/" rsync_excludes << "--exclude" << "cache/" rsync = node.rsync + ["-avp"] + ENV.fetch('CHAKE_RSYNC_OPTIONS', '').split rsync_logging = Rake.application.options.silent && '--quiet' || '--verbose' hash_files = Dir.glob(File.join($chake_tmpdir, '*.sha1sum')) files = Dir.glob("**/*").select { |f| !File.directory?(f) } - encrypted.keys - encrypted.values - hash_files if_files_changed(hostname, 'plain', files) do sh *rsync, '--delete', rsync_logging, *rsync_excludes, './', node.rsync_dest end if_files_changed(hostname, 'enc', encrypted.keys) do Dir.mktmpdir do |tmpdir| encrypted.each do |encrypted_file, target_file| target = File.join(tmpdir, target_file) mkdir_p(File.dirname(target)) sh 'gpg', '--quiet', '--batch', '--use-agent', '--output', target, '--decrypt', encrypted_file end sh *rsync, rsync_logging, tmpdir + '/', node.rsync_dest end end end converge_dependencies = [:converge_common, "bootstrap:#{hostname}", "upload:#{hostname}"] desc "converge #{hostname}" task "converge:#{hostname}" => converge_dependencies do chef_logging = Rake.application.options.silent && '-l fatal' || '' node.run_as_root "chef-solo -c #{node.path}/#{chef_config} #{chef_logging} -j #{node.path}/#{$chake_tmpdir}/#{hostname}.json" end desc 'apply on #{hostname}' task "apply:#{hostname}", [:recipe] => [:recipe_input] do |task, args| chef_logging = Rake.application.options.silent && '-l fatal' || '' node.run_as_root "chef-solo -c #{node.path}/#{chef_config} #{chef_logging} -j #{node.path}/#{$chake_tmpdir}/#{hostname}.json --override-runlist recipe[#{$recipe_to_apply}]" end task "apply:#{hostname}" => converge_dependencies desc "run a command on #{hostname}" task "run:#{hostname}", [:command] => [:run_input] do node.run($cmd_to_run) end desc "Logs in to a shell on #{hostname}" task "login:#{hostname}" do node.run_shell end desc 'checks connectivity and setup on all nodes' task "check:#{hostname}" do node.run('sudo true') end end task :run_input, :command do |task,args| $cmd_to_run = args[:command] if !$cmd_to_run puts "# Enter command to run (use arrow keys for history):" $cmd_to_run = Chake::Readline::Commands.readline end if !$cmd_to_run || $cmd_to_run.strip == '' puts puts "I: no command provided, operation aborted." exit(1) end end task :recipe_input, :recipe do |task,args| $recipe_to_apply = args[:recipe] if !$recipe_to_apply recipes = Dir['**/*/recipes/*.rb'].map do |f| f =~ %r{(.*/)?(.*)/recipes/(.*).rb$} cookbook = $2 recipe = $3 recipe = nil if recipe == 'default' [cookbook,recipe].compact.join('::') end.sort puts 'Available recipes:' IO.popen('column', 'w') do |column| column.puts(recipes) end $recipe_to_apply = Chake::Readline::Recipes.readline if !$recipe_to_apply || $recipe_to_apply.empty? puts puts "I: no recipe provided, operation aborted." exit(1) end if !recipes.include?($recipe_to_apply) abort "E: no such recipe: #{$recipe_to_apply}" end end end desc "upload to all nodes" task :upload => $nodes.map { |node| "upload:#{node.hostname}" } desc "bootstrap all nodes" task :bootstrap => $nodes.map { |node| "bootstrap:#{node.hostname}" } desc "converge all nodes (default)" task "converge" => $nodes.map { |node| "converge:#{node.hostname}" } desc "Apply on all nodes" task "apply", [:recipe] => $nodes.map { |node| "apply:#{node.hostname}" } desc "run on all nodes" task :run, [:command] => $nodes.map { |node| "run:#{node.hostname}" } task :default => :converge desc 'checks connectivity and setup on all nodes' task :check => ($nodes.map { |node| "check:#{node.hostname}" }) do puts "✓ all hosts OK" end chake-0.12/lib/chake/0000755000004100000410000000000012643206100014352 5ustar www-datawww-datachake-0.12/lib/chake/tmpdir.rb0000644000004100000410000000012412643206100016173 0ustar www-datawww-datamodule Chake def self.tmpdir ENV.fetch('CHAKE_TMPDIR', 'tmp/chake') end end chake-0.12/lib/chake/bootstrap/0000755000004100000410000000000012643206100016367 5ustar www-datawww-datachake-0.12/lib/chake/bootstrap/99_unsupported.sh0000644000004100000410000000031012643206100021626 0ustar www-datawww-dataecho "---------------------" echo "Unsupported platform:" echo "---------------------" echo for file in /etc/os-release /etc/issue; do if [ -f $file ]; then cat $file break fi done exit 1 chake-0.12/lib/chake/bootstrap/01_debian.sh0000644000004100000410000000030412643206100020442 0ustar www-datawww-dataif [ -x /usr/bin/apt-get ]; then apt-get update export DEBIAN_FRONTEND=noninteractive apt-get -q -y install rsync chef update-rc.d chef-client disable service chef-client stop exit fi chake-0.12/lib/chake/bootstrap/01_centos7.sh0000644000004100000410000001035312643206100020607 0ustar www-datawww-dataif [ -f /etc/centos-release ] && grep -q 'CentOS Linux release 7' /etc/centos-release; then cat > /etc/yum.repos.d/chef.key < /etc/yum.repos.d/chef.repo < /etc/hostname hostname --file /etc/hostname fqdn=$(hostname --fqdn || true) if [ "$fqdn" != "$hostname" ]; then # if hostname is bar.example.com, we also want `bar` to be in /etc/hosts short_hostname=$(echo "$hostname" | cut -d . -f 1) if [ "$short_hostname" != "$hostname" ] && ! grep -q "\s${short_hostname}" /etc/hosts; then hostname="$hostname $short_hostname" fi printf "127.0.1.1\t%s\n" "$hostname" >> /etc/hosts fi # Stop cloud-init from resetting the hostname if [ -f /etc/cloud/cloud.cfg ]; then sed -i -e '/^\s*-\s*\(set_hostname\|update_hostname\)/d' /etc/cloud/cloud.cfg fi chake-0.12/lib/chake/readline.rb0000644000004100000410000000272712643206100016472 0ustar www-datawww-datarequire 'etc' require 'readline' require 'chake/tmpdir' module Chake class Readline class << self def history_file raise NotImplementedError end def history @history ||= [] end def prompt raise NotImplementedError end def init return if !File.exists?(history_file) @history = File.readlines(history_file).map(&:strip) end def finish return if !File.writable?(File.dirname(history_file)) || history.empty? File.open(history_file, 'w') do |f| history.last(500).each do |line| f.puts(line) end end end def readline ::Readline::HISTORY.clear history.each do |cmd| ::Readline::HISTORY.push(cmd) end input = ::Readline.readline(prompt) if input && input.strip != '' && input != @last history.push(input) end input end end class Commands < Readline def self.history_file File.join(Chake.tmpdir, '.commands_history') end def self.prompt '$ ' end end class Recipes < Readline def self.history_file File.join(Chake.tmpdir, '.recipes_history') end def self.prompt '> ' end end end end Chake::Readline.constants.each do |subclass| subclass = Chake::Readline.const_get(subclass) subclass.init at_exit do subclass.finish end end chake-0.12/lib/chake/version.rb0000644000004100000410000000004412643206100016362 0ustar www-datawww-datamodule Chake VERSION = "0.12" end chake-0.12/lib/chake/backend.rb0000644000004100000410000000261512643206100016272 0ustar www-datawww-datamodule Chake class Backend < Struct.new(:node) class CommandFailed < Exception end def scp ['scp'] end def scp_dest '' end def rsync ['rsync'] end def rsync_dest node.path + '/' end def run(cmd) printf "%#{Node.max_node_name_length}s: $ %s\n", node.hostname, cmd output = IO.popen(command_runner + [cmd]) output.each_line do |line| printf "%#{Node.max_node_name_length}s: %s\n", node.hostname, line.strip end output.close if $? status = $?.exitstatus if status != 0 raise CommandFailed.new([node.hostname, 'FAILED with exit status %d' % status].join(': ')) end end end def run_shell system(*shell_command) end def run_as_root(cmd) if node.remote_username == 'root' run(cmd) else run('sudo ' + cmd) end end def to_s self.class.backend_name end def skip? false end def self.backend_name name.split("::").last.downcase end def self.inherited(subclass) @backends ||= [] @backends << subclass end def self.get(name) backend = @backends.find { |b| b.backend_name == name } backend || raise(ArgumentError.new("Invalid backend name: #{name}")) end end end require 'chake/backend/ssh' require 'chake/backend/local' chake-0.12/lib/chake/node.rb0000644000004100000410000000243212643206100015625 0ustar www-datawww-datarequire 'uri' require 'etc' require 'forwardable' require 'chake/backend' module Chake class Node extend Forwardable attr_reader :hostname attr_reader :port attr_reader :username attr_reader :remote_username attr_reader :path attr_reader :data def self.max_node_name_length @max_node_name_length ||= 0 end def self.max_node_name_length=(value) @max_node_name_length = value end def initialize(hostname, data = {}) uri = URI.parse(hostname) if !uri.host && ((!uri.scheme && uri.path) || (uri.scheme && uri.opaque)) uri = URI.parse("ssh://#{hostname}") end if uri.path && uri.path.empty? uri.path = nil end @backend_name = uri.scheme @hostname = uri.host @port = uri.port @username = uri.user || Etc.getpwuid.name @remote_username = uri.user @path = uri.path || "/var/tmp/chef.#{username}" @data = data if @hostname.length > self.class.max_node_name_length self.class.max_node_name_length = @hostname.length end end def backend @backend ||= Chake::Backend.get(@backend_name).new(self) end def_delegators :backend, :run, :run_as_root, :run_shell, :rsync, :rsync_dest, :scp, :scp_dest, :skip? end end chake-0.12/lib/chake/backend/0000755000004100000410000000000012643206100015741 5ustar www-datawww-datachake-0.12/lib/chake/backend/ssh.rb0000644000004100000410000000305712643206100017070 0ustar www-datawww-datamodule Chake class Backend class Ssh < Backend def scp ['scp', ssh_config, scp_options].flatten.compact end def scp_dest ssh_target + ':' end def rsync [ssh_prefix, 'rsync', rsync_ssh].flatten.compact end def rsync_dest [ssh_target, node.path + '/'].join(':') end def command_runner [ssh_prefix, 'ssh', ssh_config, ssh_options, ssh_target].flatten.compact end def shell_command command_runner end private def rsync_ssh @rsync_ssh ||= begin ssh_command = 'ssh' if File.exist?(ssh_config_file) ssh_command += ' -F ' + ssh_config_file end if node.port ssh_command += ' -p ' + node.port.to_s end if ssh_command == 'ssh' [] else ['-e', ssh_command] end end end def ssh_config File.exist?(ssh_config_file) && ['-F', ssh_config_file] || [] end def ssh_config_file @ssh_config_file ||= ENV.fetch('CHAKE_SSH_CONFIG', '.ssh_config') end def ssh_prefix @ssh_prefix ||= ENV.fetch('CHAKE_SSH_PREFIX', '').split end def ssh_target [node.remote_username, node.hostname].compact.join('@') end def ssh_options node.port && ['-p', node.port.to_s] || [] end def scp_options node.port && ['-P', node.port.to_s] || [] end end end end chake-0.12/lib/chake/backend/local.rb0000644000004100000410000000046212643206100017362 0ustar www-datawww-datarequire 'socket' module Chake class Backend class Local < Backend def command_runner ['sh', '-c'] end def shell_command ENV.fetch('SHELL', Etc.getpwuid.shell) end def skip? node.hostname != Socket.gethostname end end end end chake-0.12/metadata.yml0000644000004100000410000001001512643206100015031 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: chake version: !ruby/object:Gem::Version version: '0.12' platform: ruby authors: - Antonio Terceiro autorequire: bindir: bin cert_chain: [] date: 2016-01-06 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.5' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.5' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: simplecov requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: asciidoctor requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.5.3 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.5.3 - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' description: chake allows one to manage a number of hosts via SSH by combining chef (solo) and rake. It doesn't require a chef server; all you need is a workstation from where you can SSH into all your hosts. chake automates copying the configuration management repository to the target host (including managing encrypted files), running chef on them, and running arbitrary commands on the hosts. email: - terceiro@softwarelivre.org executables: - chake extensions: [] extra_rdoc_files: [] files: - ".gitignore" - ".gitlab-ci.yml" - ChangeLog.md - Gemfile - LICENSE.txt - README.md - Rakefile - bin/chake - chake.gemspec - chake.spec.erb - examples/test/Rakefile - examples/test/config.rb - examples/test/cookbooks/example/files/host-homer/test.asc - examples/test/cookbooks/example/recipes/default.rb - lib/chake.rb - lib/chake/backend.rb - lib/chake/backend/local.rb - lib/chake/backend/ssh.rb - lib/chake/bootstrap/00_set_hostname.sh - lib/chake/bootstrap/01_centos7.sh - lib/chake/bootstrap/01_debian.sh - lib/chake/bootstrap/99_unsupported.sh - lib/chake/node.rb - lib/chake/readline.rb - lib/chake/tmpdir.rb - lib/chake/version.rb - man/.gitignore - man/Rakefile - man/readme2man.sed - spec/chake/backend/local_spec.rb - spec/chake/backend/ssh_spec.rb - spec/chake/backend_spec.rb - spec/chake/node_spec.rb - spec/spec_helper.rb homepage: https://gitlab.com/terceiro/chake licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.5.1 signing_key: specification_version: 4 summary: serverless configuration management tool for chef test_files: - spec/chake/backend/local_spec.rb - spec/chake/backend/ssh_spec.rb - spec/chake/backend_spec.rb - spec/chake/node_spec.rb - spec/spec_helper.rb has_rdoc: chake-0.12/man/0000755000004100000410000000000012643206100013304 5ustar www-datawww-datachake-0.12/man/Rakefile0000644000004100000410000000152212643206100014751 0ustar www-datawww-datatask :default => 'man/chake.1' file 'man/chake.1' => ['man/chake.adoc'] do command = 'asciidoctor --backend manpage --out-file man/chake.1 man/chake.adoc' if ENV['SOURCE_DATE_EPOCH'] date = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i) command << " --attribute docdate=#{date.strftime('%Y-%m-%d')}" end sh command end file 'man/chake.adoc' => ['README.md', 'man/readme2man.sed'] do |t| sh "sed -f man/readme2man.sed README.md > #{t.name} || (rm -f #{t.name}; false)" end task :install => 'man/chake.1' do prefix = ENV['PREFIX'] || File.exists?('debian/rules') && '/usr' || '/usr/local' target = [ENV["DESTDIR"], prefix , 'share/man/man1'].compact man = File.join(*target) sh 'install', '-d', '-m', '0755', man sh 'install', '-m', '0644', 'man/chake.1', man end task :clean do rm_f 'man/chake.1' rm_f 'man/chake.adoc' end chake-0.12/man/readme2man.sed0000644000004100000410000000013312643206100016011 0ustar www-datawww-data1a :doctype: manpage /^## Install/,/^[^#]/ d /^## Contributing/,$ d s/^##\(.*\)/## \U\1/ chake-0.12/man/.gitignore0000644000004100000410000000001112643206100015264 0ustar www-datawww-datachake.1* chake-0.12/.gitlab-ci.yml0000644000004100000410000000021712643206100015165 0ustar www-datawww-databefore_script: - pwd - echo $USER - bundle install --path .bundle tests: script: bundle exec rake cache: paths: - .bundle chake-0.12/.gitignore0000644000004100000410000000032012643206100014514 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp /examples/test/nodes.yaml /examples/test/.tmp .*.html chake-0.12/chake.spec.erb0000644000004100000410000000150412643206100015227 0ustar www-datawww-data%define gem_name <%= pkg.name %> Summary: <%= pkg.summary %> Name: <%= pkg.name %> Version: <%= pkg.version %> Release: 1 Source0: %{name}-%{version}.tar.gz License: <%= pkg.license %> Group: Development/Tools Prefix: %{_prefix} Vendor: <%= pkg.authors.first %> <<%= pkg.email.first %>> Url: <%= pkg.homepage %> BuildArch: noarch BuildRequires: ruby, rubygems-devel, rubygem-rake, rubygem-bundler Requires: ruby, rubygem-rake %description <%= pkg.description %> %prep %setup -n %{name}-%{version} %build %{__rm} -rf %{buildroot} sed -i -e 's#spec.files\s*=.*#spec.files = Dir.glob("**/*")#' %{name}.gemspec rake bundler:build %gem_install -n pkg/%{name}-%{version}.gem %install cp -a usr %{buildroot}/usr %clean rm -rf $RPM_BUILD_ROOT %files %{gem_instdir}/ %exclude %{gem_cache} %{gem_spec} %{_bindir}/chake %doc %{gem_docdir} chake-0.12/chake.gemspec0000644000004100000410000000254512643206100015157 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'chake/version' Gem::Specification.new do |spec| spec.name = "chake" spec.version = Chake::VERSION spec.authors = ["Antonio Terceiro"] spec.email = ["terceiro@softwarelivre.org"] spec.summary = %q{serverless configuration management tool for chef} spec.description = %q{chake allows one to manage a number of hosts via SSH by combining chef (solo) and rake. It doesn't require a chef server; all you need is a workstation from where you can SSH into all your hosts. chake automates copying the configuration management repository to the target host (including managing encrypted files), running chef on them, and running arbitrary commands on the hosts.} spec.homepage = "https://gitlab.com/terceiro/chake" spec.license = "MIT" spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ["lib"] spec.add_development_dependency "bundler", "~> 1.5" spec.add_development_dependency "rspec" spec.add_development_dependency "simplecov" spec.add_development_dependency "asciidoctor", '>= 1.5.3' spec.add_dependency "rake" end chake-0.12/README.md0000644000004100000410000002175512643206100014022 0ustar www-datawww-data# chake(1) ## NAME chake - serverless configuration with chef ## Introduction chake is a tool that helps you manage multiple hosts with, without the need for a chef server. Configuration is managed in a local directory, which should probably be under version control with **git(1)** or anything else. Configuration is usually deployed via rsync over SSH, and applied by invoking **chef-solo(1)** over SSH on each host. ## Installation $ gem install chake ## Creating the repository ``` $ chake init [create] nodes.yaml [ mkdir] nodes.d/ [create] config.rb [ mkdir] config/roles [ mkdir] cookbooks/basics/recipes/ [create] cookbooks/basics/recipes/default.rb [create] Rakefile ``` A brief explanation of the created files: * `nodes.yaml`: where you will list the hosts you will be managing, and what recipes to apply to each of them. * `nodes.d`: a directory with multiple files in the same format as nodes.yaml. All files matching `*.yaml` in it will be added to the list of nodes. * `config.rb`: contains the chef-solo configuration. You can modify it, but usually you won't need to. * `config/roles`: directory is where you can put your role definitions. * `cookbooks`: directory where you will store your cookbooks. A sample cookbook called "basics" is created, but feel free to remove it and add actual cookbooks. * `Rakefile`: Contains just the `require 'chake'` line. You can augment it with other tasks specific to your intrastructure. After the repository is created, you can call either `chake` or `rake`, as they are completely equivalent. ## Managing nodes Just after you created your repository, the contents of `nodes.yaml` is the following: ```yaml host1.mycompany.com: run_list: - recipe[basics] ``` You can list your hosts with `rake nodes`: ``` $ rake nodes host1.mycompany.com ssh ``` To add more nodes, just append to `nodes.yaml`: ```yaml host1.mycompany.com: run_list: - recipe[basics] host2.mycompany.com: run_list: - recipes[basics] ``` And chake now knows about your new node: ``` $ rake nodes host1.mycompany.com ssh host2.mycompany.com ssh ``` ## Preparings nodes to be managed Nodes have very few initial requirements to be managed with `chake`: - The node must be accessible via SSH. - The user you connect to the node must either be `root`, or be allowed to run `sudo` (in which case `sudo` must be installed). **A note on password prompts:** every time chake calls ssh on a node, you may be required to type in your password; every time chake calls sudo on the node, you may be require to type in your password. For managing one or two nodes this is probably fine, but for larger numbers of nodes it is not practical. To avoid password prompts, you can: - Configure SSH key-based authentication. This is more secure than using passwords. While you are at it, you also probably want disable password authentication completely, and only allow key-based authentication - Configure passwordless `sudo` access for the user you use to connect to your nodes. ## Checking connectivity and initial host setup To check whether hosts are correcly configured, you can use the `check` task: ```bash $ rake check ``` That will run the the `sudo true` command on each host. If that pass without you having to passwords, you are sure that * you have SSH access to each host; and * the user you are connecting as has password-less sudo correctly setup. ```bash $ rake check ``` ## Applying cookbooks To apply the configuration to all nodes, run ```bash $ rake converge ``` To apply the configuration to a single node, run ```bash $ rake converge:$NODE ``` To apply a single recipe on all nodes, run ```bash $ rake apply[myrecipe] ``` To apply a single recipe on a specific node, run ```bash $ rake apply:$NODE[myrecipe] ``` If you don't inform a recipe in the command line, you will be prompted for one. To run a shell command on all nodes, run ``` $ rake run[command] ``` If the `command` you want to run contains spaces, or other characters that are special do the shell, you have to quote them. To run a shell command on a specific node, run ``` $ rake run:$NODE[command] ``` If you don't inform a command in the command line, you will be prompted for one. To check the existing tasks, run ```bash $ rake -T ``` ## Writing cookbooks Since chake is actually a wrapper for Chef Solo, you should read the [chef documentation](https://docs.chef.io/). In special, look at the [Chef Solo Documentation](https://docs.chef.io/chef_solo.html). ## The node bootstrapping process When chake acts on a node for the first time, it has to bootstrap it. The bootstrapping process includes doing the following: - installing chef and rsync - disabling the chef client daemon - setting up the hostname ## Node URLs The keys in the hash that is represented in `nodes.yaml` is a node URL. All components of the URL but the hostname are optional, so just listing hostnames is the simplest form of specifying your nodes. Here are all the components of the node URLs: ``` [backend://][username@]hostname[:port][/path] ``` * `backend`: backend to use to connect to the host. `ssh` or `local` (default: `ssh`) * `username`: user name to connect with (default: the username on your local workstation) * `hostname`: the hostname to connect to (default: _none_) * `port`: port number to connect to (default: 22) * `/path`: where to store the cookbooks at the node (default: `/var/tmp/chef.$USERNAME`) ## Extra features ### Hooks You can define rake tasks that will be executed before bootstrapping nodes, before uploading configuration management content to nodes, and before converging. To do this, you just need to enhance the corresponding tasks: * `bootstrap_common`: executed before bootstrapping nodes (even if nodes have already been bootstrapped) * `upload_common`: executed before uploading content to the node * `converge_common`: executed before converging (i.e. running chef) Example: ``` task :bootstrap_common do sh './scripts/pre-bootstrap-checks' end ``` ### Encrypted files Any files ending matching `*.gpg` and `*.asc` will be decrypted with GnuPG before being sent to the node. You can use them to store passwords and other sensitive information (SSL keys, etc) in the repository together with the rest of the configuration. ### repository-local SSH configuration If you need special SSH configuration parameters, you can create a file called `.ssh_config` (or whatever file name you have in the `$CHAKE_SSH_CONFIG` environment variable, see below for details) in at the root of your repository, and chake will use it when calling `ssh`. ### Logging in to a host To easily login to one of your host, just run `rake login:$HOSTNAME`. This will automatically use the repository-local SSH configuration as above so you don't have to type `-F .ssh_config` all the time. ### Running all SSH invocations with some prefix command Some times, you will also want or need to prefix your SSH invocations with some prefix command in order to e.g. tunnel it through some central exit node. You can do this by setting `$CHAKE_SSH_PREFIX` on your environment. Example: ``` CHAKE_SSH_PREFIX=tsocks rake converge ``` The above will make all SSH invocations to all hosts be called as `tsocks ssh [...]` ### Converging local host If you want to manage your local workstation with chake, you can declare a local node like this in `nodes.yaml`: ```yaml local://thunderbolt: run_list: - role[workstation] ``` To apply the configuration to the local host, you can use the conventional `rake converse:thunderbolt`, or the special target `rake local`. When converging all nodes, `chake` will skip nodes that are declared with the `local://` backend and whose hostname does not match the hostname in the declaration. For example: ```yaml local://desktop: run_list: - role[workstation] local://laptop: run_list: - role[workstation] ``` When you run `rake converge` on `desktop`, `laptop` will be skipped, and vice-versa. ## Environment variables * `$CHAKE_SSH_CONFIG`: Local SSH configuration file. Defaults to `.ssh_config`. * `$CHAKE_SSH_PREFIX`: Command to prefix SSH (and rsync over SSH) calls with. * `$CHAKE_RSYNC_OPTIONS`: extra options to pass to `rsync`. Useful to e.g. exclude large files from being upload to each server. * `$CHAKE_NODES`: File containing the list of servers to be managed. Default: `nodes.yaml`. * `$CHAKE_NODES_D`: Directory containing node definition files servers to be managed. Default: `nodes.d`. * `$CHAKE_TMPDIR`: Directory used to store temporary cache files. Default: `tmp/chake`. * `$CHAKE_CHEF_CONFIG`: Chef configuration file, relative to the root of the repository. Default: `config.rb`. ## See also * **rake(1)**, **chef-solo(1)** * Chef documentation: https://docs.chef.io/ ## Contributing 1. Fork it ( http://github.com/terceiro/chake/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request