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