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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 697605d1f72f7a26bebd85a9b5f6013156a1f2421c40705911bbf8135ae49f3f
4
- data.tar.gz: 9b978a43781d5cfc5037e21e2ed6fc2e5d661c3664390f9c40b307c438180bd6
3
+ metadata.gz: cb945c84ce8cafd1cd3e604049885d5a306cc5ad2c7b7a9b9e3fff3e3a91bd3d
4
+ data.tar.gz: 8427fb88c352244a6168eec50e234d43bcd1557dcb9281c6883d1c1c92e9bf0d
5
5
  SHA512:
6
- metadata.gz: c76ae5d7963c9a398acd4da2157aa50684e141bdd3573fbc3dee1f0781250a3950eae5a855efe00258d6f903c1c7480d45600b312ba6327b7897497d77e82f32
7
- data.tar.gz: a097963a15df265a58fe846e3200cc77477eead26ae64f18d217ad4d29cb2419d0b325ba9866c9fcd73ca1341fcbe98de54182dddb6e8774b2a4d80f6882754d
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 :node, :triggered_by, :trigger, :chain, :dependencies,
4
- :capability, :obsolete, :job, :check, :triggered, :result,
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:, chain:, path: nil, params: nil, line: nil, check: nil,
8
- dependencies: nil, trigger: nil, triggered_by: nil,
9
- source: nil, tags: nil, node:
10
- @dependencies = [:file] + [dependencies].flatten - [chain.last]
11
- @trigger = [trigger, chain.last].flatten.compact
12
- @triggered_by = [triggered_by].flatten.compact
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; @check = check
14
+ @line = line
20
15
  # file specific
21
- @path = path; @params = params
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
- true if @check or self.class == Action::File
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? do |triggered_by|
47
- @node.triggered.flatten.include? triggered_by
48
- end
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
@@ -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 = @node.remote.execute(@check).exitstatus == 0
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
- Node::Template.new(
28
- name: @params[:template],
29
- capability: @chain[-1],
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
- ).render
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
@@ -65,7 +65,7 @@ class ActionList < Array
65
65
 
66
66
  def resolve_action_dependencies this
67
67
  self.select{ |action|
68
- this.dependencies.include? action.capability
68
+ this.dependencies.include? action.job.capability
69
69
  }.each{ |action|
70
70
  resolve_action_dependencies action
71
71
  }
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} #{(@action.chain).join(" > ")} " +
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 \"%a\" * '#{@path}'"
38
+ "stat -c '%a' * '#{@path}'"
21
39
  ).chomp.chomp.to_i
22
40
  end
23
41
  end
data/app/node/job.rb CHANGED
@@ -4,7 +4,7 @@
4
4
  class Node::Job
5
5
  attr_reader :capability, :params, :node
6
6
 
7
- def initialize node, capability, params
7
+ def initialize node:, capability:, params:
8
8
  @node = node
9
9
  @capability = capability
10
10
  @params = params
@@ -0,0 +1,8 @@
1
+ class JobSearch
2
+ def initialize result
3
+
4
+ end
5
+
6
+ def []
7
+ end
8
+ end
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
- @host = node.name
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 @host, 'root'
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.name], trigger: [], triggered_by: [], check: [],
13
- source: [], tag: []
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.origin do
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 tag: name, chain: "tag:#{name}" do
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
- @node.actions << Action::Command.new(
85
- node: @node,
86
- line: action,
87
- check: check,
88
- chain: @cache[:chain].dup.flatten(1),
89
- dependencies: @dependency_cache.dup,
90
- tags: [tags] + @cache[:tag].dup,
91
- trigger: [@cache[:trigger].dup, trigger].flatten(1),
92
- triggered_by: [triggered_by, @cache[:triggered_by].dup].flatten(1),
93
- job: @node.jobs.last,
94
- source: @cache[:source].dup.flatten, # information from other nodes
95
- )
96
- end
97
-
98
- def file path, trigger: nil, **named
99
- raise "RBCM: invalid file paramteres '#{named}'" if (
100
- named.keys - [:exists, :includes_line, :after, :mode, :content,
101
- :template, :context, :tags]
102
- ).any?
103
- @node.actions << Action::File.new(
104
- node: @node,
105
- path: path,
106
- params: Params.new([path], named),
107
- chain: [@cache[:chain].dup].flatten(1),
108
- tags: [named[:tags]] + @cache[:tag].dup,
109
- trigger: [@cache[:trigger].dup, trigger].flatten(1),
110
- triggered_by: @cache[:triggered_by].dup,
111
- job: @node.jobs.last,
112
- source: @cache[:source].dup.flatten, # information from other nodes
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: [], source: nil, reset: nil, tag: nil
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[:tag] << tag if tag
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[:tag].pop if tag
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 @node, capability.name, params
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.name do
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
- else # capability.type == :final
238
+ elsif capability.type == :final
208
239
  define_singleton_method capability.name do
209
- r = __cache chain: __method__ do
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
@@ -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
@@ -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 origin
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 project_file_path
5
- @path = project_file_path
4
+ def initialize project:, path:
5
+ @project = project
6
+ @path = path
6
7
  @definitions = []
7
8
  @capabilities = []
8
- file = File.read project_file_path
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: name,
16
- content: sandbox.instance_method(name)
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: name.class == Regexp ? :pattern : :node,
37
- name: name,
38
- content: Proc.new
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
@@ -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
- if File.directory? path
5
- @files = Dir["#{path}/**/*.rb"].collect{ |project_file_path|
6
- Project::ProjectFile.new project_file_path
7
- }
8
- else
9
- @files = [Project::ProjectFile.new(path)]
10
- end
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
- @files.each.capabilities.flatten(1).compact
16
+ files.each.capabilities.flatten.compact
17
17
  end
18
18
 
19
19
  def definitions type=nil
20
- with @files.each.definitions.flatten(1) do
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", "action/command",
8
- "action/file", "action/list",
9
- "node/node", "node/file",
10
- "node/job", "node/filesystem",
11
- "node/remote", "node/sandbox",
12
- "node/template",
13
- "lib/lib", "lib/array_hash",
14
- "lib/options", "lib/quick_each",
15
- "lib/params", "lib/aescrypt",
16
- "project/project", "project/definition",
17
- "project/file", "project/capability",
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 self, node_definition.name
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
- session.with(action.node.name.to_sym).exec action.check &block
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.8
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/node/template.rb
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