pennyworth-tool 0.1.0 → 0.2.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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +38 -4
  3. data/examples/README.md +1 -1
  4. data/examples/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/config.sh +0 -0
  5. data/examples/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/config.xml +0 -0
  6. data/examples/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/root/etc/sysconfig/network/ifcfg-eth0 +0 -0
  7. data/examples/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/root/home/vagrant/.ssh/authorized_keys +0 -0
  8. data/lib/pennyworth/cli.rb +11 -11
  9. data/lib/pennyworth/commands/base_command.rb +8 -8
  10. data/lib/pennyworth/commands/build_base_command.rb +36 -14
  11. data/lib/pennyworth/commands/import_base_command.rb +4 -4
  12. data/lib/pennyworth/commands/list_command.rb +1 -1
  13. data/lib/pennyworth/commands/setup_command.rb +43 -10
  14. data/lib/pennyworth/exceptions.rb +0 -12
  15. data/lib/pennyworth/local_command_runner.rb +0 -2
  16. data/lib/pennyworth/matchers.rb +148 -0
  17. data/lib/pennyworth/remote_command_runner.rb +1 -2
  18. data/lib/pennyworth/runner.rb +1 -1
  19. data/lib/pennyworth/settings.rb +2 -2
  20. data/lib/pennyworth/spec.rb +1 -0
  21. data/lib/pennyworth/version.rb +1 -3
  22. data/lib/pennyworth/vm.rb +26 -1
  23. data/spec/build_base_command_spec.rb +18 -10
  24. data/spec/data/{kiwi → boxes}/definitions/base_opensuse12.3_kvm/config.sh +0 -0
  25. data/spec/data/{kiwi → boxes}/definitions/base_opensuse12.3_kvm/config.xml +0 -0
  26. data/spec/data/{kiwi → boxes}/definitions/base_opensuse12.3_kvm/root/home/vagrant/.ssh/authorized_keys +0 -0
  27. data/spec/data/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/config.sh +0 -0
  28. data/spec/data/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/config.xml +0 -0
  29. data/spec/data/{kiwi → boxes}/definitions/base_opensuse13.1_kvm/root/home/vagrant/.ssh/authorized_keys +0 -0
  30. data/spec/data/{kiwi3/definitions/base_opensuse12.3_kvm/.gitkeep → boxes/definitions/veewee_definition/definition.rb} +0 -0
  31. data/spec/data/{kiwi2 → boxes2}/definitions/base_opensuse12.3_kvm/config.sh +0 -0
  32. data/spec/data/{kiwi2 → boxes2}/definitions/base_opensuse12.3_kvm/config.xml +0 -0
  33. data/spec/data/{kiwi2 → boxes2}/definitions/base_opensuse12.3_kvm/root/home/vagrant/.ssh/authorized_keys +0 -0
  34. data/spec/data/{kiwi2 → boxes2}/definitions/base_opensuse13.1_kvm/config.sh +0 -0
  35. data/spec/data/{kiwi2 → boxes2}/definitions/base_opensuse13.1_kvm/config.xml +0 -0
  36. data/spec/data/{kiwi2 → boxes2}/definitions/base_opensuse13.1_kvm/root/home/vagrant/.ssh/authorized_keys +0 -0
  37. data/spec/data/{kiwi3/definitions/base_opensuse13.1_kvm → boxes3/definitions/base_opensuse12.3_kvm}/.gitkeep +0 -0
  38. data/spec/data/{kiwi4/definitions/base_opensuse12.3_kvm → boxes3/definitions/base_opensuse13.1_kvm}/.gitkeep +0 -0
  39. data/spec/data/{kiwi4/definitions/base_opensuse13.1_kvm → boxes4/definitions/base_opensuse12.3_kvm}/.gitkeep +0 -0
  40. data/spec/data/boxes4/definitions/base_opensuse13.1_kvm/.gitkeep +0 -0
  41. data/spec/import_base_command_spec.rb +15 -15
  42. data/spec/matchers_spec.rb +175 -0
  43. data/spec/remote_command_runner_spec.rb +5 -15
  44. data/spec/setup_command_spec.rb +63 -1
  45. metadata +33 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a540a31b068d26b1deebf691d65d60f94dcc343a
4
- data.tar.gz: da36dd75b9c759acec70e477bafce396ebe5982a
3
+ metadata.gz: 381073546ae4950af4b0083f6a455038cf180ad9
4
+ data.tar.gz: 0ea747bfde18986eb72f3563bca2e8e738a3b546
5
5
  SHA512:
6
- metadata.gz: 44914ae8d8bc9d39451349fa82df0f59e64bc59bef3ad3a3ff384707423362ff5923fdaed46c8720d25abd45baa33cce5396b2c7ab72794528a1cc81906b85c2
7
- data.tar.gz: 95a678007e0dd64a1957faeb4a62aea969569ed31810255ae6d3120101d376007d02f0912b70476767b550db0663c9a44c8a88c6f515e1cce419621fa057d754
6
+ metadata.gz: eedf5019ded3f9c369e1529b42fd7bd972d602b6a7ed66ccda0543b1c12d8fe22c23b482424b1982a378e87896734309e2d071a99d1dbf604ccf4c39f7888342
7
+ data.tar.gz: cd0d7f4427b75622b4a7393bb3698be386e09d66f1ecd6cf869880556a6ecb672f2b6e3c148f94effa5b0a59a081b6651c9968ea7aeeb37c142a98c176a2881f
data/README.md CHANGED
@@ -300,10 +300,38 @@ with the running machine (via SSH). It supports the following methods:
300
300
  * `run_command(command, *args, options = {})`
301
301
  `run_command(command_and_args, options = {})`
302
302
 
303
- Executes a command on the running machine. The execution is implemented
304
- using [Cheetah](https://github.com/opensuse/cheetah) and the `run_command`
305
- method behaves mostly the same as
306
- [`Cheetah.run`](http://rubydoc.info/github/openSUSE/cheetah/master/Cheetah.run).
303
+ Executes a command on the running machine. A VM::CommandResult instance is
304
+ returned which can be used with special RSpec matchers to define the
305
+ expected behavior. Examples:
306
+
307
+ # Expect exit code 0 and no stderr
308
+ expect(result).to succeed
309
+
310
+ # Expect exit code 0, ignoring any stderr
311
+ expect(@vm.run_command("ls -l")).to succeed.with_or_without_stderr
312
+
313
+ # Expect a certain stdout
314
+ expect(result).to succeed.and have_stdout(/foo.*bar/)
315
+ expect(result).to succeed.and have_stdout("foo bar")
316
+
317
+ # Expect exit code 0, but some stderr output
318
+ expect(result).to succeed.with_stderr
319
+
320
+ # Expect the stdout to include a certain string
321
+ expect(result).to include_stdout("foo bar")
322
+
323
+ # Expect exit code != 0
324
+ expect(result).to fail
325
+
326
+ # Expect a specific exit code
327
+ expect(result).to fail.with_exit_code(15)
328
+
329
+ # Expect specific stderr
330
+ expect(result).to have_stderr(/This.*error/)
331
+ expect(result).to have_stderr("This is an error")
332
+
333
+ # Expect stderr to include a certain string
334
+ expect(result).to include_stderr("Warning"
307
335
 
308
336
  * `inject_file(source, destination)`
309
337
 
@@ -333,6 +361,12 @@ VM
333
361
  : Virtual machine ran in KVM. Pennyworth supports running VMs described in a
334
362
  Vagrantfile as well as non vagrant managed ones.
335
363
 
364
+ ## Release Cycle
365
+
366
+ As for now we don't have a time based release cycle. We update and publish the
367
+ gem as needed. However don't hesitate to report bugs or better yet submit fixes
368
+ as pull requests. We will make sure to update the gem with the latest code.
369
+
336
370
  ## Further information
337
371
 
338
372
  Further information like a [FAQ](https://github.com/SUSE/pennyworth/wiki/Debugging)
@@ -6,7 +6,7 @@ It provides a very basic openSUSE 13.1 machine.
6
6
 
7
7
  ## Base Image
8
8
 
9
- The base image is defined in `examples/kiwi/definitions/base_opensuse13.1_kvm`.
9
+ The base image is defined in `examples/boxes/definitions/base_opensuse13.1_kvm`.
10
10
  It can be build with:
11
11
 
12
12
  `$ bin/pennyworth -d examples/ build-base`
@@ -26,8 +26,8 @@ module Pennyworth
26
26
  provide a well-defined test environment for automated tests.
27
27
 
28
28
  Use the global `--definitions-dir` option to specify the path to the
29
- directory containing Kiwi and Vagrant definitions. The directory needs to
30
- contain `kiwi/` and `vagrant/` subdirectories. Default is `~/.pennyworth`.
29
+ directory containing Kiwi, Veewee and Vagrant definitions. The directory needs to
30
+ contain `boxes/` and `vagrant/` subdirectories. Default is `~/.pennyworth`.
31
31
 
32
32
  Pennyworth writes a log file to `/tmp/pennyworth.log`.
33
33
 
@@ -141,13 +141,13 @@ module Pennyworth
141
141
  LONGDESC
142
142
  arg_name "IMAGE_NAME", :optional
143
143
  command "build-base" do |c|
144
- c.flag [:kiwi_tmp_dir, :k], :type => String, :required => false,
145
- :desc => "Temporary KIWI directory for building the Vagrant box.",
146
- :arg_name => "KIWI-TMP-Dir"
144
+ c.flag [:temp_dir, :t], :type => String, :required => false,
145
+ :desc => "Temporary directory for building the Vagrant box.",
146
+ :arg_name => "TMP-Dir"
147
147
  c.action do |global_options,options,args|
148
148
  image_name = args.shift
149
- tmp_dir = options[:kiwi_tmp_dir] || "/tmp/pennyworth-kiwi-builds"
150
- BuildBaseCommand.new(Cli.settings.kiwi_dir).execute(tmp_dir, image_name)
149
+ tmp_dir = options[:temp_dir] || "/tmp/pennyworth-builds"
150
+ BuildBaseCommand.new(Cli.settings.boxes_dir).execute(tmp_dir, image_name)
151
151
  end
152
152
  end
153
153
 
@@ -165,7 +165,7 @@ module Pennyworth
165
165
  image_name = args.shift
166
166
  VagrantCommand.setup_environment(@@settings.vagrant_dir)
167
167
  if options[:url]
168
- kiwi_dir = File.expand_path("~/.pennyworth/kiwi")
168
+ boxes_dir = File.expand_path("~/.pennyworth/boxes")
169
169
  remote_url = options[:url]
170
170
  opts = {
171
171
  local: false,
@@ -175,12 +175,12 @@ module Pennyworth
175
175
  STDERR.puts "You need to specify a definitions directory when not using --url."
176
176
  exit 1
177
177
  end
178
- kiwi_dir = Cli.settings.kiwi_dir
178
+ boxes_dir = Cli.settings.boxes_dir
179
179
  opts = {
180
180
  local: true
181
181
  }
182
182
  end
183
- ImportBaseCommand.new(kiwi_dir, remote_url).execute(image_name, opts)
183
+ ImportBaseCommand.new(boxes_dir, remote_url).execute(image_name, opts)
184
184
  end
185
185
  end
186
186
 
@@ -238,7 +238,7 @@ module Pennyworth
238
238
  command :list do |c|
239
239
  c.action do |global_options,options,args|
240
240
  VagrantCommand.setup_environment(@@settings.vagrant_dir)
241
- ListCommand.new(Cli.settings.kiwi_dir).execute
241
+ ListCommand.new(Cli.settings.boxes_dir).execute
242
242
  end
243
243
  end
244
244
 
@@ -18,11 +18,11 @@
18
18
  module Pennyworth
19
19
  class BaseCommand < Command
20
20
 
21
- attr_reader :kiwi_dir
21
+ attr_reader :boxes_dir
22
22
 
23
- def initialize(kiwi_dir, remote_url = nil)
23
+ def initialize(boxes_dir, remote_url = nil)
24
24
  super()
25
- @kiwi_dir = kiwi_dir
25
+ @boxes_dir = boxes_dir
26
26
  @remote_url = remote_url
27
27
  end
28
28
 
@@ -41,7 +41,7 @@ module Pennyworth
41
41
  end
42
42
 
43
43
  def local_base_images
44
- Dir.glob(File.join(@kiwi_dir, "definitions", "*")).
44
+ Dir.glob(File.join(@boxes_dir, "definitions", "*")).
45
45
  select { |f| File.directory?(f) }.
46
46
  map { |d| File.basename(d) }
47
47
  end
@@ -59,7 +59,7 @@ module Pennyworth
59
59
 
60
60
  def read_box_sources_state(box_name)
61
61
  sources = Hash.new
62
- source_dir = File.join(@kiwi_dir, "definitions", box_name)
62
+ source_dir = File.join(@boxes_dir, "definitions", box_name)
63
63
  Find.find(source_dir) do |file|
64
64
  next if File.directory?(file)
65
65
  relative_path = file.gsub(/^#{File.join(source_dir, "/")}/, "")
@@ -69,7 +69,7 @@ module Pennyworth
69
69
  end
70
70
 
71
71
  def read_box_target_state(box_name)
72
- box_file = File.join(@kiwi_dir, box_name) + ".box"
72
+ box_file = File.join(@boxes_dir, box_name) + ".box"
73
73
  if File.exist?(box_file)
74
74
  target_state = Digest::MD5.file(box_file).hexdigest
75
75
  end
@@ -77,13 +77,13 @@ module Pennyworth
77
77
  end
78
78
 
79
79
  def write_box_state_file(box_state)
80
- File.open(File.join(@kiwi_dir, "box_state.yaml"), "w") do |f|
80
+ File.open(File.join(@boxes_dir, "box_state.yaml"), "w") do |f|
81
81
  f.write(box_state.to_yaml)
82
82
  end
83
83
  end
84
84
 
85
85
  def read_local_box_state_file
86
- box_state_file = File.join(@kiwi_dir, "box_state.yaml")
86
+ box_state_file = File.join(@boxes_dir, "box_state.yaml")
87
87
  if File.exist? box_state_file
88
88
  box_state = YAML.load_file(box_state_file)
89
89
  else
@@ -18,7 +18,7 @@
18
18
  module Pennyworth
19
19
  class BuildBaseCommand < BaseCommand
20
20
 
21
- def initialize(kiwi_dir)
21
+ def initialize(boxes_dir)
22
22
  super
23
23
  end
24
24
 
@@ -29,7 +29,7 @@ module Pennyworth
29
29
  images = process_base_image_parameter(local_base_images, image_name)
30
30
  log "Creating base images..."
31
31
  images.each do |image|
32
- Dir.chdir kiwi_dir do
32
+ Dir.chdir(boxes_dir) do
33
33
  log
34
34
  log "--- #{image} ---"
35
35
  source_state = read_box_sources_state(image)
@@ -38,10 +38,7 @@ module Pennyworth
38
38
  log " Sources not changed, skipping build"
39
39
  else
40
40
  log " Building base image..."
41
- base_image_create(File.join(kiwi_dir, "definitions", image), tmp_dir)
42
- log " Exporting image as box for vagrant..."
43
- base_image_export(File.join(kiwi_dir, image), tmp_dir)
44
- base_image_cleanup_build(tmp_dir)
41
+ base_image_create(image, tmp_dir)
45
42
 
46
43
  box_state[image] = {
47
44
  "sources" => source_state,
@@ -55,9 +52,31 @@ module Pennyworth
55
52
 
56
53
  private
57
54
 
58
- # Creates a KVM image from the according Kiwi description.
59
- # See pennyworth/kiwi/definitions for the definitions we use.
60
- def base_image_create(description_dir, tmp_dir)
55
+ # Creates a KVM image from the according Kiwi or Veewe description.
56
+ def base_image_create(image, tmp_dir)
57
+ description_dir = File.join(boxes_dir, "definitions", image)
58
+
59
+ if File.exists?(File.join(description_dir, "config.xml"))
60
+ build_kiwi(image, tmp_dir)
61
+ elsif File.exists?(File.join(description_dir, "definition.rb"))
62
+ build_veewee(image)
63
+ else
64
+ raise BuildFailed, "Unknown definition format in '#{description_dir}'. " \
65
+ "Supported are Kiwi and Veewee definitions"
66
+ end
67
+ end
68
+
69
+ def build_veewee(image)
70
+ Cheetah.run "veewee", "kvm", "build", image, "--force", "--auto"
71
+ Cheetah.run "veewee", "kvm", "halt", image
72
+ log " Exporting image as box for vagrant..."
73
+ Cheetah.run "veewee", "kvm", "export", image, "--force"
74
+ rescue Cheetah::ExecutionFailed => e
75
+ raise ExecutionFailed.new(e)
76
+ end
77
+
78
+ def build_kiwi(image, tmp_dir)
79
+ description_dir = File.join(boxes_dir, "definitions", image)
61
80
  FileUtils.mkdir_p(tmp_dir)
62
81
  logfile = "#{tmp_dir}/kiwi-terminal-output.log"
63
82
  log " The build log is available under #{logfile}"
@@ -65,17 +84,20 @@ module Pennyworth
65
84
  Cheetah.run "sudo", "/usr/sbin/kiwi", "--build", description_dir,
66
85
  "--destdir", tmp_dir, "--logfile", "#{logfile}",
67
86
  :stdout => :capture
68
- rescue Cheetah::ExecutionFailed => e
69
- raise ExecutionFailed.new(e)
70
- end
87
+ rescue Cheetah::ExecutionFailed => e
88
+ raise ExecutionFailed.new(e)
89
+ end
90
+ log " Exporting image as box for vagrant..."
91
+ base_image_export(image, tmp_dir)
92
+ base_image_cleanup_build(tmp_dir)
71
93
  end
72
94
 
73
- def base_image_export(kiwi_dir, tmp_dir)
95
+ def base_image_export(name, tmp_dir)
74
96
  Dir.chdir(tmp_dir) do
75
97
  image = Dir.glob("*.box").first
76
98
  if image
77
99
  from_file = File.join(tmp_dir, image)
78
- to_file = kiwi_dir.gsub(/\/$/, "") + ".box"
100
+ to_file = File.join(boxes_dir, name).gsub(/\/$/, "") + ".box"
79
101
  begin
80
102
  Cheetah.run "sudo", "mv", from_file, to_file, :stdout => :capture
81
103
  rescue Cheetah::ExecutionFailed => e
@@ -58,7 +58,7 @@ module Pennyworth
58
58
  end
59
59
 
60
60
  def read_import_state_file
61
- import_state_file = File.join(kiwi_dir, "import_state.yaml")
61
+ import_state_file = File.join(boxes_dir, "import_state.yaml")
62
62
  if File.exist? import_state_file
63
63
  import_state = YAML.load_file(import_state_file)
64
64
  else
@@ -68,9 +68,9 @@ module Pennyworth
68
68
  end
69
69
 
70
70
  def write_import_state_file(import_state)
71
- FileUtils.mkdir_p(kiwi_dir) unless Dir.exists?(kiwi_dir)
71
+ FileUtils.mkdir_p(boxes_dir) unless Dir.exists?(boxes_dir)
72
72
 
73
- File.open(File.join(kiwi_dir, "import_state.yaml"), "w") do |f|
73
+ File.open(File.join(boxes_dir, "import_state.yaml"), "w") do |f|
74
74
  f.write(import_state.to_yaml)
75
75
  end
76
76
  end
@@ -102,7 +102,7 @@ module Pennyworth
102
102
  # environments.
103
103
  def base_image_import(box, options)
104
104
  if options[:local]
105
- box_path = File.join(kiwi_dir, box + ".box")
105
+ box_path = File.join(boxes_dir, box + ".box")
106
106
  else
107
107
  box_path = URLs.join(@remote_url, options[:subdir], box + ".box")
108
108
  end
@@ -18,7 +18,7 @@
18
18
  module Pennyworth
19
19
  class ListCommand < BaseCommand
20
20
  def execute
21
- if @kiwi_dir
21
+ if @boxes_dir
22
22
  puts "Vagrant box definitions managed by pennyworth:"
23
23
  local_base_images.each do |b|
24
24
  puts " #{b}"
@@ -36,6 +36,9 @@ module Pennyworth
36
36
  allow_libvirt_access
37
37
  allow_qemu_kvm_access
38
38
  allow_arp_access
39
+
40
+ # Enable required services
41
+ enable_services
39
42
  end
40
43
 
41
44
  def show_warning_for_unsupported_platforms
@@ -45,7 +48,7 @@ module Pennyworth
45
48
 
46
49
  if os_release
47
50
  version = os_release[/^VERSION_ID="(.*)"/, 1]
48
- distribution = os_release[/^NAME="(.*)"/, 1]
51
+ distribution = os_release[/^NAME=(.*)/, 1]
49
52
  end
50
53
 
51
54
  if !os_release || !supported_os.include?("#{distribution} #{version}")
@@ -59,6 +62,32 @@ module Pennyworth
59
62
  File.read(os_release_file) if File.exist?(os_release_file)
60
63
  end
61
64
 
65
+ def vagrant_installed?
66
+ begin
67
+ vagrant = Cheetah.run "rpm", "-q", "vagrant", stdout: :capture
68
+ rescue
69
+ return false
70
+ end
71
+
72
+ @vagrant_version = vagrant.lines.select { |plugin| plugin.start_with?("vagrant") }
73
+
74
+ !vagrant.match(/vagrant-[1-]\.[7-]\.[2-]/).nil?
75
+ end
76
+
77
+ def vagrant_libvirt_installed?
78
+ begin
79
+ vagrant_libvirt = Cheetah.run "vagrant", "plugin", "list", stdout: :capture
80
+ rescue
81
+ return false
82
+ end
83
+
84
+ @vagrant_libvirt_version = vagrant_libvirt.lines.select { |plugin|
85
+ plugin.start_with?("vagrant-libvirt")
86
+ }
87
+
88
+ !vagrant_libvirt.match(/vagrant-libvirt \(\d\.\d\.29|[3-9]\d\)/).nil?
89
+ end
90
+
62
91
  private
63
92
 
64
93
  def zypper_install(package)
@@ -75,22 +104,19 @@ module Pennyworth
75
104
 
76
105
  def install_packages
77
106
  log "Installing packages:"
78
-
79
107
  packages = config["packages"]["local"]
80
108
 
81
109
  if config["packages"][base_system]
82
110
  packages += config["packages"][base_system]
83
111
  end
84
112
 
113
+ config["packages"]["remote"].reject! { |url| url.match(/vagrant_/) && vagrant_installed? }
114
+ packages += config["packages"]["remote"]
115
+
85
116
  packages.each do |name|
86
117
  log " * Installing #{name}..."
87
118
  zypper_install(name)
88
119
  end
89
-
90
- config["packages"]["remote"].each do |url|
91
- log " * Downloading and installing #{url}..."
92
- zypper_install(url)
93
- end
94
120
  end
95
121
 
96
122
  # The kvm package does come with a udev rule file to adjust ownership of
@@ -104,9 +130,10 @@ module Pennyworth
104
130
  end
105
131
 
106
132
  def install_vagrant_plugin
107
- log "Installing libvirt plugin for Vagrant..."
108
-
109
- Cheetah.run "vagrant", "plugin", "install", "vagrant-libvirt"
133
+ if !vagrant_libvirt_installed?
134
+ log "Installing libvirt plugin for Vagrant..."
135
+ Cheetah.run "vagrant", "plugin", "install", "vagrant-libvirt"
136
+ end
110
137
  end
111
138
 
112
139
  def add_user_to_groups
@@ -202,6 +229,12 @@ module Pennyworth
202
229
  Cheetah.run "sudo", "chmod", permissions, file
203
230
  end
204
231
 
232
+ def enable_services
233
+ ["libvirtd", "dnsmasq"].each do |service|
234
+ Cheetah.run "sudo", "systemctl", "enable", service
235
+ end
236
+ end
237
+
205
238
  def base_system
206
239
  Cheetah.run(["lsb_release", "--release"], :stdout => :capture).split[1]
207
240
  end
@@ -24,16 +24,4 @@ module Pennyworth
24
24
  class InvalidHostError < StandardError; end
25
25
  class HostFileError < StandardError; end
26
26
  class LockError < StandardError; end
27
-
28
- class ExecutionFailed < StandardError
29
- def initialize(e)
30
- @message = e.message
31
- @message += "\nStandard output:\n #{e.stdout}\n"
32
- @message += "\nError output:\n #{e.stderr}\n"
33
- end
34
-
35
- def to_s
36
- @message
37
- end
38
- end
39
27
  end