loom-core 0.0.1
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 +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
|