knife-vcloud 0.2.3 → 1.0.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 (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