kameleon-builder 2.0.0.dev
Sign up to get free protection for your applications and to get access to all the features.
- data/.editorconfig +23 -0
- data/.env +51 -0
- data/.gitignore +22 -0
- data/AUTHORS +19 -0
- data/CHANGELOG +36 -0
- data/COPYING +340 -0
- data/Gemfile +4 -0
- data/README.md +53 -0
- data/Rakefile +24 -0
- data/Vagrantfile +68 -0
- data/bin/kameleon +16 -0
- data/contrib/kameleon_bashrc.sh +138 -0
- data/contrib/scripts/VirtualBox_deploy.sh +12 -0
- data/contrib/scripts/chroot_env +9 -0
- data/contrib/scripts/create_passwd.py +17 -0
- data/contrib/scripts/umount-chroot.sh +290 -0
- data/contrib/steps/bootstrap/debian/bootstrap_if_needed.yaml +47 -0
- data/contrib/steps/bootstrap/debian/bootstrap_static.yaml +38 -0
- data/contrib/steps/setup/add_timestamp.yaml +6 -0
- data/contrib/steps/setup/autologin.yaml +16 -0
- data/contrib/steps/setup/copy_ssh_auth_file.yaml +10 -0
- data/contrib/steps/setup/debian/add_network_interface.yaml +7 -0
- data/contrib/steps/setup/debian/cluster_tools_install.yaml +16 -0
- data/contrib/steps/setup/debian/network_config_static.yaml +17 -0
- data/contrib/steps/setup/generate_user_ssh_key.yaml +15 -0
- data/contrib/steps/setup/install_my_ssh_key.yaml +26 -0
- data/contrib/steps/setup/make_swap_file.yaml +9 -0
- data/contrib/steps/setup/root_ssh_config.yaml +18 -0
- data/contrib/steps/setup/set_user_password.yaml +7 -0
- data/contrib/steps/setup/system_optimization.yaml +8 -0
- data/docs/.gitignore +1 -0
- data/docs/Makefile +177 -0
- data/docs/make.bat +242 -0
- data/docs/source/_static/.gitignore +0 -0
- data/docs/source/aliases.rst +29 -0
- data/docs/source/checkpoint.rst +28 -0
- data/docs/source/cli.rst +3 -0
- data/docs/source/commands.rst +62 -0
- data/docs/source/conf.py +254 -0
- data/docs/source/context.rst +42 -0
- data/docs/source/faq.rst +3 -0
- data/docs/source/getting_started.rst +3 -0
- data/docs/source/index.rst +38 -0
- data/docs/source/installation.rst +3 -0
- data/docs/source/recipe.rst +256 -0
- data/docs/source/why.rst +3 -0
- data/docs/source/workspace.rst +11 -0
- data/kameleon-builder.gemspec +37 -0
- data/lib/kameleon.rb +75 -0
- data/lib/kameleon/cli.rb +176 -0
- data/lib/kameleon/context.rb +83 -0
- data/lib/kameleon/engine.rb +357 -0
- data/lib/kameleon/environment.rb +38 -0
- data/lib/kameleon/error.rb +51 -0
- data/lib/kameleon/logger.rb +53 -0
- data/lib/kameleon/recipe.rb +474 -0
- data/lib/kameleon/shell.rb +290 -0
- data/lib/kameleon/step.rb +213 -0
- data/lib/kameleon/utils.rb +45 -0
- data/lib/kameleon/version.rb +3 -0
- data/templates/COPYRIGHT +21 -0
- data/templates/aliases/defaults.yaml +83 -0
- data/templates/checkpoints/docker.yaml +14 -0
- data/templates/checkpoints/qcow2.yaml +44 -0
- data/templates/debian-wheezy-chroot.yaml +98 -0
- data/templates/debian-wheezy-docker.yaml +97 -0
- data/templates/fedora-docker.yaml +96 -0
- data/templates/steps/bootstrap/debian/debootstrap.yaml +13 -0
- data/templates/steps/bootstrap/fedora/docker_bootstrap.yaml +25 -0
- data/templates/steps/bootstrap/fedora/yum_bootstrap.yaml +22 -0
- data/templates/steps/bootstrap/prepare_appliance_with_nbd.yaml +93 -0
- data/templates/steps/bootstrap/prepare_docker.yaml +38 -0
- data/templates/steps/bootstrap/start_chroot.yaml +53 -0
- data/templates/steps/bootstrap/start_docker.yaml +12 -0
- data/templates/steps/export/build_appliance_from_docker.yaml +105 -0
- data/templates/steps/export/clean_appliance.yaml +3 -0
- data/templates/steps/export/save_appliance_from_nbd.yaml +54 -0
- data/templates/steps/setup/create_user.yaml +12 -0
- data/templates/steps/setup/debian/kernel_install.yaml +20 -0
- data/templates/steps/setup/debian/keyboard_config.yaml +10 -0
- data/templates/steps/setup/debian/network_config.yaml +30 -0
- data/templates/steps/setup/debian/software_install.yaml +15 -0
- data/templates/steps/setup/debian/system_config.yaml +12 -0
- data/templates/steps/setup/fedora/kernel_install.yaml +27 -0
- data/templates/steps/setup/fedora/software_install.yaml +10 -0
- data/tests/helper.rb +22 -0
- data/tests/recipes/dummy_recipe.yaml +48 -0
- data/tests/recipes/steps/bootstrap/dummy_distro/dummy_bootstrap_static.yaml +4 -0
- data/tests/recipes/steps/export/dummy_save_appliance.yaml +9 -0
- data/tests/recipes/steps/setup/default/dummy_root_passwd.yaml +8 -0
- data/tests/recipes/steps/setup/dummy_distro/dummy_software_install.yaml +7 -0
- data/tests/test_context.rb +16 -0
- data/tests/test_recipe.rb +15 -0
- data/tests/test_version.rb +9 -0
- metadata +300 -0
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'kameleon/utils'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
|
5
|
+
module Kameleon
|
6
|
+
class Shell
|
7
|
+
ECHO_CMD = "echo"
|
8
|
+
READ_CHUNK_SIZE = 1048576
|
9
|
+
EXIT_TIMEOUT = 60
|
10
|
+
|
11
|
+
attr :exit_status, :process
|
12
|
+
|
13
|
+
def initialize(context_name, cmd, shell_workdir, local_workdir, kwargs = {})
|
14
|
+
@logger = Log4r::Logger.new("kameleon::[shell]")
|
15
|
+
@cmd = cmd.chomp
|
16
|
+
@context_name = context_name
|
17
|
+
@local_workdir = local_workdir
|
18
|
+
@shell_workdir = shell_workdir
|
19
|
+
@bashrc_file = "/tmp/kameleon_#{@context_name}_bash_rc"
|
20
|
+
@bash_history_file = "/tmp/kameleon_#{@context_name}_bash_history"
|
21
|
+
@bash_env_file = "/tmp/kameleon_#{@context_name}_bash_env"
|
22
|
+
change_dir_cmd = ""
|
23
|
+
if @shell_workdir
|
24
|
+
unless @shell_workdir.eql? "/"
|
25
|
+
change_dir_cmd = "mkdir -p #{@shell_workdir} &&"
|
26
|
+
end
|
27
|
+
change_dir_cmd = "#{change_dir_cmd} cd #{@shell_workdir} && "
|
28
|
+
end
|
29
|
+
@default_bashrc_file = File.join(Kameleon.source_root,
|
30
|
+
"contrib",
|
31
|
+
"kameleon_bashrc.sh")
|
32
|
+
bash_cmd = "bash --rcfile #{@bashrc_file}"
|
33
|
+
@shell_cmd = "source #{@default_bashrc_file} 2> /dev/null; "\
|
34
|
+
"#{@cmd} -c '#{change_dir_cmd}#{bash_cmd}'"
|
35
|
+
@logger.debug("Initialize shell (#{self})")
|
36
|
+
# Injecting all variables of the options and assign the variables
|
37
|
+
instance_variables.each do |v|
|
38
|
+
@logger.debug(" #{v} = #{instance_variable_get(v)}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def start
|
43
|
+
@sent_first_cmd = false
|
44
|
+
@process, @stdout, @stderr = fork("pipe")
|
45
|
+
end
|
46
|
+
|
47
|
+
def stop
|
48
|
+
@process.stop
|
49
|
+
end
|
50
|
+
|
51
|
+
def exited?
|
52
|
+
@process.exited?
|
53
|
+
end
|
54
|
+
|
55
|
+
def restart
|
56
|
+
stop
|
57
|
+
start
|
58
|
+
end
|
59
|
+
|
60
|
+
def send_file(source_path, remote_dest_path, chunk_size=READ_CHUNK_SIZE)
|
61
|
+
copy_process, = fork("pipe")
|
62
|
+
copy_process.io.stdin << "cat > #{remote_dest_path}\n"
|
63
|
+
copy_process.io.stdin.flush
|
64
|
+
open(source_path, "rb") do |f|
|
65
|
+
begin
|
66
|
+
copy_process.io.stdin << f.read(chunk_size)
|
67
|
+
end until f.eof?
|
68
|
+
end
|
69
|
+
copy_process.io.stdin.flush
|
70
|
+
copy_process.io.stdin.close
|
71
|
+
copy_process.wait
|
72
|
+
copy_process.poll_for_exit(EXIT_TIMEOUT)
|
73
|
+
end
|
74
|
+
|
75
|
+
def init_shell_cmd
|
76
|
+
bashrc_content = ""
|
77
|
+
if File.file?(@default_bashrc_file)
|
78
|
+
tpl = ERB.new(File.read(@default_bashrc_file))
|
79
|
+
bashrc_content = tpl.result(binding)
|
80
|
+
end
|
81
|
+
bashrc = Shellwords.escape(bashrc_content)
|
82
|
+
shell_cmd = "mkdir -p $(dirname #{@bashrc_file})\n"
|
83
|
+
shell_cmd << "echo #{bashrc} > #{@bashrc_file}\n"
|
84
|
+
shell_cmd << "source #{@bashrc_file}\n"
|
85
|
+
shell_cmd
|
86
|
+
end
|
87
|
+
|
88
|
+
def send_command cmd
|
89
|
+
shell_cmd = "#{ ECHO_CMD } -n #{ cmd.begin_err } 1>&2\n"
|
90
|
+
shell_cmd << "#{ ECHO_CMD } -n #{ cmd.begin_out }\n"
|
91
|
+
unless @sent_first_cmd
|
92
|
+
shell_cmd << init_shell_cmd
|
93
|
+
@sent_first_cmd = true
|
94
|
+
end
|
95
|
+
shell_cmd << "KAMELEON_LAST_COMMAND=#{Shellwords.escape(cmd.value)}\n"
|
96
|
+
shell_cmd << "( set -o posix ; set ) > #{@bash_env_file}\n"
|
97
|
+
shell_cmd << "env | xargs -I {} echo export {} >> #{@bash_env_file}\n"
|
98
|
+
shell_cmd << "#{ cmd.value }\nexport __exit_status__=$?\n"
|
99
|
+
shell_cmd << "#{ ECHO_CMD } $KAMELEON_LAST_COMMAND >> \"$HISTFILE\"\n"
|
100
|
+
shell_cmd << "#{ ECHO_CMD } -n #{ cmd.end_err } 1>&2\n"
|
101
|
+
shell_cmd << "#{ ECHO_CMD } -n #{ cmd.end_out }\n"
|
102
|
+
@process.io.stdin.puts shell_cmd
|
103
|
+
@process.io.stdin.flush
|
104
|
+
end
|
105
|
+
|
106
|
+
def execute(cmd, kwargs = {})
|
107
|
+
cmd_obj = Command.new(cmd)
|
108
|
+
send_command cmd_obj = Command.new(cmd)
|
109
|
+
iodata = {:stderr => { :io => @stderr,
|
110
|
+
:name => 'stderr',
|
111
|
+
:begin => false,
|
112
|
+
:end => false,
|
113
|
+
:begin_pat => cmd_obj.begin_err_pat,
|
114
|
+
:end_pat => cmd_obj.end_err_pat,
|
115
|
+
:redirect => kwargs[:stderr],
|
116
|
+
:yield => lambda{|buf| yield(nil, buf)} },
|
117
|
+
:stdout => { :io => @stdout,
|
118
|
+
:name => 'stdout',
|
119
|
+
:begin => false,
|
120
|
+
:end => false,
|
121
|
+
:begin_pat => cmd_obj.begin_out_pat,
|
122
|
+
:end_pat => cmd_obj.end_out_pat,
|
123
|
+
:redirect => kwargs[:stdout],
|
124
|
+
:yield => lambda{|buf| yield(buf, nil)} }
|
125
|
+
}
|
126
|
+
while true
|
127
|
+
iodata.each do |_, iodat|
|
128
|
+
if iodat[:end] and not iodat[:begin]
|
129
|
+
raise ShellError, "Cannot read #{iodat[:begin]} from shell"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
if iodata.all? { |k, iodat| iodat[:end] and iodat[:begin]}
|
133
|
+
break
|
134
|
+
end
|
135
|
+
readers = (iodata.map { |_, v| v[:io] unless v[:end] })
|
136
|
+
ready = IO.select(readers.compact, nil, nil, 0.1)
|
137
|
+
ready ||= [[]]
|
138
|
+
readers = ready[0]
|
139
|
+
# Check the readers to see if they're ready
|
140
|
+
if readers && !readers.empty?
|
141
|
+
readers.each do |r|
|
142
|
+
# Read from the IO object
|
143
|
+
iodat = r == @stdout ? iodata[:stdout] : iodata[:stderr]
|
144
|
+
data = read_io(r)
|
145
|
+
# We don't need to do anything if the data is empty
|
146
|
+
next if data.empty?
|
147
|
+
if !iodat[:begin] && (m = iodat[:begin_pat].match(data))
|
148
|
+
iodat[:begin] = true
|
149
|
+
data = m[1]
|
150
|
+
end
|
151
|
+
next unless iodat[:begin] and not iodat[:end] # ignore chaff
|
152
|
+
if !iodat[:end] && (m = iodat[:end_pat].match(data))
|
153
|
+
iodat[:end] = true
|
154
|
+
data = m[1]
|
155
|
+
end
|
156
|
+
next if data.empty?
|
157
|
+
if iodat[:redirect]
|
158
|
+
iodat[:redirect] << data
|
159
|
+
else
|
160
|
+
iodat[:yield].call data if block_given?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
iodata = nil
|
166
|
+
return get_status
|
167
|
+
end
|
168
|
+
|
169
|
+
def fork_and_wait
|
170
|
+
process, = fork("inherit")
|
171
|
+
process.wait
|
172
|
+
end
|
173
|
+
|
174
|
+
protected
|
175
|
+
|
176
|
+
def get_status
|
177
|
+
var_name = "__exit_status__"
|
178
|
+
@process.io.stdin << "#{ ECHO_CMD } \"#{ var_name }=${#{ var_name }}\"\n"
|
179
|
+
@process.io.stdin.flush
|
180
|
+
while((line = @stdout.gets))
|
181
|
+
if (m = %r/#{ var_name }\s*=\s*(.*)/.match line)
|
182
|
+
exit_status = m[1]
|
183
|
+
unless exit_status =~ /^\s*\d+\s*$/o
|
184
|
+
raise ShellError, "could not determine exit status from <#{ exit_status.inspect }>"
|
185
|
+
end
|
186
|
+
@exit_status = Integer exit_status
|
187
|
+
return @exit_status
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def read_io(io)
|
193
|
+
data = ""
|
194
|
+
while true
|
195
|
+
begin
|
196
|
+
# Do a simple non-blocking read on the IO object
|
197
|
+
data << io.read_nonblock(READ_CHUNK_SIZE)
|
198
|
+
rescue Exception => e
|
199
|
+
breakable = false
|
200
|
+
if e.is_a?(EOFError)
|
201
|
+
# An `EOFError` means this IO object is done!
|
202
|
+
breakable = true
|
203
|
+
elsif defined?(IO::WaitReadable) && e.is_a?(IO::WaitReadable)
|
204
|
+
breakable = true
|
205
|
+
elsif e.is_a?(Errno::EAGAIN)
|
206
|
+
breakable = true
|
207
|
+
end
|
208
|
+
break if breakable
|
209
|
+
raise
|
210
|
+
end
|
211
|
+
end
|
212
|
+
data
|
213
|
+
end
|
214
|
+
|
215
|
+
def fork(io)
|
216
|
+
command = ["bash", "-c", @shell_cmd]
|
217
|
+
@logger.notice("Starting process: #{@cmd.inspect}")
|
218
|
+
ChildProcess.posix_spawn = true
|
219
|
+
process = ChildProcess.build(*command)
|
220
|
+
# Create the pipes so we can read the output in real time as
|
221
|
+
# we execute the command.
|
222
|
+
if io.eql? "pipe"
|
223
|
+
stdout, stdout_writer = IO.pipe
|
224
|
+
stderr, stderr_writer = IO.pipe
|
225
|
+
process.io.stdout = stdout_writer
|
226
|
+
process.io.stderr = stderr_writer
|
227
|
+
# sets up pipe so process.io.stdin will be available after .start
|
228
|
+
process.duplex = true
|
229
|
+
elsif io.eql? "inherit"
|
230
|
+
process.io.inherit!
|
231
|
+
end
|
232
|
+
|
233
|
+
# Start the process
|
234
|
+
begin
|
235
|
+
process.cwd = @local_workdir
|
236
|
+
process.start
|
237
|
+
# Wait to child starting
|
238
|
+
sleep(0.2)
|
239
|
+
rescue ChildProcess::LaunchError => e
|
240
|
+
# Raise our own version of the error
|
241
|
+
raise ShellError, "Cannot launch #{command.inspect}: #{e.message}"
|
242
|
+
end
|
243
|
+
if io.eql? "pipe"
|
244
|
+
# Make sure the stdin does not buffer
|
245
|
+
process.io.stdin.sync = true
|
246
|
+
stdout_writer.close()
|
247
|
+
stderr_writer.close()
|
248
|
+
return process, stdout, stderr
|
249
|
+
else
|
250
|
+
return process, $stdout, $stderr
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class Command
|
255
|
+
class << self
|
256
|
+
def counter; @counter ||= 0; end
|
257
|
+
def counter= n; @counter = n; end
|
258
|
+
end
|
259
|
+
attr :value
|
260
|
+
attr :number
|
261
|
+
attr :id
|
262
|
+
attr :slug
|
263
|
+
attr :begin_out
|
264
|
+
attr :begin_out_pat
|
265
|
+
attr :end_out
|
266
|
+
attr :end_out_pat
|
267
|
+
attr :begin_err
|
268
|
+
attr :begin_err_pat
|
269
|
+
attr :end_err
|
270
|
+
attr :end_err_pat
|
271
|
+
|
272
|
+
def initialize(raw)
|
273
|
+
@value = raw.to_s.strip
|
274
|
+
@number = self.class.counter
|
275
|
+
@slug = Kameleon::Utils.generate_slug(@value)[0...30]
|
276
|
+
@id = "%d_%d_%d" % [$$, @number, rand(Time.now.usec)]
|
277
|
+
@begin_out = "__CMD_OUT_%s_BEGIN__" % @id
|
278
|
+
@end_out = "__CMD_OUT_%s_END__" % @id
|
279
|
+
@begin_out_pat = %r/#{ Regexp.escape(@begin_out) }(.*)/m
|
280
|
+
@end_out_pat = %r/(.*)#{ Regexp.escape(@end_out) }/m
|
281
|
+
@begin_err = "__CMD_ERR_%s_BEGIN__" % @id
|
282
|
+
@end_err = "__CMD_ERR_%s_END__" % @id
|
283
|
+
@begin_err_pat = %r/#{ Regexp.escape(@begin_err) }(.*)/m
|
284
|
+
@end_err_pat = %r/(.*)#{ Regexp.escape(@end_err) }/m
|
285
|
+
self.class.counter += 1
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
module Kameleon
|
2
|
+
|
3
|
+
class Command
|
4
|
+
attr_accessor :string_cmd, :microstep_name
|
5
|
+
|
6
|
+
def initialize(yaml_cmd, microstep_name)
|
7
|
+
@string_cmd = YAML.dump(yaml_cmd).gsub("---", "").strip
|
8
|
+
@microstep_name = microstep_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def resolve!
|
12
|
+
key
|
13
|
+
value
|
14
|
+
end
|
15
|
+
|
16
|
+
def key
|
17
|
+
if @key.nil?
|
18
|
+
@key = YAML.load(@string_cmd).keys.first
|
19
|
+
end
|
20
|
+
@key
|
21
|
+
rescue
|
22
|
+
lines = @string_cmd.split( /\r?\n/ ).map {|l| "> #{l}" }
|
23
|
+
fail RecipeError, "Syntax error for microstep #{@microstep_name} : \n"\
|
24
|
+
"#{ lines.join "\n"}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def value
|
28
|
+
if @value.nil?
|
29
|
+
object = YAML.load(@string_cmd)
|
30
|
+
if object.kind_of? Command
|
31
|
+
@value = object
|
32
|
+
else
|
33
|
+
raise RecipeError unless object.kind_of? Hash
|
34
|
+
raise RecipeError unless object.keys.count == 1
|
35
|
+
_, val = object.first
|
36
|
+
unless val.kind_of?(Array)
|
37
|
+
val = val.to_s
|
38
|
+
end
|
39
|
+
# Nested commands
|
40
|
+
if val.kind_of? Array
|
41
|
+
val = val.map { |item| Command.new(item, @microstep_name) }
|
42
|
+
end
|
43
|
+
@value = val
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@value
|
47
|
+
rescue
|
48
|
+
lines = YAML.dump(object).gsub("---", "").strip
|
49
|
+
lines = lines.split( /\r?\n/ ).map {|l| "> #{l}" }
|
50
|
+
fail RecipeError, "Syntax error for microstep #{@microstep_name} : \n"\
|
51
|
+
"#{ lines.join "\n"}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_array
|
55
|
+
if value.kind_of? Array
|
56
|
+
map = value.map { |val| val.to_array }
|
57
|
+
return { key => map }
|
58
|
+
else
|
59
|
+
return { key => value }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def gsub!(arg1, arg2)
|
64
|
+
if value.kind_of? Array
|
65
|
+
value.each { |cmd| cmd.gsub!(arg1, arg2) }
|
66
|
+
else
|
67
|
+
@value.gsub!(arg1, arg2)
|
68
|
+
end
|
69
|
+
@string_cmd = YAML.dump(to_array).gsub("---", "").strip
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
class Microstep
|
75
|
+
attr_accessor :commands, :name, :identifier, :slug, :in_cache,
|
76
|
+
:on_checkpoint, :order
|
77
|
+
|
78
|
+
def initialize(string_or_hash)
|
79
|
+
@identifier = nil
|
80
|
+
@in_cache = false
|
81
|
+
@on_checkpoint = "use_cache"
|
82
|
+
@commands = []
|
83
|
+
@name, cmd_list = string_or_hash.first
|
84
|
+
cmd_list.each do |cmd_hash|
|
85
|
+
if cmd_hash.kind_of? Command
|
86
|
+
@commands.push cmd_hash
|
87
|
+
else
|
88
|
+
if cmd_hash.kind_of?(Hash) && cmd_hash.keys.first == "on_checkpoint"
|
89
|
+
@on_checkpoint = cmd_hash["on_checkpoint"]
|
90
|
+
else
|
91
|
+
@commands.push Command.new(cmd_hash, @name)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rescue
|
96
|
+
fail RecipeError, "Syntax error for microstep #{name}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def resolve!
|
100
|
+
@commands.each {|cmd| cmd.resolve! }
|
101
|
+
end
|
102
|
+
|
103
|
+
def gsub!(arg1, arg2)
|
104
|
+
@commands.each {|cmd| cmd.gsub!(arg1, arg2) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def unshift(cmd_list)
|
108
|
+
cmd_list.reverse.each {|cmd| @commands.unshift cmd}
|
109
|
+
end
|
110
|
+
|
111
|
+
def push(cmd)
|
112
|
+
@commands.push cmd
|
113
|
+
end
|
114
|
+
|
115
|
+
def calculate_identifier(salt)
|
116
|
+
commands_str = @commands.map { |cmd| cmd.string_cmd.to_s }
|
117
|
+
content_id = commands_str.join(' ') + salt
|
118
|
+
@identifier = "#{ Digest::SHA1.hexdigest content_id }"[0..11]
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_array
|
122
|
+
microstep_array = @commands.map do |cmd|
|
123
|
+
cmd.to_array
|
124
|
+
end
|
125
|
+
return microstep_array
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
class Macrostep
|
131
|
+
attr_accessor :name, :clean_microsteps, :init_microsteps, :microsteps,
|
132
|
+
:path, :variables
|
133
|
+
|
134
|
+
def initialize(name, microsteps, variables, path)
|
135
|
+
@name = name
|
136
|
+
@variables = variables
|
137
|
+
@path = path
|
138
|
+
@microsteps = microsteps
|
139
|
+
@clean_microsteps = []
|
140
|
+
@init_microsteps = []
|
141
|
+
end
|
142
|
+
|
143
|
+
def resolve_variables!(global)
|
144
|
+
# Resolve dynamically-defined variables !!
|
145
|
+
tmp_resolved_vars = {}
|
146
|
+
@variables.clone.each do |key, value|
|
147
|
+
yaml_vars = { key => value }.to_yaml.chomp
|
148
|
+
yaml_resolved = Utils.resolve_vars(yaml_vars,
|
149
|
+
@path,
|
150
|
+
tmp_resolved_vars.merge(global))
|
151
|
+
tmp_resolved_vars.merge! YAML.load(yaml_resolved.chomp)
|
152
|
+
end
|
153
|
+
@variables.merge! tmp_resolved_vars
|
154
|
+
@microsteps.each do |m|
|
155
|
+
m.commands.each do |cmd|
|
156
|
+
cmd.string_cmd = Utils.resolve_vars(cmd.string_cmd,
|
157
|
+
@path,
|
158
|
+
global.merge(@variables))
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def sequence
|
164
|
+
@init_microsteps.each { |m| yield m }
|
165
|
+
@microsteps.each { |m| yield m }
|
166
|
+
@clean_microsteps.each { |m| yield m }
|
167
|
+
end
|
168
|
+
|
169
|
+
def to_array
|
170
|
+
macrostep_array = []
|
171
|
+
@variables.each do |k, v|
|
172
|
+
macrostep_array.push({ k => v })
|
173
|
+
end
|
174
|
+
sequence do |microstep|
|
175
|
+
macrostep_array.push({ microstep.name => microstep.to_array })
|
176
|
+
end
|
177
|
+
return macrostep_array
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
class Section
|
183
|
+
attr_accessor :name, :clean_macrostep, :init_macrostep, :macrosteps
|
184
|
+
|
185
|
+
def initialize(name)
|
186
|
+
@name = name
|
187
|
+
@clean_macrostep = Macrostep.new("_clean_#{name}", [], {}, nil)
|
188
|
+
@init_macrostep = Macrostep.new("_init_#{name}", [], {}, nil)
|
189
|
+
@macrosteps = []
|
190
|
+
end
|
191
|
+
|
192
|
+
def sequence
|
193
|
+
yield @init_macrostep
|
194
|
+
@macrosteps.each { |m| yield m }
|
195
|
+
yield @clean_macrostep
|
196
|
+
end
|
197
|
+
|
198
|
+
def to_array
|
199
|
+
section_array = []
|
200
|
+
sequence do |macrostep|
|
201
|
+
macrostep.sequence do |microstep|
|
202
|
+
hash = {
|
203
|
+
"identifier" => microstep.identifier.to_s,
|
204
|
+
"cmds" => microstep.to_array
|
205
|
+
}
|
206
|
+
section_array.push({ microstep.slug => hash })
|
207
|
+
end
|
208
|
+
end
|
209
|
+
return section_array
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|