chake 0.19 → 0.80

Sign up to get free protection for your applications and to get access to all the features.
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