chake-0.20/ 0000755 0000041 0000041 00000000000 13567462520 012550 5 ustar www-data www-data chake-0.20/README.md 0000644 0000041 0000041 00000024216 13567462520 014034 0 ustar www-data www-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
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 o 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
```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)
* `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:
```
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 converge: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.
### 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)**, **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
chake-0.20/bin/ 0000755 0000041 0000041 00000000000 13567462520 013320 5 ustar www-data www-data chake-0.20/bin/chake 0000755 0000041 0000041 00000001432 13567462520 014321 0 ustar www-data www-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
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.20/spec/ 0000755 0000041 0000041 00000000000 13567462520 013502 5 ustar www-data www-data chake-0.20/spec/chake/ 0000755 0000041 0000041 00000000000 13567462520 014555 5 ustar www-data www-data chake-0.20/spec/chake/node_spec.rb 0000644 0000041 0000041 00000003753 13567462520 017051 0 ustar www-data www-data require '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.20/spec/chake/backend_spec.rb 0000644 0000041 0000041 00000000031 13567462520 017475 0 ustar www-data www-data require 'chake/backend'
chake-0.20/spec/chake/backend/ 0000755 0000041 0000041 00000000000 13567462520 016144 5 ustar www-data www-data chake-0.20/spec/chake/backend/local_spec.rb 0000644 0000041 0000041 00000001011 13567462520 020566 0 ustar www-data www-data require '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.20/spec/chake/backend/ssh_spec.rb 0000644 0000041 0000041 00000002132 13567462520 020276 0 ustar www-data www-data require '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.20/spec/spec_helper.rb 0000644 0000041 0000041 00000002415 13567462520 016322 0 ustar www-data www-data begin
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 + ['/bin/sh'], mode='w+', Hash).and_return(io)
expect(io).to receive(:write).with('something').ordered
expect(io).to receive(:close_write).ordered
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.20/coverage/ 0000755 0000041 0000041 00000000000 13567462520 014343 5 ustar www-data www-data chake-0.20/coverage/index.html 0000644 0000041 0000041 00000207504 13567462520 016350 0 ustar www-data www-data
Code coverage for Chake
Generated 2019-11-27T08:59:16-03:00
All Files
(92.47%
covered at
3.83
hits/line)
7 files in total.
186 relevant lines.
172 lines covered and
14 lines missed