chake 0.19 → 0.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.ackrc +1 -0
  3. data/.gitignore +2 -0
  4. data/.gitlab-ci.yml +21 -9
  5. data/.manifest +63 -0
  6. data/.rubocop.yml +53 -0
  7. data/.rubocop_todo.yml +40 -0
  8. data/ChangeLog.md +36 -0
  9. data/README.chef.md +70 -0
  10. data/README.itamae.md +58 -0
  11. data/README.md +124 -85
  12. data/README.shell.md +30 -0
  13. data/Rakefile +40 -13
  14. data/bin/chake +12 -1
  15. data/chake.gemspec +16 -16
  16. data/examples/test/.ssh_config +4 -0
  17. data/examples/test/Rakefile +1 -1
  18. data/examples/test/Vagrantfile +6 -0
  19. data/examples/test/config.rb +4 -4
  20. data/examples/test/cookbooks/basics/recipes/default.rb +1 -0
  21. data/examples/test/cookbooks/example/files/default/test +1 -0
  22. data/examples/test/cookbooks/example/files/{host-homer → host-lemur}/test.asc +0 -0
  23. data/lib/chake.rb +92 -168
  24. data/lib/chake/bootstrap/{01_debian.sh → chef/01_debian.sh} +0 -0
  25. data/lib/chake/bootstrap/{99_unsupported.sh → chef/99_unsupported.sh} +0 -0
  26. data/lib/chake/config.rb +16 -0
  27. data/lib/chake/config_manager.rb +93 -0
  28. data/lib/chake/config_manager/chef.rb +35 -0
  29. data/lib/chake/config_manager/itamae.rb +58 -0
  30. data/lib/chake/config_manager/shell.rb +34 -0
  31. data/lib/chake/config_manager/skel/chef/Rakefile +1 -0
  32. data/lib/chake/config_manager/skel/chef/config.rb +4 -0
  33. data/lib/chake/config_manager/skel/chef/cookbooks/basics/recipes/default.rb +1 -0
  34. data/lib/chake/config_manager/skel/chef/nodes.yaml +3 -0
  35. data/lib/chake/config_manager/skel/itamae/Rakefile +1 -0
  36. data/lib/chake/config_manager/skel/itamae/cookbooks/basics/default.rb +1 -0
  37. data/lib/chake/config_manager/skel/itamae/nodes.yaml +3 -0
  38. data/lib/chake/config_manager/skel/itamae/roles/basic.rb +1 -0
  39. data/lib/chake/config_manager/skel/shell/Rakefile +1 -0
  40. data/lib/chake/config_manager/skel/shell/nodes.yaml +3 -0
  41. data/lib/chake/connection.rb +83 -0
  42. data/lib/chake/{backend → connection}/local.rb +2 -8
  43. data/lib/chake/{backend → connection}/ssh.rb +6 -14
  44. data/lib/chake/node.rb +49 -29
  45. data/lib/chake/readline.rb +6 -10
  46. data/lib/chake/version.rb +1 -1
  47. data/man/Rakefile +27 -14
  48. data/man/readme2man.sed +5 -5
  49. data/spec/chake/backend/local_spec.rb +5 -6
  50. data/spec/chake/backend/ssh_spec.rb +8 -10
  51. data/spec/chake/backend_spec.rb +1 -2
  52. data/spec/chake/config_manager/chef_spec.rb +38 -0
  53. data/spec/chake/config_manager/itamae_spec.rb +69 -0
  54. data/spec/chake/config_manager/shell_spec.rb +54 -0
  55. data/spec/chake/config_manager_spec.rb +24 -0
  56. data/spec/chake/node_spec.rb +38 -15
  57. data/spec/spec_helper.rb +23 -19
  58. metadata +61 -14
  59. data/lib/chake/backend.rb +0 -78
@@ -4,11 +4,8 @@ require 'readline'
4
4
  require 'chake/tmpdir'
5
5
 
6
6
  module Chake
7
-
8
7
  class Readline
9
-
10
8
  class << self
11
-
12
9
  def history_file
13
10
  raise NotImplementedError
14
11
  end
@@ -22,12 +19,14 @@ module Chake
22
19
  end
23
20
 
24
21
  def init
25
- return if !File.exists?(history_file)
22
+ return unless File.exist?(history_file)
23
+
26
24
  @history = File.readlines(history_file).map(&:strip)
27
25
  end
28
26
 
29
27
  def finish
30
28
  return if !File.writable?(File.dirname(history_file)) || history.empty?
29
+
31
30
  File.open(history_file, 'w') do |f|
32
31
  history.last(500).each do |line|
33
32
  f.puts(line)
@@ -41,18 +40,16 @@ module Chake
41
40
  ::Readline::HISTORY.push(cmd)
42
41
  end
43
42
  input = ::Readline.readline(prompt)
44
- if input && input.strip != '' && input != @last
45
- history.push(input)
46
- end
43
+ history.push(input) if input && input.strip != '' && input != @last
47
44
  input
48
45
  end
49
-
50
46
  end
51
47
 
52
48
  class Commands < Readline
53
49
  def self.history_file
54
50
  File.join(Chake.tmpdir, '.commands_history')
55
51
  end
52
+
56
53
  def self.prompt
57
54
  '$ '
58
55
  end
@@ -62,13 +59,12 @@ module Chake
62
59
  def self.history_file
63
60
  File.join(Chake.tmpdir, '.recipes_history')
64
61
  end
62
+
65
63
  def self.prompt
66
64
  '> '
67
65
  end
68
66
  end
69
-
70
67
  end
71
-
72
68
  end
73
69
 
74
70
  Chake::Readline.constants.each do |subclass|
@@ -1,3 +1,3 @@
1
1
  module Chake
2
- VERSION = "0.19"
2
+ VERSION = '0.80'.freeze
3
3
  end
@@ -1,22 +1,35 @@
1
- task :default => 'man/chake.1'
1
+ MAIN_MANPAGE = 'man/chake.1'.freeze
2
+ OTHER_MANPAGES = %w[
3
+ man/chake-chef.7
4
+ man/chake-itamae.7
5
+ man/chake-shell.7
6
+ ].freeze
7
+ MANPAGES = [MAIN_MANPAGE] + OTHER_MANPAGES
2
8
 
3
- file 'man/chake.1' => ['man/chake.adoc'] do
4
- sh 'asciidoctor --backend manpage --out-file man/chake.1 man/chake.adoc'
5
- end
9
+ task default: :man
10
+ task man: MANPAGES
6
11
 
7
- file 'man/chake.adoc' => ['README.md', 'man/readme2man.sed'] do |t|
8
- sh "sed -f man/readme2man.sed README.md > #{t.name} || (rm -f #{t.name}; false)"
12
+ MANPAGES.each do |man|
13
+ source = "README#{man.pathmap('%n').sub(/^chake/, '').sub('-', '.')}.md"
14
+ file man => [source, 'man/readme2man.sed'] do
15
+ sh "sed -f man/readme2man.sed #{source} > #{man}.ronn || (rm -f #{man}.ronn; false)"
16
+ sh "ronn --roff #{man}.ronn"
17
+ sh "rm -f #{man}.ronn"
18
+ end
9
19
  end
10
20
 
11
- task :install => 'man/chake.1' do
12
- prefix = ENV['PREFIX'] || File.exists?('debian/rules') && '/usr' || '/usr/local'
13
- target = [ENV["DESTDIR"], prefix , 'share/man/man1'].compact
14
- man = File.join(*target)
15
- sh 'install', '-d', '-m', '0755', man
16
- sh 'install', '-m', '0644', 'man/chake.1', man
21
+ task install: MANPAGES do
22
+ prefix = ENV['PREFIX'] || File.exist?('debian/rules') && '/usr' || '/usr/local'
23
+ man1 = File.join(*[ENV['DESTDIR'], prefix, 'share/man/man1'].compact)
24
+ man7 = File.join(*[ENV['DESTDIR'], prefix, 'share/man/man7'].compact)
25
+ target = { '.1' => man1, '.7' => man7 }
26
+ sh 'install', '-d', '-m', '0755', man1
27
+ sh 'install', '-d', '-m', '0755', man7
28
+ MANPAGES.each do |m|
29
+ sh 'install', '-m', '0644', m, target[m.pathmap('%x')]
30
+ end
17
31
  end
18
32
 
19
33
  task :clean do
20
- rm_f 'man/chake.1'
21
- rm_f 'man/chake.adoc'
34
+ rm_f MANPAGES
22
35
  end
@@ -1,6 +1,6 @@
1
- 1a :doctype: manpage
2
-
3
- /^## Install/,/^[^#]/ d
4
- /^## Contributing/,$ d
5
-
1
+ # Capitalize section titles
6
2
  s/^\(##\+\)\(.*\)/\1 \U\2/
3
+
4
+ # Turn fenced code blocks into 4-space indented blocks
5
+ /^```/,/```/ s/^/ /
6
+ /^ ```.*/ d
@@ -1,14 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Chake::Backend::Local do
3
+ describe Chake::Connection::Local do
4
+ include_examples 'Chake::Connection', Chake::Connection::Local
4
5
 
5
- include_examples "Chake::Backend", Chake::Backend::Local
6
+ let(:node) { Chake::Node.new('local://myusername@myhost/srv/chake') }
6
7
 
7
- let(:node) { Chake::Node.new('local://myusername@myhost/srv/chef') }
8
+ it('runs commands with sh -c') { expect(connection.command_runner).to eq(['sh', '-c']) }
8
9
 
9
- it('runs commands with sh -c') { expect(backend.command_runner).to eq(['sh', '-c']) }
10
-
11
- it('rsyncs locally') { expect(backend.rsync_dest).to eq('/srv/chef/') }
10
+ it('rsyncs locally') { expect(connection.rsync_dest).to eq('/srv/chake/') }
12
11
 
13
12
  it('skips if hostname is not the local hostname') do
14
13
  allow(Socket).to receive(:gethostname).and_return('otherhost')
@@ -1,14 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Chake::Backend::Ssh do
3
+ describe Chake::Connection::Ssh do
4
+ include_examples 'Chake::Connection', Chake::Connection::Ssh
4
5
 
5
- include_examples "Chake::Backend", Chake::Backend::Ssh
6
+ let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chake') }
6
7
 
7
- let(:node) { Chake::Node.new('ssh://myuser@myhost/srv/chef') }
8
+ it('runs commands with ssh') { expect(connection.command_runner).to eq(['ssh', 'myuser@myhost']) }
8
9
 
9
- it('runs commands with ssh') { expect(backend.command_runner).to eq(['ssh', 'myuser@myhost']) }
10
-
11
- it('rsyncs over ssh') { expect(backend.rsync_dest).to eq('myuser@myhost:/srv/chef/') }
10
+ it('rsyncs over ssh') { expect(connection.rsync_dest).to eq('myuser@myhost:/srv/chake/') }
12
11
 
13
12
  it 'uses no remote username if none was passed' do
14
13
  node = Chake::Node.new('theserver')
@@ -24,14 +23,13 @@ describe Chake::Backend::Ssh do
24
23
  context 'with a custom port' do
25
24
  let(:node) { Chake::Node.new('ssh://myhost:2222') }
26
25
  it 'uses port with ssh' do
27
- expect(backend.command_runner).to eq(['ssh', '-p', '2222', 'myhost'])
26
+ expect(connection.command_runner).to eq(['ssh', '-p', '2222', 'myhost'])
28
27
  end
29
28
  it 'uses port with scp' do
30
- expect(backend.scp).to eq(['scp', '-P', '2222'])
29
+ expect(connection.scp).to eq(['scp', '-P', '2222'])
31
30
  end
32
31
  it 'uses port with rsync' do
33
- expect(backend.send(:rsync_ssh)).to eq(['-e', 'ssh -p 2222'])
32
+ expect(connection.send(:rsync_ssh)).to eq(['-e', 'ssh -p 2222'])
34
33
  end
35
34
  end
36
-
37
35
  end
@@ -1,2 +1 @@
1
- require 'chake/backend'
2
-
1
+ require 'chake/connection'
@@ -0,0 +1,38 @@
1
+ require 'chake/node'
2
+ require 'chake/config_manager/chef'
3
+
4
+ describe Chake::ConfigManager::Chef do
5
+ let(:node) do
6
+ Chake::Node.new('foobar')
7
+ end
8
+
9
+ subject do
10
+ Chake::ConfigManager::Chef.new(node)
11
+ end
12
+
13
+ it 'provides a name' do
14
+ expect(subject.name).to eq('chef')
15
+ end
16
+
17
+ it 'calls chef-solo on converge' do
18
+ expect(subject).to receive(:logging).and_return('-l debug')
19
+ 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})
20
+ subject.converge
21
+ end
22
+
23
+ it 'calls chef-solo on apply' do
24
+ expect(subject).to receive(:logging).and_return('-l debug')
25
+ 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\]})
26
+ subject.apply('myrecipe')
27
+ end
28
+
29
+ context 'logging' do
30
+ it 'logs when requested' do
31
+ expect(subject.send(:logging)).to eq('')
32
+ end
33
+ it 'only show fatal errrrs when requested' do
34
+ node.silent = true
35
+ expect(subject.send(:logging)).to eq('-l fatal')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,69 @@
1
+ require 'chake/node'
2
+ require 'chake/config_manager/itamae'
3
+
4
+ describe Chake::ConfigManager::Itamae do
5
+ let(:hostname) { 'foobar' }
6
+ let(:node) do
7
+ Chake::Node.new(hostname).tap do |n|
8
+ n.silent = true
9
+ n.data['itamae'] = ['foo.rb', 'bar.rb']
10
+ end
11
+ end
12
+ let(:cfg) { Chake::ConfigManager::Itamae.new(node) }
13
+ let(:output) { StringIO.new("line1\nline2\n") }
14
+
15
+ it 'does not require bootstrapping' do
16
+ expect(cfg.needs_bootstrap?).to eq(false)
17
+ end
18
+
19
+ it 'does not require uploading' do
20
+ expect(cfg.needs_upload?).to eq(false)
21
+ end
22
+
23
+ it 'calls itamae when converging' do
24
+ expect(IO).to receive(:popen).with(
25
+ array_including('itamae', 'foo.rb', 'bar.rb'),
26
+ 'r',
27
+ err: %i[child out]
28
+ ).and_return(output)
29
+ cfg.converge
30
+ end
31
+
32
+ it 'calls itamae when applying' do
33
+ expect(IO).to receive(:popen).with(
34
+ array_including('itamae', 'foobarbaz.rb'),
35
+ 'r',
36
+ err: %i[child out]
37
+ ).and_return(output)
38
+ cfg.apply('foobarbaz.rb')
39
+ end
40
+
41
+ context 'for ssh hosts' do
42
+ let(:hostname) { 'ssh://theusernanme@thehostname' }
43
+ it 'calls itamae ssh subcommand' do
44
+ expect(IO).to receive(:popen).with(
45
+ array_including('itamae', 'ssh', '--host=thehostname', '--user=theusernanme'),
46
+ anything,
47
+ err: anything
48
+ ).and_return(output)
49
+ cfg.converge
50
+ end
51
+ end
52
+
53
+ context 'for local hosts' do
54
+ let(:hostname) { 'local://localhostname' }
55
+ it 'calls itamae with local subcommand' do
56
+ expect(IO).to receive(:popen).with(
57
+ ['itamae', 'local', /--node-json=.*/, 'foo.rb', 'bar.rb'],
58
+ anything,
59
+ err: anything
60
+ ).and_return(output)
61
+ cfg.converge
62
+ end
63
+ end
64
+
65
+ it 'throws an error for unsupported connection' do
66
+ allow(node).to receive(:connection).and_return(Object.new)
67
+ expect(-> { cfg.converge }).to raise_error(NotImplementedError)
68
+ end
69
+ end
@@ -0,0 +1,54 @@
1
+ require 'chake/node'
2
+ require 'chake/config_manager'
3
+ require 'chake/config_manager/shell'
4
+
5
+ describe Chake::ConfigManager::Shell do |_c|
6
+ let(:node) do
7
+ Chake::Node.new('foobar').tap do |n|
8
+ allow(n).to receive(:path).and_return(nil)
9
+ end
10
+ end
11
+ it 'accepts node with explicit config_manager in data' do
12
+ node.data['config_manager'] = 'shell'
13
+ expect(Chake::ConfigManager.get(node)).to be_a(Chake::ConfigManager::Shell)
14
+ end
15
+ it 'accepts node with `shell` in data' do
16
+ node.data['shell'] = ['date']
17
+ expect(Chake::ConfigManager.get(node)).to be_a(Chake::ConfigManager::Shell)
18
+ end
19
+
20
+ let(:subject) { Chake::ConfigManager::Shell.new(node) }
21
+
22
+ it 'calls all shell commands on converge' do
23
+ node.data['shell'] = %w[date true]
24
+ expect(node).to receive(:run_as_root).with("sh -xec 'date && true'")
25
+ subject.converge
26
+ end
27
+
28
+ it 'changes to node path to run commands' do
29
+ node.data['shell'] = %w[true]
30
+ allow(node).to receive(:path).and_return('/foo')
31
+ expect(node).to receive(:run_as_root).with("sh -xec 'cd /foo && true'")
32
+ subject.converge
33
+ end
34
+
35
+ it 'calls given shell command on apply' do
36
+ node.data['shell'] = %w[date true]
37
+ expect(node).to receive(:run_as_root).with("sh -xec 'reboot'")
38
+ subject.apply('reboot')
39
+ end
40
+
41
+ it 'hides output on converge in silent mode' do
42
+ node.data['shell'] = ['date']
43
+ node.silent = true
44
+ expect(node).to receive(:run_as_root).with("sh -ec 'date' >/dev/null")
45
+ subject.converge
46
+ end
47
+
48
+ it 'hides output on apply in silent mode' do
49
+ node.data['shell'] = ['date']
50
+ node.silent = true
51
+ expect(node).to receive(:run_as_root).with("sh -ec 'reboot' >/dev/null")
52
+ subject.apply('reboot')
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ require 'pathname'
2
+ require 'chake/node'
3
+ require 'chake/config_manager'
4
+
5
+ describe Chake::ConfigManager do
6
+ subject { Chake::ConfigManager.new(Chake::Node.new('ssh://user@hostname.tld')) }
7
+ it 'provides a path' do
8
+ allow(subject).to receive(:name).and_return('xyz')
9
+ expect(subject.path).to eq('/var/tmp/xyz.user')
10
+ end
11
+
12
+ it 'provides bootstrap scripts' do
13
+ bootstrap_steps = subject.bootstrap_steps
14
+ expect(bootstrap_steps).to_not be_empty
15
+ bootstrap_steps.each do |path|
16
+ expect(Pathname(path)).to exist
17
+ end
18
+ end
19
+
20
+ it 'requires uploading and bootstrapping by default' do
21
+ expect(subject.needs_upload?).to eq(true)
22
+ expect(subject.needs_bootstrap?).to eq(true)
23
+ end
24
+ end
@@ -1,7 +1,6 @@
1
1
  require 'chake/node'
2
2
 
3
3
  describe Chake::Node do
4
-
5
4
  before do
6
5
  ent = double
7
6
  allow(ent).to receive(:name).and_return('jonhdoe')
@@ -10,23 +9,24 @@ describe Chake::Node do
10
9
 
11
10
  let(:simple) { Chake::Node.new('hostname') }
12
11
  it('has a name') { expect(simple.hostname).to eq('hostname') }
13
- it('uses ssh by default') { expect(simple.backend).to be_an_instance_of(Chake::Backend::Ssh) }
12
+ it('uses ssh by default') { expect(simple.connection).to be_an_instance_of(Chake::Connection::Ssh) }
14
13
  it('user current username by default') {
15
14
  expect(simple.username).to eq('jonhdoe')
16
15
  }
17
- it('writes to /var/tmp/chef.$username') {
18
- expect(simple.path).to eq('/var/tmp/chef.jonhdoe')
16
+ it('writes to specified path') {
17
+ node = Chake::Node.new('ssh://host.tld/path/to/config')
18
+ expect(node.path).to eq('/path/to/config')
19
19
  }
20
20
 
21
21
  let(:with_username) { Chake::Node.new('username@hostname') }
22
22
  it('accepts username') { expect(with_username.username).to eq('username') }
23
- it('uses ssh') { expect(with_username.backend).to be_an_instance_of(Chake::Backend::Ssh) }
23
+ it('uses ssh') { expect(with_username.connection).to be_an_instance_of(Chake::Connection::Ssh) }
24
24
 
25
- let(:with_backend) { Chake::Node.new('local://hostname')}
26
- it('accepts backend as URI scheme') { expect(with_backend.backend).to be_an_instance_of(Chake::Backend::Local) }
25
+ let(:with_connection) { Chake::Node.new('local://hostname') }
26
+ it('accepts connection as URI scheme') { expect(with_connection.connection).to be_an_instance_of(Chake::Connection::Local) }
27
27
 
28
- it('wont accept any backend') do
29
- expect { Chake::Node.new('foobar://bazqux').backend }.to raise_error(ArgumentError)
28
+ it('wont accept any connection') do
29
+ expect { Chake::Node.new('foobar://bazqux').connection }.to raise_error(ArgumentError)
30
30
  end
31
31
 
32
32
  let(:with_data) { Chake::Node.new('local://localhost', 'run_list' => ['recipe[common]']) }
@@ -42,20 +42,43 @@ describe Chake::Node do
42
42
  let(:with_port_but_no_scheme) { Chake::Node.new('foo.bar.com:2222') }
43
43
  it('accepts a port specification without a scheme') do
44
44
  expect(with_port_but_no_scheme.port).to eq(2222)
45
- expect(with_port_but_no_scheme.backend.to_s).to eq('ssh')
45
+ expect(with_port_but_no_scheme.connection.to_s).to eq('ssh')
46
46
  end
47
47
 
48
- [:run, :run_as_root, :rsync_dest].each do |method|
49
- it("delegates #{method} to backend") do
48
+ %i[run run_as_root rsync_dest].each do |method|
49
+ it("delegates #{method} to connection") do
50
50
  node = simple
51
51
 
52
- backend = double
52
+ connection = double
53
53
  args = Object.new
54
- allow(node).to receive(:backend).and_return(backend)
54
+ allow(node).to receive(:connection).and_return(connection)
55
55
 
56
- expect(backend).to receive(method).with(args)
56
+ expect(connection).to receive(method).with(args)
57
57
  node.send(method, args)
58
58
  end
59
59
  end
60
60
 
61
+ it 'delegates converge to config_manager' do
62
+ node = simple
63
+ expect(node.config_manager).to receive(:converge)
64
+ node.converge
65
+ end
66
+
67
+ it 'delegates apply to config_manager' do
68
+ node = simple
69
+ expect(node.config_manager).to receive(:apply).with('myrecipe')
70
+ node.apply('myrecipe')
71
+ end
72
+
73
+ it 'falls back to writing to path specified by config manager' do
74
+ expect(simple.path).to eq(simple.config_manager.path)
75
+ end
76
+
77
+ it 'calculates max node name length' do
78
+ Chake::Node.max_node_name_length = 0
79
+ Chake::Node.new('foobar')
80
+ expect(Chake::Node.max_node_name_length).to eq(6)
81
+ Chake::Node.new('foobarbaz')
82
+ expect(Chake::Node.max_node_name_length).to eq(9)
83
+ end
61
84
  end