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
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
|