knife-vcloud 0.2.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +44 -1
  3. data/README.md +356 -91
  4. data/lib/chef/knife/{vc_catalog_item_show.rb → catalog/vc_catalog_item_show.rb} +13 -13
  5. data/lib/chef/knife/{vc_catalog_show.rb → catalog/vc_catalog_show.rb} +9 -12
  6. data/lib/chef/knife/common/vc_bootstrap_common.rb +208 -0
  7. data/lib/chef/knife/common/vc_catalog_common.rb +58 -0
  8. data/lib/chef/knife/common/vc_common.rb +165 -0
  9. data/lib/chef/knife/common/vc_network_common.rb +34 -0
  10. data/lib/chef/knife/common/vc_vapp_common.rb +49 -0
  11. data/lib/chef/knife/common/vc_vdc_common.rb +43 -0
  12. data/lib/chef/knife/common/vc_vm_common.rb +80 -0
  13. data/lib/chef/knife/network/vc_network_show.rb +45 -0
  14. data/lib/chef/knife/{vc_org_list.rb → org/vc_org_list.rb} +3 -5
  15. data/lib/chef/knife/{vc_org_show.rb → org/vc_org_show.rb} +10 -11
  16. data/lib/chef/knife/ovf/vc_ovf_upload.rb +71 -0
  17. data/lib/chef/knife/vapp/vc_vapp_bootstrap.rb +51 -0
  18. data/lib/chef/knife/vapp/vc_vapp_clone.rb +80 -0
  19. data/lib/chef/knife/{vc_vapp_create.rb → vapp/vc_vapp_create.rb} +10 -9
  20. data/lib/chef/knife/{vc_vapp_delete.rb → vapp/vc_vapp_delete.rb} +12 -10
  21. data/lib/chef/knife/vapp/vc_vapp_network_external.rb +101 -0
  22. data/lib/chef/knife/vapp/vc_vapp_network_internal.rb +151 -0
  23. data/lib/chef/knife/vapp/vc_vapp_reboot.rb +45 -0
  24. data/lib/chef/knife/vapp/vc_vapp_reset.rb +44 -0
  25. data/lib/chef/knife/vapp/vc_vapp_show.rb +90 -0
  26. data/lib/chef/knife/vapp/vc_vapp_snapshot.rb +58 -0
  27. data/lib/chef/knife/{vc_vapp_start.rb → vapp/vc_vapp_start.rb} +7 -7
  28. data/lib/chef/knife/{vc_vapp_stop.rb → vapp/vc_vapp_stop.rb} +7 -7
  29. data/lib/chef/knife/vapp/vc_vapp_suspend.rb +44 -0
  30. data/lib/chef/knife/vc_commands.rb +70 -0
  31. data/lib/chef/knife/vc_login.rb +2 -2
  32. data/lib/chef/knife/{vc_vdc_show.rb → vdc/vc_vdc_show.rb} +13 -14
  33. data/lib/chef/knife/vm/vc_vm_bootstrap.rb +48 -0
  34. data/lib/chef/knife/vm/vc_vm_config_guest.rb +110 -0
  35. data/lib/chef/knife/{vc_vm_config_network.rb → vm/vc_vm_config_network.rb} +17 -11
  36. data/lib/chef/knife/vm/vc_vm_reboot.rb +44 -0
  37. data/lib/chef/knife/vm/vc_vm_reset.rb +44 -0
  38. data/lib/chef/knife/vm/vc_vm_set_disks.rb +78 -0
  39. data/lib/chef/knife/vm/vc_vm_set_info.rb +79 -0
  40. data/lib/chef/knife/{vc_vm_show.rb → vm/vc_vm_show.rb} +35 -18
  41. data/lib/chef/knife/vm/vc_vm_start.rb +44 -0
  42. data/lib/chef/knife/vm/vc_vm_stop.rb +44 -0
  43. data/lib/chef/knife/vm/vc_vm_suspend.rb +44 -0
  44. data/lib/knife-vcloud/version.rb +3 -0
  45. metadata +69 -38
  46. data/lib/chef/knife/vc_common.rb +0 -103
  47. data/lib/chef/knife/vc_vapp_config_network.rb +0 -63
  48. data/lib/chef/knife/vc_vapp_show.rb +0 -59
  49. data/lib/chef/knife/vc_vm_config_guest.rb +0 -67
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Stefano Tortarolo (<stefano.tortarolo@gmail.com>)
3
- # Copyright:: Copyright (c) 2012
3
+ # Copyright:: Copyright (c) 2012-2013
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,39 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require 'chef/knife/vc_common'
20
-
21
19
  class Chef
22
20
  class Knife
23
21
  class VcCatalogItemShow < Chef::Knife
24
22
  include Knife::VcCommon
23
+ include Knife::VcCatalogCommon
25
24
 
26
- banner "knife vc catalog item show [CATALOG_ID] (options)"
25
+ banner "knife vc catalog item show [CATALOG_ITEM] (options)"
27
26
 
28
27
  def run
29
28
  $stdout.sync = true
30
29
 
31
- item_id = @name_args.first
30
+ item_arg = @name_args.first
32
31
 
33
32
  connection.login
33
+ catalog_item = get_catalog_item(item_arg)
34
+ connection.logout
34
35
 
35
36
  header = [
36
37
  ui.color('Name', :bold),
37
38
  ui.color('Template ID', :bold)
38
39
  ]
39
40
 
40
- description, items = connection.show_catalog_item item_id
41
- connection.logout
42
-
43
- puts "#{ui.color('Description:', :cyan)} #{description}"
41
+ ui.msg "#{ui.color('Description:', :cyan)} #{catalog_item[:description]}"
44
42
  list = header
45
43
  list.flatten!
46
- items.each do |k, v|
47
- list << (k || '')
48
- list << (v || '')
44
+
45
+ catalog_item[:items].each do |item|
46
+ list << (item[:name] || '')
47
+ list << (item[:id] || '')
48
+ # TODO: show VMs using this item? item[:vms_hash]
49
49
  end
50
50
 
51
- puts ui.list(list, :columns_across, 2)
51
+ ui.msg ui.list(list, :columns_across, 2)
52
52
  end
53
53
  end
54
54
  end
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Stefano Tortarolo (<stefano.tortarolo@gmail.com>)
3
- # Copyright:: Copyright (c) 2012
3
+ # Copyright:: Copyright (c) 2012-2013
4
4
  # License:: Apache License, Version 2.0
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,39 +16,36 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require 'chef/knife/vc_common'
20
-
21
19
  class Chef
22
20
  class Knife
23
21
  class VcCatalogShow < Chef::Knife
24
22
  include Knife::VcCommon
23
+ include Knife::VcCatalogCommon
25
24
 
26
- banner "knife vc catalog show [CATALOG_ID] (options)"
25
+ banner "knife vc catalog show [CATALOG] (options)"
27
26
 
28
27
  def run
29
28
  $stdout.sync = true
30
29
 
31
- catalog_id = @name_args.first
32
-
30
+ catalog_arg = @name_args.shift
33
31
  connection.login
32
+ catalog = get_catalog(catalog_arg)
33
+ connection.logout
34
34
 
35
35
  header = [
36
36
  ui.color('Name', :bold),
37
37
  ui.color('ID', :bold)
38
38
  ]
39
39
 
40
- description, items = connection.show_catalog catalog_id
41
- connection.logout
42
-
43
- puts "#{ui.color('Description:', :cyan)} #{description}"
40
+ ui.msg "#{ui.color('Description:', :cyan)} #{catalog[:description]}"
44
41
  list = header
45
42
  list.flatten!
46
- items.each do |k, v|
43
+ sort_by_key(catalog[:items]).each do |k, v|
47
44
  list << (k || '')
48
45
  list << (v || '')
49
46
  end
50
47
 
51
- puts ui.list(list, :columns_across, 2)
48
+ ui.msg ui.list(list, :columns_across, 2)
52
49
  end
53
50
  end
54
51
  end
@@ -0,0 +1,208 @@
1
+ #
2
+ # Author:: Stefano Tortarolo (<stefano.tortarolo@gmail.com>)
3
+ # Copyright:: Copyright (c) 2013
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ class Chef
20
+ class Knife
21
+ module VcBootstrapCommon
22
+ def self.included(includer)
23
+ includer.class_eval do
24
+ deps do
25
+ require 'chef/knife/bootstrap'
26
+ require 'chef/knife/bootstrap_windows_winrm'
27
+ require 'chef/knife/core/windows_bootstrap_context'
28
+ Chef::Knife::Bootstrap.load_deps
29
+ end
30
+
31
+ option :run_list,
32
+ :short => "-r RUN_LIST",
33
+ :long => "--run-list RUN_LIST",
34
+ :description => "Comma separated list of roles/recipes to apply",
35
+ :proc => lambda { |o| o.split(/[\s,]+/) },
36
+ :default => []
37
+
38
+ option :distro,
39
+ :short => "-d DISTRO",
40
+ :long => "--distro DISTRO",
41
+ :description => "Bootstrap a distro using a template; default is 'chef-full'",
42
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
43
+ :default => "chef-full"
44
+
45
+ option :bootstrap_windows,
46
+ :long => "--[no-]bootstrap-windows",
47
+ :description => "The machine to be bootstrapped is Windows",
48
+ :boolean => true,
49
+ :default => false
50
+
51
+ option :bootstrap_proxy,
52
+ :long => "--bootstrap-proxy PROXY_URL",
53
+ :description => "The proxy server for the node being bootstrapped",
54
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_proxy] = v }
55
+
56
+ option :ssh_user,
57
+ :short => "-x USERNAME",
58
+ :long => "--ssh-user USERNAME",
59
+ :description => "The ssh username",
60
+ :default => "root"
61
+
62
+ option :ssh_password,
63
+ :short => "-P PASSWORD",
64
+ :long => "--ssh-password PASSWORD",
65
+ :description => "The ssh password"
66
+
67
+ option :ssh_port,
68
+ :short => "-p PORT",
69
+ :long => "--ssh-port PORT",
70
+ :description => "The ssh port",
71
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key },
72
+ :default => 22
73
+
74
+ option :ssh_gateway,
75
+ :short => "-G GATEWAY",
76
+ :long => "--ssh-gateway GATEWAY",
77
+ :description => "The ssh gateway",
78
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
79
+
80
+ option :forward_agent,
81
+ :short => "-A",
82
+ :long => "--forward-agent",
83
+ :description => "Enable SSH agent forwarding",
84
+ :boolean => true
85
+
86
+ option :identity_file,
87
+ :short => "-i IDENTITY_FILE",
88
+ :long => "--identity-file IDENTITY_FILE",
89
+ :description => "The SSH identity file used for authentication"
90
+
91
+ option :host_key_verify,
92
+ :long => "--[no-]host-key-verify",
93
+ :description => "Verify host key, enabled by default.",
94
+ :boolean => true,
95
+ :default => true
96
+
97
+ option :max_tries,
98
+ :long => "--max-tries MAX_TRIES",
99
+ :description => "Max number of connection tries for each VM",
100
+ :default => 5
101
+
102
+ option :secret,
103
+ :short => "-s SECRET",
104
+ :long => "--secret ",
105
+ :description => "The secret key to use to encrypt data bag item values",
106
+ :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
107
+
108
+ option :secret_file,
109
+ :long => "--secret-file SECRET_FILE",
110
+ :description => "A file containing the secret key to use to encrypt data bag item values",
111
+ :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
112
+
113
+ option :template_file,
114
+ :long => "--template-file TEMPLATE_FILE",
115
+ :description => "Template file to use for bootstrap",
116
+ :proc => Proc.new { |sf| Chef::Config[:knife][:template_file] = tf }
117
+
118
+ Chef::Config[:knife][:hints] ||= {"vcloud" => {}}
119
+ option :hint,
120
+ :long => "--hint HINT_FILE",
121
+ :description => "Specify Ohai Hint to be set on the bootstrap target.",
122
+ :proc => Proc.new { |path| Chef::Config[:knife][:hints]["vcloud"] = path ? JSON.parse(::File.read(path)) : Hash.new }
123
+ end
124
+ end
125
+
126
+ def bootstrap_vm(vm_name, id, addresses)
127
+ ui.msg "Bootstrap VM: #{vm_name}..."
128
+
129
+ max_tries = locate_config_value(:max_tries)
130
+ ssh_port = locate_config_value(:ssh_port)
131
+
132
+ # Stop at the first reachable IP address
133
+ reachable_ip = nil
134
+ addresses.each do |address|
135
+ tries = 1
136
+
137
+ until tries > max_tries
138
+ ui.info "Trying to reach #{address} (try #{tries}/#{max_tries})"
139
+
140
+ if test_connection_ssh(address, ssh_port)
141
+ reachable_ip = address
142
+ break
143
+ end
144
+ tries += 1
145
+ end
146
+ break if reachable_ip
147
+ end
148
+
149
+ if reachable_ip
150
+ ui.msg "Bootstrap IP: #{reachable_ip}"
151
+ bootstrap_for_node(reachable_ip).run
152
+ else
153
+ ui.warn "No reachable IPs. Not bootstrapping."
154
+ end
155
+ end
156
+
157
+ private
158
+ def test_connection_ssh(hostname, port)
159
+ socket = TCPSocket.new(hostname, port)
160
+
161
+ result = IO.select([socket], nil, nil, @test_connection_timeout)
162
+ if result
163
+ ui.info("\t#{hostname}:#{port} replied with: #{socket.gets}")
164
+ true
165
+ else
166
+ false
167
+ end
168
+ rescue Errno::ETIMEDOUT, Errno::EPERM => e
169
+ ui.info("\tUnable to reach #{hostname}:#{port} => #{e.message}")
170
+ false
171
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNRESET => e
172
+ ui.info("\tUnable to reach #{hostname}:#{port} => #{e.message}")
173
+ sleep 2
174
+ false
175
+ ensure
176
+ socket && socket.close
177
+ end
178
+
179
+ def bootstrap_for_node(fqdn)
180
+ bootstrap = Chef::Knife::Bootstrap.new
181
+ bootstrap.name_args = [fqdn]
182
+ bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) || "root"
183
+ bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
184
+ bootstrap.config[:use_sudo] = true unless locate_config_value(:ssh_user) == 'root'
185
+ bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
186
+ bootstrap.config[:ssh_password] = config[:ssh_password]
187
+ bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
188
+ bootstrap.config[:ssh_gateway] = locate_config_value(:ssh_gateway)
189
+ bootstrap.config[:forward_agent] = locate_config_value(:forward_agent)
190
+ bootstrap.config[:identity_file] = locate_config_value(:identity_file)
191
+ bootstrap.config[:manual] = true
192
+ bootstrap.config[:host_key_verify] = locate_config_value(:host_key_verify)
193
+
194
+ bootstrap.config[:run_list] = config[:run_list]
195
+ bootstrap.config[:prerelease] = config[:prerelease]
196
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
197
+ bootstrap.config[:distro] = locate_config_value(:distro)
198
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
199
+ bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
200
+ bootstrap.config[:environment] = config[:environment]
201
+ bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:secret)
202
+ bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:secret_file)
203
+
204
+ bootstrap
205
+ end
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,58 @@
1
+ #
2
+ # Author:: Stefano Tortarolo (<stefano.tortarolo@gmail.com>)
3
+ # Copyright:: Copyright (c) 2013
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ class Chef
20
+ class Knife
21
+ module VcCatalogCommon
22
+ def self.included(includer)
23
+ includer.class_eval do
24
+ option :vcloud_catalog,
25
+ :long => "--catalog CATALOG_NAME",
26
+ :description => "Catalog to whom Catalog Item belongs",
27
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_catalog] = key }
28
+ end
29
+ end
30
+
31
+ def get_catalog(catalog_arg)
32
+ catalog = nil
33
+ org_name = locate_org_option
34
+
35
+ org = connection.get_organization_by_name org_name
36
+ catalog = connection.get_catalog_by_name org, catalog_arg
37
+
38
+ raise ArgumentError, "Catalog #{catalog_arg} not found" unless catalog
39
+ catalog
40
+ end
41
+
42
+ def get_catalog_item(catalog_item_arg)
43
+ item = nil
44
+ catalog_name = locate_config_value(:vcloud_catalog)
45
+
46
+ unless catalog_name
47
+ notice_msg("--catalog not specified, assuming CATALOG_ITEM is an ID")
48
+ item = connection.get_catalog_item catalog_item_arg
49
+ else
50
+ catalog = get_catalog(catalog_name)
51
+ item = connection.get_catalog_item_by_name catalog[:id], catalog_item_arg
52
+ end
53
+ raise ArgumentError, "Catalog Item #{catalog_item_arg} not found" unless item
54
+ item
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,165 @@
1
+ #
2
+ # Author:: Stefano Tortarolo (<stefano.tortarolo@gmail.com>)
3
+ # Copyright:: Copyright (c) 2012-2013
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife'
20
+ require 'date'
21
+
22
+ class Chef
23
+ class Knife
24
+ module VcCommon
25
+ def self.included(includer)
26
+ includer.class_eval do
27
+
28
+ deps do
29
+ require 'vcloud-rest/connection'
30
+ require 'chef/api_client'
31
+ end
32
+
33
+ option :vcloud_url,
34
+ :short => "-H URL",
35
+ :long => "--url URL",
36
+ :description => "The vCloud endpoint URL",
37
+ :proc => Proc.new { |url| Chef::Config[:knife][:vcloud_url] = url }
38
+
39
+ option :vcloud_user_login,
40
+ :short => "-U USER",
41
+ :long => "--user-login USER",
42
+ :description => "Your vCloud User",
43
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_user_login] = key }
44
+
45
+ option :vcloud_password_login,
46
+ :short => "-P SECRET",
47
+ :long => "--password-login SECRET",
48
+ :description => "Your vCloud secret key",
49
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_password_login] = key }
50
+
51
+ option :vcloud_org_login,
52
+ :long => "--org-login ORGANIZATION",
53
+ :description => "Your vCloud Organization",
54
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_org_login] = key }
55
+
56
+ option :vcloud_api_version,
57
+ :short => "-A API_VERSION",
58
+ :long => "--api-version API_VERSION",
59
+ :description => "vCloud API version (1.5 and 5.1 supported)",
60
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_api_version] = key }
61
+
62
+ option :vcloud_system_admin,
63
+ :long => "--[no-]system-admin",
64
+ :description => "Set to true if user is a vCloud System Administrator",
65
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_system_admin] = key },
66
+ :boolean => true,
67
+ :default => false
68
+
69
+ option :vcloud_org,
70
+ :long => "--org ORG_NAME",
71
+ :description => "Organization to use (only for System Administrators)",
72
+ :proc => Proc.new { |key| Chef::Config[:knife][:vcloud_org] = key }
73
+ end
74
+ end
75
+
76
+ def connection
77
+ unless @connection
78
+ @connection = VCloudClient::Connection.new(
79
+ locate_config_value(:vcloud_url),
80
+ locate_config_value(:vcloud_user_login),
81
+ locate_config_value(:vcloud_password_login),
82
+ locate_config_value(:vcloud_org_login),
83
+ locate_config_value(:vcloud_api_version)
84
+ )
85
+ end
86
+
87
+ @connection
88
+ end
89
+
90
+ # Locate the correct organization option
91
+ #
92
+ # System Administrators can browse several organizations and thus --org
93
+ # can be used to specify different organizations
94
+ #
95
+ # Only --org-login is valid for other users
96
+ def locate_org_option
97
+ org = locate_config_value(:vcloud_org_login)
98
+
99
+ if locate_config_value(:vcloud_system_admin)
100
+ return locate_config_value(:vcloud_org) || org
101
+ end
102
+
103
+ if locate_config_value(:vcloud_org)
104
+ ui.warn("--org option is available only for vCloud System Administrators. " \
105
+ "Using --org-login ('#{org}').")
106
+ end
107
+ return org
108
+ end
109
+
110
+ def out_msg(label, value)
111
+ if value && !value.empty?
112
+ ui.msg("#{ui.color(label, :cyan)}: #{value}")
113
+ end
114
+ end
115
+
116
+ def notice_msg(value)
117
+ if value && !value.empty?
118
+ ui.info("#{ui.color('Note:', :bold)} #{value}")
119
+ end
120
+ end
121
+
122
+ def locate_config_value(key)
123
+ key = key.to_sym
124
+ Chef::Config[:knife][key] || config[key]
125
+ end
126
+
127
+ def wait_task(connection, task_id)
128
+ result = connection.wait_task_completion task_id
129
+
130
+ elapsed = humanize_elapsed_time(result[:start_time], result[:end_time])
131
+
132
+ out_msg("Summary",
133
+ "Status: #{ui.color(result[:status], :cyan)} - time elapsed: #{elapsed}")
134
+
135
+ if result[:errormsg]
136
+ ui.warn(ui.color("ATTENTION: #{result[:errormsg]}", :red))
137
+ end
138
+
139
+ result[:errormsg].nil?
140
+ end
141
+
142
+ def pretty_symbol(key)
143
+ key.to_s.gsub('_', ' ').capitalize
144
+ end
145
+
146
+ def sort_by_key(collection)
147
+ collection.sort_by {|k, v| k }
148
+ end
149
+
150
+ private
151
+ def humanize_elapsed_time(start_time, end_time)
152
+ start_time = Time.parse(start_time || Time.now)
153
+ end_time = Time.parse(end_time || Time.now)
154
+ secs = (end_time - start_time)
155
+
156
+ [[60, :seconds],
157
+ [60, :minutes],
158
+ [24, :hours]].map do |count, name|
159
+ secs, n = secs.divmod(count)
160
+ "#{n} #{name}" unless n <= 0
161
+ end.compact.reverse.join(' ')
162
+ end
163
+ end
164
+ end
165
+ end