hobo-inviqa 0.0.7 → 0.0.8

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 (57) hide show
  1. checksums.yaml +15 -0
  2. data/.editorconfig +10 -0
  3. data/Gemfile.lock +19 -4
  4. data/Guardfile +2 -2
  5. data/Hobofile +5 -1
  6. data/README.md +8 -32
  7. data/bin/hobo +12 -18
  8. data/hobo.gemspec +3 -0
  9. data/lib/hobo.rb +8 -1
  10. data/lib/hobo/cli.rb +14 -3
  11. data/lib/hobo/error_handlers/debug.rb +5 -2
  12. data/lib/hobo/error_handlers/exit_code_map.rb +16 -0
  13. data/lib/hobo/error_handlers/friendly.rb +8 -8
  14. data/lib/hobo/errors.rb +11 -1
  15. data/lib/hobo/helper/http_download.rb +41 -0
  16. data/lib/hobo/helper/shell.rb +3 -2
  17. data/lib/hobo/helper/vm_command.rb +235 -14
  18. data/lib/hobo/lib/host_check.rb +20 -6
  19. data/lib/hobo/lib/host_check/deps.rb +22 -4
  20. data/lib/hobo/lib/host_check/git.rb +41 -17
  21. data/lib/hobo/lib/host_check/ruby.rb +30 -20
  22. data/lib/hobo/lib/host_check/vagrant.rb +37 -6
  23. data/lib/hobo/lib/s3sync.rb +22 -44
  24. data/lib/hobo/lib/seed/project.rb +10 -6
  25. data/lib/hobo/patches/slop.rb +21 -2
  26. data/lib/hobo/tasks/assets.rb +12 -15
  27. data/lib/hobo/tasks/config.rb +15 -0
  28. data/lib/hobo/tasks/deps.rb +37 -6
  29. data/lib/hobo/tasks/system.rb +15 -0
  30. data/lib/hobo/tasks/system/completions.rb +76 -0
  31. data/lib/hobo/tasks/tools.rb +10 -6
  32. data/lib/hobo/tasks/vm.rb +64 -11
  33. data/lib/hobo/ui.rb +27 -10
  34. data/lib/hobo/util.rb +36 -2
  35. data/lib/hobo/version.rb +2 -2
  36. data/spec/hobo/asset_applicator_spec.rb +2 -2
  37. data/spec/hobo/cli_spec.rb +40 -24
  38. data/spec/hobo/config/file_spec.rb +1 -3
  39. data/spec/hobo/error_handlers/debug_spec.rb +39 -5
  40. data/spec/hobo/error_handlers/friendly_spec.rb +38 -21
  41. data/spec/hobo/help_formatter_spec.rb +3 -3
  42. data/spec/hobo/helpers/file_locator_spec.rb +2 -2
  43. data/spec/hobo/helpers/shell_spec.rb +2 -2
  44. data/spec/hobo/helpers/vm_command_spec.rb +54 -21
  45. data/spec/hobo/lib/s3sync_spec.rb +6 -3
  46. data/spec/hobo/lib/seed/project_spec.rb +2 -3
  47. data/spec/hobo/lib/seed/replacer_spec.rb +1 -2
  48. data/spec/hobo/lib/seed/seed_spec.rb +2 -3
  49. data/spec/hobo/logging_spec.rb +2 -2
  50. data/spec/hobo/metadata_spec.rb +2 -2
  51. data/spec/hobo/null_spec.rb +2 -2
  52. data/spec/hobo/paths_spec.rb +1 -2
  53. data/spec/hobo/ui_spec.rb +104 -20
  54. data/spec/hobo/util_spec.rb +75 -0
  55. data/spec/spec_helper.rb +1 -0
  56. metadata +55 -46
  57. data/lib/hobo/tasks/host.rb +0 -19
data/lib/hobo/errors.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Hobo
2
2
  class Error < StandardError
3
+ attr_reader :exit_code
3
4
  end
4
5
 
5
6
  class RubyVersionError < Error
@@ -64,4 +65,13 @@ module Hobo
64
65
  super("A tool that hobo depends on could not be detected (#{dep})")
65
66
  end
66
67
  end
67
- end
68
+
69
+ class HostCheckError < Error
70
+ attr_accessor :summary, :advice
71
+ def initialize summary, advice
72
+ @summary = summary
73
+ @advice = advice
74
+ super("Host check failed: #{summary}")
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,41 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module Hobo
5
+ module Helper
6
+ def http_download url, target_file, opts = {}
7
+ opts = { :progress => Hobo.method(:progress) }.merge(opts)
8
+ uri = URI.parse(url)
9
+ size = 0
10
+ http = Net::HTTP.new(uri.host, uri.port)
11
+ http.use_ssl = uri.scheme == 'https'
12
+
13
+ # TODO: May want to verify SSL validity...
14
+ # http://notetoself.vrensk.com/2008/09/verified-https-in-ruby/#comment-22252
15
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
16
+
17
+ http.start do
18
+ begin
19
+ file = open(target_file, 'wb+')
20
+ http.request_get(uri.path) do |response|
21
+ size = response.content_length
22
+ response.read_body do |chunk|
23
+ file.write(chunk)
24
+ opts[:progress].call(
25
+ target_file,
26
+ chunk.length,
27
+ size,
28
+ :update
29
+ ) if opts[:progress]
30
+ end
31
+ end
32
+ ensure
33
+ opts[:progress].call(target_file, 0, size, :finsh) if opts[:progress]
34
+ file.close
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ include Hobo::Helper
@@ -39,7 +39,8 @@ module Hobo
39
39
  :capture => false,
40
40
  :indent => 0,
41
41
  :realtime => false,
42
- :env => {}
42
+ :env => {},
43
+ :ignore_errors => false
43
44
  }.merge! opts
44
45
 
45
46
  Hobo::Logging.logger.debug("helper.shell: Invoking '#{args.join(" ")}' with #{opts.to_s}")
@@ -71,7 +72,7 @@ module Hobo
71
72
  buffer.fsync
72
73
  buffer.rewind
73
74
 
74
- raise ::Hobo::ExternalCommandError.new(args.join(" "), external.value.exitstatus, buffer) if external.value.exitstatus != 0
75
+ raise ::Hobo::ExternalCommandError.new(args.join(" "), external.value.exitstatus, buffer) if external.value.exitstatus != 0 && !opts[:ignore_errors]
75
76
 
76
77
  return opts[:capture] ? buffer.read.strip : nil
77
78
  end
@@ -1,20 +1,69 @@
1
- require 'hobo/helper/shell'
1
+ require 'tempfile'
2
+ require 'net/ssh/simple'
2
3
 
3
4
  module Hobo
4
5
  module Helper
6
+ attr_accessor :vm_project_mount_path
7
+
8
+ # Really expensive method to auto-detect where root project mount exists in VM.
9
+ # It assumes that the project root is directly mounted.
10
+ # We cache it in configuration once we find it so it only has to happen once.
11
+ def vm_project_mount_path
12
+ configured_path = maybe(Hobo.project_config.vm.project_mount_path)
13
+ return configured_path if configured_path
14
+ return @vm_project_mount_path if @vm_project_mount_path
15
+
16
+ tmp = Tempfile.new('vm_command_locator', Hobo.project_path)
17
+ tmp.write(Hobo.project_path)
18
+
19
+ locator_file = File.basename(tmp.path)
20
+
21
+ pattern = OS.windows? ? 'vboxsf' : Hobo.project_path.shellescape
22
+
23
+ # TODO genericise the command escaping from lib/hobo/patches/slop.rb to avoid nested shell escaping hell
24
+ sed = 's/.* on \(.*\) type.*/\\\1\\\/%%/g'.gsub('%%', locator_file)
25
+ locator_results = vm_shell(
26
+ "mount | grep #{pattern} | sed -e\"#{sed}\" | xargs md5sum",
27
+ :capture => true,
28
+ :pwd => '/',
29
+ :psuedo_tty => false,
30
+ :ignore_errors => true
31
+ )
32
+
33
+ tmp.close
34
+
35
+ match = locator_results.match(/^([a-z0-9]{32})\s+(.*)$/)
36
+
37
+ raise Exception.new("Unable to locate project mount point in VM") if !match
38
+
39
+ @vm_project_mount_path = File.dirname(match[2])
40
+
41
+ # Stash it in config
42
+ Hobo.project_config[:vm] ||= {}
43
+ Hobo.project_config[:vm][:project_mount_path] = @vm_project_mount_path
44
+ Hobo::Config::File.save(Hobo.project_config_file, Hobo.project_config)
45
+
46
+ return @vm_project_mount_path
47
+ end
48
+
5
49
  def vm_shell command, opts = {}
6
- shell VmCommand.new(command).to_s, opts
50
+ shell VmCommand.new(command, opts).to_s, opts
7
51
  end
8
52
 
9
53
  def vm_mysql opts = {}
10
54
  opts = {
11
55
  :auto_echo => true,
12
56
  :db => "",
13
- :user => maybe(Hobo.project_config.mysql.username) || "root",
14
- :pass => maybe(Hobo.project_config.mysql.password) || "root"
57
+ :user => maybe(Hobo.project_config.mysql.username) || "",
58
+ :pass => maybe(Hobo.project_config.mysql.password) || "",
59
+ :mysql => 'mysql'
15
60
  }.merge(opts)
16
61
 
17
- VmCommand.new "mysql -u#{opts[:user].shellescape} -p#{opts[:pass].shellescape} #{opts[:db].shellescape}", opts
62
+ opts[:user] = "-u#{opts[:user].shellescape}" unless opts[:user].empty?
63
+ opts[:pass] = "-p#{opts[:pass].shellescape}" unless opts[:pass].empty?
64
+ opts[:db] = opts[:db].shellescape unless opts[:db].empty?
65
+
66
+ VmCommand.new "#{opts[:mysql]} #{opts[:user]} #{opts[:pass]} #{opts[:db]}".strip, opts
18
67
  end
19
68
 
20
69
  def vm_command command = nil, opts = {}
@@ -23,17 +72,89 @@ module Hobo
23
72
 
24
73
  private
25
74
 
75
+ class VmInspector
76
+ attr_accessor :ssh_config, :project_mount_path
77
+
78
+ def project_mount_path
79
+ #configured_path = maybe(Hobo.project_config.vm.project_mount_path)
80
+ #return configured_path if configured_path
81
+ #return @project_mount_path if @project_mount_path
82
+
83
+ tmp = Tempfile.new('vm_command_locator', Hobo.project_path)
84
+
85
+ begin
86
+ tmp.write(Hobo.project_path)
87
+
88
+ locator_file = File.basename(tmp.path)
89
+
90
+ pattern = OS.windows? ? 'vboxsf' : Hobo.project_path.shellescape
91
+
92
+ sed = 's/.* on \(.*\) type.*/\1\/%%/g'.gsub('%%', locator_file)
93
+ locator_results = VmCommand.new(
94
+ "mount | grep #{pattern} | sed -e\"#{sed}\" | xargs md5sum",
95
+ :capture => true,
96
+ :pwd => '/'
97
+ ).run
98
+ ensure
99
+ tmp.unlink
100
+ end
101
+
102
+ match = locator_results.match(/^([a-z0-9]{32})\s+(.*)$/)
103
+
104
+ raise Exception.new("Unable to locate project mount point in VM") if !match
105
+
106
+ @vm_project_mount_path = File.dirname(match[2])
107
+
108
+ # Stash it in config
109
+ Hobo.project_config[:vm] ||= {}
110
+ Hobo.project_config[:vm][:project_mount_path] = @vm_project_mount_path
111
+ Hobo::Config::File.save(Hobo.project_config_file, Hobo.project_config)
112
+
113
+ return @vm_project_mount_path
114
+ end
115
+
116
+ def ssh_config
117
+ return @ssh_config if @ssh_config
118
+ config = nil
119
+ locate "*Vagrantfile" do
120
+ config = bundle_shell "vagrant ssh-config", :capture => true
121
+ end
122
+
123
+ raise Exception.new "Could not retrieve VM ssh configuration" unless config
124
+
125
+ patterns = {
126
+ :ssh_user => /^\s*User (.*)$/,
127
+ :ssh_identity => /^\s*IdentityFile (.*)$/,
128
+ :ssh_host => /^\s*HostName (.*)$/,
129
+ :ssh_port => /^\s*Port (\d+)/
130
+ }
131
+
132
+ output = {}
133
+
134
+ patterns.each do |k, pattern|
135
+ match = config.match(pattern)
136
+ output[k] = match[1] if match
137
+ end
138
+
139
+ return @ssh_config = output
140
+ end
141
+ end
142
+
26
143
  class VmCommand
144
+ class << self
145
+ attr_accessor :vm_inspector
146
+ @@vm_inspector = VmInspector.new
147
+ end
148
+
27
149
  attr_accessor :opts, :command
28
150
 
29
151
  def initialize command, opts = {}
30
152
  @command = command
31
153
  @opts = {
32
154
  :auto_echo => false,
33
- :psuedo_tty => true,
34
- :ssh_identity => "#{ENV['HOME'].shellescape}/.vagrant.d/insecure_private_key",
35
- :ssh_user => "vagrant",
36
- :ssh_host => maybe(Hobo.project_config.hostname) || ""
155
+ :psuedo_tty => false,
156
+ :pwd => opts[:pwd] || @@vm_inspector.project_mount_path,
157
+ :append => ''
37
158
  }.merge(opts)
38
159
  end
39
160
 
@@ -44,12 +165,84 @@ module Hobo
44
165
  return self
45
166
  end
46
167
 
168
+ def < pipe
169
+ pipe = "echo '#{pipe.shellescape}'" if opts[:auto_echo]
170
+ @pipe_in_vm = pipe
171
+ @opts[:psuedo_tty] = false
172
+ return self
173
+ end
174
+
175
+ # TODO Refactor in to ssh helper with similar opts to shell helper
176
+ # TODO Migrate all vm_shell functionality this direction
177
+ def run
178
+ return if @command.nil?
179
+ opts = @@vm_inspector.ssh_config.merge(@opts)
180
+
181
+ Net::SSH::Simple.sync do
182
+ ssh_opts = {
183
+ :user => opts[:ssh_user],
184
+ :port => opts[:ssh_port],
185
+ :forward_agent => true,
186
+ :global_known_hosts_file => "/dev/null",
187
+ :paranoid => false,
188
+ :user_known_hosts_file => "/dev/null"
189
+ }
190
+
191
+ ssh_opts[:keys] = [opts[:ssh_identity]] if opts[:ssh_identity]
192
+
193
+ tmp = Tempfile.new "vm_command_exec"
194
+
195
+ begin
196
+ filename = File.basename(tmp.path)
197
+ remote_file = "/tmp/#{filename}"
198
+ tmp.write "#{@command}#{opts[:append]}"
199
+ tmp.close
200
+
201
+ scp_put opts[:ssh_host], tmp.path, remote_file, ssh_opts
202
+ result = ssh opts[:ssh_host], "cd #{opts[:pwd]}; exec /bin/bash #{remote_file}", ssh_opts
203
+ ssh opts[:ssh_host], "rm #{remote_file}", ssh_opts
204
+
205
+ # Throw exception if exit code not 0
206
+
207
+ return opts[:capture] ? result.stdout : result.success
208
+ ensure
209
+ tmp.unlink
210
+ end
211
+ end
212
+ end
213
+
214
+ # TODO Speed up Vagrant SSH connections
215
+ # May need to be disabled for windows (mm_send_fd: UsePrivilegeSeparation=yes not supported)
216
+ # https://gist.github.com/jedi4ever/5657094
217
+
47
218
  def to_s
48
- psuedo_tty = @opts[:psuedo_tty] ? "-t" : ""
49
- command = [
50
- "ssh -i #{opts[:ssh_identity]} #{psuedo_tty} #{opts[:ssh_user].shellescape}@#{opts[:ssh_host].shellescape}",
219
+ opts = @@vm_inspector.ssh_config.merge(@opts)
220
+
221
+ psuedo_tty = opts[:psuedo_tty] ? "-t" : ""
222
+
223
+ ssh_command = [
224
+ "ssh",
225
+ "-o 'UserKnownHostsFile /dev/null'",
226
+ "-o 'StrictHostKeyChecking no'",
227
+ "-o 'ForwardAgent yes'",
228
+ "-o 'LogLevel FATAL'",
229
+ "-p #{opts[:ssh_port]}",
230
+ "-i #{opts[:ssh_identity].shellescape}",
231
+ psuedo_tty,
232
+ "#{opts[:ssh_user].shellescape}@#{opts[:ssh_host].shellescape}"
233
+ ].join(" ")
234
+
235
+ pwd_set_command = " -- \"cd #{@opts[:pwd].shellescape}; exec /bin/bash"
236
+
237
+ vm_command = [
238
+ @pipe_in_vm,
51
239
  @command
52
- ].compact.join(" -- ")
240
+ ].compact.join(" | ")
241
+
242
+ command = [
243
+ ssh_command + pwd_set_command,
244
+ vm_command.empty? ? nil : vm_command.shellescape
245
+ ].compact.join(" -c ") + "#{opts[:append].shellescape}\""
53
246
 
54
247
  [
55
248
  @pipe,
@@ -60,8 +253,36 @@ module Hobo
60
253
  def to_str
61
254
  to_s
62
255
  end
256
+
257
+ private
258
+
259
+ def vagrant_config
260
+ return @@vagrant_config if @@vagrant_config
261
+ config = nil
262
+ locate "*Vagrantfile" do
263
+ config = bundle_shell "vagrant ssh-config", :capture => true
264
+ end
265
+
266
+ raise Exception.new "Could not retrieve VM ssh configuration" unless config
267
+
268
+ patterns = {
269
+ :ssh_user => /^\s+User (.*)$/,
270
+ :ssh_identity => /^\s+IdentityFile (.*)$/,
271
+ :ssh_host => /^\s+HostName (.*)$/,
272
+ :ssh_port => /^\s+Port (\d+)/
273
+ }
274
+
275
+ output = {}
276
+
277
+ patterns.each do |k, pattern|
278
+ match = config.match(pattern)
279
+ output[k] = match[1]
280
+ end
281
+
282
+ return @@vagrant_config = output
283
+ end
63
284
  end
64
285
  end
65
286
  end
66
287
 
67
- include Hobo::Helper
288
+ include Hobo::Helper
@@ -4,20 +4,34 @@ module Hobo
4
4
  class << self
5
5
  include Hobo::Lib::HostCheck
6
6
 
7
- def check silent = true
7
+ def check opts = {}
8
+ opts = {
9
+ :filter => nil,
10
+ :raise => false
11
+ }.merge(opts)
12
+
13
+ results = {}
8
14
  methods = Hobo::Lib::HostCheck.public_instance_methods(false)
9
15
  methods.each do |method|
16
+ next if opts[:filter] && !method.match(opts[:filter])
17
+
10
18
  name = method.to_s.gsub('_', ' ')
11
19
  name[0] = name[0].upcase
12
- begin
20
+ if opts[:raise]
13
21
  self.send method
14
- Hobo.ui.success "#{name}: OK" unless silent
15
- rescue
16
- Hobo.ui.error "#{name}: FAILED" unless silent
22
+ else
23
+ begin
24
+ self.send method
25
+ results[name] = :ok
26
+ rescue Hobo::Error => error
27
+ results[name] = error
28
+ end
17
29
  end
18
30
  end
31
+
32
+ return results
19
33
  end
20
34
  end
21
35
  end
22
36
  end
23
- end
37
+ end
@@ -2,20 +2,38 @@ module Hobo
2
2
  module Lib
3
3
  module HostCheck
4
4
  def ssh_present
5
+ advice = "The SSH command could not be located on your system.\n\n"
6
+
7
+ if OS.windows?
8
+ advice += "To make SSH available you must re-install git using the installer from http://git-scm.com/downloads ensuring you select the 'Use git and unix tools everywhere' option."
9
+ else
10
+ advice += "Please install openssh using your package manager."
11
+ end
12
+
5
13
  begin
6
14
  shell "ssh -V"
7
15
  rescue Errno::ENOENT
8
- raise Hobo::MissingDependency.new("ssh")
16
+ raise Hobo::HostCheckError.new("SSH is missing", advice)
9
17
  end
10
18
  end
11
19
 
12
20
  def php_present
21
+ advice = <<-EOF
22
+ The PHP command could not be located on your system.
23
+
24
+ This is an optional command that can speed up composer dependency installs.
25
+
26
+ Please install it from your package manager ensuring that the following command does not produce any errors:
27
+
28
+ php -r "readfile('https://getcomposer.org/installer');" | php
29
+ EOF
30
+
13
31
  begin
14
- shell "php -v"
32
+ shell "php --version"
15
33
  rescue Errno::ENOENT
16
- raise Hobo::MissingDependency.new("php")
34
+ raise Hobo::HostCheckError.new("PHP is missing", advice)
17
35
  end
18
36
  end
19
37
  end
20
38
  end
21
- end
39
+ end