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
data/lib/kameleon/cli.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'kameleon/engine'
|
2
|
+
require 'kameleon/recipe'
|
3
|
+
require 'kameleon/utils'
|
4
|
+
|
5
|
+
module Kameleon
|
6
|
+
class CLI < Thor
|
7
|
+
|
8
|
+
class_option :no_color, :type => :boolean, :default => false,
|
9
|
+
:desc => "Disable colorization in output"
|
10
|
+
class_option :debug, :type => :boolean, :default => false,
|
11
|
+
:desc => "Enable debug output"
|
12
|
+
class_option :workspace, :aliases => '-w', :type => :string,
|
13
|
+
:desc => 'Change the kameleon current work directory. ' \
|
14
|
+
'(The folder containing your recipes folder).' \
|
15
|
+
' Default : ./'
|
16
|
+
no_commands do
|
17
|
+
def logger
|
18
|
+
@logger ||= Log4r::Logger.new("kameleon::[cli]")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
method_option :template, :aliases => "-t",
|
23
|
+
:desc => "Starting from a template", :required => true,
|
24
|
+
:enum => Kameleon.templates_names
|
25
|
+
method_option :force,:type => :boolean,
|
26
|
+
:default => false, :aliases => "-f",
|
27
|
+
:desc => "overwrite the recipe"
|
28
|
+
desc "new [RECIPE_NAME]", "Creates a new recipe"
|
29
|
+
def new(recipe_name)
|
30
|
+
logger.notice("Cloning template '#{options[:template]}'")
|
31
|
+
templates_path = Kameleon.env.templates_path
|
32
|
+
recipes_path = Kameleon.env.recipes_path
|
33
|
+
|
34
|
+
template_path = File.join(templates_path, options[:template]) + '.yaml'
|
35
|
+
template_recipe = RecipeTemplate.new(template_path)
|
36
|
+
template_recipe.copy_template(recipes_path,
|
37
|
+
recipe_name,
|
38
|
+
options[:force])
|
39
|
+
recipe_path = File.join(recipes_path, recipe_name + ".yaml")
|
40
|
+
logger.notice("New recipe \"#{recipe_name}\" "\
|
41
|
+
"as been created in #{recipe_path}")
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "templates", "Lists all defined templates"
|
45
|
+
def templates
|
46
|
+
Log4r::Outputter['console'].level = Log4r::ERROR unless Kameleon.env.debug
|
47
|
+
puts "The following templates are available in " \
|
48
|
+
"#{ Kameleon.templates_path }:"
|
49
|
+
templates_hash = []
|
50
|
+
Kameleon.templates_files.each do |f|
|
51
|
+
begin
|
52
|
+
recipe = RecipeTemplate.new(f)
|
53
|
+
templates_hash.push({
|
54
|
+
"name" => recipe.name,
|
55
|
+
"description" => recipe.metainfo['description'],
|
56
|
+
})
|
57
|
+
rescue => e
|
58
|
+
raise e if Kameleon.env.debug
|
59
|
+
end
|
60
|
+
end
|
61
|
+
tp templates_hash, {"name" => {:width => 30}}, { "description" => {:width => 60}}
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "version", "Prints the Kameleon's version information"
|
65
|
+
def version
|
66
|
+
Log4r::Outputter['console'].level = Log4r::OFF unless Kameleon.env.debug
|
67
|
+
puts "Kameleon version #{Kameleon::VERSION}"
|
68
|
+
end
|
69
|
+
map %w(-v --version) => :version
|
70
|
+
|
71
|
+
|
72
|
+
desc "build [RECIPE_NAME]", "Builds the appliance from the recipe"
|
73
|
+
method_option :build_path, :type => :string ,
|
74
|
+
:default => nil, :aliases => "-b",
|
75
|
+
:desc => "Set the build directory path"
|
76
|
+
method_option :from_checkpoint, :type => :string ,
|
77
|
+
:default => nil,
|
78
|
+
:desc => "Using specific checkpoint to build the image. " \
|
79
|
+
"Default value is the last checkpoint."
|
80
|
+
method_option :no_checkpoint, :type => :boolean ,
|
81
|
+
:default => false,
|
82
|
+
:desc => "Do not use checkpoints"
|
83
|
+
def build(recipe_name)
|
84
|
+
logger.notice("Starting build recipe '#{recipe_name}'")
|
85
|
+
start_time = Time.now.to_i
|
86
|
+
recipe_path = File.join(Kameleon.env.recipes_path, recipe_name) + '.yaml'
|
87
|
+
engine = Kameleon::Engine.new(Recipe.new(recipe_path), options)
|
88
|
+
engine.build
|
89
|
+
total_time = Time.now.to_i - start_time
|
90
|
+
logger.notice("")
|
91
|
+
logger.notice("Build recipe '#{recipe_name}' is completed !")
|
92
|
+
logger.notice("Build total duration : #{total_time} secs")
|
93
|
+
logger.notice("Build directory : #{engine.cwd}")
|
94
|
+
logger.notice("Build recipe file : #{engine.build_recipe_path}")
|
95
|
+
logger.notice("Log file : #{Kameleon.env.log_file}")
|
96
|
+
end
|
97
|
+
|
98
|
+
desc "checkpoints [RECIPE_NAME]", "Lists all availables checkpoints"
|
99
|
+
method_option :build_path, :type => :string ,
|
100
|
+
:default => nil, :aliases => "-b",
|
101
|
+
:desc => "Set the build directory path"
|
102
|
+
def checkpoints(recipe_name)
|
103
|
+
Log4r::Outputter['console'].level = Log4r::ERROR unless Kameleon.env.debug
|
104
|
+
recipe_path = File.join(Kameleon.env.recipes_path, recipe_name) + '.yaml'
|
105
|
+
engine = Kameleon::Engine.new(Recipe.new(recipe_path), options)
|
106
|
+
engine.pretty_checkpoints_list
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "clear [RECIPE_NAME]", "Cleaning out context and removing all checkpoints"
|
110
|
+
method_option :build_path, :type => :string ,
|
111
|
+
:default => nil, :aliases => "-b",
|
112
|
+
:desc => "Set the build directory path"
|
113
|
+
def clear(recipe_name)
|
114
|
+
Log4r::Outputter['console'].level = Log4r::INFO
|
115
|
+
recipe_path = File.join(Kameleon.env.recipes_path, recipe_name) + '.yaml'
|
116
|
+
engine = Kameleon::Engine.new(Recipe.new(recipe_path), options)
|
117
|
+
engine.clear
|
118
|
+
end
|
119
|
+
|
120
|
+
# Hack Thor to init Kameleon env soon
|
121
|
+
def self.init(base_config)
|
122
|
+
options = base_config[:shell].base.options
|
123
|
+
workspace ||= options[:workspace] || ENV['KAMELEON_WORKSPACE'] || Dir.pwd
|
124
|
+
env_options = options.merge({:workspace => workspace})
|
125
|
+
FileUtils.mkdir_p workspace
|
126
|
+
# configure logger
|
127
|
+
env_options["debug"] = true if ENV["KAMELEON_LOG"] == "debug"
|
128
|
+
ENV["KAMELEON_LOG"] = "debug" if env_options["debug"]
|
129
|
+
if ENV["KAMELEON_LOG"] && ENV["KAMELEON_LOG"] != ""
|
130
|
+
level_name = ENV["KAMELEON_LOG"]
|
131
|
+
else
|
132
|
+
level_name = "info"
|
133
|
+
end
|
134
|
+
# Require Log4r and define the levels we'll be using
|
135
|
+
require 'log4r-color/config'
|
136
|
+
Log4r.define_levels(*Log4r::Log4rConfig::LogLevels)
|
137
|
+
|
138
|
+
begin
|
139
|
+
level = Log4r.const_get(level_name.upcase)
|
140
|
+
rescue NameError
|
141
|
+
fail KameleonError, "Invalid KAMELEON_LOG level is set: #{level_name}.\n" \
|
142
|
+
"Please use one of the standard log levels: debug," \
|
143
|
+
" info, warn, or error"
|
144
|
+
end
|
145
|
+
format = ConsoleFormatter.new
|
146
|
+
# format = Log4r::PatternFormatter.new(:pattern => '%11c: %M')
|
147
|
+
if !$stdout.tty? or env_options["no_color"]
|
148
|
+
console_output = Log4r::StdoutOutputter.new('console',
|
149
|
+
:formatter => format)
|
150
|
+
else
|
151
|
+
console_output = Log4r::ColorOutputter.new 'console', {
|
152
|
+
:colors => { :debug => :light_black,
|
153
|
+
:info => :green,
|
154
|
+
:notice => :light_blue,
|
155
|
+
:progress => :light_blue,
|
156
|
+
:warn => :yellow,
|
157
|
+
:error => :red,
|
158
|
+
:fatal => :red,
|
159
|
+
},
|
160
|
+
:formatter => format,
|
161
|
+
}
|
162
|
+
end
|
163
|
+
logger = Log4r::Logger.new('kameleon')
|
164
|
+
logger.outputters << console_output
|
165
|
+
logger.level = level
|
166
|
+
logger = nil
|
167
|
+
Kameleon.logger.debug("`kameleon` invoked: #{ARGV.inspect}")
|
168
|
+
Kameleon.env = Kameleon::Environment.new(env_options)
|
169
|
+
end
|
170
|
+
|
171
|
+
def self.start(given_args=ARGV, config={})
|
172
|
+
config[:shell] ||= Thor::Base.shell.new
|
173
|
+
dispatch(nil, given_args.dup, nil, config) { init(config) }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'kameleon/shell'
|
2
|
+
|
3
|
+
module Kameleon
|
4
|
+
class Context
|
5
|
+
|
6
|
+
attr_accessor :shell, :name
|
7
|
+
|
8
|
+
def initialize(name, cmd, workdir, exec_prefix, local_workdir)
|
9
|
+
@name = name.downcase
|
10
|
+
@logger = Log4r::Logger.new("kameleon::[#{@name}_ctx]")
|
11
|
+
@cmd = cmd
|
12
|
+
@workdir = workdir
|
13
|
+
@exec_prefix = exec_prefix
|
14
|
+
@local_workdir = local_workdir
|
15
|
+
@shell = Kameleon::Shell.new(@name, @cmd, @workdir, @local_workdir)
|
16
|
+
@logger.debug("Initialize new ctx (#{name})")
|
17
|
+
|
18
|
+
instance_variables.each do |v|
|
19
|
+
@logger.debug(" #{v} = #{instance_variable_get(v)}")
|
20
|
+
end
|
21
|
+
# Start the shell process
|
22
|
+
@shell.start
|
23
|
+
execute("echo The '#{name}_context' has been initialized", :log_level => "info")
|
24
|
+
end
|
25
|
+
|
26
|
+
def log(log_level, msg)
|
27
|
+
@logger.info msg if log_level == "info"
|
28
|
+
@logger.error msg if log_level == "error"
|
29
|
+
@logger.debug msg if log_level == "debug"
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute(cmd, kwargs = {})
|
33
|
+
cmd_with_prefix = "#{@exec_prefix} #{cmd}"
|
34
|
+
cmd_with_prefix.split( /\r?\n/ ).each {|m| @logger.debug "+ #{m}" }
|
35
|
+
log_level = kwargs.fetch(:log_level, "info")
|
36
|
+
exit_status = @shell.execute(cmd_with_prefix, kwargs) do |out, err|
|
37
|
+
out.split( /\r?\n/ ).each {|m| log(log_level, m) } unless out.nil?
|
38
|
+
err.split( /\r?\n/ ).each {|m| log("error", m) } unless err.nil?
|
39
|
+
end
|
40
|
+
@logger.debug("Exit status : #{exit_status}")
|
41
|
+
fail ExecError unless exit_status.eql? 0
|
42
|
+
rescue ShellError => e
|
43
|
+
@logger.error(e.message)
|
44
|
+
fail ExecError
|
45
|
+
end
|
46
|
+
|
47
|
+
def pipe(cmd, other_cmd, other_ctx)
|
48
|
+
tmp = Tempfile.new("pipe-#{ Kameleon::Utils.generate_slug(cmd)[0..20] }")
|
49
|
+
@logger.info("Running piped commands")
|
50
|
+
@logger.info("Saving STDOUT from #{@name}_ctx to local file #{tmp.path}")
|
51
|
+
execute(cmd, :stdout => tmp)
|
52
|
+
tmp.close
|
53
|
+
@logger.info("Forwarding #{tmp.path} to STDIN of #{other_ctx.name}_ctx")
|
54
|
+
dest_pipe_path = "./pipe-#{ Kameleon::Utils.generate_slug(other_cmd)[0..20] }"
|
55
|
+
other_ctx.send_file(tmp.path, dest_pipe_path)
|
56
|
+
other_cmd_with_pipe = "cat #{dest_pipe_path} | #{other_cmd} && rm #{dest_pipe_path}"
|
57
|
+
other_ctx.execute(other_cmd_with_pipe)
|
58
|
+
end
|
59
|
+
|
60
|
+
def start_shell
|
61
|
+
#TODO: Load env and history
|
62
|
+
@logger.info("Starting interactive shell")
|
63
|
+
@shell.fork_and_wait
|
64
|
+
end
|
65
|
+
|
66
|
+
def closed?
|
67
|
+
@shell.exited?
|
68
|
+
end
|
69
|
+
|
70
|
+
def close!
|
71
|
+
@shell.stop
|
72
|
+
end
|
73
|
+
|
74
|
+
def reopen
|
75
|
+
@shell.restart
|
76
|
+
end
|
77
|
+
|
78
|
+
def send_file(source_path, dest_path)
|
79
|
+
@shell.send_file(source_path, dest_path)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'kameleon/recipe'
|
2
|
+
require 'kameleon/context'
|
3
|
+
|
4
|
+
|
5
|
+
module Kameleon
|
6
|
+
|
7
|
+
class Engine
|
8
|
+
attr_accessor :recipe, :cwd, :build_recipe_path, :pretty_list_checkpoints
|
9
|
+
|
10
|
+
def initialize(recipe, options)
|
11
|
+
@options = options
|
12
|
+
@logger = Log4r::Logger.new("kameleon::[engine]")
|
13
|
+
@recipe = recipe
|
14
|
+
@cleaned_sections = []
|
15
|
+
@cwd = @recipe.global["kameleon_cwd"]
|
16
|
+
@build_recipe_path = File.join(@cwd, "kameleon_build_recipe.yaml")
|
17
|
+
|
18
|
+
build_recipe = load_build_recipe
|
19
|
+
# restore previous build uuid
|
20
|
+
unless build_recipe.nil?
|
21
|
+
# binding.pry
|
22
|
+
%w(kameleon_uuid kameleon_short_uuid).each do |key|
|
23
|
+
@recipe.global[key] = build_recipe["global"][key]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
@enable_checkpoint = !@options[:no_checkpoint]
|
28
|
+
# Check if the recipe have checkpoint entry
|
29
|
+
@enable_checkpoint = !@recipe.checkpoint.nil? if @enable_checkpoint
|
30
|
+
|
31
|
+
@recipe.resolve!
|
32
|
+
@in_context = nil
|
33
|
+
begin
|
34
|
+
@logger.notice("Creating kameleon working directory : #{@cwd}")
|
35
|
+
FileUtils.mkdir_p @cwd
|
36
|
+
rescue
|
37
|
+
raise BuildError, "Failed to create working directory #{@cwd}"
|
38
|
+
end
|
39
|
+
@logger.notice("Building local context [local]")
|
40
|
+
@local_context = Context.new("local", "bash", @cwd, "", @cwd)
|
41
|
+
@logger.notice("Building external context [out]")
|
42
|
+
@out_context = Context.new("out",
|
43
|
+
@recipe.global["out_context"]["cmd"],
|
44
|
+
@recipe.global["out_context"]["workdir"],
|
45
|
+
@recipe.global["out_context"]["exec_prefix"],
|
46
|
+
@cwd)
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_checkpoint(microstep_id)
|
50
|
+
cmd = @recipe.checkpoint["create"].gsub("@microstep_id", microstep_id)
|
51
|
+
create_cmd = Kameleon::Command.new({"exec_out" => cmd}, "checkpoint")
|
52
|
+
safe_exec_cmd(create_cmd, :log_level => "debug")
|
53
|
+
end
|
54
|
+
|
55
|
+
def apply_checkpoint(microstep_id)
|
56
|
+
cmd = @recipe.checkpoint["apply"].gsub("@microstep_id", microstep_id)
|
57
|
+
apply_cmd = Kameleon::Command.new({"exec_out" => cmd}, "checkpoint")
|
58
|
+
safe_exec_cmd(apply_cmd, :log_level => "debug")
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_all_checkpoints
|
62
|
+
list = ""
|
63
|
+
cmd = Kameleon::Command.new({"exec_out" => @recipe.checkpoint['list']},
|
64
|
+
"checkpoint")
|
65
|
+
safe_exec_cmd(cmd, :stdout => list)
|
66
|
+
return list.split(/\r?\n/)
|
67
|
+
end
|
68
|
+
|
69
|
+
def list_checkpoints
|
70
|
+
if @list_checkpoints.nil?
|
71
|
+
checkpoints = list_all_checkpoints
|
72
|
+
all_microsteps_ids = @recipe.microsteps.map { |m| m.identifier }
|
73
|
+
# get sorted checkpoints by microsteps order
|
74
|
+
@list_checkpoints = []
|
75
|
+
all_microsteps_ids.each do |id|
|
76
|
+
@list_checkpoints.push(id) if checkpoints.include?(id)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
return @list_checkpoints
|
80
|
+
end
|
81
|
+
|
82
|
+
def do_steps(section_name)
|
83
|
+
section = @recipe.sections.fetch(section_name)
|
84
|
+
section.sequence do |macrostep|
|
85
|
+
macrostep.sequence do |microstep|
|
86
|
+
@logger.notice("Step #{ microstep.order } : #{ microstep.slug }")
|
87
|
+
@logger.notice(" ---> #{ microstep.identifier }")
|
88
|
+
if @enable_checkpoint
|
89
|
+
if microstep.on_checkpoint == "skip"
|
90
|
+
@logger.notice(" ---> Skipped")
|
91
|
+
next
|
92
|
+
end
|
93
|
+
if microstep.in_cache && microstep.on_checkpoint == "use_cache"
|
94
|
+
@logger.notice(" ---> Using cache")
|
95
|
+
else
|
96
|
+
@logger.notice(" ---> Running step")
|
97
|
+
microstep.commands.each do |cmd|
|
98
|
+
safe_exec_cmd(cmd)
|
99
|
+
end
|
100
|
+
unless microstep.on_checkpoint == "redo"
|
101
|
+
@logger.notice(" ---> Creating checkpoint : #{ microstep.identifier }")
|
102
|
+
create_checkpoint(microstep.identifier)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
@logger.notice(" ---> Running step")
|
107
|
+
microstep.commands.each do |cmd|
|
108
|
+
safe_exec_cmd(cmd)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
@cleaned_sections.push(section.name)
|
114
|
+
end
|
115
|
+
|
116
|
+
def safe_exec_cmd(cmd, kwargs = {})
|
117
|
+
finished = false
|
118
|
+
begin
|
119
|
+
exec_cmd(cmd, kwargs)
|
120
|
+
finished = true
|
121
|
+
rescue ExecError
|
122
|
+
finished = rescue_exec_error(cmd)
|
123
|
+
end until finished
|
124
|
+
end
|
125
|
+
|
126
|
+
def exec_cmd(cmd, kwargs = {})
|
127
|
+
def skip_alert(cmd)
|
128
|
+
@logger.warn("Skipping cmd '#{cmd.string_cmd}'. The in_context is" \
|
129
|
+
" not ready yet")
|
130
|
+
end
|
131
|
+
case cmd.key
|
132
|
+
when "breakpoint"
|
133
|
+
breakpoint(cmd.value, )
|
134
|
+
when "exec_in"
|
135
|
+
skip_alert(cmd) if @in_context.nil?
|
136
|
+
@in_context.execute(cmd.value, kwargs) unless @in_context.nil?
|
137
|
+
when "exec_out"
|
138
|
+
@out_context.execute(cmd.value, kwargs)
|
139
|
+
when "exec_local"
|
140
|
+
@local_context.execute(cmd.value, kwargs)
|
141
|
+
when "pipe"
|
142
|
+
first_cmd, second_cmd = cmd.value
|
143
|
+
if ((first_cmd.key == "exec_in" || second_cmd.key == "exec_in")\
|
144
|
+
&& @in_context.nil?)
|
145
|
+
skip_alert(cmd)
|
146
|
+
else
|
147
|
+
expected_cmds = ["exec_in", "exec_out", "exec_local"]
|
148
|
+
[first_cmd.key, second_cmd.key].each do |key|
|
149
|
+
unless expected_cmds.include?(key)
|
150
|
+
@logger.error("Invalid pipe arguments. Expected "\
|
151
|
+
"#{expected_cmds} commands")
|
152
|
+
fail ExecError
|
153
|
+
end
|
154
|
+
end
|
155
|
+
map = {"exec_in" => @in_context,
|
156
|
+
"exec_out" => @out_context,
|
157
|
+
"exec_local" => @local_context,}
|
158
|
+
first_context = map[first_cmd.key]
|
159
|
+
second_context = map[second_cmd.key]
|
160
|
+
first_context.pipe(first_cmd.value, second_cmd.value, second_context)
|
161
|
+
end
|
162
|
+
when "rescue"
|
163
|
+
first_cmd, second_cmd = cmd.value
|
164
|
+
begin
|
165
|
+
exec_cmd(first_cmd)
|
166
|
+
rescue ExecError
|
167
|
+
exec_cmd(second_cmd)
|
168
|
+
end
|
169
|
+
else
|
170
|
+
@logger.warn("Unknown command : #{cmd.key}")
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def breakpoint(message, kwargs = {})
|
176
|
+
message.split( /\r?\n/ ).each {|m| @logger.error "#{m}" }
|
177
|
+
enable_retry = kwargs[:enable_retry]
|
178
|
+
msg = ""
|
179
|
+
msg << "Press [r] to retry\n" if enable_retry
|
180
|
+
msg << "Press [c] to continue with execution"
|
181
|
+
msg << "\nPress [a] to abort execution"
|
182
|
+
msg << "\nPress [l] to switch to local_context shell" unless @local_context.nil?
|
183
|
+
msg << "\nPress [o] to switch to out_context shell" unless @out_context.nil?
|
184
|
+
msg << "\nPress [i] to switch to in_context shell" unless @in_context.nil?
|
185
|
+
responses = {"c" => "continue", "a" => "abort"}
|
186
|
+
responses["r"] = "retry" if enable_retry
|
187
|
+
responses.merge!({"l" => "launch local_context"}) unless @out_context.nil?
|
188
|
+
responses.merge!({"o" => "launch out_context"}) unless @out_context.nil?
|
189
|
+
responses.merge!({"i" => "launch in_context"}) unless @in_context.nil?
|
190
|
+
while true
|
191
|
+
msg.split( /\r?\n/ ).each {|m| @logger.notice "#{m}" }
|
192
|
+
@logger.progress "answer ? [" + responses.keys().join("/") + "]: "
|
193
|
+
answer = $stdin.gets
|
194
|
+
raise AbortError, "Execution aborted..." if answer.nil?
|
195
|
+
answer.chomp!
|
196
|
+
if responses.keys.include?(answer)
|
197
|
+
@logger.notice("User choice : [#{answer}] #{responses[answer]}")
|
198
|
+
if ["o", "i", "l"].include?(answer)
|
199
|
+
if answer.eql? "l"
|
200
|
+
@local_context.start_shell
|
201
|
+
elsif answer.eql? "o"
|
202
|
+
@out_context.start_shell
|
203
|
+
else
|
204
|
+
@in_context.start_shell
|
205
|
+
end
|
206
|
+
@logger.notice("Getting back to Kameleon ...")
|
207
|
+
elsif answer.eql? "a"
|
208
|
+
raise AbortError, "Execution aborted..."
|
209
|
+
elsif answer.eql? "c"
|
210
|
+
## resetting the exit status
|
211
|
+
@in_context.execute("true") unless @in_context.nil?
|
212
|
+
@out_context.execute("true") unless @out_context.nil?
|
213
|
+
return true
|
214
|
+
elsif answer.eql? "r"
|
215
|
+
@logger.notice("Retrying the previous command...")
|
216
|
+
return false
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def rescue_exec_error(cmd)
|
223
|
+
message = "Error occured when executing the following command :\n"
|
224
|
+
cmd.string_cmd.split( /\r?\n/ ).each {|m| message << "\n> #{m}" }
|
225
|
+
return breakpoint(message, :enable_retry => true)
|
226
|
+
end
|
227
|
+
|
228
|
+
def finish_clean()
|
229
|
+
@recipe.sections.values.each do |section|
|
230
|
+
next if @cleaned_sections.include?(section.name)
|
231
|
+
begin
|
232
|
+
@logger.notice("Cleaning #{section.name} section")
|
233
|
+
section.clean_macrostep.sequence do |microstep|
|
234
|
+
microstep.commands.each do |cmd|
|
235
|
+
begin
|
236
|
+
exec_cmd(cmd)
|
237
|
+
rescue
|
238
|
+
@logger.warn("An error occurred while executing : #{cmd.value}")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def clear
|
247
|
+
@recipe.sections.values.each do |section|
|
248
|
+
@logger.notice("Cleaning #{section.name} section")
|
249
|
+
section.clean_macrostep.sequence do |microstep|
|
250
|
+
microstep.commands.each do |cmd|
|
251
|
+
if (cmd.key == "exec_out" || cmd.key == "exec_local")
|
252
|
+
begin
|
253
|
+
exec_cmd(cmd)
|
254
|
+
rescue
|
255
|
+
@logger.warn("An error occurred while executing : #{cmd.value}")
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
unless @recipe.checkpoint.nil?
|
262
|
+
@logger.notice("Removing all old checkpoints")
|
263
|
+
cmd = @recipe.checkpoint["clear"]
|
264
|
+
clear_cmd = Kameleon::Command.new({"exec_out" => cmd}, "checkpoint")
|
265
|
+
safe_exec_cmd(clear_cmd, :log_level => "info")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def build
|
270
|
+
if @enable_checkpoint
|
271
|
+
@from_checkpoint = @options[:from_checkpoint]
|
272
|
+
if @from_checkpoint.nil?
|
273
|
+
@from_checkpoint = list_checkpoints.last
|
274
|
+
else
|
275
|
+
unless list_checkpoints.include?@from_checkpoint
|
276
|
+
fail BuildError, "Unknown checkpoint hash : #{@from_checkpoint}." \
|
277
|
+
" Use checkpoints command to find a valid" \
|
278
|
+
" checkpoint"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
unless @from_checkpoint.nil?
|
282
|
+
@logger.notice("Restoring last build from step : #{@from_checkpoint}")
|
283
|
+
apply_checkpoint @from_checkpoint
|
284
|
+
@recipe.microsteps.each do |microstep|
|
285
|
+
microstep.in_cache = true
|
286
|
+
if microstep.identifier == @from_checkpoint
|
287
|
+
break
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
dump_build_recipe
|
293
|
+
begin
|
294
|
+
do_steps("bootstrap")
|
295
|
+
@logger.notice("Building internal context [in]")
|
296
|
+
@in_context = Context.new("in",
|
297
|
+
@recipe.global["in_context"]["cmd"],
|
298
|
+
@recipe.global["in_context"]["workdir"],
|
299
|
+
@recipe.global["in_context"]["exec_prefix"],
|
300
|
+
@cwd)
|
301
|
+
do_steps("setup")
|
302
|
+
do_steps("export")
|
303
|
+
rescue Exception => e
|
304
|
+
@logger.warn("Waiting for cleanup before exiting...")
|
305
|
+
@out_context.reopen if !@out_context.nil?
|
306
|
+
@in_context.reopen if !@in_context.nil?
|
307
|
+
@local_context.reopen if !@local_context.nil?
|
308
|
+
finish_clean
|
309
|
+
@out_context.close! unless @out_context.nil?
|
310
|
+
@in_context.close! unless @in_context.nil?
|
311
|
+
@local_context.close! unless @local_context.nil?
|
312
|
+
raise e
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def dump_build_recipe
|
317
|
+
File.open(@build_recipe_path, 'w') do |f|
|
318
|
+
f.write @recipe.to_hash.to_yaml
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def load_build_recipe
|
323
|
+
if File.file?(@build_recipe_path)
|
324
|
+
result = YAML.load_file(@build_recipe_path)
|
325
|
+
return result if result
|
326
|
+
end
|
327
|
+
return nil
|
328
|
+
end
|
329
|
+
|
330
|
+
def pretty_checkpoints_list
|
331
|
+
def find_microstep_slug_by_id(id)
|
332
|
+
@recipe.microsteps.each do |m|
|
333
|
+
return m.slug if m.identifier == id
|
334
|
+
end
|
335
|
+
end
|
336
|
+
dict_checkpoints = []
|
337
|
+
if @enable_checkpoint
|
338
|
+
list_checkpoints.each do |id|
|
339
|
+
slug = find_microstep_slug_by_id id
|
340
|
+
unless slug.nil?
|
341
|
+
dict_checkpoints.push({
|
342
|
+
"id" => id,
|
343
|
+
"step" => slug
|
344
|
+
})
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
if dict_checkpoints.empty?
|
349
|
+
puts "No checkpoint available for the recipe '#{recipe.name}'"
|
350
|
+
else
|
351
|
+
puts "The following checkpoints are available for " \
|
352
|
+
"the recipe '#{recipe.name}':"
|
353
|
+
tp dict_checkpoints, {"id" => {:width => 20}}, { "step" => {:width => 60}}
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|