landrush 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +12 -178
  3. data/.travis.yml +6 -1
  4. data/CHANGELOG.md +18 -1
  5. data/CONTRIBUTING.adoc +112 -0
  6. data/Gemfile +6 -9
  7. data/Guardfile +10 -0
  8. data/README.adoc +100 -0
  9. data/Rakefile +14 -2
  10. data/appveyor.yml +20 -0
  11. data/doc/Development.adoc +112 -0
  12. data/doc/ProxyMobile.adoc +66 -0
  13. data/doc/Troubleshooting.adoc +42 -0
  14. data/doc/Usage.adoc +271 -0
  15. data/features/commands.feature +35 -0
  16. data/features/dns_resolution.feature +6 -5
  17. data/features/{landrush-ip.feature → landrush_ip.feature} +0 -0
  18. data/features/step_definitions/landrush_custom_steps.rb +48 -0
  19. data/features/support/env.rb +25 -1
  20. data/landrush.gemspec +3 -3
  21. data/lib/landrush/action/common.rb +3 -11
  22. data/lib/landrush/action/install_prerequisites.rb +2 -3
  23. data/lib/landrush/action/redirect_dns.rb +1 -1
  24. data/lib/landrush/action/setup.rb +25 -30
  25. data/lib/landrush/action/teardown.rb +8 -11
  26. data/lib/landrush/cap/guest/all/read_host_visible_ip_address.rb +28 -4
  27. data/lib/landrush/cap/guest/linux/add_iptables_rule.rb +1 -1
  28. data/lib/landrush/cap/guest/linux/redirect_dns.rb +2 -2
  29. data/lib/landrush/cap/guest/suse/add_iptables_rule.rb +20 -0
  30. data/lib/landrush/cap/guest/suse/install_iptables.rb +14 -0
  31. data/lib/landrush/cap/guest/suse/iptables_installed.rb +11 -0
  32. data/lib/landrush/cap/host/suse/dnsmasq_installed.rb +11 -0
  33. data/lib/landrush/cap/host/suse/install_dnsmasq.rb +14 -0
  34. data/lib/landrush/cap/host/suse/restart_dnsmasq.rb +21 -0
  35. data/lib/landrush/cap/host/windows/configure_visibility_on_host.rb +1 -1
  36. data/lib/landrush/command.rb +42 -17
  37. data/lib/landrush/config.rb +29 -14
  38. data/lib/landrush/plugin.rb +30 -0
  39. data/lib/landrush/server.rb +96 -138
  40. data/lib/landrush/start_server.rb +11 -0
  41. data/lib/landrush/store.rb +6 -2
  42. data/lib/landrush/util/path.rb +32 -0
  43. data/lib/landrush/util/process_helper.rb +46 -0
  44. data/lib/landrush/util/retry.rb +2 -2
  45. data/lib/landrush/version.rb +1 -1
  46. data/test/landrush/action/setup_test.rb +19 -25
  47. data/test/landrush/action/teardown_test.rb +18 -15
  48. data/test/landrush/cap/guest/all/read_host_visible_ip_address_test.rb +35 -1
  49. data/test/landrush/cap/guest/linux/configured_dns_servers_test.rb +8 -8
  50. data/test/landrush/cap/guest/linux/redirect_dns_test.rb +4 -4
  51. data/test/landrush/config_test.rb +23 -2
  52. data/test/landrush/dependent_vms_test.rb +5 -5
  53. data/test/landrush/issues/255.rb +115 -0
  54. data/test/landrush/server_test.rb +22 -4
  55. data/test/landrush/store_test.rb +28 -13
  56. data/test/support/test_server_daemon.rb +2 -4
  57. data/test/test_helper.rb +37 -14
  58. metadata +30 -15
  59. data/CONTRIBUTING.md +0 -103
  60. data/NOTES.md +0 -28
  61. data/README.md +0 -406
  62. data/doc/proxy-mobile/README.md +0 -50
  63. data/features/step_definitions/dns.rb +0 -19
  64. data/features/step_definitions/ip.rb +0 -13
@@ -0,0 +1,35 @@
1
+ Feature: Landrush reload
2
+ Landrush DNS server should restart on a 'vagrant reload'
3
+
4
+ Scenario Outline: booting a box and restarting it
5
+ Given a file named "Vagrantfile" with:
6
+ """
7
+ Vagrant.configure("2") do |config|
8
+ config.vm.box = '<box>'
9
+ config.vm.synced_folder '.', '/vagrant', disabled: true
10
+ config.landrush.enabled = true
11
+ end
12
+ """
13
+ When I successfully run `bundle exec vagrant up --provider <provider>`
14
+ Then Landrush is running
15
+
16
+ When I successfully run `bundle exec vagrant landrush set foo 1.2.3.4`
17
+ And I successfully run `bundle exec vagrant landrush set bar 4.3.1.1`
18
+ And I successfully run `bundle exec vagrant landrush ls`
19
+ Then stdout from "bundle exec vagrant landrush ls" should match /^foo.*1.2.3.4$/
20
+ Then stdout from "bundle exec vagrant landrush ls" should match /^bar.*4.3.2.1$/
21
+
22
+ When I successfully run `bundle exec vagrant landrush rm --all`
23
+ And I successfully run `bundle exec vagrant landrush ls`
24
+ Then stdout from "bundle exec vagrant landrush ls" should match /^foo.*1.2.3.4$/
25
+ Then stdout from "bundle exec vagrant landrush ls" should match /^bar.*4.3.2.1$/
26
+
27
+ When I successfully run `bundle exec vagrant reload`
28
+ Then Landrush is running
29
+
30
+ When I successfully run `bundle exec vagrant landrush stop`
31
+ Then Landrush is not running
32
+
33
+ Examples:
34
+ | box | provider |
35
+ | debian/jessie64 | virtualbox |
@@ -16,9 +16,9 @@ Feature: dns_resolution
16
16
  end
17
17
  """
18
18
  When I successfully run `bundle exec vagrant up --provider <provider>`
19
- Then the hostname "my-host.landrush-acceptance-test" should resolve to "10.10.10.123" on the internal DNS server
20
- And the hostname "my-host.landrush-acceptance-test" should resolve to "10.10.10.123" on the host
19
+ Then the hostname "my-host.landrush-acceptance-test" should resolve to "10.10.10.123" on the host
21
20
  And the hostname "my-host.landrush-acceptance-test" should resolve to "10.10.10.123" on the guest
21
+ And the hostname "my-host.landrush-acceptance-test" should resolve to "10.10.10.123" on the internal DNS server
22
22
 
23
23
  When I successfully run `bundle exec vagrant landrush set my-static-host.landrush-acceptance-test 42.42.42.42`
24
24
  Then the hostname "my-static-host.landrush-acceptance-test" should resolve to "42.42.42.42" on the internal DNS server
@@ -30,6 +30,7 @@ Feature: dns_resolution
30
30
  And the hostname "my-static-cname-host.landrush-acceptance-test" should resolve to "42.42.42.42" on the host
31
31
 
32
32
  Examples:
33
- | box | provider |
34
- | debian/jessie64 | virtualbox |
35
- #| ubuntu/wily64 | virtualbox |
33
+ | box | provider |
34
+ | debian/jessie64 | virtualbox |
35
+ #| opensuse/openSUSE-42.1-x86_64 | virtualbox |
36
+ #| ubuntu/wily64 | virtualbox |
@@ -0,0 +1,48 @@
1
+ require 'landrush/server'
2
+
3
+ Then(/^the hostname "([^"]+)" should resolve to "([^"]+)" on the internal DNS server$/) do |host, ip|
4
+ port = Landrush::Server.port
5
+ resolver = Resolv::DNS.new(nameserver_port: [['localhost', port]], search: ['local'], ndots: 1)
6
+ ip_resolved = resolver.getaddress(host).to_s
7
+ expect(ip_resolved).to eq(ip)
8
+ end
9
+
10
+ Then(/^the hostname "([^"]+)" should resolve to "([^"]+)" on the host$/) do |host, ip|
11
+ addrinfo = Addrinfo.getaddrinfo(host, nil, Socket::AF_INET)
12
+ ip_resolved = addrinfo.first.ip_address
13
+ expect(ip_resolved).to eq(ip)
14
+ end
15
+
16
+ Then(/^the hostname "([^"]+)" should resolve to "([^"]+)" on the guest/) do |host, ip|
17
+ run("bundle exec vagrant ssh -c \"dig +short '#{host}' A\"")
18
+ expect(last_command_started).to have_output(/^#{ip}$/)
19
+ end
20
+
21
+ Then(/^the host visible IP address of the guest is the IP of interface "([^"]+)"/) do |interface|
22
+ cmd = "bundle exec vagrant ssh -c \"ip addr list #{interface} | grep 'inet ' | cut -d' ' -f6| cut -d/ -f1\""
23
+ run(cmd)
24
+ expect(last_command_started).to have_output(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
25
+
26
+ ip = last_command_started.output.split("\n").last
27
+
28
+ run('bundle exec vagrant landrush list')
29
+
30
+ expect(last_command_started).to have_output(/#{ip}$/)
31
+ end
32
+
33
+ Then(/^Landrush is( not)? running$/) do |negated|
34
+ run('bundle exec vagrant landrush status')
35
+ if negated
36
+ expect(last_command_started).to have_output(/Daemon status: stopped/)
37
+ else
38
+ expect(last_command_started).to have_output(/Daemon status: running pid=[0-9]+/)
39
+ end
40
+ end
41
+
42
+ Then(%r{^stdout from "([^"]*)" should( not)? match /(.*)/$}) do |cmd, negated, regexp|
43
+ if negated
44
+ aruba.command_monitor.find(Aruba.platform.detect_ruby(cmd)).send(:stdout) !~ /#{regexp}/
45
+ else
46
+ aruba.command_monitor.find(Aruba.platform.detect_ruby(cmd)).send(:stdout) =~ /#{regexp}/
47
+ end
48
+ end
@@ -1,15 +1,39 @@
1
1
  require 'aruba/cucumber'
2
2
  require 'komenda'
3
+ require 'fileutils'
4
+ require 'find'
3
5
 
4
6
  Aruba.configure do |config|
5
- config.exit_timeout = 300
7
+ config.exit_timeout = 3600
6
8
  config.activate_announcer_on_command_failure = [:stdout, :stderr]
7
9
  config.working_directory = 'build/aruba'
8
10
  end
9
11
 
12
+ Before do |_scenario|
13
+ # Making sure that all tests run in a pristine environment
14
+ # Create the Vagrant home directory for the tests
15
+ vagrant_home = File.join(File.dirname(__FILE__), '..', '..', 'build', 'vagrant.d')
16
+ # Make sure the Vagrant home directory is "clean".
17
+ # We keep the boxes directory to not have to re-download the boxes each time
18
+ ENV['VAGRANT_HOME'] = vagrant_home
19
+ Dir.new(ENV['VAGRANT_HOME']).entries.reject { |file| 'boxes'.eql?(file) || '.'.eql?(file) || '..'.eql?(file) }
20
+ .each { |file| FileUtils.rmtree(File.join(ENV['VAGRANT_HOME'], file)) }
21
+
22
+ # Actual gems are in ~/vagrant.d/gems/gems
23
+ gems_path = File.join(vagrant_home, 'gems', 'gems')
24
+ FileUtils.mkdir_p gems_path
25
+
26
+ # Find the path to the Bundler gems
27
+ bundler_gem_path = File.join(Bundler.rubygems.find_name('bundler').first.base_dir, 'gems')
28
+
29
+ # Copy the gems to the Vagrant gems dir
30
+ FileUtils.cp_r bundler_gem_path, gems_path, verbose: false
31
+ end
32
+
10
33
  After do |_scenario|
11
34
  Komenda.run('bundle exec vagrant landrush stop', fail_on_fail: true)
12
35
 
36
+ # If there is a Vagrantfile from previous run, delete it
13
37
  if File.exist?(File.join(aruba.config.working_directory, 'Vagrantfile'))
14
38
  Komenda.run('bundle exec vagrant destroy -f', cwd: aruba.config.working_directory, fail_on_fail: true)
15
39
  end
@@ -19,16 +19,16 @@ Gem::Specification.new do |spec|
19
19
  and down, and you can configure static entries to be returned from the
20
20
  server as well. See the README for more documentation.
21
21
  DESCRIP
22
- spec.summary = %q{a vagrant plugin providing consistent DNS visible on host and guests}
22
+ spec.summary = 'a vagrant plugin providing consistent DNS visible on host and guests'
23
23
  spec.homepage = 'https://github.com/vagrant-landrush/landrush'
24
24
  spec.license = 'MIT'
25
25
 
26
- spec.files = `git ls-files`.split($/)
26
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
27
27
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
28
28
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
29
29
  spec.require_paths = ['lib']
30
30
 
31
31
  spec.add_dependency 'rubydns', '0.8.5'
32
32
  spec.add_dependency 'win32-process'
33
- spec.add_dependency 'landrush-ip', '~> 0.2.3'
33
+ spec.add_dependency 'landrush-ip', '~> 0.2.5'
34
34
  end
@@ -1,5 +1,6 @@
1
1
  module Landrush
2
2
  module Action
3
+ # A module containing shared functionality for Vagrant middleware classes
3
4
  module Common
4
5
  SUPPORTED_PROVIDERS = {
5
6
  'VagrantPlugins::ProviderVirtualBox::Provider' => :virtualbox,
@@ -16,14 +17,7 @@ module Landrush
16
17
 
17
18
  def initialize(app, env)
18
19
  @app = app
19
- end
20
-
21
- def handle_action_stack(env)
22
20
  @env = env
23
-
24
- yield
25
-
26
- app.call(env)
27
21
  end
28
22
 
29
23
  def virtualbox?
@@ -47,7 +41,7 @@ module Landrush
47
41
  raise "The landrush plugin does not support the #{key} provider yet!"
48
42
  end
49
43
 
50
- if provider_name == :parallels && Gem::Version.new(VagrantPlugins::Parallels::VERSION) < Gem::Version.new("1.0.3")
44
+ if provider_name == :parallels && Gem::Version.new(VagrantPlugins::Parallels::VERSION) < Gem::Version.new('1.0.3')
51
45
  raise "The landrush plugin supports the Parallels provider v1.0.3 and later. Please, update your 'vagrant-parallels' plugin."
52
46
  end
53
47
 
@@ -73,9 +67,7 @@ module Landrush
73
67
  end
74
68
 
75
69
  def read_machine_hostname
76
- if machine.config.vm.hostname
77
- return machine.config.vm.hostname
78
- end
70
+ return machine.config.vm.hostname if machine.config.vm.hostname
79
71
 
80
72
  "#{Pathname.pwd.basename}.#{config.tld}"
81
73
  end
@@ -4,9 +4,8 @@ module Landrush
4
4
  include Common
5
5
 
6
6
  def call(env)
7
- handle_action_stack(env) do
8
- install_prerequisites if enabled?
9
- end
7
+ install_prerequisites if enabled?
8
+ app.call(env)
10
9
  end
11
10
 
12
11
  def install_prerequisites
@@ -4,7 +4,7 @@ module Landrush
4
4
  include Common
5
5
 
6
6
  def call(env)
7
- handle_action_stack(env) {}
7
+ app.call(env)
8
8
 
9
9
  # This is after the middleware stack returns, which, since we're right
10
10
  # before the Network action, should mean that all interfaces are good
@@ -7,11 +7,11 @@ module Landrush
7
7
  # Make sure we use the right data directory for Landrush
8
8
  # Seems Vagrant only makes home_path available in this case, compared to custom commands where there is also data_dir
9
9
  Server.working_dir = File.join(env[:home_path], 'data', 'landrush')
10
+ Server.gems_dir = File.join(env[:gems_path].to_s, 'gems')
11
+ Server.ui = env[:ui]
10
12
 
11
- handle_action_stack(env) do
12
- pre_boot_setup if enabled?
13
- end
14
-
13
+ pre_boot_setup if enabled?
14
+ app.call(env)
15
15
  # This is after the middleware stack returns, which, since we're right
16
16
  # before the Network action, should mean that all interfaces are good
17
17
  # to go.
@@ -19,21 +19,25 @@ module Landrush
19
19
  end
20
20
 
21
21
  def host_ip_address
22
- static_private_network_ip || machine.guest.capability(:read_host_visible_ip_address)
22
+ if private_network_ips.include? machine.config.landrush.host_ip_address
23
+ machine.config.landrush.host_ip_address
24
+ else
25
+ machine.guest.capability(:read_host_visible_ip_address)
26
+ end
23
27
  end
24
28
 
25
29
  private
26
30
 
27
31
  def pre_boot_setup
28
- record_dependent_vm
29
32
  add_prerequisite_network_interface
30
- configure_server
31
- start_server
32
33
  end
33
34
 
34
35
  def post_boot_setup
36
+ record_dependent_vm
37
+ configure_server
35
38
  record_machine_dns_entry
36
39
  setup_static_dns
40
+ start_server
37
41
  return unless machine.config.landrush.host_redirect_dns?
38
42
  env[:host].capability(:configure_visibility_on_host, host_ip_address, config.tld)
39
43
  end
@@ -62,15 +66,13 @@ module Landrush
62
66
  def setup_static_dns
63
67
  config.hosts.each do |hostname, dns_value|
64
68
  dns_value ||= host_ip_address
65
- unless Store.hosts.has?(hostname, dns_value)
66
- info "adding static DNS entry: #{hostname} => #{dns_value}"
67
- Store.hosts.set hostname, dns_value
68
- if ip_address?(dns_value)
69
- reverse_dns = IPAddr.new(dns_value).reverse
70
- info "adding static reverse DNS entry: #{reverse_dns} => #{dns_value}"
71
- Store.hosts.set(reverse_dns, hostname)
72
- end
73
- end
69
+ next if Store.hosts.has?(hostname, dns_value)
70
+ info "adding static DNS entry: #{hostname} => #{dns_value}"
71
+ Store.hosts.set hostname, dns_value
72
+ next unless ip_address?(dns_value)
73
+ reverse_dns = IPAddr.new(dns_value).reverse
74
+ info "adding static reverse DNS entry: #{reverse_dns} => #{dns_value}"
75
+ Store.hosts.set(reverse_dns, hostname)
74
76
  end
75
77
  end
76
78
 
@@ -97,19 +99,12 @@ module Landrush
97
99
  machine.config.vm.networks.any? { |type, _| type == :private_network }
98
100
  end
99
101
 
100
- # machine.config.vm.networks is an array of two elements. The first containing the type as symbol, the second is a
101
- # hash containing other config data which varies between types
102
- def static_private_network_ip
103
- # select all statically defined private network ip
104
- private_networks = machine.config.vm.networks.select {|network| :private_network == network[0] && !network[1][:ip].nil?}
105
- .map {|network| network[1][:ip]}
106
- if machine.config.landrush.host_ip_address.nil?
107
- private_networks[0] if private_networks.length == 1
108
- elsif private_networks.include? machine.config.landrush.host_ip_address
109
- machine.config.landrush.host_ip_address
110
- end
111
- # If there is more than one private network or there is no match between config.landrush.host_ip_address
112
- # and the discovered addresses we will pass on to read_host_visible_ip_address capability
102
+ # @return [Array<String] IPv4 addresses of all private networks
103
+ def private_network_ips
104
+ # machine.config.vm.networks is an array of two elements. The first containing the type as symbol, the second is a
105
+ # hash containing other config data which varies between types
106
+ machine.config.vm.networks.select { |network| :private_network == network[0] && !network[1][:ip].nil? }
107
+ .map { |network| network[1][:ip] }
113
108
  end
114
109
  end
115
110
  end
@@ -8,26 +8,23 @@ module Landrush
8
8
  # Seems Vagrant only makes home_path available in this case, compared to custom commands where there is also data_dir
9
9
  Server.working_dir = File.join(env[:home_path], 'data', 'landrush')
10
10
 
11
- handle_action_stack(env) do
12
- teardown if enabled?
13
- end
11
+ teardown if enabled?
12
+ app.call(env)
14
13
  end
15
14
 
16
15
  def teardown
17
16
  teardown_machine_dns
18
17
  DependentVMs.remove(machine_hostname)
19
18
 
20
- if DependentVMs.none?
21
- teardown_static_dns
22
- teardown_server
23
- end
19
+ return unless DependentVMs.none?
20
+ teardown_static_dns
21
+ teardown_server
24
22
  end
25
23
 
26
24
  def teardown_machine_dns
27
- if Store.hosts.has? machine_hostname
28
- info "removing machine entry: #{machine_hostname}"
29
- Store.hosts.delete(machine_hostname)
30
- end
25
+ return unless Store.hosts.has? machine_hostname
26
+ info "removing machine entry: #{machine_hostname}"
27
+ Store.hosts.delete(machine_hostname)
31
28
  end
32
29
 
33
30
  def teardown_static_dns
@@ -14,11 +14,26 @@ module Landrush
14
14
  addresses
15
15
  end
16
16
 
17
+ def self.filter_preferred_addresses(addresses)
18
+ if @machine.config.landrush.host_interface_class == :any
19
+ addresses = addresses.select do |addr|
20
+ (addr.key?('ipv4') && !addr['ipv4'].empty?) ||
21
+ (addr.key?('ipv6') && !addr['ipv6'].empty?)
22
+ end
23
+ else
24
+ key = @machine.config.landrush.host_interface_class.to_s
25
+
26
+ addresses = addresses.select do |addr|
27
+ (addr.key?(key) && !addr[key].empty?)
28
+ end
29
+ end
30
+
31
+ addresses
32
+ end
33
+
17
34
  def self.read_host_visible_ip_address(machine)
18
35
  @machine = machine
19
36
 
20
- @machine.guest.capability(:landrush_ip_install) unless @machine.guest.capability(:landrush_ip_installed)
21
-
22
37
  addr = nil
23
38
  addresses = machine.guest.capability(:landrush_ip_get)
24
39
 
@@ -33,15 +48,24 @@ module Landrush
33
48
 
34
49
  if addr.nil?
35
50
  addresses = filter_addresses addresses
51
+ raise 'No addresses found' if addresses.empty?
36
52
 
53
+ addresses = filter_preferred_addresses addresses
37
54
  raise 'No addresses found' if addresses.empty?
38
55
 
39
56
  addr = addresses.last
40
57
  end
41
58
 
42
- ip = IPAddr.new(addr['ipv4'])
59
+ # Keep preferring IPv4 over IPv6.
60
+ key = if machine.config.landrush.host_interface_class == :any
61
+ addr['ipv4'].empty? ? 'ipv6' : 'ipv4'
62
+ else
63
+ machine.config.landrush.host_interface_class.to_s
64
+ end
65
+
66
+ ip = IPAddr.new(addr[key])
43
67
 
44
- machine.env.ui.info "[landrush] Using #{addr['name']} (#{addr['ipv4']})"
68
+ machine.env.ui.info "[landrush] Using #{addr['name']} (#{addr[key]})"
45
69
 
46
70
  ip.to_s
47
71
  end
@@ -10,7 +10,7 @@ module Landrush
10
10
  machine.communicate.sudo(command) do |data, type|
11
11
  if [:stderr, :stdout].include?(type)
12
12
  color = (type == :stdout) ? :green : :red
13
- machine.env.ui.info(data.chomp, :color => color, :prefix => false)
13
+ machine.env.ui.info(data.chomp, color: color, prefix: false)
14
14
  end
15
15
  end
16
16
  end
@@ -2,9 +2,9 @@ module Landrush
2
2
  module Cap
3
3
  module Linux
4
4
  module RedirectDns
5
- def self.redirect_dns(machine, target={})
5
+ def self.redirect_dns(machine, target = {})
6
6
  dns_servers = machine.guest.capability(:configured_dns_servers)
7
- %w[tcp udp].each do |proto|
7
+ %w(tcp udp).each do |proto|
8
8
  dns_servers.each do |dns_server|
9
9
  machine.guest.capability(
10
10
  :add_iptables_rule,
@@ -0,0 +1,20 @@
1
+ module Landrush
2
+ module Cap
3
+ module Suse
4
+ module AddIptablesRule
5
+ def self.add_iptables_rule(machine, rule)
6
+ _run(machine, %(/usr/sbin/iptables -C #{rule} 2> /dev/null || /usr/sbin/iptables -A #{rule}))
7
+ end
8
+
9
+ def self._run(machine, command)
10
+ machine.communicate.sudo(command) do |data, type|
11
+ if [:stderr, :stdout].include?(type)
12
+ color = (type == :stdout) ? :green : :red
13
+ machine.env.ui.info(data.chomp, color: color, prefix: false)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end