pennyworth-tool 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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