rbcm 0.0.8 → 0.0.9

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