rbcm 0.0.8 → 0.0.9
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/app/action/action.rb +31 -23
- data/app/action/command.rb +6 -4
- data/app/action/file.rb +12 -8
- data/app/action/list.rb +1 -1
- data/app/cli.rb +15 -12
- data/app/node/file.rb +20 -2
- data/app/node/job.rb +1 -1
- data/app/node/job_search.rb +8 -0
- data/app/node/node.rb +8 -2
- data/app/node/remote.rb +4 -4
- data/app/node/sandbox.rb +76 -42
- data/app/project/addon.rb +27 -0
- data/app/project/capability.rb +8 -2
- data/app/project/definition.rb +4 -4
- data/app/project/file.rb +29 -10
- data/app/project/project.rb +59 -11
- data/app/project/template.rb +50 -0
- data/app/project/template_list.rb +19 -0
- data/app/rbcm.rb +24 -18
- metadata +19 -2
- data/app/node/template.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb945c84ce8cafd1cd3e604049885d5a306cc5ad2c7b7a9b9e3fff3e3a91bd3d
|
4
|
+
data.tar.gz: 8427fb88c352244a6168eec50e234d43bcd1557dcb9281c6883d1c1c92e9bf0d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1743ddf1827c4b10afd779ade73711959b6f680c35d35a46287866812023ae9150a0200282ffd591e7167f67abc3507d5259df333da383da7b0ee90b046c1db1
|
7
|
+
data.tar.gz: 705a679fed8af59bf5330d0a285232d49743ecae299572ab859d902ac1878f8d1ad17b9419fa28ead1d7c1d5c64902d6bb7790750fcbb518c6f37adfc6ea23f5
|
data/app/action/action.rb
CHANGED
@@ -1,28 +1,36 @@
|
|
1
1
|
class Action
|
2
2
|
attr_accessor :approved, :applied
|
3
|
-
attr_reader :
|
4
|
-
:
|
5
|
-
:source, :path, :tags
|
3
|
+
attr_reader :triggered_by, :trigger, :chain, :dependencies,
|
4
|
+
:obsolete, :job, :check, :triggered, :result,
|
5
|
+
:source, :path, :line, :state, :tags, :working_dir
|
6
6
|
|
7
|
-
def initialize job:,
|
8
|
-
dependencies: nil,
|
9
|
-
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
13
|
-
@triggered = []; @source = source
|
14
|
-
@node = node; @job = job
|
15
|
-
@chain = chain; @capability = chain.last
|
16
|
-
@obsolete = nil; @approved = nil
|
17
|
-
@tags = tags.compact.flatten
|
7
|
+
def initialize job:, params: nil, line: nil, check: nil,
|
8
|
+
dependencies: nil, state:
|
9
|
+
@job = job
|
10
|
+
@triggered = []
|
11
|
+
@obsolete = nil
|
12
|
+
@approved = nil
|
18
13
|
# command specific
|
19
|
-
@line = line
|
14
|
+
@line = line
|
20
15
|
# file specific
|
21
|
-
@
|
16
|
+
@params = params
|
17
|
+
@path = params.first if job.capability.name == :file
|
18
|
+
# extract state
|
19
|
+
[:chain, :trigger, :triggered_by, :check, :source, :tags, :working_dirs].each do |key|
|
20
|
+
instance_variable_set "@#{key}", state[key]
|
21
|
+
end
|
22
|
+
@working_dir = @working_dirs.last
|
23
|
+
@dependencies = [:file] + [dependencies].flatten - [@chain.last]
|
24
|
+
end
|
25
|
+
|
26
|
+
def project_file
|
27
|
+
@chain.reverse.find{ |element|
|
28
|
+
defined?(element.project_file) and element.project_file
|
29
|
+
}.project_file
|
22
30
|
end
|
23
31
|
|
24
32
|
def checkable?
|
25
|
-
|
33
|
+
@check.any? or self.class == Action::File
|
26
34
|
end
|
27
35
|
|
28
36
|
def neccessary?
|
@@ -43,9 +51,9 @@ class Action
|
|
43
51
|
end
|
44
52
|
|
45
53
|
def triggered?
|
46
|
-
triggered_by.empty? or triggered_by.one?
|
47
|
-
@node.triggered.flatten.include? triggered_by
|
48
|
-
|
54
|
+
triggered_by.empty? or triggered_by.one?{ |triggered_by|
|
55
|
+
@job.node.triggered.flatten.include? triggered_by
|
56
|
+
}
|
49
57
|
end
|
50
58
|
|
51
59
|
def applied?
|
@@ -63,11 +71,11 @@ class Action
|
|
63
71
|
|
64
72
|
def approve! input=:y
|
65
73
|
if [:a, :y].include? input
|
66
|
-
@node.files[@path].content = content if self.class == Action::File
|
74
|
+
@job.node.files[@path].content = content if self.class == Action::File
|
67
75
|
@approved = true
|
68
76
|
siblings.each.approve! if input == :a
|
69
|
-
@node.triggered << @trigger
|
70
|
-
@triggered = @trigger.compact - @node.triggered
|
77
|
+
@job.node.triggered << @trigger
|
78
|
+
@triggered = @trigger.compact - @job.node.triggered
|
71
79
|
else
|
72
80
|
@approved = false
|
73
81
|
end
|
data/app/action/command.rb
CHANGED
@@ -4,8 +4,10 @@ class Action::Command < Action
|
|
4
4
|
# determine wether the command is neccessary
|
5
5
|
def check!
|
6
6
|
return if @obsolete != nil
|
7
|
-
if @check
|
8
|
-
@obsolete = @
|
7
|
+
if @check.any?
|
8
|
+
@obsolete = @check.all? do |check|
|
9
|
+
@job.node.remote.execute(check).exitstatus == 0
|
10
|
+
end
|
9
11
|
else
|
10
12
|
@obsolete = false
|
11
13
|
end
|
@@ -13,7 +15,7 @@ class Action::Command < Action
|
|
13
15
|
|
14
16
|
# matching commands on other nodes to be approved at once
|
15
17
|
def siblings
|
16
|
-
@node.rbcm.actions.select{ |action|
|
18
|
+
@job.node.rbcm.actions.select{ |action|
|
17
19
|
action.chain[1..-1] == @chain[1..-1] and action.line == @line
|
18
20
|
} - [self]
|
19
21
|
end
|
@@ -21,6 +23,6 @@ class Action::Command < Action
|
|
21
23
|
# execute the command remote
|
22
24
|
def apply!
|
23
25
|
@applied = true
|
24
|
-
@result = @node.remote.execute(@line)
|
26
|
+
@result = @job.node.remote.execute(@line)
|
25
27
|
end
|
26
28
|
end
|
data/app/action/file.rb
CHANGED
@@ -4,11 +4,11 @@ class Action::File < Action
|
|
4
4
|
|
5
5
|
def check!
|
6
6
|
# compare
|
7
|
-
@node.files[path].content
|
7
|
+
@job.node.files[path].content
|
8
8
|
end
|
9
9
|
|
10
10
|
def obsolete
|
11
|
-
@node.files[path].content.chomp.chomp == content.chomp.chomp
|
11
|
+
@job.node.files[path].content.chomp.chomp == content.chomp.chomp
|
12
12
|
end
|
13
13
|
|
14
14
|
def siblings
|
@@ -17,22 +17,26 @@ class Action::File < Action
|
|
17
17
|
|
18
18
|
def apply!
|
19
19
|
@applied = true
|
20
|
-
@result = @node.remote.execute("echo #{Shellwords.escape content} > #{path}")
|
20
|
+
@result = @job.node.remote.execute("echo #{Shellwords.escape content} > #{path}")
|
21
21
|
end
|
22
22
|
|
23
23
|
def content
|
24
24
|
@content ||= if @params[:content]
|
25
25
|
@params[:content].to_s
|
26
26
|
elsif @params[:template]
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
# @node.rbcm.project.templates.find_for(
|
28
|
+
# @params[:template]
|
29
|
+
# ).render context: @params[:context]
|
30
|
+
# Node::Template.new(
|
31
|
+
# name: @params[:template]
|
32
|
+
# ).render context: @params[:context]
|
33
|
+
project_file.project.templates_.for(self).render(
|
30
34
|
context: @params[:context]
|
31
|
-
)
|
35
|
+
)
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
39
|
def same_file
|
36
|
-
@node.actions.file(path) - [self]
|
40
|
+
@job.node.actions.file(path) - [self]
|
37
41
|
end
|
38
42
|
end
|
data/app/action/list.rb
CHANGED
data/app/cli.rb
CHANGED
@@ -38,7 +38,7 @@ class CLI
|
|
38
38
|
|
39
39
|
def check action
|
40
40
|
@action = action
|
41
|
-
render checking: action.check
|
41
|
+
render checking: action.check.join("; ")
|
42
42
|
action.check!
|
43
43
|
end
|
44
44
|
|
@@ -49,10 +49,12 @@ class CLI
|
|
49
49
|
render :command if action.class == Action::Command
|
50
50
|
next if not action.approvable?
|
51
51
|
render :siblings if action.siblings.any?
|
52
|
-
render :source if action.source.any?
|
52
|
+
render :source if action.source.flatten.compact.any?
|
53
53
|
render :diff if action.class == Action::File
|
54
54
|
render :prompt
|
55
|
-
sleep 0.25 unless [:a,:y,:n].include? r = STDIN.getch.to_sym # avoid 'ctrl-c'-trap
|
55
|
+
sleep 0.25 unless [:a,:y,:n,:i].include? r = STDIN.getch.to_sym # avoid 'ctrl-c'-trap
|
56
|
+
(binding.pry; sleep 1) if r == :i
|
57
|
+
(puts; exit) if r == :q
|
56
58
|
action.approve! r
|
57
59
|
render :approved
|
58
60
|
render :triggered if action.triggered.any?
|
@@ -64,7 +66,7 @@ class CLI
|
|
64
66
|
@action = action
|
65
67
|
response = action.apply!
|
66
68
|
render :title, color: response.exitstatus == 0 ? :green : :red
|
67
|
-
render :command if response.exitstatus != 0
|
69
|
+
render :command if action.class == Action::Command and response.exitstatus != 0
|
68
70
|
render response: response if response.length > 0
|
69
71
|
end
|
70
72
|
end
|
@@ -75,22 +77,23 @@ class CLI
|
|
75
77
|
out "#{first ? nil : "┗━━──"}\n\n┏━━#{format :invert, :bold}#{" "*16}#{section}#{" "*16}#{format}━──\n┃"
|
76
78
|
elsif element == :title
|
77
79
|
triggerd_by = "#{format :trigger, :bold} #{@action.triggered_by.join(", ")} " if @action.triggered_by.any?
|
78
|
-
out "┣━ #{triggerd_by}#{format color, :bold} #{
|
80
|
+
out "┣━ #{triggerd_by}#{format color, :bold} #{@action.chain.flatten.compact.join(" > ")} " +
|
79
81
|
"#{format} #{format :params}#{@action.job.params if @action.job}#{format}" +
|
80
82
|
" #{format :tag}#{"tags: " if @action.tags.any?}#{@action.tags.join(", ")}#{format}"
|
81
83
|
elsif element == :capabilities
|
82
84
|
out prefix + "capabilities: #{Node::Sandbox.capabilities.join(", ")}"
|
83
85
|
elsif element == :project
|
84
|
-
out prefix + "project: #{@rbcm.project.files.count} ruby files"
|
86
|
+
out prefix + "project: #{@rbcm.project.files.count} ruby files, #{@rbcm.project.templates.count} templates"
|
87
|
+
out prefix + " #{@rbcm.project.directories.count} directories, #{@rbcm.project.other.count} other files"
|
85
88
|
elsif element == :nodes
|
86
89
|
out prefix + @rbcm.nodes.values.collect{ |node|
|
87
|
-
name = node.name.+(":").ljust(@rbcm.nodes.keys.each.length.max+1, " ")
|
90
|
+
name = node.name.to_s.+(":").ljust(@rbcm.nodes.keys.each.length.max+1, " ")
|
88
91
|
jobs = node.jobs.count.to_s.rjust(@rbcm.nodes.values.collect{|node| node.jobs.count}.max.digits.count, " ")
|
89
92
|
actions = node.actions.count.to_s.rjust(@rbcm.nodes.values.collect{|node| node.actions.count}.max.digits.count, " ")
|
90
93
|
"#{name} #{jobs} jobs, #{actions} actions"
|
91
94
|
}.flatten(1).join("\n#{prefix}")
|
92
95
|
elsif element == :command
|
93
|
-
check_string = " UNLESS #{@action.check}" if @action.check
|
96
|
+
check_string = " UNLESS #{@action.check.join("; ")}" if @action.check.any?
|
94
97
|
out prefix + "$> #{@action.line}\e[2m#{check_string}\e[0m"
|
95
98
|
elsif element == :siblings
|
96
99
|
string = @action.siblings.collect do |sibling|
|
@@ -98,17 +101,17 @@ class CLI
|
|
98
101
|
end.join
|
99
102
|
out prefix + "#{format :siblings}siblings:#{format} #{string}"
|
100
103
|
elsif element == :source
|
101
|
-
out prefix + "source: #{format :bold}#{@source.join("#{format}, #{format :bold}")}#{format}"
|
104
|
+
out prefix + "source: #{format :bold}#{@action.source.join("#{format}, #{format :bold}")}#{format}"
|
102
105
|
elsif element == :prompt
|
103
106
|
color = @action.siblings.any? ? :siblings : :light
|
104
|
-
print prefix + "APPROVE? #{format color}[a]ll#{format}, [y]es, [N]o: "
|
107
|
+
print prefix + "APPROVE? #{format color}[a]ll#{format}, [y]es, [N]o, [i]nteractive, [q]uit: "
|
105
108
|
elsif element == :triggered
|
106
109
|
out prefix +
|
107
110
|
"triggered: #{format :trigger} #{@action.triggered.join(", ")} \e[0m;" +
|
108
111
|
" again: #{@action.trigger.-(@action.triggered).join(", ")}"
|
109
112
|
elsif element == :diff
|
110
113
|
out prefix[0..-2] + Diffy::Diff.new(
|
111
|
-
@action.node.files[@action.path].content,
|
114
|
+
@action.job.node.files[@action.path].content,
|
112
115
|
@action.content
|
113
116
|
).to_s(:color).split("\n").join("\n#{prefix[0..-2]}")
|
114
117
|
elsif element == :approved
|
@@ -117,7 +120,7 @@ class CLI
|
|
117
120
|
elsif element.class == String
|
118
121
|
out prefix + "#{element}"
|
119
122
|
elsif checking
|
120
|
-
out prefix + "CHECKING #{@action.node.name}: #{checking}"
|
123
|
+
out prefix + "CHECKING #{@action.job.node.name}: #{checking}"
|
121
124
|
elsif response
|
122
125
|
out prefix + response.to_s.chomp.split("\n").join("\n#{prefix}")
|
123
126
|
elsif element == :applied
|
data/app/node/file.rb
CHANGED
@@ -4,7 +4,7 @@ class Node::NodeFile
|
|
4
4
|
@filesystem = filesystem
|
5
5
|
end
|
6
6
|
|
7
|
-
attr_writer :content, :mode
|
7
|
+
attr_writer :content, :user, :group, :mode
|
8
8
|
|
9
9
|
def content
|
10
10
|
@content ||= (
|
@@ -15,9 +15,27 @@ class Node::NodeFile
|
|
15
15
|
)
|
16
16
|
end
|
17
17
|
|
18
|
+
def diffable # TODO?
|
19
|
+
"#{content}" +
|
20
|
+
"\\" +
|
21
|
+
"PERMISSIONS #{user}:#{group} #{mode}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def user
|
25
|
+
@user ||= @filesystem.node.remote.execute(
|
26
|
+
"stat -c '%U' '#{@path}'"
|
27
|
+
).chomp.chomp
|
28
|
+
end
|
29
|
+
|
30
|
+
def group
|
31
|
+
@group ||= @filesystem.node.remote.execute(
|
32
|
+
"stat -c '%G' '#{@path}'"
|
33
|
+
).chomp.chomp
|
34
|
+
end
|
35
|
+
|
18
36
|
def mode
|
19
37
|
@mode ||= @filesystem.node.remote.execute(
|
20
|
-
"stat -c
|
38
|
+
"stat -c '%a' * '#{@path}'"
|
21
39
|
).chomp.chomp.to_i
|
22
40
|
end
|
23
41
|
end
|
data/app/node/job.rb
CHANGED
data/app/node/node.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
class Node
|
2
|
-
attr_reader :jobs, :definitions, :files, :name, :remote, :rbcm, :sandbox
|
2
|
+
attr_reader :jobs, :definitions, :files, :name, :remote, :rbcm, :sandbox,
|
3
|
+
:path
|
3
4
|
attr_accessor :actions, :memberships, :triggered
|
4
5
|
|
5
|
-
def initialize rbcm, name
|
6
|
+
def initialize rbcm, name, path
|
6
7
|
@rbcm = rbcm
|
7
8
|
@name = name
|
9
|
+
@path = path
|
8
10
|
@definitions = []
|
9
11
|
@sandbox = Node::Sandbox.new self
|
10
12
|
@remote = Node::Remote.new self
|
@@ -33,4 +35,8 @@ class Node
|
|
33
35
|
memberships.include? group
|
34
36
|
}.values.flatten(1)
|
35
37
|
end
|
38
|
+
|
39
|
+
def to_str
|
40
|
+
name.to_s
|
41
|
+
end
|
36
42
|
end
|
data/app/node/remote.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
class Node::Remote
|
2
|
-
attr_reader :files
|
3
|
-
|
4
2
|
def initialize node
|
5
|
-
@
|
3
|
+
@node = node
|
6
4
|
@files = Node::NodeFilesystem.new node
|
7
5
|
end
|
8
6
|
|
7
|
+
attr_reader :node, :files
|
8
|
+
|
9
9
|
def execute action
|
10
|
-
@session ||= Net::SSH.start @
|
10
|
+
@session ||= Net::SSH.start @node.name, 'root'
|
11
11
|
@session.exec! action
|
12
12
|
end
|
13
13
|
end
|
data/app/node/sandbox.rb
CHANGED
@@ -9,8 +9,8 @@ class Node::Sandbox
|
|
9
9
|
@name = node.name
|
10
10
|
@dependency_cache = []
|
11
11
|
@cache = {
|
12
|
-
chain: [@node
|
13
|
-
source: [],
|
12
|
+
chain: [@node], trigger: [], triggered_by: [], check: [],
|
13
|
+
source: [], tags: [], working_dirs: []
|
14
14
|
}
|
15
15
|
# define in instance, otherwise method-binding will be wrong (to class)
|
16
16
|
@@capabilities = @node.rbcm.project.capabilities.each.name
|
@@ -21,21 +21,22 @@ class Node::Sandbox
|
|
21
21
|
[:file, :run].each do |base_capability|
|
22
22
|
__add_capability Project::Capability.new(
|
23
23
|
name: base_capability,
|
24
|
-
content: method(base_capability).unbind
|
24
|
+
content: method(base_capability).unbind,
|
25
|
+
project_file: false
|
25
26
|
)
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
29
30
|
def evaluate definitions
|
30
31
|
[definitions].flatten.each do |definition|
|
31
|
-
__cache chain: definition
|
32
|
+
__cache chain: definition do
|
32
33
|
instance_eval &definition.content
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
38
|
def tag name, &block
|
38
|
-
__cache
|
39
|
+
__cache tags: name, chain: "tag:#{name}" do
|
39
40
|
instance_eval &block
|
40
41
|
end
|
41
42
|
end
|
@@ -81,36 +82,59 @@ class Node::Sandbox
|
|
81
82
|
end
|
82
83
|
|
83
84
|
def run action, check: nil, tags: nil, trigger: nil, triggered_by: nil
|
84
|
-
|
85
|
-
node
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
85
|
+
__cache check: check, tags: tags, trigger: trigger, triggered_by: triggered_by, working_dirs: working_dir do
|
86
|
+
@node.actions << Action::Command.new(
|
87
|
+
job: @node.jobs.last,
|
88
|
+
line: action,
|
89
|
+
dependencies: @dependency_cache.dup,
|
90
|
+
state: @cache.collect{|k,v| [k, v.dup]}.to_h,
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def file path, tags: nil, trigger: nil, triggered_by: nil, **named
|
96
|
+
raise "RBCM: invalid file paramteres '#{named}'" if (
|
97
|
+
named.keys - [:exists, :includes_line, :after, :mode, :content,
|
98
|
+
:template, :context, :tags, :user, :group]
|
99
|
+
).any?
|
100
|
+
job = @node.jobs.last
|
101
|
+
run "mkdir -p #{File.dirname path}",
|
102
|
+
check: "ls #{File.dirname path}"
|
103
|
+
__cache tags: tags, trigger: trigger, triggered_by: triggered_by, working_dirs: working_dir do
|
104
|
+
@node.actions << Action::File.new(
|
105
|
+
job: job,
|
106
|
+
params: Params.new([path], named),
|
107
|
+
state: @cache.collect{|k,v| [k, v.dup]}.to_h
|
108
|
+
)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def dir path="", templates:, context: {}, tags: nil, trigger: nil, triggered_by: nil
|
113
|
+
__cache tags: tags, trigger: trigger, triggered_by: triggered_by, working_dirs: working_dir do
|
114
|
+
@node.rbcm.project.templates_.under("#{working_dir}/#{templates}").each do |template|
|
115
|
+
# binding.pry
|
116
|
+
# sleep 1
|
117
|
+
file template.clean_full_path.gsub(/#{working_dir}/),
|
118
|
+
template: template.clean_path,
|
119
|
+
context: context
|
120
|
+
end
|
121
|
+
|
122
|
+
# @node.rbcm.project.templates.select{ |template|
|
123
|
+
# /^#{working_dir}/.match? template
|
124
|
+
# }.each do |template|
|
125
|
+
# file path + template.gsub(/^#{working_dir}\/#{templates}/,"").gsub(".erb", "").gsub(".mustache", ""),
|
126
|
+
# template: template,
|
127
|
+
# context: context
|
128
|
+
# end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def working_dir
|
133
|
+
@cache[:chain].select{ |i|
|
134
|
+
i.class == Project::Definition or (
|
135
|
+
i.class == Project::Capability and not [:file, :run].include? i.name
|
136
|
+
)
|
137
|
+
}.last.project_file.path.split("/")[0..-2].join("/")
|
114
138
|
end
|
115
139
|
|
116
140
|
def decrypt secret
|
@@ -164,24 +188,27 @@ class Node::Sandbox
|
|
164
188
|
end
|
165
189
|
return r.collect &block if block_given? # no-each-syntax
|
166
190
|
return r
|
191
|
+
JobSearch.new r
|
167
192
|
end
|
168
193
|
|
169
194
|
def __cache trigger: nil, triggered_by: nil, params: nil, check: nil,
|
170
|
-
chain:
|
195
|
+
chain: nil, source: nil, reset: nil, tags: nil, working_dirs: nil
|
171
196
|
@cache[:source].append [] if chain
|
172
197
|
@cache[:source].last << source if source
|
173
198
|
@cache[:chain] << chain if chain
|
174
|
-
@cache[:
|
199
|
+
@cache[:tags] << tags if tags
|
175
200
|
@cache[:trigger] << trigger if trigger
|
176
201
|
@cache[:triggered_by] << triggered_by if triggered_by
|
177
202
|
@cache[:check] << check if check
|
203
|
+
@cache[:working_dirs] << working_dirs if working_dirs
|
178
204
|
r = yield if block_given?
|
179
205
|
@cache[:source].pop if chain
|
180
206
|
@cache[:chain].pop if chain
|
181
|
-
@cache[:
|
207
|
+
@cache[:tags].pop if tags
|
182
208
|
@cache[:trigger].pop if trigger
|
183
209
|
@cache[:triggered_by].pop if triggered_by
|
184
210
|
@cache[:check].pop if check
|
211
|
+
@cache[:working_dirs].pop if working_dirs
|
185
212
|
@cache[reset] = [] if reset
|
186
213
|
r
|
187
214
|
end
|
@@ -194,25 +221,32 @@ class Node::Sandbox
|
|
194
221
|
if capability.type == :regular
|
195
222
|
define_singleton_method capability.name do |*ordered, **named|
|
196
223
|
params = Params.new ordered, named
|
197
|
-
@node.jobs.append Node::Job.new
|
224
|
+
@node.jobs.append Node::Job.new(
|
225
|
+
node: @node,
|
226
|
+
capability: capability,
|
227
|
+
params: params
|
228
|
+
)
|
198
229
|
@node.triggered.append capability.name
|
199
230
|
r = __cache trigger: params[:trigger],
|
200
231
|
triggered_by: params[:triggered_by],
|
201
|
-
chain: capability
|
232
|
+
chain: capability do
|
202
233
|
send "__#{__method__}", *params.delete(:trigger, :triggered_by).sendable
|
203
234
|
end
|
204
235
|
@dependency_cache = [:file]
|
205
236
|
r
|
206
237
|
end
|
207
|
-
|
238
|
+
elsif capability.type == :final
|
208
239
|
define_singleton_method capability.name do
|
209
|
-
r = __cache chain:
|
240
|
+
r = __cache chain: capability do
|
210
241
|
send "__#{__method__}"
|
211
242
|
end
|
212
243
|
@dependency_cache = [:file]
|
213
244
|
r
|
214
245
|
end
|
246
|
+
else
|
247
|
+
raise "unknown capability type #{capability.type}"
|
215
248
|
end
|
249
|
+
# return JobSearch.new r
|
216
250
|
end
|
217
251
|
|
218
252
|
def self.capabilities
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Addon < Project
|
2
|
+
def initialize type:, name:
|
3
|
+
@type, @name = type, name
|
4
|
+
if [:file, :dir].include? type
|
5
|
+
super name
|
6
|
+
elsif type == :github
|
7
|
+
super load_from_github name
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :project, :type, :name, :repo
|
12
|
+
|
13
|
+
def load_from_github repo
|
14
|
+
addon_dir = "/tmp/rbcm-addons/"
|
15
|
+
dir = "#{addon_dir}#{repo}"
|
16
|
+
repo = if Dir.exist? dir
|
17
|
+
Git.open dir
|
18
|
+
else
|
19
|
+
Git.clone "https://github.com/#{repo}.git",
|
20
|
+
repo,
|
21
|
+
path: addon_dir
|
22
|
+
end
|
23
|
+
repo.checkout "master"
|
24
|
+
repo.pull
|
25
|
+
return dir
|
26
|
+
end
|
27
|
+
end
|
data/app/project/capability.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
# holds a capability in form of an unbound method, extracted from
|
2
2
|
# Project::Sandbox module
|
3
|
+
# type - regular: 'cap', final: 'cap!'
|
3
4
|
|
4
5
|
class Project::Capability
|
5
|
-
def initialize name:, content:
|
6
|
+
def initialize name:, content:, project_file:
|
6
7
|
@name = name
|
7
8
|
@content = content
|
8
9
|
@type = type
|
10
|
+
@project_file = project_file
|
9
11
|
end
|
10
12
|
|
11
|
-
attr_reader :name, :content
|
13
|
+
attr_reader :name, :content, :project_file
|
12
14
|
|
13
15
|
def type
|
14
16
|
@name[-1] == "!" ? :final : :regular
|
15
17
|
end
|
18
|
+
|
19
|
+
def to_str
|
20
|
+
name.to_s
|
21
|
+
end
|
16
22
|
end
|
data/app/project/definition.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# holds a definition on form of a proc to be executed in a nodes sandbox
|
2
2
|
|
3
3
|
class Project::Definition
|
4
|
-
def initialize type:, name:, content:
|
5
|
-
@type, @name, @content = type, name, content
|
4
|
+
def initialize type:, name:, content:, project_file:
|
5
|
+
@type, @name, @content, @project_file = type, name, content, project_file
|
6
6
|
end
|
7
7
|
|
8
|
-
attr_reader :type, :name, :content
|
8
|
+
attr_reader :type, :name, :content, :project_file
|
9
9
|
|
10
|
-
def
|
10
|
+
def to_str
|
11
11
|
"#{type}:#{name}"
|
12
12
|
end
|
13
13
|
end
|
data/app/project/file.rb
CHANGED
@@ -1,42 +1,61 @@
|
|
1
1
|
# extracts capabilities and definitions from project files
|
2
2
|
|
3
3
|
class Project::ProjectFile
|
4
|
-
def initialize
|
5
|
-
@
|
4
|
+
def initialize project:, path:
|
5
|
+
@project = project
|
6
|
+
@path = path
|
6
7
|
@definitions = []
|
7
8
|
@capabilities = []
|
8
|
-
|
9
|
+
@addons = []
|
10
|
+
file = File.read path
|
9
11
|
method_names_cache = methods(false)
|
10
12
|
instance_eval file
|
11
13
|
sandbox = Project::Sandbox.dup
|
12
14
|
sandbox.module_eval(file)
|
13
15
|
sandbox.instance_methods.each do |name|
|
16
|
+
raise "ERROR: capability name '#{name}' not allowed" if [:node, :group].include? name
|
14
17
|
@capabilities.append Project::Capability.new(
|
15
|
-
name:
|
16
|
-
content:
|
18
|
+
name: name,
|
19
|
+
content: sandbox.instance_method(name),
|
20
|
+
project_file: self
|
17
21
|
)
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
|
-
attr_reader :capabilities, :definitions, :path
|
25
|
+
attr_reader :project, :capabilities, :definitions, :addon_names, :path, :addons
|
22
26
|
|
23
27
|
private
|
24
28
|
|
29
|
+
def addon branch: "master", **named
|
30
|
+
raise "illegal project source: #{keys}" if (
|
31
|
+
keys = named.keys - [:github, :dir, :file]
|
32
|
+
).any?
|
33
|
+
named.each do |type, name|
|
34
|
+
@addons.append Addon.new type: type, name: name
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
25
38
|
def group name=nil
|
26
39
|
@definitions.append Project::Definition.new(
|
27
40
|
type: :group,
|
28
41
|
name: name,
|
29
|
-
content: Proc.new
|
42
|
+
content: Proc.new,
|
43
|
+
project_file: self
|
30
44
|
)
|
31
45
|
end
|
32
46
|
|
33
47
|
def node names=nil
|
34
48
|
[names].flatten(1).each do |name|
|
35
49
|
@definitions.append Project::Definition.new(
|
36
|
-
type:
|
37
|
-
name:
|
38
|
-
content:
|
50
|
+
type: name.class == Regexp ? :pattern : :node,
|
51
|
+
name: name,
|
52
|
+
content: Proc.new,
|
53
|
+
project_file: self
|
39
54
|
)
|
40
55
|
end
|
41
56
|
end
|
57
|
+
|
58
|
+
def relative_path
|
59
|
+
@path.gsub /^#{@project.path}/, ""
|
60
|
+
end
|
42
61
|
end
|
data/app/project/project.rb
CHANGED
@@ -1,25 +1,73 @@
|
|
1
1
|
class Project
|
2
|
-
def initialize path
|
2
|
+
def initialize path, template_engines: [:mustache, :erb], addon: false
|
3
3
|
@path = path
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
@files = []
|
5
|
+
@templates = []
|
6
|
+
@templates_ = Project::TemplateList.new
|
7
|
+
@other = []
|
8
|
+
@directories = []
|
9
|
+
@template_engines = template_engines
|
10
|
+
load_files path
|
11
11
|
end
|
12
12
|
|
13
|
-
attr_reader :path, :files
|
13
|
+
attr_reader :path, :files, :templates, :other, :directories, :templates_
|
14
14
|
|
15
15
|
def capabilities
|
16
|
-
|
16
|
+
files.each.capabilities.flatten.compact
|
17
17
|
end
|
18
18
|
|
19
19
|
def definitions type=nil
|
20
|
-
with
|
20
|
+
with files.each.definitions.flatten do
|
21
21
|
return select{|definition| definition.type == type} if type
|
22
22
|
return self
|
23
23
|
end
|
24
24
|
end
|
25
|
+
|
26
|
+
def files
|
27
|
+
(@files + all_addons.each.files).flatten
|
28
|
+
end
|
29
|
+
|
30
|
+
def addons
|
31
|
+
@files.each.addons.flatten
|
32
|
+
end
|
33
|
+
|
34
|
+
# collect addons recursively
|
35
|
+
def all_addons project=self
|
36
|
+
( project.addons + project.addons.collect{|project| all_addons project}
|
37
|
+
).flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
#TODO?
|
41
|
+
def template name
|
42
|
+
# @templates.find{|name| name...}
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def load_files path
|
48
|
+
if File.directory? path
|
49
|
+
Dir["#{path}/**/*"].each do |file_path|
|
50
|
+
if file_path.end_with? ".rb"
|
51
|
+
@files.append Project::ProjectFile.new(
|
52
|
+
project: self,
|
53
|
+
path: file_path
|
54
|
+
)
|
55
|
+
elsif @template_engines.include? file_path.split(".").last.to_sym
|
56
|
+
@templates << file_path.sub(@path, "")
|
57
|
+
@templates_.append Project::Template.new(
|
58
|
+
project: self,
|
59
|
+
path: file_path
|
60
|
+
)
|
61
|
+
elsif File.directory? path
|
62
|
+
@directories << file_path.sub(@path, "")
|
63
|
+
else
|
64
|
+
@other << file_path.sub(@path, "")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
log "templates: #{@templates}"
|
68
|
+
else
|
69
|
+
@files = [Project::ProjectFile.new(@path)]
|
70
|
+
end
|
71
|
+
raise "ERROR: empty project" unless @files.any?
|
72
|
+
end
|
25
73
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Project::Template
|
2
|
+
@@engines = [:erb, :mustache]
|
3
|
+
|
4
|
+
def initialize project:, path:
|
5
|
+
@project = project
|
6
|
+
@path = path
|
7
|
+
@content = File.read path
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :project, :path
|
11
|
+
|
12
|
+
def render context: {}
|
13
|
+
cache = @content
|
14
|
+
engine_names.each do |layer|
|
15
|
+
if layer == :mustache
|
16
|
+
require "mustache"
|
17
|
+
cache = Mustache.render(@content, **context)
|
18
|
+
elsif layer == :erb
|
19
|
+
# https://zaiste.net/rendering_erb_template_with_bindings_from_hash/
|
20
|
+
require "ostruct"; require "erb"
|
21
|
+
cache = ERB.new(@content).result(
|
22
|
+
OpenStruct.new(context).instance_eval{binding}
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return cache
|
27
|
+
end
|
28
|
+
|
29
|
+
def clean_full_path
|
30
|
+
path.gsub(
|
31
|
+
/#{engine_names.reverse.collect{|e| ".#{e}"}.join}$/, ''
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def clean_path
|
36
|
+
path.gsub(/^#{project.path}/, '').gsub(
|
37
|
+
/#{engine_names.reverse.collect{|e| ".#{e}"}.join}$/, ''
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
def clean_filename
|
42
|
+
File.basename(clean_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def engine_names
|
46
|
+
path.split(".").reverse.collect{ |layer|
|
47
|
+
layer.to_sym if @@engines.include? layer.to_sym
|
48
|
+
}.compact
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Project::TemplateList < Array
|
2
|
+
def for file_action
|
3
|
+
template_name = file_action.job.params[:template]
|
4
|
+
if template_name.start_with? "/" # `/template.txt`
|
5
|
+
find{|template| template.clean_path == template_name}
|
6
|
+
else # `template.txt`
|
7
|
+
find{ |template|
|
8
|
+
template.path.start_with? File.dirname(file_action.project_file.path) and
|
9
|
+
template.clean_filename == template_name
|
10
|
+
}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def under path
|
15
|
+
find_all{ |template|
|
16
|
+
template.path.start_with? path
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
data/app/rbcm.rb
CHANGED
@@ -2,20 +2,23 @@ require "net/ssh"
|
|
2
2
|
require "fileutils"
|
3
3
|
require "shellwords"
|
4
4
|
require "diffy"
|
5
|
+
require "pry"
|
6
|
+
require "git"
|
5
7
|
|
6
8
|
APPDIR = File.expand_path File.dirname(__FILE__)
|
7
|
-
[ "action/action",
|
8
|
-
"action/file",
|
9
|
-
"node/node",
|
10
|
-
"node/job",
|
11
|
-
"node/remote",
|
12
|
-
"node/
|
13
|
-
"lib/lib",
|
14
|
-
"lib/options",
|
15
|
-
"lib/params",
|
16
|
-
"project/project",
|
17
|
-
"project/file",
|
18
|
-
"project/sandbox",
|
9
|
+
[ "action/action", "action/command",
|
10
|
+
"action/file", "action/list",
|
11
|
+
"node/node", "node/file",
|
12
|
+
"node/job", "node/filesystem",
|
13
|
+
"node/remote", "node/sandbox",
|
14
|
+
"node/job_search",
|
15
|
+
"lib/lib", "lib/array_hash",
|
16
|
+
"lib/options", "lib/quick_each",
|
17
|
+
"lib/params", "lib/aescrypt",
|
18
|
+
"project/project", "project/definition",
|
19
|
+
"project/file", "project/capability",
|
20
|
+
"project/sandbox", "project/addon",
|
21
|
+
"project/template", "project/template_list",
|
19
22
|
"cli"
|
20
23
|
].each{|requirement| require "#{APPDIR}/#{requirement}.rb"}
|
21
24
|
|
@@ -27,7 +30,11 @@ class RBCM
|
|
27
30
|
@group_additions = ArrayHash.new
|
28
31
|
@nodes = {}
|
29
32
|
@project.definitions(:node).each do |node_definition|
|
30
|
-
@nodes[node_definition.name] ||= Node.new
|
33
|
+
@nodes[node_definition.name] ||= Node.new(
|
34
|
+
self,
|
35
|
+
node_definition.name,
|
36
|
+
node_definition.project_file.path
|
37
|
+
)
|
31
38
|
@nodes[node_definition.name] << node_definition
|
32
39
|
# apply pattern definitions to node
|
33
40
|
@nodes[node_definition.name] << @project.definitions(:pattern).collect do |pattern_definition|
|
@@ -39,9 +46,6 @@ class RBCM
|
|
39
46
|
@project.definitions(:group).each do |group_definition|
|
40
47
|
@groups[group_definition.name] << group_definition
|
41
48
|
end
|
42
|
-
# else
|
43
|
-
# tell project path to template class
|
44
|
-
Node::Template.project_path = @project.path
|
45
49
|
end
|
46
50
|
|
47
51
|
attr_reader :nodes, :groups, :project, :actions
|
@@ -64,7 +68,7 @@ class RBCM
|
|
64
68
|
end
|
65
69
|
log "parsing 'cap!'"
|
66
70
|
nodes.values.each do |node|
|
67
|
-
node.capabilities.each{|capability| node.sandbox.send "#{capability}!"}
|
71
|
+
node.capabilities.each{|capability| node.sandbox.send "#{capability.name}!"}
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
@@ -77,7 +81,9 @@ class RBCM
|
|
77
81
|
end
|
78
82
|
end
|
79
83
|
actions.checkable.each do |action|
|
80
|
-
|
84
|
+
actions.check.each do |check|
|
85
|
+
session.with(action.job.node.name.to_sym).exec check &block
|
86
|
+
end
|
81
87
|
end
|
82
88
|
session.loop
|
83
89
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbcm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Wiegand
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - '='
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 4.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.11.3
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.11.3
|
55
69
|
description: manage your servers via simple config-files
|
56
70
|
email: martin@wiegand.tel
|
57
71
|
executables:
|
@@ -73,15 +87,18 @@ files:
|
|
73
87
|
- app/node/file.rb
|
74
88
|
- app/node/filesystem.rb
|
75
89
|
- app/node/job.rb
|
90
|
+
- app/node/job_search.rb
|
76
91
|
- app/node/node.rb
|
77
92
|
- app/node/remote.rb
|
78
93
|
- app/node/sandbox.rb
|
79
|
-
- app/
|
94
|
+
- app/project/addon.rb
|
80
95
|
- app/project/capability.rb
|
81
96
|
- app/project/definition.rb
|
82
97
|
- app/project/file.rb
|
83
98
|
- app/project/project.rb
|
84
99
|
- app/project/sandbox.rb
|
100
|
+
- app/project/template.rb
|
101
|
+
- app/project/template_list.rb
|
85
102
|
- app/rbcm.rb
|
86
103
|
- bin/rbcm
|
87
104
|
homepage: https://github.com/CroneKorkN/rbcm
|
data/app/node/template.rb
DELETED
@@ -1,49 +0,0 @@
|
|
1
|
-
class Node::Template
|
2
|
-
@@engines = [:erb, :mustache]
|
3
|
-
|
4
|
-
def initialize name:, capability: nil, context: {}
|
5
|
-
@name = name
|
6
|
-
@capability = capability
|
7
|
-
@context = context
|
8
|
-
end
|
9
|
-
|
10
|
-
def render
|
11
|
-
content = File.read path
|
12
|
-
layers.each do |layer|
|
13
|
-
if layer == :mustache
|
14
|
-
require "mustache"
|
15
|
-
content = Mustache.render(content, **@context)
|
16
|
-
elsif layer == :erb
|
17
|
-
# https://zaiste.net/rendering_erb_template_with_bindings_from_hash/
|
18
|
-
require "ostruct"; require "erb"
|
19
|
-
content = ERB.new(content).result(
|
20
|
-
OpenStruct.new(@context).instance_eval{binding}
|
21
|
-
)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
return content
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def path
|
30
|
-
@path ||= Dir["#{@@project_path}/**/#{@name}*"].first
|
31
|
-
end
|
32
|
-
|
33
|
-
def filename
|
34
|
-
File.basename(path)
|
35
|
-
end
|
36
|
-
|
37
|
-
def layers
|
38
|
-
@layers = []
|
39
|
-
filename.split(".").reverse.each.to_sym.each do |layer|
|
40
|
-
break unless @@engines.include? layer
|
41
|
-
@layers << layer
|
42
|
-
end
|
43
|
-
return @layers
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.project_path= path
|
47
|
-
@@project_path = path
|
48
|
-
end
|
49
|
-
end
|