nucleon 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/Gemfile +4 -8
  2. data/Gemfile.lock +0 -28
  3. data/README.rdoc +13 -5
  4. data/Rakefile +9 -1
  5. data/VERSION +1 -1
  6. data/bin/nucleon +55 -0
  7. data/lib/core/codes.rb +107 -0
  8. data/lib/core/config/collection.rb +57 -0
  9. data/lib/core/config/options.rb +70 -0
  10. data/lib/core/config.rb +342 -0
  11. data/lib/core/core.rb +54 -0
  12. data/lib/core/errors.rb +84 -0
  13. data/lib/core/facade.rb +283 -0
  14. data/lib/core/gems.rb +80 -0
  15. data/lib/core/manager.rb +594 -0
  16. data/lib/core/mixin/action/commit.rb +58 -0
  17. data/lib/core/mixin/action/project.rb +53 -0
  18. data/lib/core/mixin/action/push.rb +52 -0
  19. data/lib/core/mixin/config/collection.rb +53 -0
  20. data/lib/core/mixin/config/options.rb +39 -0
  21. data/lib/core/mixin/macro/object_interface.rb +361 -0
  22. data/lib/core/mixin/macro/plugin_interface.rb +380 -0
  23. data/lib/core/mixin/settings.rb +46 -0
  24. data/lib/core/mixin/sub_config.rb +148 -0
  25. data/lib/core/mod/hash.rb +29 -0
  26. data/lib/core/plugin/action.rb +371 -0
  27. data/lib/core/plugin/base.rb +313 -0
  28. data/lib/core/plugin/command.rb +98 -0
  29. data/lib/core/plugin/event.rb +53 -0
  30. data/lib/core/plugin/extension.rb +12 -0
  31. data/lib/core/plugin/project.rb +890 -0
  32. data/lib/core/plugin/template.rb +80 -0
  33. data/lib/core/plugin/translator.rb +38 -0
  34. data/lib/core/util/cli.rb +353 -0
  35. data/lib/core/util/console.rb +237 -0
  36. data/lib/core/util/data.rb +404 -0
  37. data/lib/core/util/disk.rb +114 -0
  38. data/lib/core/util/git.rb +43 -0
  39. data/lib/core/util/liquid.rb +17 -0
  40. data/lib/core/util/logger.rb +147 -0
  41. data/lib/core/util/package.rb +93 -0
  42. data/lib/core/util/shell.rb +239 -0
  43. data/lib/nucleon/action/add.rb +69 -0
  44. data/lib/nucleon/action/create.rb +52 -0
  45. data/lib/nucleon/action/extract.rb +49 -0
  46. data/lib/nucleon/action/remove.rb +51 -0
  47. data/lib/nucleon/action/save.rb +53 -0
  48. data/lib/nucleon/action/update.rb +37 -0
  49. data/lib/nucleon/command/bash.rb +146 -0
  50. data/lib/nucleon/event/regex.rb +52 -0
  51. data/lib/nucleon/project/git.rb +465 -0
  52. data/lib/nucleon/project/github.rb +108 -0
  53. data/lib/nucleon/template/json.rb +16 -0
  54. data/lib/nucleon/template/wrapper.rb +16 -0
  55. data/lib/nucleon/template/yaml.rb +16 -0
  56. data/lib/nucleon/translator/json.rb +27 -0
  57. data/lib/nucleon/translator/yaml.rb +27 -0
  58. data/lib/nucleon.rb +18 -15
  59. data/locales/en.yml +3 -132
  60. data/nucleon.gemspec +66 -27
  61. data/spec/core/util/console_spec.rb +489 -0
  62. metadata +109 -96
@@ -0,0 +1,93 @@
1
+
2
+ module Nucleon
3
+ module Util
4
+ class Package
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Constructor / Destructor
8
+
9
+ def initialize(options = {})
10
+ if options.is_a?(String)
11
+ @data = Config.new
12
+ decode(options)
13
+ else
14
+ @data = Config.ensure(options)
15
+ end
16
+ end
17
+
18
+ #-----------------------------------------------------------------------------
19
+ # Property accessors / modifiers
20
+
21
+ attr_reader :data
22
+
23
+ #-----------------------------------------------------------------------------
24
+ # Operations
25
+
26
+ def encode
27
+ Base64.encode64(Data.to_json(data.export, false))
28
+ end
29
+
30
+ def decode(encoded_string)
31
+ data.import(Data.symbol_map(Data.parse_json(Base64.decode64(encoded_string))))
32
+ end
33
+
34
+ #---
35
+
36
+ def add_file(file, target_path = nil, perm = 0600)
37
+ target_path = file if target_path.nil?
38
+ file = File.expand_path(file)
39
+
40
+ if File.exists?(file)
41
+ content = Disk.read(file)
42
+ data.set([ :files, target_path ], { :perm => perm, :content => content }) if content
43
+ end
44
+ self
45
+ end
46
+
47
+ #---
48
+
49
+ def add_files(base_path, file_glob, target_path = nil, perm = 0600)
50
+ target_path = base_path if target_path.nil?
51
+ curr_dir = Dir.pwd
52
+
53
+ Dir.chdir(File.expand_path(base_path))
54
+ Dir.glob(file_glob.gsub(/^[\/\\]+/, '')) do |file|
55
+ content = Disk.read(file)
56
+
57
+ if content
58
+ data.set([ :dir, target_path, file ], { :perm => perm, :content => content })
59
+ end
60
+ end
61
+ Dir.chdir(curr_dir)
62
+ self
63
+ end
64
+
65
+ #---
66
+
67
+ def extract(base_path)
68
+ success = true
69
+
70
+ data.get_hash(:files).each do |target_path, info|
71
+ file = File.join(base_path.to_s, target_path.to_s)
72
+ perm = info[:perm]
73
+ content = info[:content]
74
+
75
+ FileUtils.mkdir_p(File.dirname(file))
76
+ success = false unless Disk.write(file, content) && File.chmod(perm, file)
77
+ end
78
+
79
+ data.get_hash(:dir).each do |target_path, files|
80
+ files.each do |file, info|
81
+ file = File.join(base_path.to_s, target_path.to_s, file.to_s)
82
+ perm = info[:perm]
83
+ content = info[:content]
84
+
85
+ FileUtils.mkdir_p(File.dirname(file))
86
+ success = false unless Disk.write(file, content) && File.chmod(perm, file)
87
+ end
88
+ end
89
+ success
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,239 @@
1
+
2
+ module Nucleon
3
+ module Util
4
+ class Shell < Core
5
+
6
+ include Celluloid
7
+
8
+ #-----------------------------------------------------------------------------
9
+
10
+ @@supervisors = {}
11
+
12
+ #-----------------------------------------------------------------------------
13
+ # Execution result interface
14
+
15
+ class Result
16
+ attr_accessor :status
17
+ attr_reader :command
18
+
19
+ def initialize(command)
20
+ @command = command
21
+ @output = ''
22
+ @errors = ''
23
+ @status = Nucleon.code.success
24
+ end
25
+
26
+ #---
27
+
28
+ def output
29
+ @output.strip
30
+ end
31
+
32
+ def errors
33
+ @errors.strip
34
+ end
35
+
36
+ #---
37
+
38
+ def append_output(output_str)
39
+ @output << output_str
40
+ end
41
+
42
+ def append_errors(error_str)
43
+ @errors << error_str
44
+ end
45
+ end
46
+
47
+ #-----------------------------------------------------------------------------
48
+ # Shell interface
49
+
50
+ def self.connection(name = :core)
51
+ name = name.to_sym
52
+ init_shell(name) unless @@supervisors.has_key?(name)
53
+ @@supervisors[name]
54
+ end
55
+
56
+ def self.init_shell(name)
57
+ name = name.to_sym
58
+
59
+ Shell.supervise_as name
60
+ @@supervisors[name] = Celluloid::Actor[name]
61
+ end
62
+
63
+ #---
64
+
65
+ execute_block_on_receiver :exec
66
+
67
+ def exec(command, options = {}, &code)
68
+ config = Config.ensure(options)
69
+
70
+ min = config.get(:min, 1).to_i
71
+ tries = config.get(:tries, min).to_i
72
+ tries = ( min > tries ? min : tries )
73
+
74
+ info_prefix = config.get(:info_prefix, '')
75
+ info_suffix = config.get(:info_suffix, '')
76
+ error_prefix = config.get(:error_prefix, '')
77
+ error_suffix = config.get(:error_suffix, '')
78
+
79
+ ui = config.get(:ui, Nucleon.ui)
80
+
81
+ conditions = Nucleon.events(config.get(:exit, {}), true)
82
+
83
+ $stdout.sync = true
84
+ $stderr.sync = true
85
+
86
+ system_result = Result.new(command)
87
+
88
+ for i in tries.downto(1)
89
+ logger.info(">> running: #{command}")
90
+
91
+ begin
92
+ t1, output_new, output_orig, output_reader = pipe_exec_stream($stdout, conditions, {
93
+ :prefix => info_prefix,
94
+ :suffix => info_suffix,
95
+ }, 'output') do |data|
96
+ system_result.append_output(data)
97
+ block_given? ? code.call(:output, command, data) : true
98
+ end
99
+
100
+ t2, error_new, error_orig, error_reader = pipe_exec_stream($stderr, conditions, {
101
+ :prefix => error_prefix,
102
+ :suffix => error_suffix,
103
+ }, 'error') do |data|
104
+ system_result.append_errors(data)
105
+ block_given? ? code.call(:error, command, data) : true
106
+ end
107
+
108
+ system_success = system(command)
109
+ system_result.status = $?.exitstatus
110
+
111
+ ensure
112
+ output_success = close_exec_pipe(t1, $stdout, output_orig, output_new, 'output')
113
+ error_success = close_exec_pipe(t2, $stderr, error_orig, error_new, 'error')
114
+ end
115
+
116
+ success = ( system_success && output_success && error_success )
117
+
118
+ min -= 1
119
+ break if success && min <= 0 && conditions.empty?
120
+ end
121
+ system_result
122
+ end
123
+
124
+ #---
125
+
126
+ def pipe_exec_stream(output, conditions, options, label, &code)
127
+ original = output.dup
128
+ read, write = IO.pipe
129
+
130
+ match_prefix = ( options[:match_prefix] ? options[:match_prefix] : 'EXIT' )
131
+
132
+ thread = process_stream(read, original, options, label) do |data|
133
+ check_conditions(data, conditions, match_prefix) do
134
+ block_given? ? code.call(data) : true
135
+ end
136
+ end
137
+
138
+ thread.abort_on_exception = false
139
+
140
+ output.reopen(write)
141
+ return thread, write, original, read
142
+ end
143
+ protected :pipe_exec_stream
144
+
145
+ #---
146
+
147
+ def close_exec_pipe(thread, output, original, write, label)
148
+ output.reopen(original)
149
+
150
+ write.close
151
+ success = thread.value
152
+
153
+ original.close
154
+ return success
155
+ end
156
+ protected :close_exec_pipe
157
+
158
+ #---
159
+
160
+ def check_conditions(data, conditions, match_prefix = '', &code)
161
+ prefix = ''
162
+
163
+ unless ! conditions || conditions.empty?
164
+ conditions.each do |key, event|
165
+ if event.check(data)
166
+ prefix = match_prefix
167
+ conditions.delete(key)
168
+ end
169
+ end
170
+ end
171
+
172
+ result = true
173
+ if block_given?
174
+ result = code.call
175
+
176
+ unless prefix.empty?
177
+ case result
178
+ when Hash
179
+ result[:prefix] = prefix
180
+ else
181
+ result = { :success => result, :prefix => prefix }
182
+ end
183
+ end
184
+ end
185
+ return result
186
+ end
187
+ protected :check_conditions
188
+
189
+ #---
190
+
191
+ def process_stream(input, output, options, label, &code)
192
+ return Thread.new do
193
+ success = true
194
+ default_prefix = ( options[:prefix] ? options[:prefix] : '' )
195
+ default_suffix = ( options[:suffix] ? options[:suffix] : '' )
196
+
197
+ begin
198
+ while ( data = input.readpartial(1024) )
199
+ message = data.strip
200
+ newline = ( data[-1,1].match(/\n/) ? true : false )
201
+
202
+ unless message.empty?
203
+ lines = message.split(/\n/)
204
+ lines.each_with_index do |line, index|
205
+ prefix = default_prefix
206
+ suffix = default_suffix
207
+
208
+ unless line.empty?
209
+ if block_given?
210
+ result = code.call(line)
211
+
212
+ if result && result.is_a?(Hash)
213
+ prefix = result[:prefix]
214
+ suffix = result[:suffix]
215
+ result = result[:success]
216
+ end
217
+ success = result if success
218
+ end
219
+
220
+ prefix = ( prefix && ! prefix.empty? ? "#{prefix}: " : '' )
221
+ suffix = ( suffix && ! suffix.empty? ? suffix : '' )
222
+ eol = ( index < lines.length - 1 || newline ? "\n" : ' ' )
223
+
224
+ output.write(prefix.lstrip + line + suffix.rstrip + eol)
225
+ end
226
+ end
227
+ end
228
+ end
229
+ rescue EOFError
230
+ end
231
+
232
+ input.close()
233
+ success
234
+ end
235
+ end
236
+ protected :process_stream
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,69 @@
1
+
2
+ module Nucleon
3
+ module Action
4
+ class Add < Plugin::Action
5
+
6
+ include Mixin::Action::Project
7
+ include Mixin::Action::Push
8
+
9
+ #-----------------------------------------------------------------------------
10
+ # Add action interface
11
+
12
+ def normalize
13
+ super('nucleon add <subproject/path> <subproject:::reference>')
14
+
15
+ codes :project_failure => 20,
16
+ :add_failure => 21,
17
+ :push_failure => 22
18
+ end
19
+
20
+ #-----------------------------------------------------------------------------
21
+ # Action operations
22
+
23
+ def parse(parser)
24
+ parser.arg_str(:sub_path, nil,
25
+ 'nucleon.core.actions.add.options.sub_path'
26
+ )
27
+ parser.arg_str(:sub_reference, nil,
28
+ 'nucleon.core.actions.add.options.sub_reference'
29
+ )
30
+ parser.option_bool(:editable, false,
31
+ '--editable',
32
+ 'nucleon.core.actions.add.options.editable'
33
+ )
34
+ project_options(parser, true, true)
35
+ push_options(parser, true)
36
+ end
37
+
38
+ #---
39
+
40
+ def execute
41
+ super do |node, network, status|
42
+ info('nucleon.core.actions.add.start')
43
+
44
+ if project = project_load(Dir.pwd, false)
45
+ sub_info = project.translate_reference(settings[:sub_reference], settings[:editable])
46
+ sub_path = settings[:sub_path]
47
+
48
+ if sub_info
49
+ sub_url = sub_info[:url]
50
+ sub_revision = sub_info[:revision]
51
+ else
52
+ sub_url = settings[:sub_reference]
53
+ sub_revision = nil
54
+ end
55
+
56
+ if project.add_subproject(sub_path, sub_url, sub_revision)
57
+ status = code.push_failure unless push(project)
58
+ else
59
+ status = code.add_failure
60
+ end
61
+ else
62
+ status = code.project_failure
63
+ end
64
+ status
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,52 @@
1
+
2
+ module Nucleon
3
+ module Action
4
+ class Create < Plugin::Action
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Create action interface
8
+
9
+ def normalize
10
+ super('nucleon create [ <project:::reference> ]')
11
+
12
+ codes :project_failure => 20
13
+ end
14
+
15
+ #-----------------------------------------------------------------------------
16
+ # Action operations
17
+
18
+ def parse(parser)
19
+ parser.option_str(:path, Dir.pwd,
20
+ '--path PROJECT_DIR',
21
+ 'nucleon.core.actions.create.options.path'
22
+ )
23
+ parser.option_str(:revision, :master,
24
+ '--revision REVISION/BRANCH',
25
+ 'nucleon.core.actions.create.options.revision'
26
+ )
27
+ parser.arg_str(:reference,
28
+ 'github:::coralnexus/puppet-cloud-template',
29
+ 'nucleon.core.actions.create.options.reference'
30
+ )
31
+ end
32
+
33
+ #---
34
+
35
+ def execute
36
+ super do |node, network, status|
37
+ info('nucleon.core.actions.create.start')
38
+
39
+ project = Nucleon.project(extended_config(:project, {
40
+ :create => true,
41
+ :directory => settings[:path],
42
+ :url => settings[:reference],
43
+ :revision => settings[:revision],
44
+ :pull => true
45
+ }))
46
+
47
+ project ? status : code.project_failure
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module Nucleon
3
+ module Action
4
+ class Extract < Plugin::Action
5
+
6
+ #-----------------------------------------------------------------------------
7
+ # Action settings
8
+
9
+ def configure
10
+ super do
11
+ codes :extract_failure
12
+
13
+ register :path, :str, nil do |value|
14
+ unless File.directory?(value)
15
+ warn('nucleon.actions.extract.errors.path', { :value => value })
16
+ next false
17
+ end
18
+ true
19
+ end
20
+ register :encoded, :str, nil do |value|
21
+ @package = Util::Package.new(value)
22
+ if @package.data.export.empty?
23
+ warn('nucleon.actions.extract.errors.encoded', { :value => value })
24
+ next false
25
+ end
26
+ true
27
+ end
28
+ end
29
+ end
30
+
31
+ #---
32
+
33
+ def arguments
34
+ [ :path, :encoded ]
35
+ end
36
+
37
+ #-----------------------------------------------------------------------------
38
+ # Action operations
39
+
40
+ def execute
41
+ super do |node, network|
42
+ unless @package.extract(settings[:path])
43
+ myself.status = code.extract_failure
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module Nucleon
3
+ module Action
4
+ class Remove < Plugin::Action
5
+
6
+ include Mixin::Action::Project
7
+ include Mixin::Action::Push
8
+
9
+ #-----------------------------------------------------------------------------
10
+ # Remove action interface
11
+
12
+ def normalize
13
+ super('nucleon remove <subproject/path>')
14
+
15
+ codes :project_failure => 20,
16
+ :delete_failure => 21,
17
+ :push_failure => 22
18
+ end
19
+
20
+ #-----------------------------------------------------------------------------
21
+ # Action operations
22
+
23
+ def parse(parser)
24
+ parser.arg_str(:sub_path, nil,
25
+ 'nucleon.core.actions.remove.options.sub_path'
26
+ )
27
+ project_options(parser, true, true)
28
+ push_options(parser, true)
29
+ end
30
+
31
+ #---
32
+
33
+ def execute
34
+ super do |node, network, status|
35
+ info('nucleon.core.actions.remove.start')
36
+
37
+ if project = project_load(Dir.pwd, false)
38
+ if project.delete_subproject(settings[:sub_path])
39
+ status = code.push_failure unless push(project)
40
+ else
41
+ status = code.delete_failure
42
+ end
43
+ else
44
+ status = code.project_failure
45
+ end
46
+ status
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,53 @@
1
+
2
+ module Nucleon
3
+ module Action
4
+ class Save < Plugin::Action
5
+
6
+ include Mixin::Action::Project
7
+ include Mixin::Action::Commit
8
+ include Mixin::Action::Push
9
+
10
+ #-----------------------------------------------------------------------------
11
+ # Save action interface
12
+
13
+ def normalize
14
+ super('nucleon save [ <file> ... ]')
15
+
16
+ codes :project_failure => 20,
17
+ :commit_failure => 21,
18
+ :push_failure => 22
19
+ end
20
+
21
+ #-----------------------------------------------------------------------------
22
+ # Action operations
23
+
24
+ def parse(parser)
25
+ parser.arg_array(:files, '.',
26
+ 'nucleon.core.actions.save.options.files'
27
+ )
28
+ project_options(parser, true, false)
29
+ commit_options(parser, false)
30
+ push_options(parser, true)
31
+ end
32
+
33
+ #---
34
+
35
+ def execute
36
+ super do |node, network, status|
37
+ info('nucleon.core.actions.save.start')
38
+
39
+ if project = project_load(Dir.pwd, false)
40
+ if commit(project, settings[:files])
41
+ status = code.push_failure unless push(project)
42
+ else
43
+ status = code.commit_failure
44
+ end
45
+ else
46
+ status = code.project_failure
47
+ end
48
+ status
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+
2
+ module Nucleon
3
+ module Action
4
+ class Update < Plugin::Action
5
+
6
+ include Mixin::Action::Project
7
+
8
+ #-----------------------------------------------------------------------------
9
+ # Update action interface
10
+
11
+ def normalize
12
+ super('nucleon update')
13
+
14
+ codes :project_failure => 20
15
+ end
16
+
17
+ #-----------------------------------------------------------------------------
18
+ # Action operations
19
+
20
+ def parse(parser)
21
+ project_options(parser, true, true)
22
+ end
23
+
24
+ #---
25
+
26
+ def execute
27
+ super do |node, network, status|
28
+ info('nucleon.core.actions.update.start')
29
+
30
+ project = project_load(Dir.pwd, true)
31
+ status = code.project_failure unless project
32
+ status
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end