chake-0.91/0000755000004100000410000000000014537556746012574 5ustar www-datawww-datachake-0.91/README.md0000644000004100000410000003031514537556746014055 0ustar www-datawww-datachake(1) -- serverless configuration management tool ======================================== ## SYNOPSIS `chake` init `chake` [rake arguments] ## Description chake is a tool that helps you manage multiple hosts without the need for a central server. Configuration is managed in a local directory, which should (but doesn't need to) be under version control with **git(1)** or any other version control system. Configuration is deployed to managed hosts remotely, either by invoking a configuration management tool that will connect to them, or by first uploading the necessary configuration and them remotely running a tool on the hosts. ## Supported configuration managers. chake supports the following configuration management tools: * **itamae**: configuration is applied by running the itamae command line tool on the management host; no configuration needs to be uploaded to the managed hosts. See chake-itamae(7) for details. * **shell**: the local repository is copied to the host, and the shell commands specified in the node configuration is executed from the directory where that copy is. See chake-shell(7) for details. * **chef**: the local repository is copied to the host, and **chef-solo** is executed remotely on the managed host. See chake-chef(7) for details. Beyond applying configuration management recipes on the hosts, chake also provides useful tools to manage multiple hosts, such as listing nodes, running commands against all of them simultaneously, logging in to interactive shells, and others. ## creating the repository $ chake init[:configmanager] This will create an initial directory structure. Some of the files are specific to your your chosen **configmanager**, which can be one of [SUPPORTED CONFIGURATION MANAGERS]. The following files, though, will be common to any usage of chake: * `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. * `Rakefile`: Contains just the `require 'chake'` line. You can augment it with other tasks specific to your intrastructure. If you omit _configmanager_, `itamae` will be used by default. 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: itamae: - roles/basic.rb ``` The exact contents depends on the chosen configuration management tool. 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: itamae: - roles/basic.rb host2.mycompany.com: itamae: - roles/basic.rb ``` 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 correctly configured, you can use the `check` task: ``` $ rake check ``` That will run the the `sudo true` command on each host. If that pass without you having to type any passwords, it means that: * you have SSH access to each host; and * the user you are connecting as has password-less sudo correctly setup. ## Applying configuration Note that by default all tasks that apply to all hosts will run in parallel, using rake's support for multitasks. If for some reason you need to prevent that, you can pass `-j1` (or --jobs=1`) in the rake invocation. Note that by default rake will only run N+4 tasks in parallel, where N is the number of cores on the machine you are running it. If you have more than N+4 hosts and want all of them to be handled in parallel, you might want to pass `-j` (or `--jobs`), without any number, as the last argument; with that rake will have no limit on the number of tasks to perform in parallel. To apply the configuration to all nodes, run ``` $ rake converge ``` To apply the configuration to a single node, run ``` $ rake converge:$NODE ``` To apply a single recipe on all nodes, run ``` $ rake apply[myrecipe] ``` What `recipe` is depends on the configuration manager. To apply a single recipe on a specific node, run ``` $ 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 ``` The above will prompt you for a command, then execute it on all nodes. To pass the command to run in the command line, use the following syntax: ``` $ 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, for example: ``` $ rake run["cat /etc/hostname"] ``` To run a shell command on a specific node, run ``` $ rake run:$NODE[command] ``` As before, if you run just `rake run:$NODE`, you will be prompted for the command. To list all existing tasks, run: ``` $ rake -T ``` ## Writing configuration management code As chake supports different configuration management tools, the specifics of configuration management code depends on the the tool you choose. See the corresponding documentation. ## The node bootstrapping process Some of the configuration management tools require some software to be installed on the managed hosts. When that's the case, chake acts on a node for the first time, it has to bootstrap it. The bootstrapping process includes doing the following: - installing and configuring the needed software - 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: ``` [connection://][username@]hostname[:port][/path] ``` * `connection`: what 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) * `connect_common`: executed before doing any action that connects to any of the hosts. This can be used for example to generate a ssh configuration file based on the contents of the nodes definition files. Example: ```ruby task :bootstrap_common do sh './scripts/pre-bootstrap-checks' end ``` ### Encrypted files `chake` supports encrypted files matching either `\*.gpg` or `\*.asc`. There are two ways of specicying per-host encrypted files: 1. listing them in the `encrypted` attribute in the node configuration file. Example: ```yaml host1.mycompany.com: itamae: - roles/basic.rb encrypted: - foo.txt.asc ``` 2. (deprecated) any files matching `\*\*/files/{default,host-#{node}}/\*.{asc,gpg}` and `\*\*/files/\*.{asc,gpg}`, **if** `encrypted` is not defined in the node configuration. They will be decrypted with GnuPG before being sent to the node (for the configuration management tools that required files to be sent), without the `\*.asc` or `\*.gpg` extension. You can use them to store passwords and other sensitive information (SSL keys, etc) in the repository together with the rest of the configuration. For configuration managers that don't require uploading files to the managed node, this decryption will happen right before converging or applying single recipes, and the decrypted files will be wiped right after that. If you use this feature, make sure that you have the `wipe` program installed. This way chake will be able to delete the decrypted files in a slightly more secure way, after being done with them. ### 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: ```bash 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 using the "local" connection type, like this (in `nodes.yaml`): ```yaml local://thunderbolt: itamae: - role/workstation.rb ``` To apply the configuration to the local host, you can use the conventional `rake converge:thunderbolt`, or the special target `rake local`. When converging all nodes, `chake` will skip nodes that are declared with the `local://` connection and whose hostname does not match the hostname in the declaration. For example: ```yaml local://desktop: itamae: - role/workstation.rb local://laptop: itamae: - role/workstation.rb ``` When you run `rake converge` on `desktop`, `laptop` will be skipped, and vice-versa. ### Accessing node data from your own tasks It's often useful to be able to run arbitrary commands against the data you have about nodes. You can use the `Chake.nodes` for that. For example, if you want to geolocate each of yours hosts: ```ruby task :geolocate do Chake.nodes.each do |node| puts "#{node.hostname}: %s" % `geoiplookup #{node.hostname}`.strip end end ``` ## 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)** * **chake-itamae(7)**, https://itamae.kitchen/ * **chake-shell(7)** * **chake-chef(7)**, **chef-solo(1)**, https://docs.chef.io/ chake-0.91/bin/0000755000004100000410000000000014537556746013344 5ustar www-datawww-datachake-0.91/bin/chake0000755000004100000410000000141514537556746014346 0ustar www-datawww-data#!/usr/bin/env ruby require 'rake' rakefiles = %w[rakefile Rakefile rakefile.rb Rakefile.rb] if rakefiles.none? { |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 # cleanup after finishing at_exit do FileUtils.rm_rf tmpdir end end class Rake::Application alias orig_thread_pool thread_pool def thread_pool # :nodoc: if Chake.respond_to?(:nodes) @thread_pool ||= Rake::ThreadPool.new(Chake.nodes.size + 1) else orig_thread_pool end end end Rake.application.run chake-0.91/.manifest0000644000004100000410000000422414537556746014405 0ustar www-datawww-data.ackrc .gitignore .gitlab-ci.yml .manifest .rubocop.yml .rubocop_todo.yml ChangeLog.md Gemfile LICENSE.txt README.chef.md README.itamae-remote.md README.itamae.md README.md README.shell.md Rakefile activate.sh bin/chake chake.gemspec chake.spec.erb examples/test/.ssh_config examples/test/Rakefile examples/test/Vagrantfile examples/test/config.rb examples/test/cookbooks/basics/recipes/default.rb examples/test/cookbooks/example/files/default/test examples/test/cookbooks/example/files/host-lemur/test.asc examples/test/cookbooks/example/recipes/default.rb lib/chake.rb lib/chake/bootstrap/00_set_hostname.sh lib/chake/bootstrap/chef/01_installed.sh lib/chake/bootstrap/chef/02_debian.sh lib/chake/bootstrap/chef/99_unsupported.sh lib/chake/bootstrap/itamae-remote/01_installed.sh lib/chake/bootstrap/itamae-remote/02_debian.sh lib/chake/bootstrap/itamae-remote/99_unsupported.sh lib/chake/config.rb lib/chake/config_manager.rb lib/chake/config_manager/chef.rb lib/chake/config_manager/itamae.rb lib/chake/config_manager/itamae_remote.rb lib/chake/config_manager/shell.rb lib/chake/config_manager/skel/chef/Rakefile lib/chake/config_manager/skel/chef/config.rb lib/chake/config_manager/skel/chef/cookbooks/basics/recipes/default.rb lib/chake/config_manager/skel/chef/nodes.yaml lib/chake/config_manager/skel/itamae/Rakefile lib/chake/config_manager/skel/itamae/cookbooks/basics/default.rb lib/chake/config_manager/skel/itamae/nodes.yaml lib/chake/config_manager/skel/itamae/roles/basic.rb lib/chake/config_manager/skel/shell/Rakefile lib/chake/config_manager/skel/shell/nodes.yaml lib/chake/connection.rb lib/chake/connection/local.rb lib/chake/connection/ssh.rb lib/chake/node.rb lib/chake/readline.rb lib/chake/tmpdir.rb lib/chake/version.rb lib/chake/wipe.rb lib/chake/yaml.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/config_manager/chef_spec.rb spec/chake/config_manager/itamae_remote_spec.rb spec/chake/config_manager/itamae_spec.rb spec/chake/config_manager/shell_spec.rb spec/chake/config_manager_spec.rb spec/chake/node_spec.rb spec/integration_tests_spec.rb spec/spec_helper.rb chake-0.91/spec/0000755000004100000410000000000014537556746013526 5ustar www-datawww-datachake-0.91/spec/integration_tests_spec.rb0000644000004100000410000000206714537556746020637 0ustar www-datawww-datarequire 'fileutils' require 'pathname' require 'tmpdir' describe 'Chake' do include FileUtils def sh(*args) cmd = Shellwords.join(args) lib = [Pathname.new(__FILE__).parent.parent / 'lib', ENV.fetch('RUBYLIB', nil)].compact.join(':') path = [Pathname.new(__FILE__).parent.parent / 'bin', ENV.fetch('PATH', nil)].join(':') env = { 'RUBYLIB' => lib, 'PATH' => path } unless system(env, *args, out: ['.out', 'w'], err: ['.err', 'w']) out = File.read('.out') err = File.read('.err') raise "Command [#{cmd}] failed with exit status #{$CHILD_STATUS} (PATH = #{path}, RUBYLIB = #{lib}).\nstdout:\n#{out}\nstderr:\n#{err}" end rm_f '.log' end def chake(*args) cmd = [Gem.ruby, '-S', 'chake'] + args sh(*cmd) end def rake(*args) cmd = [Gem.ruby, '-S', 'rake'] + args sh(*cmd) end def project Dir.mktmpdir do |dir| Dir.chdir(dir) do yield dir end end end it 'loads node information' do project do chake 'init' rake 'nodes' end end end chake-0.91/spec/chake/0000755000004100000410000000000014537556746014601 5ustar www-datawww-datachake-0.91/spec/chake/config_manager_spec.rb0000644000004100000410000000117714537556746021105 0ustar www-datawww-datarequire 'pathname' require 'chake/node' require 'chake/config_manager' describe Chake::ConfigManager do subject { Chake::ConfigManager.new(Chake::Node.new('ssh://user@hostname.tld')) } it 'provides a path' do allow(subject).to receive(:name).and_return('xyz') expect(subject.path).to eq('/var/tmp/xyz.user') end it 'provides bootstrap scripts' do bootstrap_steps = subject.bootstrap_steps expect(bootstrap_steps).to_not be_empty bootstrap_steps.each do |path| expect(Pathname(path)).to exist end end it 'requires uploading by default' do expect(subject.needs_upload?).to eq(true) end end chake-0.91/spec/chake/config_manager/0000755000004100000410000000000014537556746017540 5ustar www-datawww-datachake-0.91/spec/chake/config_manager/itamae_remote_spec.rb0000644000004100000410000000220614537556746023712 0ustar www-datawww-datarequire 'spec_helper' require 'chake/node' require 'chake/config_manager/itamae_remote' describe Chake::ConfigManager::ItamaeRemote do let(:node) do Chake::Node.new('somehost').tap do |n| n.silent = true n.data['itamae-remote'] = ['foo.rb', 'bar.rb'] end end let(:cfg) { Chake::ConfigManager.get(node) } it 'is detected correctly' do expect(cfg).to be_a(Chake::ConfigManager::ItamaeRemote) end it 'requires uploading' do expect(cfg.needs_upload?).to eq(true) end it 'calls itamae remotely to converge' do expect(node).to receive(:run_as_root).with( a_string_matching(%r{itamae.*#{node.path}/foo.rb.*#{node.path}/bar.rb}) ) cfg.converge end it 'calls itamae remotely to apply' do expect(node).to receive(:run_as_root).with( a_string_matching(%r{itamae.*#{node.path}/doit.rb}) ) cfg.apply('doit.rb') end it 'handles silent mode' do node.silent = true expect(node).to receive(:run_as_root).with( a_string_matching(/--log-level\\=warn/) ) cfg.converge end it 'has a name with dashes' do expect(cfg.name).to eq('itamae-remote') end end chake-0.91/spec/chake/config_manager/chef_spec.rb0000644000004100000410000000214714537556746022010 0ustar www-datawww-datarequire 'chake/node' require 'chake/config_manager/chef' describe Chake::ConfigManager::Chef do let(:node) do Chake::Node.new('foobar') end subject do Chake::ConfigManager::Chef.new(node) end it 'provides a name' do expect(subject.name).to eq('chef') end it 'calls chef-solo on converge' do expect(subject).to receive(:logging).and_return('-l debug') expect(node).to receive(:run_as_root).with(%r{chef-solo -c #{node.path}/config.rb -l debug -j #{node.path}/#{Chake.tmpdir}/foobar.json}) subject.converge end it 'calls chef-solo on apply' do expect(subject).to receive(:logging).and_return('-l debug') expect(node).to receive(:run_as_root).with(%r{chef-solo -c #{node.path}/config.rb -l debug -j #{node.path}/#{Chake.tmpdir}/foobar.json --override-runlist recipe\[myrecipe\]}) subject.apply('myrecipe') end context 'logging' do it 'logs when requested' do expect(subject.send(:logging)).to eq('') end it 'only show fatal errrrs when requested' do node.silent = true expect(subject.send(:logging)).to eq('-l fatal') end end end chake-0.91/spec/chake/config_manager/itamae_spec.rb0000644000004100000410000000443714537556746022347 0ustar www-datawww-datarequire 'spec_helper' require 'chake/node' require 'chake/config_manager/itamae' describe Chake::ConfigManager::Itamae do let(:hostname) { 'foobar' } let(:node) do Chake::Node.new(hostname).tap do |n| n.silent = true n.data['itamae'] = ['foo.rb', 'bar.rb'] end end let(:cfg) { Chake::ConfigManager::Itamae.new(node) } let(:output) { StringIO.new("line1\nline2\n") } it 'does not require uploading' do expect(cfg.needs_upload?).to eq(false) end it 'calls itamae when converging' do expect(IO).to receive(:popen).with( array_including('itamae', 'foo.rb', 'bar.rb'), 'r', err: %i[child out] ).and_return(output) cfg.converge end it 'calls itamae when applying' do expect(IO).to receive(:popen).with( array_including('itamae', 'foobarbaz.rb'), 'r', err: %i[child out] ).and_return(output) cfg.apply('foobarbaz.rb') end context 'for ssh hosts' do let(:hostname) { 'ssh://theusernanme@thehostname' } it 'calls itamae ssh subcommand' do expect(IO).to receive(:popen).with( array_including('itamae', 'ssh', '--host=thehostname', '--user=theusernanme'), anything, err: anything ).and_return(output) cfg.converge end end context 'for local hosts' do let(:hostname) { 'local://localhostname' } it 'calls itamae with local subcommand' do expect(IO).to receive(:popen).with( array_including('itamae', 'local', /--node-json=.*/, 'foo.rb', 'bar.rb'), anything, err: anything ).and_return(output) cfg.converge end end it 'throws an error for unsupported connection' do allow(node).to receive(:connection).and_return(Object.new) expect { cfg.converge }.to raise_error(NotImplementedError) end it 'handles silent mode' do expect(IO).to receive(:popen).with( array_including('--log-level=warn'), anything, err: anything ).and_return(output) cfg.converge end RSpec::Matchers.define_negated_matcher :array_excluding, :include it 'handles non-silent mode' do node.silent = false expect(IO).to receive(:popen).with( array_excluding('--log-level=warn'), anything, err: anything ).and_return(output) silence($stdout) { cfg.converge } end end chake-0.91/spec/chake/config_manager/shell_spec.rb0000644000004100000410000000325314537556746022211 0ustar www-datawww-datarequire 'chake/node' require 'chake/config_manager' require 'chake/config_manager/shell' describe Chake::ConfigManager::Shell do |_c| let(:node) do Chake::Node.new('foobar').tap do |n| allow(n).to receive(:path).and_return(nil) end end it 'accepts node with explicit config_manager in data' do node.data['config_manager'] = 'shell' expect(Chake::ConfigManager.get(node)).to be_a(Chake::ConfigManager::Shell) end it 'accepts node with `shell` in data' do node.data['shell'] = ['date'] expect(Chake::ConfigManager.get(node)).to be_a(Chake::ConfigManager::Shell) end let(:subject) { Chake::ConfigManager::Shell.new(node) } it 'calls all shell commands on converge' do node.data['shell'] = %w[date true] expect(node).to receive(:run_as_root).with("sh -xec 'date && true'") subject.converge end it 'changes to node path to run commands' do node.data['shell'] = %w[true] allow(node).to receive(:path).and_return('/foo') expect(node).to receive(:run_as_root).with("sh -xec 'cd /foo && true'") subject.converge end it 'calls given shell command on apply' do node.data['shell'] = %w[date true] expect(node).to receive(:run_as_root).with("sh -xec 'reboot'") subject.apply('reboot') end it 'hides output on converge in silent mode' do node.data['shell'] = ['date'] node.silent = true expect(node).to receive(:run_as_root).with("sh -ec 'date' >/dev/null") subject.converge end it 'hides output on apply in silent mode' do node.data['shell'] = ['date'] node.silent = true expect(node).to receive(:run_as_root).with("sh -ec 'reboot' >/dev/null") subject.apply('reboot') end end chake-0.91/spec/chake/node_spec.rb0000644000004100000410000000540314537556746017067 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.connection).to be_an_instance_of(Chake::Connection::Ssh) } it('user current username by default') { expect(simple.username).to eq('jonhdoe') } it('writes to specified path') { node = Chake::Node.new('ssh://host.tld/path/to/config') expect(node.path).to eq('/path/to/config') } let(:with_username) { Chake::Node.new('username@hostname') } it('accepts username') { expect(with_username.username).to eq('username') } it('uses ssh') { expect(with_username.connection).to be_an_instance_of(Chake::Connection::Ssh) } let(:with_connection) { Chake::Node.new('local://hostname') } it('accepts connection as URI scheme') { expect(with_connection.connection).to be_an_instance_of(Chake::Connection::Local) } it('wont accept any connection') do expect { Chake::Node.new('foobar://bazqux').connection }.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.connection.to_s).to eq('ssh') end %i[run run_as_root rsync_dest].each do |method| it("delegates #{method} to connection") do node = simple connection = double args = Object.new allow(node).to receive(:connection).and_return(connection) expect(connection).to receive(method).with(args) node.send(method, args) end end it 'delegates converge to config_manager' do node = simple expect(node.config_manager).to receive(:converge) node.converge end it 'delegates apply to config_manager' do node = simple expect(node.config_manager).to receive(:apply).with('myrecipe') node.apply('myrecipe') end it 'falls back to writing to path specified by config manager' do expect(simple.path).to eq(simple.config_manager.path) end it 'calculates max node name length' do Chake::Node.max_node_name_length = 0 Chake::Node.new('foobar') expect(Chake::Node.max_node_name_length).to eq(6) Chake::Node.new('foobarbaz') expect(Chake::Node.max_node_name_length).to eq(9) end end chake-0.91/spec/chake/backend_spec.rb0000644000004100000410000000003314537556746017523 0ustar www-datawww-datarequire 'chake/connection' chake-0.91/spec/chake/backend/0000755000004100000410000000000014537556746016170 5ustar www-datawww-datachake-0.91/spec/chake/backend/local_spec.rb0000644000004100000410000000103114537556746020614 0ustar www-datawww-datarequire 'spec_helper' describe Chake::Connection::Local do include_examples 'Chake::Connection', Chake::Connection::Local let(:node) { Chake::Node.new('local://myusername@myhost/srv/chake') } it('runs commands with sh -c') { expect(connection.command_runner).to eq(['sh', '-c']) } it('rsyncs locally') { expect(connection.rsync_dest).to eq('/srv/chake/') } 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.91/spec/chake/backend/ssh_spec.rb0000644000004100000410000000216214537556746020325 0ustar www-datawww-datarequire 'spec_helper' describe Chake::Connection::Ssh do include_examples 'Chake::Connection', Chake::Connection::Ssh let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chake') } it('runs commands with ssh') { expect(connection.command_runner).to eq(['ssh', 'myuser@myhost']) } it('rsyncs over ssh') { expect(connection.rsync_dest).to eq('myuser@myhost:/srv/chake/') } 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(connection.command_runner).to eq(['ssh', '-p', '2222', 'myhost']) end it 'uses port with scp' do expect(connection.scp).to eq(['scp', '-P', '2222']) end it 'uses port with rsync' do expect(connection.send(:rsync_ssh)).to eq(['-e', 'ssh -p 2222']) end end end chake-0.91/spec/spec_helper.rb0000644000004100000410000000321014537556746016340 0ustar www-datawww-databegin require 'simplecov' SimpleCov.start do minimum_coverage 35.3 track_files 'lib/**/*.rb' add_filter %r{^/spec/} add_filter %r{^/lib/chake/config_manager/skel/} end rescue LoadError puts "W: simplecov not installed, we won't have a coverage report" end require 'chake/node' require 'chake/connection' require 'rspec/version' if RSpec::Version::STRING < '2.14' puts 'Skipping tests, need RSpec >= 2.14' exit end shared_examples 'Chake::Connection' do |connection_class| let(:connection) { connection_class.new(node) } it('runs commands') do io = StringIO.new("line 1\n line 2\n") expect(IO).to receive(:popen).with(connection.command_runner + ['/bin/sh'], 'w+', Hash).and_return(io) expect(io).to receive(:write).with('something').ordered expect(io).to receive(:close_write).ordered expect(node).to receive(:log).with('$ something') expect(node).to receive(:log).with('line 1') expect(node).to receive(:log).with(' line 2') connection.run('something') end it('runs as root') do expect(connection).to receive(:run).with('sudo something') connection.run_as_root('something') end it('does not use sudo if already root') do allow(connection.node).to receive(:remote_username).and_return('root') expect(connection).to receive(:run).with('something') connection.run_as_root('something') end end module Helpers def silence(stream) orig_stream = stream.clone begin File.open('/dev/null', 'w') do |f| stream.reopen(f) yield end ensure stream.reopen(orig_stream) end end end RSpec.configure do |c| c.include Helpers end chake-0.91/.rubocop.yml0000644000004100000410000000146314537556746015052 0ustar www-datawww-datainherit_from: .rubocop_todo.yml AllCops: NewCops: enable Exclude: - pkg/**/* Layout/LineLength: Enabled: false Metrics/AbcSize: Enabled: false Metrics/BlockLength: Enabled: false Metrics/MethodLength: Enabled: false Metrics/PerceivedComplexity: Enabled: false Style/Documentation: Enabled: false Style/FormatString: EnforcedStyle: percent Style/FrozenStringLiteralComment: Enabled: false Style/GlobalVars: Exclude: - lib/chake.rb Style/GuardClause: Enabled: false Style/HashEachMethods: Enabled: false Style/HashTransformKeys: Enabled: false Style/HashTransformValues: Enabled: false Style/IfUnlessModifier: Enabled: false Style/SymbolArray: Enabled: false Gemspec/RequiredRubyVersion: Enabled: false Gemspec/DeprecatedAttributeAssignment: Enabled: false chake-0.91/README.chef.md0000644000004100000410000000341414537556746014761 0ustar www-datawww-datachake-chef(7) -- configure chake nodes with chef-solo ===================================================== ## Description This configuration manager will allow you to manage nodes by running **chef-solo(1)** on each remote node. When `chake init` runs, the following chef-specific files will be created: A brief explanation of the created files that are specific to **chef**: * `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. ## Configuration Nodes can be configured to be managed with chef by having a `run_list` key in their configuration: ```yaml host1.mycompany.com: run_list: - role[server] - recipe[service1] service1: option1: "here we go" ``` Any extra configuration under `host1.mycompany.com` will be saved to a JSON file and given to the chef-solo --node-json option in the command line. For example, the above configuration will produce a JSON file that looks like this: ```json { "run_list": [ "role[server]", "recipe[service1]" ] , "service1": { "option1": "here we go" } } ``` Inside Chef recipes, you can access those values by using the `node` object. For example: ```ruby template "/etc/service1.conf.d/option1.conf" do variables option1: node["option1"] end ``` ## Bootstrapping The bootstrap process for _chef_ involves getting chef-solo installed. The node hostname will also be set based on the hostname informed in the configuration file. ## See also * **chake(1)** * * chake-0.91/chake.spec.erb0000644000004100000410000000150414537556746015272 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.91/.gitignore0000644000004100000410000000033014537556746014560 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 *.1 *.7 chake-0.91/README.itamae-remote.md0000644000004100000410000000320614537556746016604 0ustar www-datawww-datachake-itamae-remote(7) -- configure chake nodes with a remote itamae ==================================================================== ## Description This configuration manager will run **itamae(1)** on each remote node. This is different from the _itamae_ configuration manager, which runs itamae on your workstation (the host running chake). _itamae-remote_ will run itamae individually on each node, which is one order of magnitude faster. This requires itamae to be installed on each node, and that will be taken care of automatically by the bootstrapping process. ## Configuration The _itamae-remote_ configuration manager requires one key called `itamae-remote`, and the value must be a list of strings representing the list of recipes to apply to the node when converging. ```yaml host1.mycompany.com: itamae-remote: - cookbooks/basic/default.rb - roles/server.rb service1: option1: "here we go" ``` Any extra configuration under `host1.mycompany.com` will be saved to a JSON file and given to the itamae --node-json option in the command line. For example, the above configuration will produce a JSON file that looks like this: ```json { "itamae": [ "cookbooks/basic.rb", "roles/server.rb" ] , "service1": { "option1": "here we go" } } ``` Inside itamae recipes, you can access those values by using the `node` object. For example: ```ruby template "/etc/service1.conf.d/option1.conf" do variables option1: node["option1"] end ``` ## Bootstrapping The bootstrapping process will make sure itamae is installed. The node hostname will be set according to your chake configuration. ## See also * **chake(1)** chake-0.91/man/0000755000004100000410000000000014537556746013347 5ustar www-datawww-datachake-0.91/man/.gitignore0000644000004100000410000000002414537556746015333 0ustar www-datawww-datachake.1* chake.adoc chake-0.91/man/Rakefile0000644000004100000410000000213614537556746015016 0ustar www-datawww-dataMAIN_MANPAGE = 'man/chake.1'.freeze OTHER_MANPAGES = %w[ man/chake-chef.7 man/chake-itamae.7 man/chake-itamae-remote.7 man/chake-shell.7 ].freeze MANPAGES = [MAIN_MANPAGE] + OTHER_MANPAGES task default: :man task man: MANPAGES MANPAGES.each do |man| source = "README#{man.pathmap('%n').sub(/^chake/, '').sub('-', '.')}.md" file man => [source, 'man/readme2man.sed'] do sh "sed -f man/readme2man.sed #{source} > #{man}.ronn || (rm -f #{man}.ronn; false)" sh "ronn --roff #{man}.ronn" sh "rm -f #{man}.ronn" sh 'sed', '-i', '-e', 's/\\\\\'/\'/g', man end end task install: MANPAGES do prefix = ENV['PREFIX'] || (File.exist?('debian/rules') && '/usr') || '/usr/local' man1 = File.join(*[ENV.fetch('DESTDIR', nil), prefix, 'share/man/man1'].compact) man7 = File.join(*[ENV.fetch('DESTDIR', nil), prefix, 'share/man/man7'].compact) target = { '.1' => man1, '.7' => man7 } sh 'install', '-d', '-m', '0755', man1 sh 'install', '-d', '-m', '0755', man7 MANPAGES.each do |m| sh 'install', '-m', '0644', m, target[m.pathmap('%x')] end end task :clean do rm_f MANPAGES end chake-0.91/man/readme2man.sed0000644000004100000410000000022514537556746016056 0ustar www-datawww-data# Capitalize section titles s/^\(##\+\)\(.*\)/\1 \U\2/ # Turn fenced code blocks into 4-space indented blocks /^```/,/```/ s/^/ / /^ ```.*/ d chake-0.91/activate.sh0000644000004100000410000000014314537556746014726 0ustar www-datawww-database=$(dirname $BASH_SOURCE) export RUBYLIB=${base}/lib:${RUBYLIB} export PATH=${base}/bin:${PATH} chake-0.91/examples/0000755000004100000410000000000014537556746014412 5ustar www-datawww-datachake-0.91/examples/test/0000755000004100000410000000000014537556746015371 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/0000755000004100000410000000000014537556746017362 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/basics/0000755000004100000410000000000014537556746020626 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/basics/recipes/0000755000004100000410000000000014537556746022260 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/basics/recipes/default.rb0000644000004100000410000000003114537556746024223 0ustar www-datawww-datapackage 'openssh-server' chake-0.91/examples/test/cookbooks/example/0000755000004100000410000000000014537556746021015 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/example/recipes/0000755000004100000410000000000014537556746022447 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/example/recipes/default.rb0000644000004100000410000000040314537556746024415 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.91/examples/test/cookbooks/example/files/0000755000004100000410000000000014537556746022117 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/example/files/default/0000755000004100000410000000000014537556746023543 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/example/files/default/test0000644000004100000410000000000514537556746024440 0ustar www-datawww-datatest chake-0.91/examples/test/cookbooks/example/files/host-lemur/0000755000004100000410000000000014537556746024216 5ustar www-datawww-datachake-0.91/examples/test/cookbooks/example/files/host-lemur/test.asc0000644000004100000410000000156414537556746025673 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.91/examples/test/Rakefile0000644000004100000410000000066314537556746017043 0ustar www-datawww-data$LOAD_PATH.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.91/examples/test/Vagrantfile0000644000004100000410000000016514537556746017560 0ustar www-datawww-data# -*- mode: ruby -*- # vi: set ft=ruby : Vagrant.configure('2') do |config| config.vm.box = 'debian/buster64' end chake-0.91/examples/test/config.rb0000644000004100000410000000020014537556746017153 0ustar www-datawww-dataroot = __dir__ file_cache_path "#{root}/cache" cookbook_path "#{root}/cookbooks" role_path "#{root}/config/roles" chake-0.91/examples/test/.ssh_config0000644000004100000410000000014214537556746017511 0ustar www-datawww-dataHost test.local IdentityFile .vagrant/machines/default/libvirt/private_key # vim: ft=sshconfig chake-0.91/.rubocop_todo.yml0000644000004100000410000000216114537556746016073 0ustar www-datawww-data# This configuration was generated by # `rubocop --auto-gen-config` # on 2020-03-25 17:43:43 -0300 using RuboCop version 0.52.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent Layout/HeredocIndentation: Exclude: - 'lib/chake.rb' # Offense count: 3 Lint/AmbiguousOperator: Exclude: - 'lib/chake.rb' # Offense count: 1 Lint/InterpolationCheck: Exclude: - 'lib/chake.rb' # Offense count: 1 # Configuration parameters: Blacklist. # Blacklist: END, (?-mix:EO[A-Z]{1}) Naming/HeredocDelimiterNaming: Exclude: - 'lib/chake.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Exclude: - 'bin/chake' chake-0.91/README.itamae.md0000644000004100000410000000262414537556746015316 0ustar www-datawww-datachake-itamae(7) -- configure chake nodes with itamae ==================================================== ## Description This configuration manager will run **itamae(1)** against your nodes. ## Configuration The _itamae_ configuration manager requires one key called `itamae`, and the value must be a list of strings representing the list of recipes to apply to the node when converging. ```yaml host1.mycompany.com: itamae: - cookbooks/basic/default.rb - roles/server.rb service1: option1: "here we go" ``` Any extra configuration under `host1.mycompany.com` will be saved to a JSON file and given to the itamae --node-json option in the command line. For example, the above configuration will produce a JSON file that looks like this: ```json { "itamae": [ "cookbooks/basic.rb", "roles/server.rb" ] , "service1": { "option1": "here we go" } } ``` Inside itamae recipes, you can access those values by using the `node` object. For example: ```ruby template "/etc/service1.conf.d/option1.conf" do variables option1: node["option1"] end ``` ## Bootstrapping Very little bootstrapping is required for this configuration manager, as itamae requires no setup on the node site since the Ruby code in the recipes is interpreted locally and not on the nodes. During bootstrapping, only the node hostname will be set according to your chake configuration. ## See also * **chake(1)** chake-0.91/Rakefile0000644000004100000410000000570214537556746014245 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 'bundler:build' => :manifest 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' => ['bundler:clobber', 'build:tarball'] do dirname = "#{pkg.name}-#{pkg.version}" v = `git describe`.strip.tr('-', '.').sub(/^v/, '') chdir 'pkg' do sh 'gem2deb', '--no-wnpp-check', '-s', '-p', pkg.name, "#{dirname}.tar.gz" sh "rename s/#{pkg.version}/#{v}/ *.orig.tar.gz" chdir dirname do ln 'man/Rakefile', 'debian/dh_ruby.rake' sh "dch --preserve -v #{v}-1 'Development snapshot'" sh "sed -i -e 's/#{pkg.version}/#{v}/' lib/chake/version.rb" sh 'dpkg-buildpackage', '--diff-ignore=version.rb', '-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 'dpkg-buildpackage --diff-ignore=version.rb -us -uc' sh 'sudo apt-get install -qy --reinstall $(debc --list-debs)' end 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}" raise "Version #{pkg.version} was already released!" end end desc 'checks if the latest release is properly documented in ChangeLog.md' task :check_changelog do sh 'grep', "^#\\s*#{pkg.version}", 'ChangeLog.md' rescue StandardError puts "Version #{pkg.version} not documented in ChangeLog.md!" raise end desc 'Updates manifest file' task :manifest do manifest = File.read('.manifest') git = `git ls-files` if manifest != git File.write('.manifest', git) sh 'git commit .manifest -m "Update manifest"' end end desc 'Makes a release' task release: [:check_tag, :check_changelog, :default, 'bundler:release'] desc 'Check coding style' task :style do sh 'rubocop' end desc 'Check spelling in the source code' task :codespell do sh 'codespell', '--skip=.git', '--skip=coverage', '--skip=*.asc', '--skip=*.swp', '--skip=tags' end task default: [:test, :style, :codespell] task clean: 'bundler:clobber' load './man/Rakefile' chake-0.91/lib/0000755000004100000410000000000014537556746013342 5ustar www-datawww-datachake-0.91/lib/chake/0000755000004100000410000000000014537556746014415 5ustar www-datawww-datachake-0.91/lib/chake/wipe.rb0000644000004100000410000000057514537556746015715 0ustar www-datawww-datarequire 'singleton' module Chake class Wipe include Singleton if system('which', 'wipe', out: '/dev/null', err: :out) def wipe(file) system('wipe', '-rfs', file) end else warn 'W: please install the \`wipe\` program for secure deletion, falling back to unlink(2)' def wipe(file) File.unlink(file) end end end end chake-0.91/lib/chake/version.rb0000644000004100000410000000005314537556746016425 0ustar www-datawww-datamodule Chake VERSION = '0.91'.freeze end chake-0.91/lib/chake/connection.rb0000644000004100000410000000310714537556746017102 0ustar www-datawww-datarequire 'English' module Chake Connection = Struct.new(:node) class Connection class CommandFailed < RuntimeError end def scp ['scp'] end def scp_dest '' end def rsync ['rsync'] end def rsync_dest "#{node.path}/" end def run(cmd) node.log('$ %s' % { command: cmd }) io = IO.popen(command_runner + ['/bin/sh'], 'w+', err: %i[child out]) io.write(cmd) io.close_write read_output(io) end def read_output(io) io.each_line do |line| node.log(line.gsub(/\s*$/, '')) end io.close if $CHILD_STATUS status = $CHILD_STATUS.exitstatus if status != 0 raise CommandFailed, [node.hostname, 'FAILED with exit status %d' % { status: 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.connection_name end def skip? false end def self.connection_name name.split('::').last.downcase end def self.inherited(subclass) super @connections ||= [] @connections << subclass end def self.get(name) connection = @connections.find { |b| b.connection_name == name } raise ArgumentError, "Invalid connection name: #{name}" unless connection connection end end end require 'chake/connection/ssh' require 'chake/connection/local' chake-0.91/lib/chake/config_manager.rb0000644000004100000410000000403314537556746017701 0ustar www-datawww-datarequire 'pathname' module Chake class ConfigManager attr_reader :node def initialize(node) @node = node end def converge; end def apply(config); end def path "/var/tmp/#{name}.#{node.username}" end def name self.class.short_name end def to_s name end def bootstrap_steps base = File.join(File.absolute_path(File.dirname(__FILE__)), 'bootstrap') steps = Dir[File.join(base, '*.sh')] + Dir[File.join(base, name, '*.sh')] steps.sort_by { |f| File.basename(f) } end def needs_upload? true end def self.short_name name.split('::').last.gsub(/([[:lower:]])([[:upper:]])/) do first = Regexp.last_match(1) last = Regexp.last_match(2).downcase "#{first}-#{last}" end.downcase end def self.priority(new_priority = nil) @priority ||= new_priority || 50 end def self.inherited(klass) super @subclasses ||= [] @subclasses << klass end def self.get(node) available = @subclasses.sort_by(&:priority) manager = available.find { |c| c.short_name == node.data['config_manager'] } manager ||= available.find { |c| c.accept?(node) } raise ArgumentError, "Can't find configuration manager class for node #{node.hostname}. Available: #{available}.join(', ')}" unless manager manager.new(node) end def self.accept?(_node) false end def self.all @subclasses end def self.init skel = Pathname(__FILE__).parent / 'config_manager' / 'skel' / short_name skel.glob('**/*').each do |source| target = source.relative_path_from(skel) if target.exist? puts "exists: #{target}" else if source.directory? FileUtils.mkdir_p target else FileUtils.cp source, target end puts "create: #{target}" end end end end end Dir["#{File.dirname(__FILE__)}/config_manager/*.rb"].sort.each do |f| require f end chake-0.91/lib/chake/config_manager/0000755000004100000410000000000014537556746017354 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/0000755000004100000410000000000014537556746020312 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/chef/0000755000004100000410000000000014537556746021217 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/chef/cookbooks/0000755000004100000410000000000014537556746023210 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/chef/cookbooks/basics/0000755000004100000410000000000014537556746024454 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/chef/cookbooks/basics/recipes/0000755000004100000410000000000014537556746026106 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/chef/cookbooks/basics/recipes/default.rb0000644000004100000410000000003114537556746030051 0ustar www-datawww-datapackage 'openssh-server' chake-0.91/lib/chake/config_manager/skel/chef/Rakefile0000644000004100000410000000002014537556746022654 0ustar www-datawww-datarequire 'chake' chake-0.91/lib/chake/config_manager/skel/chef/nodes.yaml0000644000004100000410000000006614537556746023215 0ustar www-datawww-datahost1.mycompany.com: run_list: - recipe[basics] chake-0.91/lib/chake/config_manager/skel/chef/config.rb0000644000004100000410000000020014537556746023001 0ustar www-datawww-dataroot = __dir__ file_cache_path "#{root}/cache" cookbook_path "#{root}/cookbooks" role_path "#{root}/config/roles" chake-0.91/lib/chake/config_manager/skel/shell/0000755000004100000410000000000014537556746021421 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/shell/Rakefile0000644000004100000410000000002014537556746023056 0ustar www-datawww-datarequire 'chake' chake-0.91/lib/chake/config_manager/skel/shell/nodes.yaml0000644000004100000410000000006714537556746023420 0ustar www-datawww-datahost1.mycompany.com: shell: - echo "HELLO WORLD" chake-0.91/lib/chake/config_manager/skel/itamae/0000755000004100000410000000000014537556746021552 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/itamae/roles/0000755000004100000410000000000014537556746022676 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/itamae/roles/basic.rb0000644000004100000410000000004514537556746024303 0ustar www-datawww-datainclude_recipe '../cookbooks/basics' chake-0.91/lib/chake/config_manager/skel/itamae/cookbooks/0000755000004100000410000000000014537556746023543 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/itamae/cookbooks/basics/0000755000004100000410000000000014537556746025007 5ustar www-datawww-datachake-0.91/lib/chake/config_manager/skel/itamae/cookbooks/basics/default.rb0000644000004100000410000000003114537556746026752 0ustar www-datawww-datapackage 'openssh-server' chake-0.91/lib/chake/config_manager/skel/itamae/Rakefile0000644000004100000410000000002014537556746023207 0ustar www-datawww-datarequire 'chake' chake-0.91/lib/chake/config_manager/skel/itamae/nodes.yaml0000644000004100000410000000006414537556746023546 0ustar www-datawww-datahost1.mycompany.com: itamae: - roles/basic.rb chake-0.91/lib/chake/config_manager/itamae_remote.rb0000644000004100000410000000156614537556746022524 0ustar www-datawww-datarequire 'shellwords' require 'chake/config' require 'chake/tmpdir' module Chake class ConfigManager class ItamaeRemote < ConfigManager def converge recipes = node.data['itamae-remote'] return if recipes.empty? run_itamae(*recipes) end def apply(config) run_itamae(config) end def needs_upload? true end def self.accept?(node) node.data.key?('itamae-remote') end private def run_itamae(*recipes) cmd = ['itamae', 'local', "--node-json=#{json_config}"] if node.silent cmd << '--log-level=warn' end cmd += recipes.map { |r| File.join(node.path, r) } node.run_as_root(Shellwords.join(cmd)) end def json_config File.join(node.path, Chake.tmpdir, "#{node.hostname}.json") end end end end chake-0.91/lib/chake/config_manager/shell.rb0000644000004100000410000000121014537556746021002 0ustar www-datawww-datarequire 'shellwords' require 'chake/config' module Chake class ConfigManager class Shell < ConfigManager def converge commands = node.data['shell'].join(' && ') node.run_as_root sh(commands) end def apply(config) node.run_as_root sh(config) end def self.accept?(node) node.data.key?('shell') end private def sh(command) if node.path command = "cd #{node.path} && " + command end if node.silent "sh -ec '#{command}' >/dev/null" else "sh -xec '#{command}'" end end end end end chake-0.91/lib/chake/config_manager/chef.rb0000644000004100000410000000160314537556746020606 0ustar www-datawww-datarequire 'chake/config' require 'chake/tmpdir' module Chake class ConfigManager class Chef < ConfigManager CONFIG = ENV['CHAKE_CHEF_CONFIG'] || 'config.rb' def converge node.run_as_root "sh -c 'rm -f #{node.path}/nodes/*.json && chef-solo -c #{node.path}/#{CONFIG} #{logging} -j #{json_config}'" end def apply(config) node.run_as_root "sh -c 'rm -f #{node.path}/nodes/*.json && chef-solo -c #{node.path}/#{CONFIG} #{logging} -j #{json_config} --override-runlist recipe[#{config}]'" end priority 99 def self.accept?(_node) true # this is the default, but after everything else end private def json_config parts = [node.path, Chake.tmpdir, "#{node.hostname}.json"].compact File.join(parts) end def logging (node.silent && '-l fatal') || '' end end end end chake-0.91/lib/chake/config_manager/itamae.rb0000644000004100000410000000277014537556746021147 0ustar www-datawww-datarequire 'shellwords' require 'chake/config' require 'chake/tmpdir' module Chake class ConfigManager class Itamae < ConfigManager def converge recipes = node.data['itamae'] return if recipes.empty? run_itamae(*recipes) end def apply(config) run_itamae(config) end def needs_upload? false end def self.accept?(node) node.data.key?('itamae') end private def run_itamae(*recipes) cmd = ['itamae'] case node.connection when Chake::Connection::Ssh cmd << 'ssh' << "--user=#{node.username}" << "--host=#{node.hostname}" cmd += ssh_config when Chake::Connection::Local if node.username == 'root' cmd.prepend 'sudo' end cmd << 'local' else raise NotImplementedError, "Connection type #{node.connection.class} not supported for itamee" end cmd << "--node-json=#{json_config}" if node.silent cmd << '--log-level=warn' end cmd += recipes node.log("$ #{cmd.join(' ')}") io = IO.popen(cmd, 'r', err: %i[child out]) node.connection.read_output(io) end def json_config File.join(Chake.tmpdir, "#{node.hostname}.json") end def ssh_config ssh_config = node.connection.send(:ssh_config_file) # FIXME File.exist?(ssh_config) ? ["--ssh-config=#{ssh_config}"] : [] end end end end chake-0.91/lib/chake/tmpdir.rb0000644000004100000410000000012414537556746016236 0ustar www-datawww-datamodule Chake def self.tmpdir ENV.fetch('CHAKE_TMPDIR', 'tmp/chake') end end chake-0.91/lib/chake/readline.rb0000644000004100000410000000270114537556746016525 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 unless File.exist?(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) history.push(input) if input && input.strip != '' && input != @last 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.91/lib/chake/node.rb0000644000004100000410000000343014537556746015667 0ustar www-datawww-datarequire 'uri' require 'etc' require 'forwardable' require 'chake/connection' require 'chake/config_manager' module Chake class Node extend Forwardable attr_reader :hostname, :port, :username, :remote_username, :data attr_accessor :silent def self.max_node_name_length @max_node_name_length ||= 0 end class << self attr_writer :max_node_name_length end def initialize(hostname, data = {}) uri = parse_uri(hostname) @connection_name = uri.scheme @hostname = uri.host @port = uri.port @username = uri.user || Etc.getpwuid.name @remote_username = uri.user @path = uri.path @data = data set_max_node_length end def connection @connection ||= Chake::Connection.get(@connection_name).new(self) end def_delegators :connection, :run, :run_as_root, :run_shell, :rsync, :rsync_dest, :scp, :scp_dest, :skip? def config_manager @config_manager ||= Chake::ConfigManager.get(self) end def_delegators :config_manager, :converge, :apply, :path, :bootstrap_steps, :needs_upload? def path @path ||= config_manager.path end def log(msg) return if silent puts("%#{Node.max_node_name_length}s: %s\n" % { host: hostname, msg: msg }) end private def parse_uri(hostname) uri = URI.parse(hostname) if incomplete_uri(uri) uri = URI.parse("ssh://#{hostname}") end uri.path = nil if uri.path.empty? uri end def incomplete_uri(uri) !uri.host && ((!uri.scheme && uri.path) || (uri.scheme && uri.opaque)) end def set_max_node_length return if @hostname.length <= self.class.max_node_name_length self.class.max_node_name_length = @hostname.length end end end chake-0.91/lib/chake/yaml.rb0000644000004100000410000000034714537556746015710 0ustar www-datawww-datarequire 'yaml' module Chake module YAML def self.load_file(filename) if RUBY_VERSION >= '3.1' ::YAML.load_file(filename, aliases: true) else ::YAML.load_file(filename) end end end end chake-0.91/lib/chake/bootstrap/0000755000004100000410000000000014537556746016432 5ustar www-datawww-datachake-0.91/lib/chake/bootstrap/chef/0000755000004100000410000000000014537556746017337 5ustar www-datawww-datachake-0.91/lib/chake/bootstrap/chef/99_unsupported.sh0000644000004100000410000000072414537556746022607 0ustar www-datawww-dataecho "---------------------" echo "Unsupported platform: Installing chef-solo with omnibus package" echo "---------------------" echo for file in /etc/os-release /etc/issue; do if [ -f $file ]; then cat $file break fi done if ! which chef-solo >/dev/null ; then # Install chef-solo via omnibus package that chef provides # This script should install chef-solo in any Linux distribution wget -O- https://opscode.com/chef/install.sh | bash exit fi chake-0.91/lib/chake/bootstrap/chef/02_debian.sh0000644000004100000410000000030414537556746021413 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.91/lib/chake/bootstrap/chef/01_installed.sh0000644000004100000410000000012114537556746022144 0ustar www-datawww-data# chef-solo already installed if which chef-solo >/dev/null 2>&1; then exit fi chake-0.91/lib/chake/bootstrap/itamae-remote/0000755000004100000410000000000014537556746021163 5ustar www-datawww-datachake-0.91/lib/chake/bootstrap/itamae-remote/99_unsupported.sh0000644000004100000410000000044614537556746024434 0ustar www-datawww-dataecho "---------------------" echo "Unsupported platform: Installing itamae with rubygems" echo "---------------------" echo for file in /etc/os-release /etc/issue; do if [ -f $file ]; then cat $file break fi done if ! which itamae >/dev/null ; then gem install itamae exit fi chake-0.91/lib/chake/bootstrap/itamae-remote/02_debian.sh0000644000004100000410000000021114537556746023234 0ustar www-datawww-dataif [ -x /usr/bin/apt-get ]; then apt-get update export DEBIAN_FRONTEND=noninteractive apt-get -q -y install rsync itamae exit fi chake-0.91/lib/chake/bootstrap/itamae-remote/01_installed.sh0000644000004100000410000000011314537556746023771 0ustar www-datawww-data# itamae already installed if which itamae >/dev/null 2>&1; then exit fi chake-0.91/lib/chake/bootstrap/00_set_hostname.sh0000644000004100000410000000126114537556746021756 0ustar www-datawww-datahostname="$1" if [ "$(hostname)" != "${hostname}" ]; then echo "$hostname" > /etc/hostname hostname --file /etc/hostname fi 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.91/lib/chake/connection/0000755000004100000410000000000014537556746016554 5ustar www-datawww-datachake-0.91/lib/chake/connection/local.rb0000644000004100000410000000046214537556746020175 0ustar www-datawww-datarequire 'socket' module Chake class Connection class Local < Connection 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.91/lib/chake/connection/ssh.rb0000644000004100000410000000302114537556746017672 0ustar www-datawww-datamodule Chake class Connection class Ssh < Connection 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 ssh_command += " -p #{node.port}" if node.port 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.91/lib/chake/config.rb0000644000004100000410000000131314537556746016205 0ustar www-datawww-datarequire 'chake/node' require 'chake/yaml' module Chake class << self attr_accessor :nodes end end nodes_file = ENV['CHAKE_NODES'] || 'nodes.yaml' nodes_directory = ENV['CHAKE_NODES_D'] || 'nodes.d' nodes = (File.exist?(nodes_file) && Chake::YAML.load_file(nodes_file)) || {} nodes.values.each do |node| node['chake_metadata'] = { 'definition_file' => nodes_file } end Dir.glob(File.join(nodes_directory, '*.yaml')).sort.each do |f| file_nodes = Chake::YAML.load_file(f) file_nodes.values.each do |node| node['chake_metadata'] = { 'definition_file' => f } end nodes.merge!(file_nodes) end Chake.nodes = nodes.map { |node, data| Chake::Node.new(node, data) }.reject(&:skip?).uniq(&:hostname) chake-0.91/lib/chake.rb0000644000004100000410000002050614537556746014745 0ustar www-datawww-datarequire 'yaml' require 'json' require 'tmpdir' require 'chake/config' require 'chake/version' require 'chake/readline' require 'chake/wipe' desc 'Initializes current directory with sample structure' task init: 'init:itamae' Chake::ConfigManager.all.map do |cfgmgr| desc "Initializes current directory for #{cfgmgr.short_name}" task "init:#{cfgmgr.short_name}" do cfgmgr.init end end desc 'list nodes' task :nodes do fields = %i[hostname connection config_manager] lengths = fields.map do |f| [f.length, Chake.nodes.map { |n| n.send(f).to_s.length }.max].max end columns = lengths.map { |l| "%-#{l}s" }.join(' ') puts(columns % fields) puts(columns % lengths.map { |l| '-' * l }) Chake.nodes.each do |node| puts(columns % fields.map { |f| node.send(f) }) end end def encrypted_for(node) encrypted_files = Array(node.data['encrypted']) if encrypted_files.empty? encrypted_files = Dir.glob("**/files/{default,host-#{node.hostname}}/*.{asc,gpg}") + Dir.glob('**/files/*.{asc,gpg}') end encrypted_files.each_with_object({}) do |key, hash| hash[key] = key.sub(/\.(asc|gpg)$/, '') end end desc 'list encrypted files per node' task :encrypted do Chake.nodes.each do |node| puts "#{node.hostname}: #{Array(encrypted_for(node).keys).join(', ')}" end end def maybe_decrypt(node) if node.needs_upload? return yield end files = encrypted_for(node) files.each do |encrypted, target| sh "gpg --use-agent --quiet --decrypt --output #{target} #{encrypted}" end begin yield ensure files.each do |_, target| Chake::Wipe.instance.wipe(target) end end end def if_files_changed(node, group_name, files) return if files.empty? hash_io = IO.popen(%w[xargs sha1sum], 'w+') hash_io.puts(File.join(Chake.tmpdir, "#{node}.bootstrap")) files.sort.each { |f| hash_io.puts(f) } hash_io.close_write current_hash = hash_io.read hash_file = File.join(Chake.tmpdir, "#{node}.#{group_name}.sha1sum") hash_on_disk = nil hash_on_disk = File.read(hash_file) if File.exist?(hash_file) yield if current_hash != hash_on_disk FileUtils.mkdir_p(File.dirname(hash_file)) File.write(hash_file, current_hash) end def write_json_file(file, data) File.chmod(0o600, file) if File.exist?(file) File.open(file, 'w', 0o600) do |f| f.write(JSON.pretty_generate(data)) f.write("\n") end end desc 'Executed before bootstrapping' task bootstrap_common: :connect_common desc 'Executed before uploading' task upload_common: :connect_common desc 'Executed before uploading' task converge_common: :connect_common desc 'Executed before connecting to any host' task :connect_common Chake.nodes.each do |node| node.silent = Rake.application.options.silent hostname = node.hostname bootstrap_script = File.join(Chake.tmpdir, "#{hostname}.bootstrap") bootstrap_steps = node.bootstrap_steps bootstrap_code = (["#!/bin/sh\n", "set -eu\n"] + bootstrap_steps.map { |f| File.read(f) }).join desc "bootstrap #{hostname}" task "bootstrap:#{hostname}" => :bootstrap_common do mkdir_p Chake.tmpdir unless File.directory?(Chake.tmpdir) if !File.exist?(bootstrap_script) || File.read(bootstrap_script) != bootstrap_code # create bootstrap script File.write(bootstrap_script, bootstrap_code) chmod 0o755, bootstrap_script # 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}") end # overwrite config with current contents config = File.join(Chake.tmpdir, "#{hostname}.json") write_json_file(config, node.data) end desc "upload data to #{hostname}" task "upload:#{hostname}" => ["bootstrap:#{hostname}", :upload_common] do next unless node.needs_upload? encrypted = encrypted_for(node) rsync_excludes = (encrypted.values + encrypted.keys).map { |f| ['--exclude', f] }.flatten rsync_excludes << '--exclude' << '.git/' rsync_excludes << '--exclude' << 'cache/' rsync_excludes << '--exclude' << 'nodes/' rsync_excludes << '--exclude' << 'local-mode-cache/' rsync = node.rsync + ['-ap'] + ENV.fetch('CHAKE_RSYNC_OPTIONS', '').split rsync_logging = (Rake.application.options.trace && '--verbose') || '--quiet' hash_files = Dir.glob(File.join(Chake.tmpdir, '*.sha1sum')) files = Dir.glob('**/*').reject { |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)) rm_f target File.open(target, 'w', 0o400) do |output| IO.popen(['gpg', '--quiet', '--batch', '--use-agent', '--decrypt', encrypted_file]) do |data| output.write(data.read) end end puts "#{target} (decrypted)" 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 maybe_decrypt(node) do node.converge end end desc 'apply on #{hostname}' task "apply:#{hostname}", [:recipe] => %i[recipe_input connect_common] do |_task, _args| maybe_decrypt(node) do node.apply($recipe_to_apply) end end task "apply:#{hostname}" => converge_dependencies desc "run a command on #{hostname}" task "run:#{hostname}", [:command] => %i[run_input connect_common] do node.run($cmd_to_run) end desc "Logs in to a shell on #{hostname}" task "login:#{hostname}" => :connect_common do node.run_shell end desc 'checks connectivity and setup on all nodes' task "check:#{hostname}" => :connect_common do node.run('sudo echo OK') end end task :run_input, :command do |_task, args| $cmd_to_run = args[:command] unless $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] unless $recipe_to_apply recipes = Dir['**/*/recipes/*.rb'].map do |f| f =~ %r{(.*/)?(.*)/recipes/(.*).rb$} cookbook = Regexp.last_match(2) recipe = Regexp.last_match(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 unless recipes.include?($recipe_to_apply) abort "E: no such recipe: #{$recipe_to_apply}" end end end desc 'upload to all nodes' multitask upload: Chake.nodes.map { |node| "upload:#{node.hostname}" } desc 'bootstrap all nodes' multitask bootstrap: Chake.nodes.map { |node| "bootstrap:#{node.hostname}" } desc 'converge all nodes (default)' multitask 'converge' => Chake.nodes.map { |node| "converge:#{node.hostname}" } desc 'Apply on all nodes' multitask 'apply', [:recipe] => Chake.nodes.map { |node| "apply:#{node.hostname}" } desc 'run on all nodes' multitask :run, [:command] => Chake.nodes.map { |node| "run:#{node.hostname}" } task default: :converge desc 'checks connectivity and setup on all nodes' multitask check: (Chake.nodes.map { |node| "check:#{node.hostname}" }) do puts '✓ all hosts OK' puts ' - ssh connection works' puts ' - password-less sudo works' end desc 'runs a Ruby console in the chake environment' task :console do require 'irb' IRB.setup('__FILE__', argv: []) workspace = IRB::WorkSpace.new(self) puts 'chake - interactive console' puts '---------------------------' puts 'all node data in available in Chake.nodes' puts IRB::Irb.new(workspace).run(IRB.conf) end chake-0.91/ChangeLog.md0000644000004100000410000001770014537556746014752 0ustar www-datawww-data# 0.91 - itamae: handle empty recipe list - Add support for configuring encrypted files explicitly - itamae-remote: handle empty recipe list - Rakefile: deb:install: install dependencies as well - activate.sh: add script to use this source dir in a shell - Make rsync invocations quiet by default # 0.90.3 - `itamae_spec`: fix rspec warning about syntax for `expect { }.to raise` - bootstrap: `00_set_hostname.sh`: don't set hostname if not needed - Chake::Connection: add missing require for `$CHILD_STATUS` # 0.90.2 - upload: make sure to reupload on config manager changes - Apply suggestions by rubocop 1.39.0 - Chake::Connection: avoid setting constant inside of block - rubocop: keep assignment to `test_files` in the gemspec - gemspec: set `spec.metadata['rubygems_mfa_required']` # 0.90.1 * Fix loading node data under ruby < 3.1 # 0.90 * itamae: use --sudo when root for local backend * Chake::ConfigManager: fix typo * chake/config: allow aliases in YAML data * codespell: ignore tags file * ChangeLog.md: fix typo found by codespell * Implement new configuration manager: itamae-remote # 0.82 * gemspec: drop bundler version constraint * Chake::Wipe: improve wording in warning message * chake/config: store the node definition file in the node data # 0.81.1 * manpages: make sure all instances of \' are fixed * rake nodes: format output by ourselves # 0.81 * bootstrap/chef: exit if chef-solo is available * Always bootstrap nodes * Decrypt files in place when upload is not needed * itamae: handle silent mode * manpages: drop accute accent erroneously added by ronn # 0.80 This release adds support for multiple configuration managers. Chef is now only one of the options. There is also now support for configuration management with itamae, and lightweight configuration management tool inspired by Chef, and via shell commands. This should be mostly transparent to current Chef users, but new repositories initiated by chake will use itamae by default. Other notable changes: * rake nodes: list configuration manager and format as table * Chake::Connection: fix handling of stderr * Rebootstrap nodes when changing config managers * bootstrap, upload: skip when config manager does not need them # 0.21.2 * Chake::Backend#run: don't strip leading whitespace # 0.21.1 * Fix converge when the connection is not already made as root. This bug was introduced by the change in the previous release. # 0.21 * converge, apply: allow removing data from the node JSON attributes # 0.20 * check: give some feedback by running `sudo echo OK` instead of `sudo true` * Get rid of global variables * bin/chake: make rake run one thread for each node * Chake::Backend: run commands by opening a shell and writing to it * Document Chake.nodes # 0.19 * Protect node JSON files from other users # 0.18 * add console task * manpage: fix header transformation * manpage: ignore intermediary .adoc file # 0.17.1 * manpage: drop ad-hoc handling of `SOURCE_DATE_EPOCH` (let asciidoctor handle it) # 0.17 * make rsync exclude extra directories who are created as root by chef-solo at the server side. This fixes the case where upload phase when the SSH user is not root. # 0.16 * make `run` also capture stderr, for now mixed together with stdout. In the future that may be improved for example to print stderr output in red when running on a TTY. # 0.15 * improve text in the parallel execution docs * add new hook: `connect_common`, which will run before any attempt to connect to any node. * make output of `check` target more explicit about what was tested # 0.14 * Fix typo in README.md * thanks to Luciano Prestes Cavalcanti * Turn "all hosts" tasks (converge, upload, bootstrap, run, apply) into multitasks. This will make them run in parallel. # 0.13 * transmit decrypted files with mode 0400 * Use the Omnibus packages from Chef upstream on platforms where we don't have proper Chef packages from the OS official repository. # 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.91/Gemfile0000644000004100000410000000013214537556746014063 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in chake.gemspec gemspec chake-0.91/README.shell.md0000644000004100000410000000144014537556746015160 0ustar www-datawww-datachake-shell(7) -- configure chake nodes with shell ================================================== ## Description This configuration manager is a simpler wrapper for running a list of shell commands on the nodes. ## Configuration The _shell_ configuration manager requires one key called `shell`, and the value must be a list of strings representing the list of commands to run on the node when converging. ```yaml host1.mycompany.com: shell: - echo "HELLO WORLD" ``` ## Bootstrapping Very little bootstrapping is required for this configuration manager, as we hope every node you could possibly want to manage with it already has a POSIX shell as `/bin/sh`. During bootstrapping, only the node hostname will be set according to your chake configuration. ## See also * **chake(1)** chake-0.91/chake.gemspec0000644000004100000410000000264514537556746015223 0ustar www-datawww-datalib = File.expand_path('lib', __dir__) $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 = 'serverless configuration management tool for chef' spec.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." spec.homepage = 'https://gitlab.com/terceiro/chake' spec.license = 'MIT' spec.files = File.read('.manifest').split("\n") + ['.manifest'] 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' spec.add_development_dependency 'ronn-ng' spec.add_development_dependency 'rspec' spec.add_development_dependency 'rubocop' spec.add_development_dependency 'simplecov' spec.add_dependency 'rake' spec.metadata['rubygems_mfa_required'] = 'true' end chake-0.91/LICENSE.txt0000644000004100000410000000210614537556746014416 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.91/.gitlab-ci.yml0000644000004100000410000000063714537556746015236 0ustar www-datawww-dataimage: debian:testing .install: &install - apt-get update && apt-get install -qy ruby asciidoctor ruby-bundler ruby-rspec rubocop ruby-simplecov codespell ronn tests: before_script: *install script: - rake test manpages: before_script: *install script: - rake man style: before_script: *install script: - rake style codespell: before_script: *install script: - rake codespell chake-0.91/.ackrc0000644000004100000410000000004714537556746013661 0ustar www-datawww-data--ignore-dir=coverage --ignore-dir=pkg