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.
- checksums.yaml +4 -4
- data/.ackrc +1 -0
- data/.gitignore +2 -0
- data/.gitlab-ci.yml +21 -9
- data/.manifest +63 -0
- data/.rubocop.yml +53 -0
- data/.rubocop_todo.yml +40 -0
- data/ChangeLog.md +36 -0
- data/README.chef.md +70 -0
- data/README.itamae.md +58 -0
- data/README.md +124 -85
- data/README.shell.md +30 -0
- data/Rakefile +40 -13
- data/bin/chake +12 -1
- data/chake.gemspec +16 -16
- data/examples/test/.ssh_config +4 -0
- data/examples/test/Rakefile +1 -1
- data/examples/test/Vagrantfile +6 -0
- data/examples/test/config.rb +4 -4
- data/examples/test/cookbooks/basics/recipes/default.rb +1 -0
- data/examples/test/cookbooks/example/files/default/test +1 -0
- data/examples/test/cookbooks/example/files/{host-homer → host-lemur}/test.asc +0 -0
- data/lib/chake.rb +92 -168
- data/lib/chake/bootstrap/{01_debian.sh → chef/01_debian.sh} +0 -0
- data/lib/chake/bootstrap/{99_unsupported.sh → chef/99_unsupported.sh} +0 -0
- data/lib/chake/config.rb +16 -0
- data/lib/chake/config_manager.rb +93 -0
- data/lib/chake/config_manager/chef.rb +35 -0
- data/lib/chake/config_manager/itamae.rb +58 -0
- data/lib/chake/config_manager/shell.rb +34 -0
- data/lib/chake/config_manager/skel/chef/Rakefile +1 -0
- data/lib/chake/config_manager/skel/chef/config.rb +4 -0
- data/lib/chake/config_manager/skel/chef/cookbooks/basics/recipes/default.rb +1 -0
- data/lib/chake/config_manager/skel/chef/nodes.yaml +3 -0
- data/lib/chake/config_manager/skel/itamae/Rakefile +1 -0
- data/lib/chake/config_manager/skel/itamae/cookbooks/basics/default.rb +1 -0
- data/lib/chake/config_manager/skel/itamae/nodes.yaml +3 -0
- data/lib/chake/config_manager/skel/itamae/roles/basic.rb +1 -0
- data/lib/chake/config_manager/skel/shell/Rakefile +1 -0
- data/lib/chake/config_manager/skel/shell/nodes.yaml +3 -0
- data/lib/chake/connection.rb +83 -0
- data/lib/chake/{backend → connection}/local.rb +2 -8
- data/lib/chake/{backend → connection}/ssh.rb +6 -14
- data/lib/chake/node.rb +49 -29
- data/lib/chake/readline.rb +6 -10
- data/lib/chake/version.rb +1 -1
- data/man/Rakefile +27 -14
- data/man/readme2man.sed +5 -5
- data/spec/chake/backend/local_spec.rb +5 -6
- data/spec/chake/backend/ssh_spec.rb +8 -10
- data/spec/chake/backend_spec.rb +1 -2
- data/spec/chake/config_manager/chef_spec.rb +38 -0
- data/spec/chake/config_manager/itamae_spec.rb +69 -0
- data/spec/chake/config_manager/shell_spec.rb +54 -0
- data/spec/chake/config_manager_spec.rb +24 -0
- data/spec/chake/node_spec.rb +38 -15
- data/spec/spec_helper.rb +23 -19
- metadata +61 -14
- data/lib/chake/backend.rb +0 -78
File without changes
|
File without changes
|
data/lib/chake/config.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'chake/node'
|
2
|
+
|
3
|
+
module Chake
|
4
|
+
class << self
|
5
|
+
attr_accessor :nodes
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
nodes_file = ENV['CHAKE_NODES'] || 'nodes.yaml'
|
10
|
+
nodes_directory = ENV['CHAKE_NODES_D'] || 'nodes.d'
|
11
|
+
node_data = File.exist?(nodes_file) && YAML.load_file(nodes_file) || {}
|
12
|
+
Dir.glob(File.join(nodes_directory, '*.yaml')).sort.each do |f|
|
13
|
+
node_data.merge!(YAML.load_file(f))
|
14
|
+
end
|
15
|
+
|
16
|
+
Chake.nodes = node_data.map { |node, data| Chake::Node.new(node, data) }.reject(&:skip?).uniq(&:hostname)
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Chake
|
4
|
+
class ConfigManager
|
5
|
+
attr_reader :node
|
6
|
+
|
7
|
+
def initialize(node)
|
8
|
+
@node = node
|
9
|
+
end
|
10
|
+
|
11
|
+
def converge; end
|
12
|
+
|
13
|
+
def apply(config); end
|
14
|
+
|
15
|
+
def path
|
16
|
+
"/var/tmp/#{name}.#{node.username}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def name
|
20
|
+
self.class.short_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
name
|
25
|
+
end
|
26
|
+
|
27
|
+
def bootstrap_steps
|
28
|
+
base = File.join(File.absolute_path(File.dirname(__FILE__)), 'bootstrap')
|
29
|
+
steps = Dir[File.join(base, '*.sh')] + Dir[File.join(base, name, '*.sh')]
|
30
|
+
steps.sort_by { |f| File.basename(f) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def needs_bootstrap?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def needs_upload?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.short_name
|
42
|
+
name.split('::').last.downcase
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.priority(new_prioriry = nil)
|
46
|
+
@priority ||= new_prioriry || 50
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.inherited(klass)
|
50
|
+
super
|
51
|
+
@subclasses ||= []
|
52
|
+
@subclasses << klass
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.get(node)
|
56
|
+
available = @subclasses.sort_by(&:priority)
|
57
|
+
manager = available.find { |c| c.short_name == node.data['config_manager'] }
|
58
|
+
manager ||= available.find { |c| c.accept?(node) }
|
59
|
+
raise ArgumentError, "Can't find configuration manager class for node #{node.hostname}. Available: #{available}.join(', ')}" unless manager
|
60
|
+
|
61
|
+
manager.new(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.accept?(_node)
|
65
|
+
false
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.all
|
69
|
+
@subclasses
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.init
|
73
|
+
skel = Pathname(__FILE__).parent / 'config_manager' / 'skel' / short_name
|
74
|
+
skel.glob('**/*').each do |source|
|
75
|
+
target = source.relative_path_from(skel)
|
76
|
+
if target.exist?
|
77
|
+
puts "exists: #{target}"
|
78
|
+
else
|
79
|
+
if source.directory?
|
80
|
+
FileUtils.mkdir_p target
|
81
|
+
else
|
82
|
+
FileUtils.cp source, target
|
83
|
+
end
|
84
|
+
puts "create: #{target}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Dir["#{File.dirname(__FILE__)}/config_manager/*.rb"].sort.each do |f|
|
92
|
+
require f
|
93
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'chake/config'
|
2
|
+
require 'chake/tmpdir'
|
3
|
+
|
4
|
+
module Chake
|
5
|
+
class ConfigManager
|
6
|
+
class Chef < ConfigManager
|
7
|
+
CONFIG = ENV['CHAKE_CHEF_CONFIG'] || 'config.rb'
|
8
|
+
|
9
|
+
def converge
|
10
|
+
node.run_as_root "sh -c 'rm -f #{node.path}/nodes/*.json && chef-solo -c #{node.path}/#{CONFIG} #{logging} -j #{json_config}'"
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply(config)
|
14
|
+
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}]'"
|
15
|
+
end
|
16
|
+
|
17
|
+
priority 99
|
18
|
+
|
19
|
+
def self.accept?(_node)
|
20
|
+
true # this is the default, but after everything else
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def json_config
|
26
|
+
parts = [node.path, Chake.tmpdir, "#{node.hostname}.json"].compact
|
27
|
+
File.join(parts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def logging
|
31
|
+
node.silent && '-l fatal' || ''
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'chake/config'
|
3
|
+
require 'chake/tmpdir'
|
4
|
+
|
5
|
+
module Chake
|
6
|
+
class ConfigManager
|
7
|
+
class Itamae < ConfigManager
|
8
|
+
def converge
|
9
|
+
run_itamae(*node.data['itamae'])
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply(config)
|
13
|
+
run_itamae(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def needs_bootstrap?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def needs_upload?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.accept?(node)
|
25
|
+
node.data.key?('itamae')
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def run_itamae(*recipes)
|
31
|
+
cmd = ['itamae']
|
32
|
+
case node.connection
|
33
|
+
when Chake::Connection::Ssh
|
34
|
+
cmd << 'ssh' << "--user=#{node.username}" << "--host=#{node.hostname}"
|
35
|
+
cmd += ssh_config
|
36
|
+
when Chake::Connection::Local
|
37
|
+
cmd << 'local'
|
38
|
+
else
|
39
|
+
raise NotImplementedError, "Connection type #{node.connection.class} not supported for itamee"
|
40
|
+
end
|
41
|
+
cmd << "--node-json=#{json_config}"
|
42
|
+
cmd += recipes
|
43
|
+
node.log("$ #{cmd.join(' ')}")
|
44
|
+
io = IO.popen(cmd, 'r', err: %i[child out])
|
45
|
+
node.connection.read_output(io)
|
46
|
+
end
|
47
|
+
|
48
|
+
def json_config
|
49
|
+
File.join(Chake.tmpdir, "#{node.hostname}.json")
|
50
|
+
end
|
51
|
+
|
52
|
+
def ssh_config
|
53
|
+
ssh_config = node.connection.send(:ssh_config_file) # FIXME
|
54
|
+
File.exist?(ssh_config) ? ["--ssh-config=#{ssh_config}"] : []
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'chake/config'
|
3
|
+
|
4
|
+
module Chake
|
5
|
+
class ConfigManager
|
6
|
+
class Shell < ConfigManager
|
7
|
+
def converge
|
8
|
+
commands = node.data['shell'].join(' && ')
|
9
|
+
node.run_as_root sh(commands)
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply(config)
|
13
|
+
node.run_as_root sh(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.accept?(node)
|
17
|
+
node.data.key?('shell')
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def sh(command)
|
23
|
+
if node.path
|
24
|
+
command = "cd #{node.path} && " + command
|
25
|
+
end
|
26
|
+
if node.silent
|
27
|
+
"sh -ec '#{command}' >/dev/null"
|
28
|
+
else
|
29
|
+
"sh -xec '#{command}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'chake'
|
@@ -0,0 +1 @@
|
|
1
|
+
package 'openssh-server'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'chake'
|
@@ -0,0 +1 @@
|
|
1
|
+
package 'openssh-server'
|
@@ -0,0 +1 @@
|
|
1
|
+
include_recipe '../cookbooks/basics'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'chake'
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Chake
|
2
|
+
Connection = Struct.new(:node) do
|
3
|
+
class CommandFailed < RuntimeError
|
4
|
+
end
|
5
|
+
|
6
|
+
def scp
|
7
|
+
['scp']
|
8
|
+
end
|
9
|
+
|
10
|
+
def scp_dest
|
11
|
+
''
|
12
|
+
end
|
13
|
+
|
14
|
+
def rsync
|
15
|
+
['rsync']
|
16
|
+
end
|
17
|
+
|
18
|
+
def rsync_dest
|
19
|
+
"#{node.path}/"
|
20
|
+
end
|
21
|
+
|
22
|
+
def run(cmd)
|
23
|
+
node.log('$ %<command>s' % { command: cmd })
|
24
|
+
io = IO.popen(command_runner + ['/bin/sh'], 'w+', err: %i[child out])
|
25
|
+
io.write(cmd)
|
26
|
+
io.close_write
|
27
|
+
read_output(io)
|
28
|
+
end
|
29
|
+
|
30
|
+
def read_output(io)
|
31
|
+
io.each_line do |line|
|
32
|
+
node.log(line.gsub(/\s*$/, ''))
|
33
|
+
end
|
34
|
+
io.close
|
35
|
+
if $CHILD_STATUS
|
36
|
+
status = $CHILD_STATUS.exitstatus
|
37
|
+
if status != 0
|
38
|
+
raise CommandFailed, [node.hostname, 'FAILED with exit status %<status>d' % { status: status }].join(': ')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_shell
|
44
|
+
system(*shell_command)
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_as_root(cmd)
|
48
|
+
if node.remote_username == 'root'
|
49
|
+
run(cmd)
|
50
|
+
else
|
51
|
+
run("sudo #{cmd}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
self.class.connection_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def skip?
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.connection_name
|
64
|
+
name.split('::').last.downcase
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.inherited(subclass)
|
68
|
+
super
|
69
|
+
@connections ||= []
|
70
|
+
@connections << subclass
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.get(name)
|
74
|
+
connection = @connections.find { |b| b.connection_name == name }
|
75
|
+
raise ArgumentError, "Invalid connection name: #{name}" unless connection
|
76
|
+
|
77
|
+
connection
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
require 'chake/connection/ssh'
|
83
|
+
require 'chake/connection/local'
|
@@ -1,11 +1,8 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
3
|
module Chake
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
class Local < Backend
|
8
|
-
|
4
|
+
class Connection
|
5
|
+
class Local < Connection
|
9
6
|
def command_runner
|
10
7
|
['sh', '-c']
|
11
8
|
end
|
@@ -17,9 +14,6 @@ module Chake
|
|
17
14
|
def skip?
|
18
15
|
node.hostname != Socket.gethostname
|
19
16
|
end
|
20
|
-
|
21
17
|
end
|
22
|
-
|
23
18
|
end
|
24
|
-
|
25
19
|
end
|
@@ -1,15 +1,12 @@
|
|
1
1
|
module Chake
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
class Ssh < Backend
|
6
|
-
|
2
|
+
class Connection
|
3
|
+
class Ssh < Connection
|
7
4
|
def scp
|
8
5
|
['scp', ssh_config, scp_options].flatten.compact
|
9
6
|
end
|
10
7
|
|
11
8
|
def scp_dest
|
12
|
-
ssh_target
|
9
|
+
"#{ssh_target}:"
|
13
10
|
end
|
14
11
|
|
15
12
|
def rsync
|
@@ -17,7 +14,7 @@ module Chake
|
|
17
14
|
end
|
18
15
|
|
19
16
|
def rsync_dest
|
20
|
-
[ssh_target, node.path
|
17
|
+
[ssh_target, "#{node.path}/"].join(':')
|
21
18
|
end
|
22
19
|
|
23
20
|
def command_runner
|
@@ -35,11 +32,9 @@ module Chake
|
|
35
32
|
begin
|
36
33
|
ssh_command = 'ssh'
|
37
34
|
if File.exist?(ssh_config_file)
|
38
|
-
ssh_command +=
|
39
|
-
end
|
40
|
-
if node.port
|
41
|
-
ssh_command += ' -p ' + node.port.to_s
|
35
|
+
ssh_command += " -F #{ssh_config_file}"
|
42
36
|
end
|
37
|
+
ssh_command += " -p #{node.port}" if node.port
|
43
38
|
if ssh_command == 'ssh'
|
44
39
|
[]
|
45
40
|
else
|
@@ -71,9 +66,6 @@ module Chake
|
|
71
66
|
def scp_options
|
72
67
|
node.port && ['-P', node.port.to_s] || []
|
73
68
|
end
|
74
|
-
|
75
69
|
end
|
76
|
-
|
77
70
|
end
|
78
|
-
|
79
71
|
end
|
data/lib/chake/node.rb
CHANGED
@@ -2,58 +2,78 @@ require 'uri'
|
|
2
2
|
require 'etc'
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
|
-
require 'chake/
|
5
|
+
require 'chake/connection'
|
6
|
+
require 'chake/config_manager'
|
6
7
|
|
7
8
|
module Chake
|
8
|
-
|
9
9
|
class Node
|
10
|
-
|
11
10
|
extend Forwardable
|
12
11
|
|
13
|
-
attr_reader :hostname
|
14
|
-
|
15
|
-
|
16
|
-
attr_reader :remote_username
|
17
|
-
attr_reader :path
|
18
|
-
attr_reader :data
|
12
|
+
attr_reader :hostname, :port, :username, :remote_username, :data
|
13
|
+
|
14
|
+
attr_accessor :silent
|
19
15
|
|
20
16
|
def self.max_node_name_length
|
21
17
|
@max_node_name_length ||= 0
|
22
18
|
end
|
23
|
-
|
24
|
-
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_writer :max_node_name_length
|
25
22
|
end
|
26
23
|
|
27
24
|
def initialize(hostname, data = {})
|
28
|
-
uri =
|
29
|
-
|
30
|
-
uri = URI.parse("ssh://#{hostname}")
|
31
|
-
end
|
32
|
-
if uri.path && uri.path.empty?
|
33
|
-
uri.path = nil
|
34
|
-
end
|
35
|
-
|
36
|
-
@backend_name = uri.scheme
|
37
|
-
|
25
|
+
uri = parse_uri(hostname)
|
26
|
+
@connection_name = uri.scheme
|
38
27
|
@hostname = uri.host
|
39
28
|
@port = uri.port
|
40
29
|
@username = uri.user || Etc.getpwuid.name
|
41
30
|
@remote_username = uri.user
|
42
|
-
@path = uri.path
|
31
|
+
@path = uri.path
|
43
32
|
@data = data
|
33
|
+
set_max_node_length
|
34
|
+
end
|
35
|
+
|
36
|
+
def connection
|
37
|
+
@connection ||= Chake::Connection.get(@connection_name).new(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def_delegators :connection, :run, :run_as_root, :run_shell, :rsync, :rsync_dest, :scp, :scp_dest, :skip?
|
44
41
|
|
45
|
-
|
46
|
-
|
42
|
+
def config_manager
|
43
|
+
@config_manager ||= Chake::ConfigManager.get(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def_delegators :config_manager, :converge, :apply, :path, :bootstrap_steps, :needs_bootstrap?, :needs_upload?
|
47
|
+
|
48
|
+
def path
|
49
|
+
@path ||= config_manager.path
|
50
|
+
end
|
51
|
+
|
52
|
+
def log(msg)
|
53
|
+
return if silent
|
54
|
+
|
55
|
+
puts("%#{Node.max_node_name_length}<host>s: %<msg>s\n" % { host: hostname, msg: msg })
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def parse_uri(hostname)
|
61
|
+
uri = URI.parse(hostname)
|
62
|
+
if incomplete_uri(uri)
|
63
|
+
uri = URI.parse("ssh://#{hostname}")
|
47
64
|
end
|
65
|
+
uri.path = nil if uri.path.empty?
|
66
|
+
uri
|
48
67
|
end
|
49
68
|
|
50
|
-
def
|
51
|
-
|
69
|
+
def incomplete_uri(uri)
|
70
|
+
!uri.host && ((!uri.scheme && uri.path) || (uri.scheme && uri.opaque))
|
52
71
|
end
|
53
72
|
|
54
|
-
|
73
|
+
def set_max_node_length
|
74
|
+
return if @hostname.length <= self.class.max_node_name_length
|
55
75
|
|
76
|
+
self.class.max_node_name_length = @hostname.length
|
77
|
+
end
|
56
78
|
end
|
57
|
-
|
58
79
|
end
|
59
|
-
|