kameleon-builder 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. data/.editorconfig +0 -0
  2. data/.env +63 -15
  3. data/.gitignore +1 -0
  4. data/README.rst +4 -2
  5. data/Vagrantfile +13 -52
  6. data/bin/kameleon +5 -0
  7. data/completion/_kameleon.zsh +18 -0
  8. data/completion/kameleon.bash +13 -0
  9. data/completion/kameleon.fish +10 -0
  10. data/contrib/polipo_env.sh +2 -0
  11. data/contrib/steps/export/save_as_g5k.yaml +63 -0
  12. data/contrib/steps/setup/add_to_sudoers.yaml +5 -0
  13. data/docs/Makefile +10 -6
  14. data/docs/README.md +17 -0
  15. data/docs/source/_static/kameleon-logo.png +0 -0
  16. data/docs/source/_static/kameleon-logo.xcf +0 -0
  17. data/docs/source/_static/kameleon-long.png +0 -0
  18. data/docs/source/aliases.rst +4 -2
  19. data/docs/source/checkpoint.rst +2 -0
  20. data/docs/source/commands.rst +4 -3
  21. data/docs/source/conf.py +15 -7
  22. data/docs/source/context.rst +7 -4
  23. data/docs/source/faq.rst +39 -1
  24. data/docs/source/getting_started.rst +227 -1
  25. data/docs/source/grid5000_tutorial.rst +110 -0
  26. data/docs/source/index.rst +7 -2
  27. data/docs/source/installation.rst +12 -4
  28. data/docs/source/persistent_cache.rst +34 -0
  29. data/docs/source/recipe.rst +23 -16
  30. data/docs/source/use_cases.rst +93 -0
  31. data/docs/source/workspace.rst +2 -0
  32. data/kameleon-builder.gemspec +7 -1
  33. data/lib/kameleon.rb +3 -6
  34. data/lib/kameleon/cli.rb +104 -50
  35. data/lib/kameleon/compat.rb +39 -0
  36. data/lib/kameleon/context.rb +43 -13
  37. data/lib/kameleon/engine.rb +118 -77
  38. data/lib/kameleon/environment.rb +3 -5
  39. data/lib/kameleon/error.rb +15 -9
  40. data/lib/kameleon/logger.rb +7 -4
  41. data/lib/kameleon/persistent_cache.rb +139 -0
  42. data/lib/kameleon/recipe.rb +200 -81
  43. data/lib/kameleon/shell.rb +51 -16
  44. data/omnibus/.gitignore +11 -0
  45. data/omnibus/.kitchen.yml +25 -0
  46. data/omnibus/Berksfile +9 -0
  47. data/omnibus/Berksfile.lock +25 -0
  48. data/omnibus/Gemfile +12 -0
  49. data/omnibus/README.md +94 -0
  50. data/omnibus/config/projects/kameleon.rb +23 -0
  51. data/omnibus/config/software/kameleon.rb +24 -0
  52. data/omnibus/config/software/polipo.rb +30 -0
  53. data/omnibus/config/software/ruby.rb +158 -0
  54. data/omnibus/files/mac_dmg/Resources/background.png +0 -0
  55. data/omnibus/files/mac_dmg/Resources/icon.png +0 -0
  56. data/omnibus/files/mac_pkg/Resources/background.png +0 -0
  57. data/omnibus/files/mac_pkg/Resources/license.html +1 -0
  58. data/omnibus/files/mac_pkg/Resources/welcome.html +9 -0
  59. data/omnibus/omnibus.rb +27 -0
  60. data/omnibus/package-scripts/kameleon/makeselfinst +27 -0
  61. data/omnibus/package-scripts/kameleon/postrm +9 -0
  62. data/templates/archlinux-desktop.yaml +25 -0
  63. data/templates/archlinux.yaml +106 -0
  64. data/templates/debian-testing.yaml +25 -0
  65. data/templates/debian7-desktop.yaml +25 -0
  66. data/templates/{debian-wheezy-docker.yaml → debian7-docker.yaml} +30 -16
  67. data/templates/debian7-g5k.yaml +97 -0
  68. data/templates/debian7-oar-dev.yaml +51 -0
  69. data/templates/debian7.yaml +128 -0
  70. data/templates/extend.erb +23 -0
  71. data/templates/fedora-rawhide.yaml +30 -0
  72. data/templates/fedora20-desktop.yaml +21 -0
  73. data/templates/fedora20.yaml +105 -0
  74. data/templates/{debian-wheezy-chroot.yaml → old-debian7.yaml} +51 -38
  75. data/templates/{aliases → steps/aliases}/defaults.yaml +37 -12
  76. data/templates/steps/bootstrap/archlinux/arch_bootstrap.yaml +219 -0
  77. data/templates/steps/bootstrap/archlinux/install_bootloader.yaml +46 -0
  78. data/templates/steps/bootstrap/archlinux/populate_disk.yaml +39 -0
  79. data/templates/steps/bootstrap/debian/debootstrap.yaml +18 -10
  80. data/templates/steps/bootstrap/debian/debootstrap_arm.yaml +31 -0
  81. data/templates/steps/bootstrap/fedora/liveos_bootstrap.yaml +123 -0
  82. data/templates/steps/bootstrap/g5k_reserv.yaml +70 -0
  83. data/templates/steps/bootstrap/initialize_disk_chroot.yaml +84 -0
  84. data/templates/steps/bootstrap/initialize_disk_qemu.yaml +72 -0
  85. data/templates/steps/bootstrap/install_bootloader.yaml +42 -0
  86. data/templates/steps/bootstrap/prepare_chroot.yaml +126 -0
  87. data/templates/steps/bootstrap/prepare_docker.yaml +19 -8
  88. data/templates/steps/bootstrap/prepare_qemu.yaml +47 -0
  89. data/templates/steps/bootstrap/start_chroot.yaml +11 -2
  90. data/templates/steps/bootstrap/start_docker.yaml +2 -2
  91. data/templates/steps/bootstrap/start_qemu.yaml +75 -0
  92. data/templates/steps/bootstrap/ubuntu/debootstrap.yaml +27 -0
  93. data/templates/steps/breakpoint.yaml +2 -0
  94. data/templates/{checkpoints → steps/checkpoints}/docker.yaml +0 -0
  95. data/templates/steps/checkpoints/qcow2.yaml +38 -0
  96. data/templates/steps/checkpoints/qemu.yaml +39 -0
  97. data/templates/steps/export/clean_appliance.yaml +7 -1
  98. data/templates/steps/export/compact_qcow_img.yaml +12 -0
  99. data/templates/steps/export/save_appliance.yaml +58 -0
  100. data/templates/steps/export/save_appliance_from_g5k.yaml +47 -0
  101. data/templates/steps/export/save_vagrant_box.yaml +29 -0
  102. data/templates/steps/setup/archlinux/configure_keyboard.yaml +9 -0
  103. data/templates/steps/setup/archlinux/configure_network.yaml +9 -0
  104. data/templates/steps/setup/archlinux/configure_ruby.yaml +7 -0
  105. data/templates/steps/setup/archlinux/configure_system.yaml +20 -0
  106. data/templates/steps/setup/archlinux/install_dev_tools.yaml +18 -0
  107. data/templates/steps/setup/archlinux/install_gnome.yaml +27 -0
  108. data/templates/steps/setup/archlinux/install_software.yaml +9 -0
  109. data/templates/steps/setup/archlinux/install_yaourt.yaml +29 -0
  110. data/templates/steps/setup/autologin.yaml +16 -0
  111. data/templates/steps/setup/create_group.yaml +12 -0
  112. data/templates/steps/setup/create_user.yaml +9 -10
  113. data/templates/steps/setup/debian/configure_apt.yaml +65 -0
  114. data/templates/steps/setup/debian/configure_kernel.yaml +18 -0
  115. data/templates/steps/setup/debian/{keyboard_config.yaml → configure_keyboard.yaml} +1 -1
  116. data/templates/steps/setup/debian/{network_config.yaml → configure_network.yaml} +0 -0
  117. data/templates/steps/setup/debian/{system_config.yaml → configure_system.yaml} +0 -0
  118. data/templates/steps/setup/debian/install_gnome.yaml +13 -0
  119. data/templates/steps/setup/debian/install_kde.yaml +13 -0
  120. data/templates/steps/setup/debian/install_software.yaml +2 -0
  121. data/templates/steps/setup/debian/oar/oar_debian_config_frontend.yaml +8 -0
  122. data/templates/steps/setup/debian/oar/oar_debian_config_node.yaml +5 -0
  123. data/templates/steps/setup/debian/oar/oar_debian_config_server.yaml +5 -0
  124. data/templates/steps/setup/debian/oar/oar_prereq_install.yaml +16 -0
  125. data/templates/steps/setup/debian/setup_vagrant_box.yaml +52 -0
  126. data/templates/steps/setup/debian/upgrade_system.yaml +15 -0
  127. data/templates/steps/setup/fedora/configure_network.yaml +30 -0
  128. data/templates/steps/setup/fedora/configure_system.yaml +59 -0
  129. data/templates/steps/setup/fedora/install_software.yaml +3 -0
  130. data/templates/steps/setup/fedora/update_system.yaml +10 -0
  131. data/templates/steps/setup/oar/oar_config_devel.yaml +21 -0
  132. data/templates/steps/setup/oar/oar_config_frontend.yaml +38 -0
  133. data/templates/steps/setup/oar/oar_config_node.yaml +4 -0
  134. data/templates/steps/setup/oar/oar_config_server.yaml +25 -0
  135. data/templates/steps/setup/oar/oar_config_system.yaml +34 -0
  136. data/templates/steps/setup/oar/oar_devel_prereq_install.yaml +5 -0
  137. data/templates/steps/setup/oar/oar_git_install.yaml +21 -0
  138. data/templates/steps/setup/ubuntu/configure_apt.yaml +67 -0
  139. data/templates/ubuntu-12.04-desktop.yaml +25 -0
  140. data/templates/ubuntu-12.04.yaml +128 -0
  141. data/templates/ubuntu-14.04-desktop.yaml +27 -0
  142. data/templates/ubuntu-14.04.yaml +25 -0
  143. data/templates/vagrant-debian7.yaml +31 -0
  144. data/version.txt +1 -1
  145. metadata +155 -28
  146. checksums.yaml +0 -7
  147. data/templates/checkpoints/qcow2.yaml +0 -44
  148. data/templates/fedora-docker.yaml +0 -96
  149. data/templates/steps/bootstrap/fedora/docker_bootstrap.yaml +0 -25
  150. data/templates/steps/bootstrap/fedora/yum_bootstrap.yaml +0 -22
  151. data/templates/steps/bootstrap/prepare_appliance_with_nbd.yaml +0 -93
  152. data/templates/steps/export/build_appliance_from_docker.yaml +0 -105
  153. data/templates/steps/export/save_appliance_from_nbd.yaml +0 -54
  154. data/templates/steps/setup/debian/kernel_install.yaml +0 -20
  155. data/templates/steps/setup/debian/software_install.yaml +0 -15
  156. data/templates/steps/setup/fedora/kernel_install.yaml +0 -27
  157. data/templates/steps/setup/fedora/software_install.yaml +0 -10
@@ -6,23 +6,21 @@ module Kameleon
6
6
 
7
7
  attr_accessor :workspace
8
8
  attr_accessor :templates_path
9
- attr_accessor :recipes_path
10
9
  attr_accessor :build_path
11
10
  attr_accessor :log_file
12
11
  attr_accessor :debug
13
12
 
14
13
 
15
14
  def initialize(options = {})
16
- @logger = Log4r::Logger.new("kameleon::[env]")
15
+ @logger = Log4r::Logger.new("kameleon::[kameleon]")
17
16
  # symbolify commandline options
18
17
  options = options.inject({}) {|result,(key,value)| result.update({key.to_sym => value})}
19
- workspace = File.expand_path(options[:workspace])
20
- build_path = File.expand_path(options[:build_path] || File.join(workspace, "builds"))
18
+ workspace = File.expand_path(Dir.pwd)
19
+ build_path = File.expand_path(options[:build_path] || File.join(workspace, "build"))
21
20
  defaults = {
22
21
  :workspace => Pathname.new(workspace),
23
22
  :templates_path => Kameleon.templates_path,
24
23
  :templates_names => Kameleon.templates_names,
25
- :recipes_path => Pathname.new(File.join(workspace, "recipes")),
26
24
  :build_path => Pathname.new(build_path),
27
25
  :log_file => Pathname.new(File.join(workspace, "kameleon.log"))
28
26
  }
@@ -1,7 +1,7 @@
1
1
  require 'thor/error'
2
2
 
3
3
  module Kameleon
4
- class KameleonError < ::StandardError
4
+ class Error < ::StandardError
5
5
  attr_accessor :object
6
6
 
7
7
  def initialize(message=nil, object=nil)
@@ -14,17 +14,19 @@ module Kameleon
14
14
  end
15
15
  end
16
16
 
17
- class ExecError < KameleonError; status_code(2) ; end
18
- class InternalError < KameleonError; status_code(3) ; end
19
- class ContextError < KameleonError; status_code(4) ; end
20
- class ShellError < KameleonError; status_code(5) ; end
21
- class RecipeError < KameleonError; status_code(6) ; end
22
- class BuildError < KameleonError; status_code(7) ; end
23
- class AbortError < KameleonError; status_code(8) ; end
17
+ class KameleonError < Error; status_code(1) ; end
18
+ class ExecError < Error; status_code(2) ; end
19
+ class InternalError < Error; status_code(3) ; end
20
+ class ContextError < Error; status_code(4) ; end
21
+ class ShellError < Error; status_code(5) ; end
22
+ class RecipeError < Error; status_code(6) ; end
23
+ class BuildError < Error; status_code(7) ; end
24
+ class AbortError < Error; status_code(8) ; end
25
+ class TemplateNotFound < Error; status_code(9) ; end
24
26
 
25
27
  def self.with_friendly_errors
26
28
  yield
27
- rescue Kameleon::KameleonError => e
29
+ rescue Kameleon::Error => e
28
30
  e.message.split( /\r?\n/ ).each {|m| Kameleon.logger.fatal m }
29
31
  exit e.status_code
30
32
  rescue Thor::UndefinedTaskError => e
@@ -38,6 +40,10 @@ module Kameleon
38
40
  rescue SystemExit, Interrupt => e
39
41
  Kameleon.logger.fatal("Quitting...")
40
42
  exit 1
43
+ rescue Errno::ENOENT => e
44
+ $stderr << "#{e.message}\n"
45
+ e.backtrace.each {|m| Kameleon.logger.debug m }
46
+ exit 16
41
47
  rescue Exception => e
42
48
  if ENV["KAMELEON_LOG"] != "debug"
43
49
  $stderr << "Unfortunately, a fatal error has occurred : "\
@@ -14,7 +14,7 @@ module Kameleon
14
14
  def format(event)
15
15
  buff = sprintf(@@basicformat, @max_level_length, event.name)
16
16
  buff << (event.tracer.nil? ? "" : "(#{event.tracer[0]})") + ": "
17
- unless Log4r::LNAMES[event.level].eql? "PROGRESS"
17
+ unless Log4r::LNAMES[event.level].include? "PROGRESS"
18
18
  @on_progress = false
19
19
  buff << format_object(event.data) + "\n"
20
20
  else
@@ -30,13 +30,14 @@ module Kameleon
30
30
 
31
31
  # Custom Log4r formatter for files
32
32
  class FileFormatter < Log4r::BasicFormatter
33
-
33
+ @@basicformat = "%*s"
34
34
  def initialize(hash={})
35
35
  super(hash)
36
+ @max_level_length = 11
36
37
  end
37
38
 
38
39
  def format(logevent)
39
- if Log4r::LNAMES[logevent.level].eql? "PROGRESS"
40
+ if Log4r::LNAMES[logevent.level].include? "PROGRESS"
40
41
  # Formats the data as is with no newline, to allow progress bars to be logged.
41
42
  sprintf("%s", logevent.data.to_s)
42
43
  else
@@ -46,7 +47,9 @@ module Kameleon
46
47
  # Prevent two newlines in the log file
47
48
  logevent.data.chop! if logevent.data =~ /\n$/
48
49
  end
49
- sprintf("[%8s %s] %s\n", Log4r::LNAMES[logevent.level], Time.now.strftime("%m/%d/%Y %I:%M:%S %p"), format_object(logevent.data))
50
+ tracer = sprintf(@@basicformat, @max_level_length, logevent.name)
51
+ tracer << (logevent.tracer.nil? ? "" : "(#{logevent.tracer[0]})") + ": "
52
+ sprintf("%s %s\n", tracer, format_object(logevent.data))
50
53
  end
51
54
  end
52
55
  end
@@ -0,0 +1,139 @@
1
+ require 'childprocess'
2
+ require 'singleton'
3
+ require 'socket'
4
+
5
+ module Kameleon
6
+ #This ruby class will control the execution of Polipo web proxy
7
+ class Persistent_cache
8
+
9
+ include Singleton
10
+ attr_reader :polipo_env, :cache_dir,:polipo_port
11
+ attr_writer :activated, :cwd, :polipo_path, :name
12
+ def initialize()
13
+ @logger = Log4r::Logger.new("kameleon::[kameleon]")
14
+ ## we must configure Polipo to be execute for the in and out context
15
+ ## we have to start polipo in the out context for debootstrap step
16
+
17
+ @polipo_env = File.join(Kameleon.source_root,
18
+ "contrib",
19
+ "polipo_env.sh")
20
+
21
+ @polipo_process = nil
22
+ @polipo_port = find_unused_port
23
+
24
+ @polipo_cmd_options = {:diskCacheRoot => "",
25
+ :idleTime => "5",
26
+ :chunkHighMark => "425165824",
27
+ :proxyPort => @polipo_port,
28
+ #:proxyOffline => "true"
29
+ :relaxTransparency =>"true"
30
+ }
31
+
32
+ @activated = false
33
+
34
+ @cache_dir = ""
35
+ @polipo_path = nil
36
+ @cwd = ""
37
+
38
+ end
39
+
40
+ def find_unused_port
41
+ ports = (8000..9000)
42
+ port = 0
43
+ tmp = nil
44
+ ports.each do |p|
45
+ begin
46
+ port = p
47
+ tmp = TCPServer.new('localhost',port)
48
+ rescue
49
+ port =0
50
+ end
51
+ break if(port>0)
52
+ end
53
+ tmp.close
54
+ port
55
+ end
56
+
57
+ def check_polipo_binary
58
+
59
+
60
+ @polipo_path ||= which("polipo")
61
+
62
+ if @polipo_path.nil? then
63
+ @logger.error("Polipo binary not found, make sure it is in your current PATH")
64
+ @logger.error("or use the option --proxy_path")
65
+ raise BuildError, "Failed to use persistent cache"
66
+ end
67
+ end
68
+
69
+ def activated?
70
+ @activated
71
+ end
72
+
73
+
74
+ def cwd=(dir)
75
+ @cwd = dir
76
+ @cache_dir = @cwd + "/cache/"
77
+ end
78
+
79
+ def create_cache_directory(step_name)
80
+ @logger.notice("Creating cache directory #{step_name} for Polipo")
81
+ directory_name = @cache_dir + "/#{step_name}"
82
+ FileUtils.mkdir_p directory_name
83
+ directory_name
84
+ end
85
+
86
+ def start_web_proxy_in(directory)
87
+ ## This function assumes that the cache directory has already been created by the engine
88
+ ## Stopping first the previous proxy
89
+ ## have to check if polipo is running
90
+ @logger.notice("Starting web proxy Polipo in directory #{directory} using port: #{@polipo_port}")
91
+ @polipo_process.stop unless @polipo_process.nil?
92
+ command = ["#{@polipo_path}/polipo"]
93
+ @polipo_cmd_options[:diskCacheRoot] = directory
94
+ @polipo_cmd_options.each{ |v,k| command.push("#{v}=#{k}") }
95
+ ChildProcess.posix_spawn = true
96
+ @polipo_process = ChildProcess.build(*command)
97
+ @polipo_process.io.stdout = Tempfile.new("polipo_output")
98
+ @polipo_process.start
99
+ end
100
+
101
+
102
+ def stop_web_proxy
103
+ @polipo_process.stop
104
+ @logger.notice("Stopping web proxy polipo")
105
+ end
106
+
107
+ def pack()
108
+ @logger.notice("Packing up the generated cache in #{@cwd}")
109
+ execute("tar","-cf #{@name}-cache.tar cache/",@cwd)
110
+ # The cache directory cannot be deleted due to the checkpoints
111
+ end
112
+
113
+ def unpack(cache_path)
114
+ @logger.notice("Unpacking persistent cache: #{cache_path}")
115
+ execute("tar","-xf #{cache_path} -C #{@cwd}")
116
+ end
117
+
118
+ def execute(cmd,args,dir=nil)
119
+ command = [cmd ] + args.split(" ")
120
+ # @logger.notice(" command generated: #{command}")
121
+ process = ChildProcess.build(*command)
122
+ process.cwd = dir unless dir.nil?
123
+ process.start
124
+ process.wait
125
+ end
126
+
127
+ def which(cmd)
128
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
129
+ exe = File.join(path, "#{cmd}")
130
+ return path if File.executable? exe
131
+ end
132
+ return nil
133
+ end
134
+
135
+
136
+ end
137
+
138
+ end
139
+
@@ -8,7 +8,7 @@ module Kameleon
8
8
  :checkpoint, :checkpoint_path, :metainfo
9
9
 
10
10
  def initialize(path)
11
- @logger = Log4r::Logger.new("kameleon::[recipe]")
11
+ @logger = Log4r::Logger.new("kameleon::[kameleon]")
12
12
  @path = Pathname.new(path)
13
13
  @name = (@path.basename ".yaml").to_s
14
14
  @recipe_content = File.open(@path, 'r') { |f| f.read }
@@ -17,7 +17,6 @@ module Kameleon
17
17
  "setup" => Section.new("setup"),
18
18
  "export" => Section.new("export"),
19
19
  }
20
- @required_global = %w(out_context in_context)
21
20
  kameleon_id = SecureRandom.uuid
22
21
  @global = {
23
22
  "kameleon_recipe_name" => @name,
@@ -25,29 +24,32 @@ module Kameleon
25
24
  "kameleon_uuid" => kameleon_id,
26
25
  "kameleon_short_uuid" => kameleon_id.split("-").last,
27
26
  "kameleon_cwd" => File.join(Kameleon.env.build_path, @name),
27
+ "in_context" => {"cmd"=> "/bin/bash"},
28
+ "out_context" => {"cmd"=> "/bin/bash"}
28
29
  }
29
30
  @aliases = {}
30
31
  @checkpoint = nil
31
32
  @files = []
32
33
  @logger.debug("Initialize new recipe (#{path})")
34
+ @base_recipes_files = [@path]
33
35
  load!
34
36
  end
35
37
 
36
38
  def load!
37
39
  # Find recipe path
38
- @logger.notice("Loading #{@path}")
40
+ @logger.debug("Loading #{@path}")
39
41
  fail RecipeError, "Could not find this following recipe : #{@path}" \
40
42
  unless File.file? @path
41
43
  yaml_recipe = YAML.load File.open @path
42
44
  unless yaml_recipe.kind_of? Hash
43
45
  fail RecipeError, "Invalid yaml error"
44
46
  end
45
- unless yaml_recipe.key? "global"
46
- fail RecipeError, "Recipe misses 'global' section"
47
- end
47
+ # Load entended recipe variables
48
+ yaml_recipe = load_base_recipe(yaml_recipe)
49
+ yaml_recipe.delete("extend")
48
50
 
49
- #Load Global variables
50
- @global.merge!(yaml_recipe.fetch("global"))
51
+ # Load Global variables
52
+ @global.merge!(yaml_recipe.fetch("global", {}))
51
53
  # Resolve dynamically-defined variables !!
52
54
  resolved_global = Utils.resolve_vars(@global.to_yaml, @path, @global)
53
55
  @global.merge! YAML.load(resolved_global)
@@ -73,7 +75,7 @@ module Kameleon
73
75
  yaml_section = yaml_recipe.fetch(section.name)
74
76
  next unless yaml_section.kind_of? Array
75
77
  yaml_section.each do |raw_macrostep|
76
-
78
+ embedded_step = false
77
79
  # Get macrostep name and arguments if available
78
80
  if raw_macrostep.kind_of? String
79
81
  name = raw_macrostep
@@ -85,13 +87,28 @@ module Kameleon
85
87
  fail RecipeError, "Malformed yaml recipe in section: "\
86
88
  "#{section.name}"
87
89
  end
88
-
90
+ # Detect if step is embedded
91
+ if not args.nil?
92
+ args.each do |arg|
93
+ if arg.kind_of? Hash
94
+ if arg.flatten[1].kind_of? Array
95
+ embedded_step = true
96
+ end
97
+ end
98
+ end
99
+ end
100
+ if embedded_step
101
+ @logger.debug("Loading embedded macrostep #{name}")
102
+ macrostep = load_macrostep(nil, name, args)
103
+ section.macrosteps.push(macrostep)
104
+ next
105
+ end
89
106
  # Load macrostep yaml
90
107
  loaded = false
91
108
  dir_to_search.each do |dir|
92
109
  macrostep_path = Pathname.new(File.join(dir, name + '.yaml'))
93
110
  if File.file?(macrostep_path)
94
- @logger.notice("Loading macrostep #{macrostep_path}")
111
+ @logger.debug("Loading macrostep #{macrostep_path}")
95
112
  macrostep = load_macrostep(macrostep_path, name, args)
96
113
  section.macrosteps.push(macrostep)
97
114
  @files.push(macrostep_path)
@@ -108,28 +125,81 @@ module Kameleon
108
125
  end
109
126
  end
110
127
  end
111
- @logger.notice("Loading recipe metadata")
128
+ @logger.debug("Loading recipe metadata")
112
129
  @metainfo = {
113
- "description" => Utils.extract_meta_var("description", @recipe_content),
114
- "recipe" => Utils.extract_meta_var("recipe", @recipe_content),
115
- "template" => Utils.extract_meta_var("template", @recipe_content),
130
+ "description" => Utils.extract_meta_var("description", @recipe_content)
116
131
  }
117
132
  end
118
133
 
134
+ def load_base_recipe(yaml_recipe)
135
+ base_recipe_name = yaml_recipe.fetch("extend", "")
136
+ return yaml_recipe if base_recipe_name.empty?
137
+
138
+ ## check that the recipe has not already been loaded
139
+ base_recipe_name << ".yaml" unless base_recipe_name.end_with? ".yaml"
140
+ base_recipe_path = File.join(File.dirname(@path), base_recipe_name)
141
+
142
+ ## check that the recipe has not already been loaded
143
+ return yaml_recipe if @base_recipes_files.include? base_recipe_path
144
+
145
+ base_recipe_path << ".yaml" unless base_recipe_path.end_with? ".yaml"
146
+ fail RecipeError, "Could not find this following recipe : #{@recipe_path}" \
147
+ unless File.file? @path
148
+ base_yaml_recipe = YAML.load File.open base_recipe_path
149
+ unless yaml_recipe.kind_of? Hash
150
+ fail RecipeError, "Invalid yaml error"
151
+ end
152
+ base_yaml_recipe.keys.each do |key|
153
+ if ["export", "bootstrap", "setup"].include? key
154
+ base_yaml_recipe.delete(key) unless yaml_recipe.keys.include? key
155
+ end
156
+ end
157
+ yaml_recipe.keys.each do |key|
158
+ if ["aliases", "checkpoint"].include? key
159
+ base_yaml_recipe[key] = yaml_recipe[key]
160
+ elsif ["export", "bootstrap", "setup"].include? key
161
+ base_section = base_yaml_recipe.fetch(key, [])
162
+ base_section = [] if base_section.nil?
163
+ recipe_section = yaml_recipe[key]
164
+ recipe_section = [] if recipe_section.nil?
165
+ index_base_steps = recipe_section.index("@base")
166
+ unless index_base_steps.nil?
167
+ recipe_section[index_base_steps] = base_section
168
+ recipe_section.flatten!
169
+ end
170
+ base_yaml_recipe[key] = recipe_section
171
+ elsif ["global"].include? key
172
+ base_section = base_yaml_recipe.fetch(key, {})
173
+ base_section = {} if base_section.nil?
174
+ recipe_section = yaml_recipe[key]
175
+ recipe_section = {} if recipe_section.nil?
176
+ base_yaml_recipe[key] = base_section.merge(recipe_section)
177
+ end
178
+ end
179
+ @base_recipes_files.push(Pathname.new(base_recipe_path))
180
+ return load_base_recipe(base_yaml_recipe)
181
+ end
182
+
119
183
  def load_aliases(yaml_recipe)
120
184
  if yaml_recipe.keys.include? "aliases"
121
185
  aliases = yaml_recipe.fetch("aliases")
122
186
  if aliases.kind_of? Hash
123
187
  @aliases = aliases
124
188
  elsif aliases.kind_of? String
125
- path = Pathname.new(File.join(File.dirname(@path), "aliases", aliases))
126
- if File.file?(path)
127
- @logger.notice("Loading aliases #{path}")
128
- @aliases = YAML.load_file(path)
129
- @files.push(path)
130
- else
131
- fail RecipeError, "Aliases file '#{path}' does not exists"
189
+ dir_search = [
190
+ File.join(File.dirname(@path), "steps", "aliases"),
191
+ File.join(File.dirname(@path), "aliases")
192
+ ]
193
+ dir_search.each do |dir_path|
194
+ path = Pathname.new(File.join(dir_path, aliases))
195
+ if File.file?(path)
196
+ @logger.debug("Loading aliases #{path}")
197
+ @aliases = YAML.load_file(path)
198
+ @files.push(path)
199
+ return path
200
+ end
132
201
  end
202
+ fail RecipeError, "Aliases file '#{path}' does not exists"
133
203
  end
134
204
  end
135
205
  end
@@ -141,31 +211,39 @@ module Kameleon
141
211
  @checkpoint = checkpoint
142
212
  @checkpoint["path"] = @path
143
213
  elsif checkpoint.kind_of? String
144
- path = Pathname.new(File.join(File.dirname(@path),
145
- "checkpoints",
146
- checkpoint))
147
- if File.file?(path)
148
- @logger.notice("Loading checkpoint configuration #{path}")
149
- @checkpoint = YAML.load_file(path)
150
- @checkpoint["path"] = path.to_s
151
- @files.push(path)
152
- else
153
- fail RecipeError, "Checkpoint configuraiton file '#{path}' " \
154
- "does not exists"
214
+ dir_search = [
215
+ File.join(File.dirname(@path), "steps", "checkpoints"),
216
+ File.join(File.dirname(@path), "checkpoints")
217
+ ]
218
+ dir_search.each do |dir_path|
219
+ path = Pathname.new(File.join(dir_path, checkpoint))
220
+ if File.file?(path)
221
+ @logger.debug("Loading checkpoint configuration #{path}")
222
+ @checkpoint = YAML.load_file(path)
223
+ @checkpoint["path"] = path.to_s
224
+ @files.push(path)
225
+ return path
226
+ end
155
227
  end
228
+ fail RecipeError, "Checkpoint configuraiton file '#{path}' " \
229
+ "does not exists"
156
230
  end
157
231
  end
158
232
  end
159
233
 
160
234
  def load_macrostep(step_path, name, args)
161
- macrostep_yaml = YAML.load_file(step_path)
235
+ if step_path.nil?
236
+ macrostep_yaml = args
237
+ else
238
+ macrostep_yaml = YAML.load_file(step_path)
239
+ # Basic macrostep syntax check
240
+ if not macrostep_yaml.kind_of? Array
241
+ fail RecipeError, "The macrostep #{step_path} is not valid "
242
+ "(should be a list of microsteps)"
243
+ end
244
+ end
162
245
  local_variables = {}
163
246
  loaded_microsteps = []
164
- # Basic macrostep syntax check
165
- if not macrostep_yaml.kind_of? Array
166
- fail RecipeError, "The macrostep #{step_path} is not valid "
167
- "(should be a list of microsteps)"
168
- end
169
247
  # Load default local variables
170
248
  macrostep_yaml.each do |yaml_microstep|
171
249
  key = yaml_microstep.keys[0]
@@ -177,33 +255,35 @@ module Kameleon
177
255
  local_variables[key] = @global.fetch(key, value)
178
256
  end
179
257
  end
180
- selected_microsteps = []
181
- if args
182
- args.each do |entry|
183
- if entry.kind_of? Hash
184
- # resolve variable before using it
185
- entry.each do |key, value|
186
- local_variables[key] = value
258
+ unless step_path.nil?
259
+ selected_microsteps = []
260
+ if args
261
+ args.each do |entry|
262
+ if entry.kind_of? Hash
263
+ # resolve variable before using it
264
+ entry.each do |key, value|
265
+ local_variables[key] = value
266
+ end
267
+ elsif entry.kind_of? String
268
+ selected_microsteps.push entry
187
269
  end
188
- elsif entry.kind_of? String
189
- selected_microsteps.push entry
190
270
  end
191
271
  end
192
- end
193
- unless selected_microsteps.empty?
194
- # Some steps are selected so remove the others
195
- # WARN: Allow the user to define this list not in the original order
196
- strip_microsteps = []
197
- selected_microsteps.each do |microstep_name|
198
- macrostep = find_microstep(microstep_name, loaded_microsteps)
199
- if macrostep.nil?
200
- fail RecipeError, "Can't find microstep '#{microstep_name}' "\
201
- "in macrostep file '#{step_path}'"
202
- else
203
- strip_microsteps.push(macrostep)
272
+ unless selected_microsteps.empty?
273
+ # Some steps are selected so remove the others
274
+ # WARN: Allow the user to define this list not in the original order
275
+ strip_microsteps = []
276
+ selected_microsteps.each do |microstep_name|
277
+ macrostep = find_microstep(microstep_name, loaded_microsteps)
278
+ if macrostep.nil?
279
+ fail RecipeError, "Can't find microstep '#{microstep_name}' "\
280
+ "in macrostep file '#{step_path}'"
281
+ else
282
+ strip_microsteps.push(macrostep)
283
+ end
204
284
  end
285
+ loaded_microsteps = strip_microsteps
205
286
  end
206
- loaded_microsteps = strip_microsteps
207
287
  end
208
288
  return Macrostep.new(name, loaded_microsteps, local_variables, step_path)
209
289
  end
@@ -276,12 +356,6 @@ module Kameleon
276
356
  end
277
357
  end
278
358
  @logger.notice("Starting recipe consistency check")
279
- missings = []
280
- @required_global.each do |key|
281
- missings.push key unless @global.key? key
282
- end
283
- fail RecipeError, "Required parameters missing in global section :" \
284
- " #{missings.join ' '}" unless missings.empty?
285
359
  # check context args
286
360
  required_args = %w(cmd)
287
361
  missings = []
@@ -427,8 +501,8 @@ module Kameleon
427
501
  "name" => @name,
428
502
  "path" => @path.to_s,
429
503
  "files" => @files.map {|p| p.to_s },
504
+ "base_recipes_files" => @base_recipes_files.map {|p| p.to_s },
430
505
  "global" => @global,
431
- "required_global" => @required_global,
432
506
  "aliases" => @aliases,
433
507
  }
434
508
  recipe_hash["checkpoint"] = @checkpoint unless @checkpoint.nil?
@@ -448,26 +522,71 @@ module Kameleon
448
522
 
449
523
  class RecipeTemplate < Recipe
450
524
 
451
- def copy_template(dest_path, recipe_name, force)
525
+ def get_answer(msg)
526
+ while true
527
+ @logger.progress_notice msg
528
+ answer = $stdin.gets.downcase
529
+ raise AbortError, "Execution aborted..." if answer.nil?
530
+ answer.chomp!
531
+ if ["y", "n" , "", "a"].include?(answer)
532
+ if ["y", ""].include? answer
533
+ return true
534
+ elsif answer.eql? "a"
535
+ raise AbortError, "Aborted..."
536
+ end
537
+ return false
538
+ end
539
+ end
540
+ end
541
+
542
+ def safe_copy_file(src, dst, force)
543
+ if File.exists? dst
544
+ diff = Diffy::Diff.new(dst.to_s, src.to_s, :source => "files").to_s
545
+ unless diff.chomp.empty?
546
+ @logger.notice("conflict #{dst}")
547
+ @logger.notice("Differences between the old and the new :")
548
+ puts Diffy::Diff.new(dst.to_s, src.to_s,
549
+ :source => "files",
550
+ :context => 1,
551
+ :include_diff_info => true).to_s
552
+ msg = "Overwrite #{dst}? [Y]es/[n]o/[a]bort : "
553
+ if force || get_answer(msg)
554
+ FileUtils.copy_file(src, dst)
555
+ end
556
+ else
557
+ @logger.notice("identical #{dst}")
558
+ end
559
+ else
560
+ unless File.dirname(dst).eql? "/"
561
+ FileUtils.mkdir_p File.dirname(dst)
562
+ end
563
+ @logger.notice("create #{dst}")
564
+ FileUtils.copy_file(src, dst)
565
+ end
566
+ end
567
+
568
+ def copy_extended_recipe(recipe_name, force)
452
569
  Dir::mktmpdir do |tmp_dir|
453
570
  recipe_path = File.join(tmp_dir, recipe_name + '.yaml')
454
- FileUtils.cp(@path, recipe_path)
571
+ ## copying recipe
455
572
  File.open(recipe_path, 'w+') do |file|
456
- tpl = ERB.new(@recipe_content)
573
+ extend_erb_tpl = File.join(Kameleon.env.templates_path, "extend.erb")
574
+ tpl = ERB.new(File.open(extend_erb_tpl, 'rb') { |f| f.read })
457
575
  result = tpl.result(binding)
458
576
  file.write(result)
459
577
  end
578
+ recipe_dst = File.join(Kameleon.env.workspace, recipe_name + '.yaml')
579
+ safe_copy_file(recipe_path, Pathname.new(recipe_dst), force)
580
+ end
581
+ end
460
582
 
461
- @files.each do |path|
462
- relative_path = path.relative_path_from(Kameleon.env.templates_path)
463
- dst = File.join(tmp_dir, File.dirname(relative_path))
464
- FileUtils.mkdir_p dst
465
- FileUtils.cp(path, dst)
466
- @logger.debug("Copying '#{path}' to '#{dst}'")
467
- end
468
- # Create recipe dir if not exists
469
- FileUtils.mkdir_p Kameleon.env.recipes_path
470
- FileUtils.cp_r(Dir[tmp_dir + '/*'], Kameleon.env.recipes_path)
583
+ def copy_template(force)
584
+ ## copying steps
585
+ files2copy = @base_recipes_files + @files
586
+ files2copy.each do |path|
587
+ relative_path = path.relative_path_from(Kameleon.env.templates_path)
588
+ dst = File.join(Kameleon.env.workspace, relative_path)
589
+ safe_copy_file(path, dst, force)
471
590
  end
472
591
  end
473
592
  end