cm 0.1.4 → 0.1.6

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.
@@ -1,11 +1,7 @@
1
1
 
2
- nucleon_require(File.dirname(__FILE__), :parallel_base)
3
-
4
- #---
5
-
6
2
  module CM
7
3
  module Plugin
8
- class Batch < Nucleon.plugin_class(:nucleon, :parallel_base)
4
+ class Batch < Nucleon.plugin_class(:nucleon, :base)
9
5
 
10
6
  #-----------------------------------------------------------------------------
11
7
  # Plugin interface
@@ -13,24 +9,24 @@ class Batch < Nucleon.plugin_class(:nucleon, :parallel_base)
13
9
  def normalize(reload)
14
10
  super
15
11
 
16
- @sequence = delete(:sequence, nil)
12
+ @plan = delete(:plan, nil) unless reload
17
13
 
18
- init_jobs
14
+ init_resources
19
15
  yield if block_given?
20
16
  end
21
17
 
22
18
  #---
23
19
 
24
- def init_jobs
25
- @jobs = []
26
- get_array(:jobs).each do |job_config|
27
- if job_config.has_key?(:sequence) # Array
28
- @jobs << sequence.create_sequence(job_config[:sequence])
20
+ def init_resources
21
+ @resources = []
22
+ get_array(:resources).each do |resource_config|
23
+ if resource_config.has_key?(:sequence) # Array
24
+ @resources << plan.create_sequence(resource_config[:sequence])
29
25
  else # Atomic
30
- @jobs << sequence.create_job(job_config)
26
+ @resources << plan.create_resource(resource_config)
31
27
  end
32
28
  end
33
- @jobs
29
+ @resources
34
30
  end
35
31
 
36
32
  #-----------------------------------------------------------------------------
@@ -43,30 +39,30 @@ class Batch < Nucleon.plugin_class(:nucleon, :parallel_base)
43
39
  #-----------------------------------------------------------------------------
44
40
  # Property accessors / modifiers
45
41
 
46
- def sequence
47
- @sequence
42
+ def plan
43
+ @plan
48
44
  end
49
45
 
50
46
  #---
51
47
 
52
- def jobs
53
- @jobs
48
+ def resources
49
+ @resources
54
50
  end
55
51
 
56
- def jobs=jobs
57
- set(:jobs, array(jobs))
58
- init_jobs
52
+ def resources=resources
53
+ set(:resources, array(resources))
54
+ init_resources
59
55
  end
60
56
 
61
57
  #-----------------------------------------------------------------------------
62
58
  # Operations
63
59
 
64
- def execute
60
+ def execute(operation)
65
61
  if initialized?
66
62
  if Nucleon.parallel?
67
- success = execute_parallel
63
+ success = execute_parallel(operation)
68
64
  else
69
- success = execute_sequence
65
+ success = execute_sequence(operation)
70
66
  end
71
67
  else
72
68
  success = false
@@ -77,17 +73,17 @@ class Batch < Nucleon.plugin_class(:nucleon, :parallel_base)
77
73
  #-----------------------------------------------------------------------------
78
74
  # Utilities
79
75
 
80
- def execute_parallel
76
+ def execute_parallel(operation)
81
77
  false # Override me!!
82
78
  end
83
79
 
84
80
  #---
85
81
 
86
- def execute_sequence
82
+ def execute_sequence(operation)
87
83
  success = true
88
- jobs.each do |job|
89
- success = false unless job.execute
90
- break if sequence.trap && sequence.step
84
+ resources.each do |resource|
85
+ success = false unless resource.execute(operation)
86
+ break if plan.trap && plan.step
91
87
  end
92
88
  success
93
89
  end
@@ -1,11 +1,7 @@
1
1
 
2
- nucleon_require(File.dirname(__FILE__), :parallel_base)
3
-
4
- #---
5
-
6
2
  module CM
7
3
  module Plugin
8
- class Configuration < Nucleon.plugin_class(:nucleon, :parallel_base)
4
+ class Configuration < Nucleon.plugin_class(:nucleon, :base)
9
5
 
10
6
  include Nucleon::Mixin::SubConfig
11
7
 
@@ -0,0 +1,315 @@
1
+
2
+ nucleon_require(File.dirname(__FILE__), :resource)
3
+
4
+ #---
5
+
6
+ module CM
7
+ module Plugin
8
+ class DockerResource < Nucleon.plugin_class(:CM, :resource)
9
+
10
+ #-----------------------------------------------------------------------------
11
+ # Plugin interface
12
+
13
+ def normalize(reload)
14
+ require 'docker'
15
+ super
16
+
17
+ codes :docker_exec_failed
18
+
19
+ settings[:docker_protocol] ||= 'unix'
20
+ settings[:docker_sock] ||= '/var/run/docker.sock'
21
+ settings[:docker_host] ||= nil
22
+ settings[:docker_port] ||= '127.0.0.1'
23
+ settings[:docker_image] ||= 'awebb/cm'
24
+
25
+ if settings[:docker_host].nil?
26
+ Docker.url = "#{settings[:docker_protocol]}://#{settings[:docker_sock]}"
27
+ else
28
+ Docker.url = "#{settings[:docker_protocol]}://#{settings[:docker_host]}:#{settings[:docker_port]}"
29
+ end
30
+
31
+ yield if block_given?
32
+ end
33
+
34
+ #---
35
+
36
+ def remove_plugin
37
+ destroy_container(plugin_instance_name)
38
+ end
39
+
40
+ #-----------------------------------------------------------------------------
41
+ # Checks
42
+
43
+ def initialized?(options = {})
44
+ true
45
+ end
46
+
47
+ #---
48
+
49
+ def internal?
50
+ File.exist?('/.dockerinit')
51
+ end
52
+
53
+ #-----------------------------------------------------------------------------
54
+ # Property accessors / modifiers
55
+
56
+ def image
57
+ get(:image, 'awebb/cm').to_s
58
+ end
59
+
60
+ #---
61
+
62
+ def startup_commands
63
+ get(:startup_commands, ['bash'])
64
+ end
65
+
66
+ #---
67
+
68
+ def container
69
+ @container
70
+ end
71
+
72
+ #---
73
+
74
+ def plan_directory
75
+ get(:plan_directory, '/opt/cm/volumes/plan')
76
+ end
77
+
78
+ def key_directory
79
+ get(:key_directory, '/opt/cm/volumes/keys')
80
+ end
81
+
82
+ #---
83
+
84
+ def host_input_directory
85
+ get(:host_input_directory, "/tmp/cm/input/#{plugin_instance_name}")
86
+ end
87
+
88
+ def input_directory
89
+ get(:input_directory, '/opt/cm/volumes/input')
90
+ end
91
+
92
+ #---
93
+
94
+ def host_output_directory
95
+ get(:host_output_directory, "/tmp/cm/output/#{plugin_instance_name}")
96
+ end
97
+
98
+ def output_directory
99
+ get(:output_directory, '/opt/cm/volumes/output')
100
+ end
101
+
102
+ #-----------------------------------------------------------------------------
103
+ # Operations
104
+
105
+ def operation_deploy
106
+ super do
107
+ data = nil
108
+
109
+ # A fork in the road!
110
+ if internal?
111
+ data = yield if block_given?
112
+ else
113
+ data = action(plugin_provider, :deploy)
114
+ myself.status = code.docker_exec_failed unless data
115
+ end
116
+ myself.data = data
117
+ myself.status == code.success
118
+ end
119
+ end
120
+
121
+ #-----------------------------------------------------------------------------
122
+ # Docker resource operation execution
123
+
124
+ def exec(command)
125
+ data = nil
126
+
127
+ create_container
128
+
129
+ results = container.exec(['bash', '-l', '-c', command]) do |stream, message|
130
+ unless message.match(/stdin\:\s+is not a tty/)
131
+ render_docker_message(stream, message)
132
+ yield(stream, message) if block_given?
133
+ end
134
+ end
135
+
136
+ if results[2] == 0
137
+ if output_config = CM.configuration(extended_config(:resource_results, {
138
+ :provider => get(:resource_result_provider, :directory),
139
+ :path => host_output_directory
140
+ }))
141
+ data = Nucleon::Util::Data.clone(output_config.export)
142
+ Nucleon.remove_plugin(output_config)
143
+ end
144
+ end
145
+
146
+ destroy_container
147
+ data
148
+ end
149
+
150
+ #---
151
+
152
+ def command(command, options = {})
153
+ config = Nucleon::Config.ensure(options)
154
+ remove_command = false
155
+
156
+ unless command.is_a?(Nucleon::Plugin::Command)
157
+ command = Nucleon.command(Nucleon::Config.new({ :command => command }, {}, true, false).import(config), :bash)
158
+ remove_command = true
159
+ end
160
+
161
+ data = exec(command.to_s.strip) do |stream, message|
162
+ yield(stream, message) if block_given?
163
+ end
164
+
165
+ Nucleon.remove_plugin(command) if remove_command
166
+ data
167
+ end
168
+
169
+ #---
170
+
171
+ def action(provider, operation)
172
+ FileUtils.mkdir_p(host_input_directory)
173
+ FileUtils.mkdir_p(host_output_directory)
174
+
175
+ action_settings = Nucleon::Util::Data.clean(plan.action_settings)
176
+ initialize_remote_config(action_settings)
177
+
178
+ encoded_config = Nucleon::Util::CLI.encode(action_settings)
179
+ action_config = extended_config(:action, {
180
+ :command => 'resource run',
181
+ :data => { :encoded => encoded_config },
182
+ :args => [ provider, operation ]
183
+ })
184
+ action_config[:data][:log_level] = Nucleon.log_level if Nucleon.log_level
185
+
186
+ data = command('cm', Nucleon::Util::Data.clean({
187
+ :subcommand => action_config,
188
+ :quiet => Nucleon::Util::Console.quiet
189
+ })) do |stream, message|
190
+ yield(stream, message) if block_given?
191
+ end
192
+
193
+ FileUtils.rm_rf(host_input_directory)
194
+ FileUtils.rm_rf(host_output_directory)
195
+ data
196
+ end
197
+
198
+ #-----------------------------------------------------------------------------
199
+ # Utilities
200
+
201
+ def create_container
202
+ container = nil
203
+
204
+ destroy_container
205
+
206
+ container_env = []
207
+ container_env << "NUCLEON_NO_PARALLEL=1" unless Nucleon.parallel?
208
+ container_env << "NUCLEON_NO_COLOR=1" unless Nucleon::Util::Console.use_colors
209
+
210
+ @container = Docker::Container.create({
211
+ 'name' => plugin_instance_name,
212
+ 'Image' => image,
213
+ 'Cmd' => array(startup_commands),
214
+ 'Tty' => true,
215
+ 'Env' => container_env,
216
+ 'Volumes' => {
217
+ plan_directory => {},
218
+ key_directory => {},
219
+ input_directory => {},
220
+ output_directory => {}
221
+ },
222
+ 'HostConfig' => {
223
+ 'Binds' => [
224
+ "#{plan.path}:#{plan_directory}:ro",
225
+ "#{plan.key_directory}:#{key_directory}:rw",
226
+ "#{host_input_directory}:#{input_directory}:ro", # config.yaml and tokens.json
227
+ "#{host_output_directory}:#{output_directory}:rw" # ??.yaml and/or ??.json
228
+ ]
229
+ }
230
+ })
231
+
232
+ if @container
233
+ @container.start!
234
+ else
235
+ error('cm.resource.docker.error.container_failed', {
236
+ :image => image
237
+ })
238
+ end
239
+ end
240
+ protected :create_container
241
+
242
+ #---
243
+
244
+ def initialize_remote_config(action_settings)
245
+ # Generate action settings file
246
+ settings = CM.configuration(extended_config(:container_input_settings_data, {
247
+ :provider => get(:container_input_settings_provider, :file),
248
+ :path => "#{host_input_directory}/action_settings.json"
249
+ }))
250
+ settings.import(action_settings)
251
+ settings.save
252
+
253
+ Nucleon.remove_plugin(settings)
254
+
255
+ # Generate and store plan configuration in local input directory
256
+ config = CM.configuration(extended_config(:container_input_config_data, {
257
+ :provider => get(:container_input_config_provider, :file),
258
+ :path => "#{host_input_directory}/config.json"
259
+ }))
260
+ config.import(plan.manifest_config)
261
+ config.save
262
+
263
+ Nucleon.remove_plugin(config)
264
+
265
+ # Generate and store plan tokens in local input directory
266
+ tokens = CM.configuration(extended_config(:container_input_token_data, {
267
+ :provider => get(:container_input_token_provider, :file),
268
+ :path => "#{host_output_directory}/tokens.json"
269
+ }))
270
+ tokens.import(plan.tokens)
271
+ tokens.save
272
+
273
+ Nucleon.remove_plugin(tokens)
274
+
275
+ # Customize action settings
276
+ action_settings[:resource_config] = myself.settings
277
+ action_settings[:settings_path] = "#{input_directory}/action_settings.json"
278
+ action_settings[:plan_path] = plan_directory
279
+ action_settings[:config_provider] = 'file'
280
+ action_settings[:config_path] = "#{input_directory}/config.json"
281
+ action_settings[:token_provider] = 'file'
282
+ action_settings[:token_path] = output_directory
283
+ action_settings[:token_file] = 'tokens.json'
284
+ action_settings[:key_path] = key_directory
285
+ end
286
+ protected :initialize_remote_config
287
+
288
+
289
+ #---
290
+
291
+ def destroy_container
292
+ containers = Docker::Container.all({ :all => 1 })
293
+
294
+ containers.each do |cont|
295
+ if cont.info.key?('Names') && cont.info['Names'].include?("/#{plugin_instance_name}")
296
+ cont.kill!
297
+ cont.remove
298
+ end
299
+ end
300
+ @container = nil
301
+ end
302
+ protected :destroy_container
303
+
304
+ #---
305
+
306
+ def render_docker_message(stream, message)
307
+ if stream == 'stderr'
308
+ puts message
309
+ else
310
+ puts message
311
+ end
312
+ end
313
+ end
314
+ end
315
+ end
@@ -7,6 +7,10 @@ module CM
7
7
  module Plugin
8
8
  class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
9
9
 
10
+ include Nucleon::Parallel # All sub providers are parallel capable
11
+
12
+ #---
13
+
10
14
  def self.register_ids
11
15
  [ :directory, :revision ]
12
16
  end
@@ -17,50 +21,67 @@ class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
17
21
  def normalize(reload)
18
22
  super
19
23
 
20
- @project = Nucleon.project(extended_config(:plan_project, {
21
- :provider => _get(:project_provider, Nucleon.type_default(:nucleon, :project)),
22
- :directory => _get(:path, Dir.pwd),
23
- :url => _get(:url),
24
- :revision => _get(:revision, :master),
25
- :create => true,
26
- :pull => true,
27
- :nucleon_resave => false,
28
- :nucleon_cache => false,
29
- :nucleon_file => false
30
- }))
31
-
32
- if project && !reload
24
+ if !reload
33
25
  @loaded_config = CM.configuration(extended_config(:config_data, {
34
26
  :provider => _get(:config_provider, :directory),
35
- :path => config_directory
27
+ :path => config_path
28
+ }))
29
+
30
+ @tokens = CM.configuration(extended_config(:token_data, {
31
+ :provider => _get(:token_provider, :file),
32
+ :path => token_path
36
33
  }))
37
34
 
38
35
  yield if block_given?
39
36
  end
40
37
  end
41
38
 
39
+ #---
40
+
41
+ def init_tokens
42
+ clear_tokens
43
+
44
+ collect_tokens = lambda do |local_settings, token|
45
+ local_settings.each do |name, value|
46
+ setting_token = [ array(token), name ].flatten
47
+
48
+ if value.is_a?(Hash)
49
+ collect_tokens.call(value, setting_token)
50
+ else
51
+ token_base = setting_token.shift
52
+ set_token(token_base, setting_token, value)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Generate config tokens
58
+ collect_tokens.call(manifest_config, 'config')
59
+ end
60
+
42
61
  #-----------------------------------------------------------------------------
43
62
  # Checks
44
63
 
45
64
  def initialized?(options = {})
46
- project && loaded_config
65
+ loaded_config
47
66
  end
48
67
 
49
68
  #-----------------------------------------------------------------------------
50
69
  # Property accessors / modifiers
51
70
 
52
- def project
53
- @project
54
- end
55
-
56
71
  def loaded_config
57
72
  @loaded_config
58
73
  end
59
74
 
60
75
  #---
61
76
 
77
+ def action_settings
78
+ _get(:action_settings, {})
79
+ end
80
+
81
+ #---
82
+
62
83
  def path
63
- project.directory
84
+ _get(:path, Dir.pwd)
64
85
  end
65
86
 
66
87
  def key_directory
@@ -85,39 +106,60 @@ class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
85
106
  get_hash(:config)
86
107
  end
87
108
 
88
- def manifest_jobs
89
- get_array(:jobs)
109
+ def manifest_resources
110
+ get_array(:resources)
111
+ end
112
+
113
+ #---
114
+
115
+ def config_path
116
+ _get(:config_path, path)
90
117
  end
91
118
 
92
119
  #---
93
120
 
94
- def config_directory
95
- _get(:config_directory, path)
121
+ def sequence
122
+ @sequence
96
123
  end
97
124
 
98
- def output_file
99
- _get(:output_file, "rendered.#{manifest_file.gsub(/#{File::SEPARATOR}+/, '.')}")
125
+ #---
126
+
127
+ def token_directory
128
+ _get(:token_directory, config_path)
100
129
  end
101
130
 
102
- def target_path
103
- ::File.join(config_directory, output_file)
131
+ def token_file
132
+ _get(:token_file, 'tokens.json')
104
133
  end
105
134
 
135
+ def token_path
136
+ ::File.join(token_directory, token_file)
137
+ end
106
138
 
107
139
  #---
108
140
 
109
- def url
110
- project.url
141
+ def tokens
142
+ @tokens.parse
111
143
  end
112
144
 
113
- def revision
114
- project.revision
145
+ def set_token(id, location, value)
146
+ @tokens["#{id}:#{array(location).join('.')}"] = value
147
+ @tokens.save
148
+ end
149
+
150
+ def remove_token(id, location)
151
+ @tokens.delete("#{id}:#{array(location).join('.')}")
152
+ @tokens.save
153
+ end
154
+
155
+ def clear_tokens
156
+ @tokens.wipe
115
157
  end
116
158
 
117
159
  #---
118
160
 
119
- def sequence
120
- @sequence
161
+ def trap
162
+ _get(:trap, false)
121
163
  end
122
164
 
123
165
  #-----------------------------------------------------------------------------
@@ -127,7 +169,7 @@ class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
127
169
  success = true
128
170
 
129
171
  if initialized?
130
- # Initialize plan manifest (default config and jobs)
172
+ # Initialize plan manifest (default config and resources)
131
173
  wipe
132
174
  import(CM.configuration(extended_config(:manifest_data, {
133
175
  :provider => _get(:manifest_provider, :file),
@@ -137,12 +179,8 @@ class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
137
179
  # Merge in configuration overlay (private config)
138
180
  override(loaded_config.get_hash(:config), :config)
139
181
 
140
- # Initialize job sequence
141
- @sequence = CM.sequence({
142
- :jobs => manifest_jobs,
143
- :settings => manifest_config,
144
- :trap => _get(:trap, false)
145
- }, _get(:sequence_provider, :default))
182
+ # Initializeresource sequence
183
+ @sequence = create_sequence(manifest_resources)
146
184
 
147
185
  yield if block_given?
148
186
  end
@@ -151,14 +189,17 @@ class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
151
189
 
152
190
  #---
153
191
 
154
- def execute(operation, options = {})
192
+ def execute(operation)
155
193
  success = true
156
194
 
157
195
  if initialized?
158
196
  if ::File.exist?(manifest_path)
159
197
  method = "operation_#{operation}"
160
- success = send(method, options) if respond_to?(method) && load
161
- success = save if success
198
+
199
+ if respond_to?(method) && load
200
+ init_tokens
201
+ success = send(method)
202
+ end
162
203
  success
163
204
  else
164
205
  error('manifest_file', { :file => manifest_path })
@@ -172,27 +213,57 @@ class Plan < Nucleon.plugin_class(:CM, :disk_configuration)
172
213
 
173
214
  #---
174
215
 
175
- def operation_deploy(options)
176
- config = Nucleon::Config.ensure(options)
177
- sequence.forward(config)
216
+ def operation_deploy
217
+ sequence.forward(:deploy)
178
218
  end
179
219
 
180
220
  #---
181
221
 
182
- def operation_destroy(options)
183
- config = Nucleon::Config.ensure(options)
184
- sequence.reverse(config)
222
+ def operation_destroy
223
+ sequence.reverse(:destroy)
224
+ end
225
+
226
+ #-----------------------------------------------------------------------------
227
+ # Utilities
228
+
229
+ def create_sequence(resources)
230
+ CM.sequence({
231
+ :plan => myself,
232
+ :settings => manifest_config,
233
+ :resources => resources,
234
+ :new => true,
235
+ }, _get(:sequence_provider, :default))
185
236
  end
186
237
 
187
238
  #---
188
239
 
189
- def save
190
- save_config(target_path, { :config => manifest_config })
240
+ def create_batch(resources)
241
+ CM.batch({
242
+ :plan => myself,
243
+ :resources => resources,
244
+ :new => true
245
+ }, _get(:batch_provider, :celluloid))
191
246
  end
192
247
 
193
- #-----------------------------------------------------------------------------
194
- # Utilities
248
+ #---
195
249
 
250
+ def create_resource(settings)
251
+ settings = Nucleon::Config.ensure(settings)
252
+ settings[:type] ||= _get(:default_resource_provider, :variables)
253
+
254
+ CM.resource({
255
+ :plan => myself,
256
+ :settings => settings.export,
257
+ :id => settings[:name]
258
+ }, settings[:type])
259
+ end
260
+
261
+ #---
262
+
263
+ def step
264
+ answer = ask('Continue? (yes|no): ', { :i18n => false })
265
+ answer.match(/^[Yy][Ee][Ss]$/) ? false : true
266
+ end
196
267
  end
197
268
  end
198
269
  end