kameleon-builder 2.2.3 → 2.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG.rst +13 -0
  2. data/contrib/kameleon_bashrc.sh +6 -6
  3. data/lib/kameleon/engine.rb +5 -2
  4. data/lib/kameleon/persistent_cache.rb +43 -25
  5. data/lib/kameleon/recipe.rb +12 -7
  6. data/lib/kameleon/step.rb +2 -0
  7. data/templates/qemu/centos6.5-x86_64.yaml +18 -9
  8. data/templates/qemu/centos7-x86_64.yaml +4 -1
  9. data/templates/qemu/fedora20-x86_64.yaml +4 -1
  10. data/templates/steps/bootstrap/centos/6.5/yum_bootstrap.yaml +65 -0
  11. data/templates/steps/bootstrap/start_qemu.yaml +4 -2
  12. data/templates/steps/bootstrap/start_virtualbox.yaml +3 -2
  13. data/templates/steps/bootstrap/switch_context_qemu.yaml +5 -4
  14. data/templates/steps/bootstrap/switch_context_virtualbox.yaml +4 -3
  15. data/templates/steps/export/save_appliance_from_g5k.yaml +1 -1
  16. data/templates/steps/export/vagrant_save_appliance.yaml +7 -0
  17. data/templates/steps/export/virtualbox_save_appliance.yaml +0 -6
  18. data/templates/steps/setup/centos/6.5/configure_repo.yaml +9 -0
  19. data/templates/steps/setup/centos/6.5/configure_system.yaml +19 -0
  20. data/templates/steps/setup/centos/6.5/minimal_install.yaml +3 -0
  21. data/templates/steps/setup/centos/6.5/setup_vagrant_box.yaml +76 -0
  22. data/templates/steps/setup/debian/setup_vagrant_box.yaml +6 -8
  23. data/templates/steps/setup/fedora/configure_system.yaml +18 -0
  24. data/templates/steps/setup/fedora/install_bootloader.yaml +1 -6
  25. data/templates/steps/setup/fedora/update_system.yaml +1 -1
  26. data/templates/virtualbox/archlinux-x86_64.yaml +0 -1
  27. data/templates/virtualbox/centos6.5-vagrant-x86_64.yaml +33 -0
  28. data/templates/virtualbox/centos6.5-x86_64.yaml +20 -10
  29. data/templates/virtualbox/centos7-x86_64.yaml +4 -2
  30. data/templates/virtualbox/debian7-amd64.yaml +11 -13
  31. data/templates/virtualbox/debian7-vagrant-amd64.yaml +32 -0
  32. data/templates/virtualbox/fedora20-x86_64.yaml +4 -2
  33. data/templates/virtualbox/ubuntu-12.04-amd64.yaml +2 -2
  34. data/version.txt +1 -1
  35. metadata +26 -12
  36. checksums.yaml +0 -7
  37. data/templates/vagrant/debian7-amd64.yaml +0 -130
@@ -1,6 +1,19 @@
1
1
  Kameleon CHANGELOG
2
2
  ==================
3
3
 
4
+ version 2.2.4
5
+ -------------
6
+ Released on Sep 2nd 2014
7
+
8
+ - [template] Added the ``-cpu host`` option to qemu to improve performances
9
+ - [template] Made centos image as close as possible as default Centos installation
10
+ - [template] Configured SELinux for Fedora and Centos
11
+ - [template] Fixed Grid'5000 export step
12
+ - [template] Extended vagrant recipes from virtualbox recipes
13
+ - [core] Adding the step elapsed time to the Kamelon output
14
+ - [proxy cache] Restructured persistent cache and improved caching of pipes
15
+ - [proxy cache] Added ``ProxyAdrres`` paramter to polipo to take into account all the host network interfaces
16
+
4
17
  version 2.2.3
5
18
  -------------
6
19
  Released on Aug 19th 2014
@@ -139,14 +139,14 @@ fi
139
139
 
140
140
  function __download {
141
141
  echo "Downloading: $1..."
142
- if which curl >/dev/null; then
143
- curl -# -L --retry 999 --retry-max-time 0 "$1" -o "$2" 2>&1
142
+ if which wget >/dev/null; then
143
+ wget --progress=bar:force "$1" -O "$2" 2>&1
144
144
  else
145
- fail "curl is missing, trying with wget..."
146
- if which wget >/dev/null; then
147
- wget --progress=bar:force "$1" -O "$2" 2>&1
145
+ fail "wget is missing, trying with curl..."
146
+ if which curl >/dev/null; then
147
+ curl -# -L --retry 999 --retry-max-time 0 "$1" -o "$2" 2>&1
148
148
  else
149
- fail "wget is missing, trying with python..."
149
+ fail "curl is missing, trying with python..."
150
150
  if which python >/dev/null; then
151
151
  python -c "
152
152
  import sys
@@ -1,6 +1,7 @@
1
1
  require 'kameleon/recipe'
2
2
  require 'kameleon/context'
3
3
  require 'kameleon/persistent_cache'
4
+ # require 'pry'
4
5
  module Kameleon
5
6
 
6
7
  class Engine
@@ -38,7 +39,7 @@ module Kameleon
38
39
  @cache.name = @recipe.name
39
40
  @cache.mode = @options[:cache] ? :build : :from
40
41
  @cache.cache_path = @options[:from_cache]
41
- @cache.recipe_files = @recipe.files # I'm passing the Pathname objects
42
+ @cache.recipe_files = @recipe.files + @recipe.base_recipes_files# I'm passing the Pathname objects
42
43
  @cache.recipe_path = @recipe.path
43
44
 
44
45
  if @recipe.global["in_context"]["proxy_cache"].nil? then
@@ -151,6 +152,7 @@ module Kameleon
151
152
  def do_steps(section_name)
152
153
  section = @recipe.sections.fetch(section_name)
153
154
  section.sequence do |macrostep|
155
+ macrostep_time = Time.now.to_i
154
156
  if @cache then
155
157
  Kameleon.ui.info("Starting proxy cache server for macrostep '#{macrostep.name}'...")
156
158
  # the following function start a polipo web proxy and stops a previous run
@@ -186,6 +188,7 @@ module Kameleon
186
188
  end
187
189
  end
188
190
  end
191
+ Kameleon.ui.info("Step #{macrostep.name} took: #{Time.now.to_i-macrostep_time} secs")
189
192
  end
190
193
  @cleaned_sections.push(section.name)
191
194
  end
@@ -235,7 +238,7 @@ module Kameleon
235
238
  end
236
239
  first_context = map[first_cmd.key]
237
240
  second_context = map[second_cmd.key]
238
- @cache.cache_cmd_id(cmd.identifier) if @cache
241
+ @cache.cache_cmd_raw(cmd.raw_cmd_id) if @cache
239
242
  first_context.pipe(first_cmd.value, second_cmd.value, second_context)
240
243
  when "rescue"
241
244
  first_cmd, second_cmd = cmd.value
@@ -1,6 +1,7 @@
1
1
  require 'childprocess'
2
2
  require 'singleton'
3
3
  require 'socket'
4
+
4
5
  module Kameleon
5
6
  #This ruby class will control the execution of Polipo web proxy
6
7
  class Persistent_cache
@@ -36,6 +37,7 @@ module Kameleon
36
37
  :proxyPort => @polipo_port,
37
38
  :relaxTransparency =>"true",
38
39
  :daemonise => false,
40
+ :proxyAddress => "0.0.0.0",
39
41
  :logFile => File.join(Kameleon.env.build_path, 'polipo.log')
40
42
  }
41
43
 
@@ -45,10 +47,9 @@ module Kameleon
45
47
  @cache_dir = Kameleon.env.cache_path
46
48
  @polipo_path = nil
47
49
  @cwd = ""
48
- #structure {:cmd => "cmd", :stdout_filename => "file_name"}
49
50
  @cmd_cached = []
50
51
  @cache_path = ""
51
- @current_cmd_id = nil
52
+ @current_raw_cmd = nil
52
53
  @current_step_dir = nil
53
54
  @recipe_file = nil
54
55
  @steps_files = []
@@ -93,7 +94,7 @@ module Kameleon
93
94
 
94
95
  def create_cache_directory(step_name)
95
96
  Kameleon.ui.debug("Creating cache directory #{step_name} for Polipo")
96
- directory_name = File.join(@cache_dir,"#{step_name}")
97
+ directory_name = File.join(@cache_dir,"DATA","#{step_name}")
97
98
  FileUtils.mkdir_p directory_name
98
99
  directory_name
99
100
  end
@@ -135,10 +136,9 @@ module Kameleon
135
136
  end
136
137
 
137
138
 
138
- # This function caches the command with its respective stdout
139
- # a command id is associate to a file
140
- def cache_cmd_id(cmd_identifier)
141
- @current_cmd_id = cmd_identifier
139
+ # This function caches the raw command specified in the recipe
140
+ def cache_cmd_raw(raw_cmd_id)
141
+ @current_raw_cmd = raw_cmd_id
142
142
  return true
143
143
  end
144
144
 
@@ -147,16 +147,18 @@ module Kameleon
147
147
  Kameleon.ui.debug("command: cp #{file_path} #{@cwd}/cache/files/")
148
148
  FileUtils.mkdir_p @current_step_dir + "/data/"
149
149
  FileUtils.cp file_path, @current_step_dir + "/data/"
150
- @cmd_cached.push({:cmd_id => @current_cmd_id,
151
- :cmd => cmd ,
150
+ @cmd_cached.push({:raw_cmd_id => @current_raw_cmd,
152
151
  :stdout_filename => File.basename(file_path)})
153
152
  end
154
153
 
155
154
  def get_cache_cmd(cmd)
156
155
  return false if @mode == :build
157
- cache_line = @cmd_cached.select{ |reg|
158
- (reg[:cmd_id] == @current_cmd_id && reg[:cmd] == cmd) }.first
159
-
156
+ cache_line = @cmd_cached.select{ |reg| reg[:raw_cmd_id] == @current_raw_cmd }.first
157
+ if cache_line.nil? then
158
+ # This error can be due to the improper format of the file cache_cmd_index
159
+ Kameleon.ui.error("Persistent cache missing file")
160
+ raise BuildError, "Failed to use persistent cache"
161
+ end
160
162
  return File.new("#{@current_step_dir}/data/#{cache_line[:stdout_filename]}","r")
161
163
  end
162
164
 
@@ -164,22 +166,34 @@ module Kameleon
164
166
  @polipo_process.stop
165
167
  Kameleon.ui.info("Stopping web proxy polipo")
166
168
  Kameleon.ui.info("Finishing persistent cache with last files")
167
-
169
+ cache_metadata_dir = File.join(@cache_dir,"metadata")
168
170
  if @mode == :build then
169
- File.open("#{@cache_dir}/cache_cmd_index",'w+') do |f|
171
+ File.open("#{cache_metadata_dir}/cache_cmd_index",'w+') do |f|
170
172
  f.puts(@cmd_cached.to_yaml)
171
173
  end
172
174
 
173
175
  unless @recipe_files.empty?
174
- recipe_dir = Pathname.new(common_prefix(@recipe_files))
175
176
  all_files = @recipe_files.push(@recipe_path)
176
- Kameleon::Utils.copy_files(recipe_dir, @cache_dir, all_files)
177
+ recipe_dir = Pathname.new(common_prefix(all_files))
178
+ cached_recipe_dir = Pathname.new(File.join(@cache_dir,"recipe"))
179
+ # binding.pry
180
+ Kameleon::Utils.copy_files(recipe_dir, cached_recipe_dir, all_files)
177
181
  end
178
182
  ## Saving metadata information
179
183
  Kameleon.ui.info("Caching recipe")
180
- File.open("#{@cached_recipe_dir}/header",'w+') do |f|
181
- f.puts({:recipe_path => @recipe_path.to_s}.to_yaml)
184
+
185
+ File.open("#{cache_metadata_dir}/header",'w+') do |f|
186
+ f.puts({:recipe_path => @recipe_path.basename.to_s}.to_yaml)
187
+ f.puts({:date => Time.now.to_i}.to_yaml)
188
+ end
189
+
190
+ #Removing empty directories
191
+ cache_data_dir = File.join(@cache_dir,"DATA")
192
+ Dir.foreach(cache_data_dir) do |item|
193
+ dir_temp = File.join(cache_data_dir,item)
194
+ Dir.delete(dir_temp) if File.stat(dir_temp).nlink == 2
182
195
  end
196
+
183
197
  pack
184
198
  end
185
199
  end
@@ -193,20 +207,24 @@ module Kameleon
193
207
  raise BuildError, "Failed to untar the persistent cache file"
194
208
  end
195
209
  ## We have to load the file
196
- @cmd_cached = YAML.load(File.read("#{@cache_dir}/cache_cmd_index"))
210
+ metadata_dir = File.join(@cache_dir,"metadata")
211
+ @cmd_cached = YAML.load(File.read("#{metadata_dir}/cache_cmd_index"))
197
212
  end
198
213
  @activated = true
199
- @cached_recipe_dir = @cache_dir
200
- FileUtils.mkdir_p @cached_recipe_dir
214
+ #@cached_recipe_dir = @cache_dir
215
+ FileUtils.mkdir_p @cache_dir
216
+ # Creating sctructure of the cache
217
+ FileUtils.mkdir_p File.join(@cache_dir,"recipe")
218
+ FileUtils.mkdir_p File.join(@cache_dir,"DATA")
219
+ FileUtils.mkdir_p File.join(@cache_dir,"metadata")
201
220
  end
202
221
 
203
222
  def get_recipe()
204
223
  cached_recipe=Dir.mktmpdir("cache")
205
- puts "cache path : #{@cache_path}"
206
- execute("tar","-xf #{@cache_path} -C #{cached_recipe} .")
224
+ execute("tar","-xf #{@cache_path} -C #{cached_recipe} ./recipe ./metadata")
207
225
  Kameleon.ui.info("Getting cached recipe")
208
- recipe_header = YAML::load(File.read("#{cached_recipe}/header"))
209
- recipe_file = recipe_header[:recipe_path]
226
+ recipe_header = YAML::load(File.read(File.join(cached_recipe,"metadata","header")))
227
+ recipe_file = File.join(cached_recipe,"recipe",recipe_header[:recipe_path])
210
228
  return recipe_file
211
229
  end
212
230
 
@@ -327,13 +327,6 @@ module Kameleon
327
327
  consistency_check
328
328
  resolve_checkpoint unless @checkpoint.nil?
329
329
 
330
- Kameleon.ui.info("Resolving variables")
331
- @sections.values.each do |section|
332
- section.macrosteps.each do |macrostep|
333
- macrostep.resolve_variables!(@global)
334
- end
335
- end
336
-
337
330
  @sections.values.each do |section|
338
331
  section.macrosteps.each do |macrostep|
339
332
  # First pass : resolve aliases
@@ -347,6 +340,18 @@ module Kameleon
347
340
  # flatten for multiple-command alias + variables
348
341
  Kameleon.ui.debug("Resolving check statements for macrostep '#{macrostep.name}'")
349
342
  macrostep.microsteps.each { |microstep| microstep.commands.flatten! }
343
+ end
344
+ end
345
+
346
+ Kameleon.ui.info("Resolving variables")
347
+ @sections.values.each do |section|
348
+ section.macrosteps.each do |macrostep|
349
+ macrostep.resolve_variables!(@global)
350
+ end
351
+ end
352
+
353
+ @sections.values.each do |section|
354
+ section.macrosteps.each do |macrostep|
350
355
  # Second pass : resolve variables + clean/init hooks
351
356
  macrostep.microsteps.each do |microstep|
352
357
  microstep.commands.map! do |cmd|
@@ -3,11 +3,13 @@ module Kameleon
3
3
  class Command
4
4
 
5
5
  attr_accessor :string_cmd
6
+ attr_accessor :raw_cmd_id
6
7
  attr_accessor :microstep_name
7
8
  attr_accessor :identifier
8
9
 
9
10
  def initialize(yaml_cmd, microstep_name)
10
11
  @string_cmd = YAML.dump(yaml_cmd).gsub("---", "").strip
12
+ @raw_cmd_id = Digest::SHA1.hexdigest(YAML.dump(yaml_cmd).gsub("---", "").strip)
11
13
  @microstep_name = microstep_name
12
14
  @identifier = nil
13
15
  end
@@ -50,7 +50,10 @@ global:
50
50
  - $$distrib
51
51
  - "fedora"
52
52
 
53
- disable_selinux: true
53
+ # SELinux configuration
54
+ selinux: permissive ## Can take one of these three values: enforcing, permissive or disabled
55
+ selinuxtype: targeted # Possible values are: strict, targeted
56
+
54
57
  ssh_config_file: $$kameleon_cwd/ssh_config
55
58
  out_context:
56
59
  cmd: ssh -F $$ssh_config_file $${kameleon_recipe_name} -t /bin/bash
@@ -58,7 +61,7 @@ global:
58
61
  proxy_cache: 10.0.2.2
59
62
 
60
63
  in_context:
61
- cmd: ssh -F $$ssh_config_file $${kameleon_recipe_name} -t chroot $$rootfs /bin/bash
64
+ cmd: ssh -F $$ssh_config_file $${kameleon_recipe_name} -t /bin/bash
62
65
  workdir: /root/kameleon_workdir
63
66
  proxy_cache: 10.0.2.2
64
67
 
@@ -67,12 +70,14 @@ bootstrap:
67
70
  - prepare_qemu
68
71
  - start_qemu
69
72
  - install_requirements:
70
- - packages: parted e2fsprogs yum rpm lynx
71
- - initialize_disk:
73
+ - packages: parted e2fsprogs yum rpm lynx extlinux
74
+ - initialize_disk
72
75
  - yum_bootstrap:
73
76
  - mirror_packages_url: http://mirrors.kernel.org/$$distrib/$$release/os/$$arch/Packages/
74
- - include_pkgs: findutils util-linux
75
- - start_chroot
77
+ - include_pkgs: >
78
+ findutils yum util-linux dhclient vim-minimal net-tools openssh-server
79
+ kernel kernel-devel acpid
80
+ - switch_context_qemu
76
81
 
77
82
  #== Install and configuration steps
78
83
  setup:
@@ -81,9 +86,13 @@ setup:
81
86
  - minimal_install
82
87
  - install_software:
83
88
  - packages: >
84
- kernel kernel-devel syslinux-extlinux bash-completion kbd dhclient
85
- sudo openssh-server openssh-clients ntp ntpdate dhclient
86
- net-tools NetworkManager
89
+ syslinux-extlinux kbd sudo openssh-clients ntp ntpdate
90
+ rsync bridge-utils bzip2 cronie cronie-anacron crontabs
91
+ dash dhclient dhcp-common dracut dracut-kernel file fuse gnupg2
92
+ iptables-ipv6 libuser logrotate m4 openssh-server passwd
93
+ pciutils-libs rsyslog dbus system-config-firewall-base which
94
+ findutils yum util-linux vim-minimal net-tools openssh-server
95
+ kernel kernel-devel acpid
87
96
  - install_bootloader
88
97
  - configure_system:
89
98
  - locales: POSIX C en_US fr_FR de_DE
@@ -50,7 +50,10 @@ global:
50
50
  - $$distrib
51
51
  - "fedora"
52
52
 
53
- disable_selinux: true
53
+ # SELinux configuration
54
+ selinux: permissive ## Can take one of these three values: enforcing, permissive or disabled
55
+ selinuxtype: targeted # Possible values are: strict, targeted
56
+
54
57
  ssh_config_file: $$kameleon_cwd/ssh_config
55
58
  out_context:
56
59
  cmd: ssh -F $$ssh_config_file $${kameleon_recipe_name} -t /bin/bash
@@ -49,7 +49,10 @@ global:
49
49
  - $$distrib/$$release
50
50
  - $$distrib
51
51
 
52
- disable_selinux: true
52
+ # SELinux configuration
53
+ selinux: permissive ## Can take one of these three values: enforcing, permissive or disabled
54
+ selinuxtype: targeted # Possible values are: strict, targeted
55
+
53
56
  ssh_config_file: $$kameleon_cwd/ssh_config
54
57
  out_context:
55
58
  cmd: ssh -F $$ssh_config_file $${kameleon_recipe_name} -t /bin/bash
@@ -0,0 +1,65 @@
1
+ - fix_yum:
2
+ - check_cmd_out: yum
3
+ - exec_out: sed -i "s/opts.ssl_verify_host/2/g" /usr/lib/pymodules/python2.7/urlgrabber/grabber.py
4
+
5
+ - init_rpm_db:
6
+ - check_cmd_out: rpm
7
+ - exec_out: mkdir -p $$rootfs/var/lib/rpm
8
+ - exec_out: rpm --root $$rootfs --initdb
9
+
10
+ - fetch_release_package:
11
+ - check_cmd_out: lynx
12
+ - exec_out: RELEASE_PACKAGE_URL=$(lynx $$mirror_packages_url -dump -listonly -nonumbers | grep $${distrib}-release | head -1)
13
+ - exec_out: |
14
+ if [ -z "${RELEASE_PACKAGE_URL-unset}" ]; then
15
+ fail "$${distrib}-release package not found!"
16
+ fi
17
+
18
+ - install_distrib_release:
19
+ - download_file_out:
20
+ - $RELEASE_PACKAGE_URL
21
+ - $KAMELEON_WORKDIR/$${distrib}-release.rpm
22
+ - exec_out: rpm --root $$rootfs -ivh --force-debian --nodeps $KAMELEON_WORKDIR/$${distrib}-release.rpm
23
+
24
+ - install_yum:
25
+ - exec_out: ln -sf $$rootfs/etc/pki/ /etc/pki
26
+ - exec_out: yum --installroot $$rootfs -y install yum
27
+ - exec_out: echo $${release} > $$rootfs/etc/yum/vars/releasever
28
+
29
+ - mount_chroot:
30
+ - exec_out: mount -o bind /dev $$rootfs/dev
31
+ - exec_out: mount -o bind /dev/pts $$rootfs/dev/pts
32
+ - exec_out: mount -t proc /proc $$rootfs/proc
33
+ - exec_out: mount -t sysfs /sys $$rootfs/sys
34
+ - exec_out: cp /etc/resolv.conf $$rootfs/etc/resolv.conf
35
+
36
+ - install_packages:
37
+ - exec_out: chroot $$rootfs yum install --releasever=$$release -y $$include_pkgs
38
+
39
+ - enable_services:
40
+ - exec_out: chroot $$rootfs chkconfig network on
41
+ - exec_out: chroot $$rootfs chkconfig sshd on
42
+
43
+ - set_interface:
44
+ - write_out:
45
+ - $$rootfs/etc/sysconfig/network-scripts/ifcfg-eth0
46
+ - |
47
+ DEVICE=eth0
48
+ BOOTPROTO=dhcp
49
+ ONBOOT=yes
50
+ HOSTNAME=$$hostname
51
+ NM_CONTROLLED=no
52
+ TYPE=Ethernet
53
+ - write_out:
54
+ - $$rootfs/etc/sysconfig/network
55
+ - |
56
+ NETWORKING=yes
57
+ HOSTNAME=$$hostname
58
+
59
+ - umount_chroot:
60
+ - on_clean:
61
+ - umount_out: $$rootfs/sys
62
+ - umount_out: $$rootfs/proc
63
+ - umount_out: $$rootfs/dev/pts
64
+ - umount_out: $$rootfs/dev
65
+ - exec_out: rm -f $$rootfs/etc/resolv.conf
@@ -22,6 +22,7 @@
22
22
  -cdrom $$kameleon_cwd/boot2kameleon.iso \
23
23
  -drive file="$$qemu_image_disk",cache=unsafe,media=disk,if=virtio,id=drive0 \
24
24
  -smp $${qemu_cpu} \
25
+ -cpu host \
25
26
  -m $$qemu_memory_size \
26
27
  -rtc base=localtime \
27
28
  -net nic,model=virtio -net user \
@@ -31,13 +32,14 @@
31
32
  -daemonize -vnc :1 $LOADVM
32
33
  - exec_local: |
33
34
  NEXT_WAIT_TIME=0
34
- until ssh-keyscan -T 1 -4 -p $$qemu_ssh_port localhost 2>&1 | grep -e ssh-rsa -e ssh-dsa &> /dev/null || [ $NEXT_WAIT_TIME -eq $$boot_timeout ];
35
+ SSH_AVAILABLE=0
36
+ until ssh-keyscan -T 1 -4 -p $$qemu_ssh_port localhost 2>&1 | grep -e ssh-rsa -e ssh-dsa &> /dev/null && SSH_AVAILABLE=1 || [ $NEXT_WAIT_TIME -eq $$boot_timeout ];
35
37
  do
36
38
  echo -en "\rWaiting for SSH to become available for out_context...($(( $$boot_timeout - 1 - NEXT_WAIT_TIME++ ))s)"
37
39
  done
38
40
  echo ""
39
41
  - rescue:
40
- - exec_local: ssh-keyscan -T 1 -4 -p $$qemu_ssh_port localhost 2>&1 | grep -e ssh-rsa -e ssh-dsa &> /dev/null
42
+ - exec_local: test $SSH_AVAILABLE -eq 1
41
43
  - breakpoint: Failed to connect to VM via SSH. Please verify the VM successfully booted with a vnc client.
42
44
 
43
45
  - force_shutdown_qemu_vm: