kameleon-builder 2.0.0.dev
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.
- 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
|