loom-core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +99 -0
- data/Guardfile +54 -0
- data/Rakefile +6 -0
- data/bin/loom +185 -0
- data/lib/env/development.rb +1 -0
- data/lib/loom.rb +44 -0
- data/lib/loom/all.rb +20 -0
- data/lib/loom/config.rb +106 -0
- data/lib/loom/core_ext.rb +37 -0
- data/lib/loom/dsl.rb +60 -0
- data/lib/loom/facts.rb +13 -0
- data/lib/loom/facts/all.rb +2 -0
- data/lib/loom/facts/fact_file_provider.rb +86 -0
- data/lib/loom/facts/fact_set.rb +138 -0
- data/lib/loom/host_spec.rb +32 -0
- data/lib/loom/inventory.rb +124 -0
- data/lib/loom/logger.rb +141 -0
- data/lib/loom/method_signature.rb +174 -0
- data/lib/loom/mods.rb +4 -0
- data/lib/loom/mods/action_proxy.rb +105 -0
- data/lib/loom/mods/all.rb +3 -0
- data/lib/loom/mods/mod_loader.rb +80 -0
- data/lib/loom/mods/module.rb +113 -0
- data/lib/loom/pattern.rb +15 -0
- data/lib/loom/pattern/all.rb +7 -0
- data/lib/loom/pattern/definition_context.rb +74 -0
- data/lib/loom/pattern/dsl.rb +176 -0
- data/lib/loom/pattern/hook.rb +28 -0
- data/lib/loom/pattern/loader.rb +48 -0
- data/lib/loom/pattern/reference.rb +71 -0
- data/lib/loom/pattern/reference_set.rb +169 -0
- data/lib/loom/pattern/result_reporter.rb +77 -0
- data/lib/loom/runner.rb +209 -0
- data/lib/loom/shell.rb +12 -0
- data/lib/loom/shell/all.rb +10 -0
- data/lib/loom/shell/api.rb +48 -0
- data/lib/loom/shell/cmd_result.rb +33 -0
- data/lib/loom/shell/cmd_wrapper.rb +164 -0
- data/lib/loom/shell/core.rb +226 -0
- data/lib/loom/shell/harness_blob.rb +26 -0
- data/lib/loom/shell/harness_command_builder.rb +50 -0
- data/lib/loom/shell/session.rb +25 -0
- data/lib/loom/trap.rb +44 -0
- data/lib/loom/version.rb +3 -0
- data/lib/loomext/all.rb +4 -0
- data/lib/loomext/corefacts.rb +6 -0
- data/lib/loomext/corefacts/all.rb +8 -0
- data/lib/loomext/corefacts/facter_provider.rb +24 -0
- data/lib/loomext/coremods.rb +5 -0
- data/lib/loomext/coremods/all.rb +13 -0
- data/lib/loomext/coremods/exec.rb +50 -0
- data/lib/loomext/coremods/files.rb +104 -0
- data/lib/loomext/coremods/net.rb +33 -0
- data/lib/loomext/coremods/package/adapter.rb +100 -0
- data/lib/loomext/coremods/package/package.rb +62 -0
- data/lib/loomext/coremods/user.rb +82 -0
- data/lib/loomext/coremods/vm.rb +0 -0
- data/lib/loomext/coremods/vm/all.rb +6 -0
- data/lib/loomext/coremods/vm/vbox.rb +84 -0
- data/loom.gemspec +39 -0
- data/loom/inventory.yml +13 -0
- data/scripts/harness.sh +242 -0
- data/spec/loom/host_spec_spec.rb +101 -0
- data/spec/loom/inventory_spec.rb +154 -0
- data/spec/loom/method_signature_spec.rb +275 -0
- data/spec/loom/pattern/dsl_spec.rb +207 -0
- data/spec/loom/shell/cmd_wrapper_spec.rb +239 -0
- data/spec/loom/shell/harness_blob_spec.rb +42 -0
- data/spec/loom/shell/harness_command_builder_spec.rb +36 -0
- data/spec/runloom.sh +35 -0
- data/spec/scripts/harness_spec.rb +385 -0
- data/spec/spec_helper.rb +94 -0
- data/spec/test.loom +370 -0
- data/spec/test_loom_spec.rb +57 -0
- metadata +287 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Loom::Shell
|
5
|
+
|
6
|
+
# A blob of commands fit for sending to the harness.
|
7
|
+
class HarnessBlob
|
8
|
+
|
9
|
+
def initialize(cmd_blob)
|
10
|
+
@cmd_blob = cmd_blob
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :cmd_blob
|
14
|
+
|
15
|
+
def encoded_script
|
16
|
+
# TODO: Fix this trailing newline hack, it is here to make encoding
|
17
|
+
# consistent with the harness.sh script, which is a bit messy with how it
|
18
|
+
# treats trailing newlines.
|
19
|
+
Base64.encode64(cmd_blob + "\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
def checksum
|
23
|
+
Digest::SHA1.hexdigest encoded_script
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Loom
|
2
|
+
module Shell
|
3
|
+
|
4
|
+
HarnessMissingStdin = Class.new Loom::LoomError
|
5
|
+
|
6
|
+
class HarnessCommandBuilder
|
7
|
+
|
8
|
+
# TODO: Resolve a real script path.
|
9
|
+
SCRIPT = "./scripts/harness.sh"
|
10
|
+
|
11
|
+
DEFAULT_RUN_OPTS = {
|
12
|
+
:cmd_shell => "/bin/dash",
|
13
|
+
:record_file => "/opt/loom/commands"
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(harness_blob)
|
17
|
+
@harness_blob = harness_blob
|
18
|
+
@run_opts = DEFAULT_RUN_OPTS.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_cmd
|
22
|
+
build_cmd :run, @harness_blob.checksum, *hash_to_opts_array(@run_opts),
|
23
|
+
{
|
24
|
+
:stdin => @harness_blob.encoded_script
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_cmd
|
29
|
+
build_cmd :check, @harness_blob.checksum, {
|
30
|
+
:stdin => @harness_blob.encoded_script
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def hash_to_opts_array(hash)
|
36
|
+
hash.to_a.map do |tuple|
|
37
|
+
"--%s %s" % [tuple.first, tuple.last]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_cmd(cmd, *args, stdin: nil)
|
42
|
+
raise HarnessMissingStdin unless stdin
|
43
|
+
|
44
|
+
heredoc = "<<'HARNESS_EOS'\n#{stdin}\nHARNESS_EOS"
|
45
|
+
cmd = "--" + cmd.to_s
|
46
|
+
"%s %s 2>/dev/null - %s %s" % [SCRIPT, cmd, args.join(" "), heredoc]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Loom::Shell
|
2
|
+
class Session
|
3
|
+
def initialize
|
4
|
+
@command_results = []
|
5
|
+
@success = true
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :command_results
|
9
|
+
|
10
|
+
def success?
|
11
|
+
@success
|
12
|
+
end
|
13
|
+
|
14
|
+
def last
|
15
|
+
@command_results.last
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(command_result)
|
19
|
+
@command_results << command_result
|
20
|
+
unless command_result.is_test
|
21
|
+
@success &&= command_result.success?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/loom/trap.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Loom
|
2
|
+
class Trap
|
3
|
+
|
4
|
+
class SignalExit < Loom::LoomError
|
5
|
+
attr_reader :signal
|
6
|
+
|
7
|
+
def initialize(signal)
|
8
|
+
@signal = signal
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# See `man 7 signal` or #{Signal.list}
|
13
|
+
module Sig
|
14
|
+
HUP = "HUP"
|
15
|
+
INT = "INT"
|
16
|
+
QUIT = "QUIT"
|
17
|
+
TERM = "TERM"
|
18
|
+
USR1 = "USR1"
|
19
|
+
USR2 = "USR2"
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.install(signal, trap_handler)
|
23
|
+
raise "unknown signal => #{signal}" unless Signal.list[signal]
|
24
|
+
Signal.trap signal do
|
25
|
+
trap_handler.handle(signal)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Handler
|
30
|
+
|
31
|
+
def initialize(&handler)
|
32
|
+
@signal_handle_counts = {}
|
33
|
+
@handler = handler
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle(signal)
|
37
|
+
@signal_handle_counts[signal] ||= 0
|
38
|
+
@signal_handle_counts[signal] += 1
|
39
|
+
|
40
|
+
@handler.call signal, @signal_handle_counts[signal]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/loom/version.rb
ADDED
data/lib/loomext/all.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module LoomExt::CoreFacts
|
2
|
+
class FacterProvider < Loom::Facts::Provider
|
3
|
+
|
4
|
+
Loom::Facts::Provider.register_factory(self) do |host_spec, shell, loom_config|
|
5
|
+
FacterProvider.new host_spec, shell
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(host_spec, shell)
|
9
|
+
@has_facter = shell.test :which, "facter"
|
10
|
+
disable(host_spec) unless @has_facter
|
11
|
+
@shell = shell
|
12
|
+
end
|
13
|
+
|
14
|
+
def collect_facts
|
15
|
+
unless @has_facter
|
16
|
+
Loom.log.error "facter not installed"
|
17
|
+
return {}
|
18
|
+
end
|
19
|
+
|
20
|
+
yaml_facts = @shell.capture :facter, "--yaml"
|
21
|
+
yaml_facts.nil? ? {} : YAML.load(yaml_facts)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module LoomExt::CoreMods
|
2
|
+
|
3
|
+
FailError = Class.new Loom::ExecutionError
|
4
|
+
|
5
|
+
##
|
6
|
+
# Executes shell commands from patterns. e.g.
|
7
|
+
#
|
8
|
+
# loom << :echo, "hello there"
|
9
|
+
class Exec < Loom::Mods::Module
|
10
|
+
register_mod :exec, :alias => [:x, :<<] do |*cmd, **opts|
|
11
|
+
shell.execute *cmd, **opts
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ChangeDirectory < Loom::Mods::Module
|
16
|
+
register_mod :change_directory, :alias => :cd do |path, &block|
|
17
|
+
shell.cd path, &block
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Timeout < Loom::Mods::Module
|
22
|
+
register_mod :timeout do |timeout: 60, &block|
|
23
|
+
shell.wrap(:timeout, timeout, :should_quote => false, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Time < Loom::Mods::Module
|
28
|
+
register_mod :time do |&block|
|
29
|
+
shell.wrap(:time, :should_quote => false, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Sudo < Loom::Mods::Module
|
34
|
+
register_mod :sudo do |user: nil, cmd: nil, &block|
|
35
|
+
shell.sudo user, cmd, &block
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Test < Loom::Mods::Module
|
40
|
+
register_mod :test do |*cmd|
|
41
|
+
shell.test *cmd
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Fail < Loom::Mods::Module
|
46
|
+
register_mod :fail do |message=nil|
|
47
|
+
raise FailError, message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module LoomExt::CoreMods
|
2
|
+
class Files < Loom::Mods::Module
|
3
|
+
|
4
|
+
register_mod :files
|
5
|
+
|
6
|
+
def init_action(paths)
|
7
|
+
@paths = [paths].flatten.compact
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
##
|
12
|
+
# Executes #{action} for each path in #{paths} or #{@paths}. If
|
13
|
+
# action is not given, a block is expected to which each path will
|
14
|
+
# be passed.
|
15
|
+
def each_path(action: nil, flags: nil, &block)
|
16
|
+
raise 'use either action or block in each_path' if action && block_given?
|
17
|
+
raise 'use either action or block in each_path' unless action || block_given?
|
18
|
+
|
19
|
+
@paths.each do |p|
|
20
|
+
next unless p
|
21
|
+
|
22
|
+
raise "prefix relative paths with '.': #{p}" unless p.match /^[.]?\//
|
23
|
+
if block
|
24
|
+
yield p
|
25
|
+
else
|
26
|
+
shell.execute action, flags, p
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return self for chaining in pattern files
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
module Actions
|
35
|
+
|
36
|
+
def cat
|
37
|
+
each_path do |p|
|
38
|
+
shell.capture :cat, p
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def rm
|
43
|
+
each_path do |p|
|
44
|
+
shell.capture :rm, "-f", p
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def match?(pattern: /./)
|
49
|
+
all = true
|
50
|
+
each_path do |p|
|
51
|
+
file = shell.capture :cat, p
|
52
|
+
all &&= file.match(pattern)
|
53
|
+
end
|
54
|
+
all
|
55
|
+
end
|
56
|
+
|
57
|
+
def gsub(pattern: nil, replace: nil, &block)
|
58
|
+
each_path do |p|
|
59
|
+
contents = shell.capture :cat, p
|
60
|
+
if contents
|
61
|
+
contents.gsub!(pattern, replace, &block) unless pattern.nil?
|
62
|
+
write contents
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def chown(user: nil, group: nil)
|
68
|
+
group_arg = group && ":" + group.to_s
|
69
|
+
|
70
|
+
each_path do |p|
|
71
|
+
shell.execute :chown, [user, group_arg].compact.map(&:to_s).join, p
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def touch
|
76
|
+
each_path :action => :touch
|
77
|
+
end
|
78
|
+
|
79
|
+
def mkdir(flags: nil, **opts)
|
80
|
+
each_path :action => :mkdir, :flags => flags
|
81
|
+
end
|
82
|
+
|
83
|
+
def append(text="")
|
84
|
+
each_path do |p|
|
85
|
+
loom.test "[ -f #{p} ]"
|
86
|
+
|
87
|
+
redirect = Loom::Shell::CmdRedirect.append_stdout p
|
88
|
+
cmd = Loom::Shell::CmdWrapper.new(
|
89
|
+
:"/bin/echo", "-e", text, redirect: redirect)
|
90
|
+
shell.execute cmd
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def write(text="")
|
95
|
+
each_path do |p|
|
96
|
+
loom.x :"/bin/echo", "-e", text, :piped_cmds => [[:tee, p]]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
Files.import_actions Files::Actions
|
104
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module LoomExt::CoreMods
|
2
|
+
class Net < Loom::Mods::Module
|
3
|
+
|
4
|
+
NoNetworkError = Class.new Loom::ExecutionError
|
5
|
+
|
6
|
+
register_mod :net
|
7
|
+
|
8
|
+
def init_action(timeout: 10, check_host: "8.8.8.8")
|
9
|
+
@net_timeout = timeout
|
10
|
+
@check_host = check_host
|
11
|
+
end
|
12
|
+
|
13
|
+
module Actions
|
14
|
+
|
15
|
+
def has_net?
|
16
|
+
loom.timeout :timeout => @net_timeout do
|
17
|
+
loom.test :sh, "-c", "while ! ping -c1 #{@check_host}; do true; done"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_net
|
22
|
+
raise NoNetworkError, "no network available" unless has_net?
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_net(&block)
|
26
|
+
check_net
|
27
|
+
yield if block_given?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
import_actions Net::Actions
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module LoomExt::CoreMods
|
2
|
+
class Package < Loom::Mods::Module
|
3
|
+
|
4
|
+
class PkgAdapter
|
5
|
+
|
6
|
+
attr_reader :loom
|
7
|
+
|
8
|
+
def initialize(loom)
|
9
|
+
@loom = loom
|
10
|
+
end
|
11
|
+
|
12
|
+
def ensure_installed(pkg_name)
|
13
|
+
install(pkg_name) unless installed?(pkg_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def installed?(pkg_name)
|
17
|
+
raise 'not implemnted'
|
18
|
+
end
|
19
|
+
|
20
|
+
def install(pkg_name)
|
21
|
+
raise 'not implemented'
|
22
|
+
end
|
23
|
+
|
24
|
+
def uninstall(pkg_name)
|
25
|
+
raise 'not implemented'
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_cache
|
29
|
+
raise 'not implemented'
|
30
|
+
end
|
31
|
+
|
32
|
+
def upgrade(pkg_name)
|
33
|
+
raise 'not implemented'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class GemAdapter < PkgAdapter
|
38
|
+
def installed?(pkg_name)
|
39
|
+
loom.test "ruby -r#{pkg_name} -e exit"
|
40
|
+
end
|
41
|
+
|
42
|
+
def install(pkg_name)
|
43
|
+
loom << "gem install #{pkg_name}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class DpkgAdapter < PkgAdapter
|
48
|
+
|
49
|
+
def installed?(pkg_name)
|
50
|
+
loom.test "dpkg -s #{pkg_name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class AptAdapter < DpkgAdapter
|
55
|
+
|
56
|
+
def install(pkg_name)
|
57
|
+
loom.net.with_net { loom << "echo apt-get install #{pkg_name}" }
|
58
|
+
end
|
59
|
+
|
60
|
+
def uninstall(pkg_name)
|
61
|
+
loom << "echo apt uninstall"
|
62
|
+
end
|
63
|
+
|
64
|
+
def update_cache
|
65
|
+
loom.net.with_net { loom << "apt update" }
|
66
|
+
end
|
67
|
+
|
68
|
+
def upgrade(pkg_name)
|
69
|
+
loom.net.with_net { loom << "apt upgrade" }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class RpmAdapter < PkgAdapter
|
74
|
+
|
75
|
+
def installed?(pkg_name)
|
76
|
+
loom.test :rpm, "-q #{pkg_name}"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class DnfAdapter < RpmAdapter
|
82
|
+
|
83
|
+
def install(pkg_name)
|
84
|
+
loom.net.with_net { loom << "dnf install #{pkg_name}" }
|
85
|
+
end
|
86
|
+
|
87
|
+
def uninstall(pkg_name)
|
88
|
+
loom << "echo dnf uninstall"
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_cache
|
92
|
+
loom.net.with_net { loom << "dnf updateinfo" }
|
93
|
+
end
|
94
|
+
|
95
|
+
def upgrade(pkg_name)
|
96
|
+
raise 'not implemented'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|