knife-hp 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +9 -9
- data/README.md +24 -17
- data/knife-hp.gemspec +2 -2
- data/lib/chef/knife/hp_base.rb +42 -25
- data/lib/chef/knife/hp_flavor_list.rb +9 -9
- data/lib/chef/knife/hp_group_list.rb +15 -13
- data/lib/chef/knife/hp_image_list.rb +2 -2
- data/lib/chef/knife/hp_network_list.rb +53 -0
- data/lib/chef/knife/hp_server_create.rb +161 -107
- data/lib/chef/knife/hp_server_delete.rb +9 -18
- data/lib/chef/knife/hp_server_list.rb +14 -23
- data/lib/knife-hp/version.rb +1 -1
- metadata +8 -7
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
*
|
4
|
-
*
|
5
|
-
*
|
6
|
-
*
|
7
|
-
*
|
8
|
-
*
|
1
|
+
## v0.4.0
|
2
|
+
* Upgrade to support 13.5 HP Helion Public Cloud Services. For more information check https://docs.hpcloud.com/migration-overview/
|
3
|
+
* KNIFE-444 knife-hp incompatible with hpcloud v13.5
|
4
|
+
* Added support for HP's regions (US East and US West) and the availability zones within those regions
|
5
|
+
* Added 'knife hp network list'
|
6
|
+
* Added floating IP address support
|
7
|
+
* Added --networks to specify networks to attach to
|
8
|
+
* Replaced --private-network with --bootstrap-network
|
9
9
|
|
10
10
|
## v0.3.1
|
11
|
-
* KNIFE-309 knife hp server list fails with nil image
|
11
|
+
* KNIFE-309 knife hp server list fails with nil image from boot from volume
|
12
12
|
|
13
13
|
## v0.3.0
|
14
14
|
* update dependency on to Fog 1.10.0
|
data/README.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
Knife HP
|
2
2
|
========
|
3
3
|
|
4
|
-
This is the official Opscode Knife plugin for HP Cloud Compute. This plugin gives knife the ability to create, bootstrap and manage instances in the HP Cloud.
|
4
|
+
This is the official Opscode Knife plugin for HP Helion Public Cloud Compute. This plugin gives knife the ability to create, bootstrap and manage instances in the HP Public Cloud 13.5 and later. You may need to go to https://horizon.hpcloud.com and enable Compute under "Activate Services" for the Availability Zones you wish to use. To properly configure your networks (include a router for external access), please refer to https://docs.hpcloud.com/hpcloudconsole.
|
5
|
+
|
6
|
+
If you are still using the older version of the API, you may need version 0.3.1 of this plugin, HP's migration and upgrade instructions are here: https://docs.hpcloud.com/migration-overview-reference/.
|
5
7
|
|
6
8
|
Please refer to the [CHANGELOG](CHANGELOG.md) for version history.
|
7
9
|
|
@@ -19,13 +21,13 @@ Depending on your system's configuration, you may need to run this command with
|
|
19
21
|
|
20
22
|
# Configuration #
|
21
23
|
|
22
|
-
In order to communicate with HP Compute Cloud's API you will need to tell Knife the Access Key ID, the Secret Key and Tenant ID (found on the "API Keys" page). You may also override the auth URI and availability zone. The easiest way to accomplish this is to create these entries in your `knife.rb` file:
|
24
|
+
In order to communicate with HP Helion Compute Cloud's API you will need to tell Knife the Access Key ID, the Secret Key and Tenant ID (found on the "API Keys" page). You may also override the auth URI and availability zone. The easiest way to accomplish this is to create these entries in your `knife.rb` file:
|
23
25
|
|
24
26
|
knife[:hp_access_key] = "Your HP Cloud Access Key ID"
|
25
27
|
knife[:hp_secret_key] = "Your HP Cloud Secret Key"
|
26
28
|
knife[:hp_tenant_id] = "Your HP Cloud Tenant ID"
|
27
29
|
knife[:hp_auth_uri] = "Your HP Cloud Auth URI" (optional, default is 'https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/')
|
28
|
-
knife[:hp_avl_zone] = "Your HP Cloud Availability Zone" (optional, default is '
|
30
|
+
knife[:hp_avl_zone] = "Your HP Cloud Availability Zone" (optional, default is 'region-a.geo-1', the other option is 'region-b.geo-1').
|
29
31
|
|
30
32
|
If your knife.rb file will be checked into a SCM system (ie readable by others) you may want to read the values from environment variables:
|
31
33
|
|
@@ -41,7 +43,7 @@ You also have the option of passing your HP Cloud API options from the command l
|
|
41
43
|
`-K` (or `--hp-secret`) your HP Cloud Secret Key
|
42
44
|
`-T` (or `--hp-tenant`) your HP Cloud Tenant ID
|
43
45
|
`--hp-auth` your HP Cloud Auth URI (optional, default is 'https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/')
|
44
|
-
`-Z` (or `--hp-zone`) your HP Cloud Availability Zone (optional, default is '
|
46
|
+
`-Z` (or `--hp-zone`) your HP Cloud Availability Zone (optional, default is 'region-a.geo-1', the other option is 'region-b.geo-1')
|
45
47
|
|
46
48
|
knife hp server create -A 'MyUsername' -K 'MyPassword' -T 'MyTenant' -f 101 -I 120 -S hpkeypair -i ~/.ssh/hpkeypair.pem -r 'role[webserver]'
|
47
49
|
|
@@ -59,46 +61,51 @@ This plugin provides the following Knife subcommands. Specific command options c
|
|
59
61
|
knife hp server create
|
60
62
|
----------------------
|
61
63
|
|
62
|
-
Provisions a new server in the HP Compute Cloud and then perform a Chef bootstrap (using the SSH protocol). The goal of the bootstrap is to get Chef installed on the target system so it can run Chef Client with a Chef Server. The main assumption is a baseline OS installation exists (provided by the provisioning). It is primarily intended for Chef Client systems that talk to a Chef Server. By default the server is bootstrapped using the [chef-full](https://github.com/opscode/chef/blob/master/chef/lib/chef/knife/bootstrap/chef-full.erb) template. This can be overridden using the `-d` or `--template-file` command options. If you do not pass a node name with `-N NAME` (or `--node-name NAME`) a name will be generated for the node.
|
64
|
+
Provisions a new server in the HP Helion Compute Cloud and then perform a Chef bootstrap (using the SSH protocol). The goal of the bootstrap is to get Chef installed on the target system so it can run Chef Client with a Chef Server. The main assumption is a baseline OS installation exists (provided by the provisioning). It is primarily intended for Chef Client systems that talk to a Chef Server. By default the server is bootstrapped using the [chef-full](https://github.com/opscode/chef/blob/master/chef/lib/chef/knife/bootstrap/chef-full.erb) template. This can be overridden using the `-d` or `--template-file` command options. If you do not pass a node name with `-N NAME` (or `--node-name NAME`) a name will be generated for the node.
|
65
|
+
|
66
|
+
knife hp server create -f 101 -I 202e7659-f7c6-444a-8b32-872fe2ed080c -S hpkeypair -i ~/.ssh/hpkeypair.pem
|
67
|
+
|
68
|
+
### networking ###
|
69
|
+
|
70
|
+
If you do not specify any `--network-ids` and do not specify the `--bootstrap-network`, the server will connect to the default network and boot off of it. If you are not bootstrapping from the HP network, you will need to request a floating IP address to be associated with your node with `--floating-ip` (or `-a`) with or without specifying the IP.
|
63
71
|
|
64
|
-
knife hp server create -f
|
72
|
+
knife hp server create -a -f 100 -I 202e7659-f7c6-444a-8b32-872fe2ed080c -S hpkeypair -i ~/.ssh/hpkeypair.pem
|
65
73
|
|
66
74
|
knife hp server delete
|
67
75
|
----------------------
|
68
76
|
|
69
|
-
Deletes an existing server in the currently configured HP Compute Cloud account. <b>PLEASE NOTE</b> - this does not delete the associated node and client objects from the Chef Server without using the `-P` or `--purge` option to purge the client.
|
77
|
+
Deletes an existing server in the currently configured HP Helion Compute Cloud account. <b>PLEASE NOTE</b> - this does not delete the associated node and client objects from the Chef Server without using the `-P` or `--purge` option to purge the client.
|
70
78
|
|
71
79
|
knife hp server list
|
72
80
|
--------------------
|
73
81
|
|
74
|
-
Outputs a list of all servers in the currently configured HP Compute Cloud account. <b>PLEASE NOTE</b> - this shows all instances associated with the account, some of which may not be currently managed by the Chef Server.
|
82
|
+
Outputs a list of all servers in the currently configured HP Helion Compute Cloud account by Region. <b>PLEASE NOTE</b> - this shows all instances associated with the account, some of which may not be currently managed by the Chef Server.
|
75
83
|
|
76
84
|
knife hp flavor list
|
77
85
|
--------------------
|
78
86
|
|
79
|
-
Outputs a list of all flavors (hardware configuration for a server) available to the currently configured HP Compute Cloud account. Each flavor has a unique combination of virtual
|
87
|
+
Outputs a list of all flavors (hardware configuration for a server) available to the currently configured HP Helion Compute Cloud account. Each flavor has a unique combination of virtual CPUs, disk space and memory capacity. This data may be useful when choosing a flavor id to pass to the `knife hp server create` subcommand.
|
80
88
|
|
81
89
|
knife hp image list
|
82
90
|
-------------------
|
83
91
|
|
84
|
-
Outputs a list of all images available to the currently configured HP Compute Cloud account. An image is a collection of files used to create or rebuild a server. The returned list filtered and does not contain images with "(deprecated)", "(Kernel)" or "(Ramdisk)" in their names (this may be disabled with `--disable-filter`). This data may be useful when choosing an image id to pass to the `knife hp server create` subcommand.
|
92
|
+
Outputs a list of all images available to the currently configured HP Helion Compute Cloud account. An image is a collection of files used to create or rebuild a server. The returned list filtered and does not contain images with "(deprecated)", "(Kernel)" or "(Ramdisk)" in their names (this may be disabled with `--disable-filter`). This data may be useful when choosing an image id to pass to the `knife hp server create` subcommand.
|
85
93
|
|
86
94
|
knife hp group list
|
87
95
|
--------------------
|
88
96
|
|
89
|
-
Outputs a list of the security groups available to the currently configured HP Compute Cloud account. Each group may have multiple rules. This data may be useful when choosing your security group(s) to pass to the `knife hp server create` subcommand.
|
97
|
+
Outputs a list of the security groups available to the currently configured HP Helion Compute Cloud account by Region. Each group may have multiple rules. This data may be useful when choosing your security group(s) to pass to the `knife hp server create` subcommand.
|
90
98
|
|
91
|
-
|
92
|
-
|
99
|
+
knife hp network list
|
100
|
+
---------------------
|
93
101
|
|
94
|
-
|
95
|
-
* The [CHANGELOG](CHANGELOG.md) has more missing/incomplete features listed.
|
102
|
+
Lists the networks available to the currently configured HP Helion Compute Cloud account by Region. This data may be useful when choosing your networks to pass to the `knife hp server create` subcommand.
|
96
103
|
|
97
104
|
# License #
|
98
105
|
|
99
|
-
Author:: Matt Ray (<matt@
|
106
|
+
Author:: Matt Ray (<matt@getchef.com>)
|
100
107
|
|
101
|
-
Copyright:: Copyright (c) 2012-
|
108
|
+
Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
102
109
|
|
103
110
|
License:: Apache License, Version 2.0
|
104
111
|
|
data/knife-hp.gemspec
CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_dependency "fog", ">= 1.
|
23
|
-
s.add_dependency "chef", ">= 0
|
22
|
+
s.add_dependency "fog", ">= 1.20"
|
23
|
+
s.add_dependency "chef", ">= 11.0"
|
24
24
|
|
25
25
|
end
|
data/lib/chef/knife/hp_base.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2012-
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -44,7 +44,7 @@ class Chef
|
|
44
44
|
option :hp_secret_key,
|
45
45
|
:short => "-K SECRET",
|
46
46
|
:long => "--hp-secret SECRET",
|
47
|
-
:description => "Your HP Cloud Secret Key",
|
47
|
+
:description => "Your HP Cloud Access Secret Key",
|
48
48
|
:proc => Proc.new { |key| Chef::Config[:knife][:hp_secret_key] = key }
|
49
49
|
|
50
50
|
option :hp_tenant_id,
|
@@ -60,28 +60,47 @@ class Chef
|
|
60
60
|
:proc => Proc.new { |endpoint| Chef::Config[:knife][:hp_auth_uri] = endpoint }
|
61
61
|
|
62
62
|
option :hp_avl_zone,
|
63
|
-
:short => "-
|
64
|
-
:long => "--hp-
|
65
|
-
:default => "
|
66
|
-
:description => "Your HP Cloud
|
67
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:hp_avl_zone] = key }
|
63
|
+
:short => "-R region",
|
64
|
+
:long => "--hp-region Region",
|
65
|
+
:default => "US-EAST",
|
66
|
+
:description => "Your HP Cloud Region (US-EAST/US-WEST)",
|
67
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:hp_avl_zone] = key.downcase }
|
68
|
+
|
68
69
|
end
|
69
70
|
end
|
70
71
|
|
71
72
|
def connection
|
72
|
-
Chef::Log.debug("hp_access_key: #{Chef::Config[:knife][:hp_access_key]}")
|
73
|
-
Chef::Log.debug("hp_secret_key: #{Chef::Config[:knife][:hp_secret_key]}")
|
74
|
-
Chef::Log.debug("hp_tenant_id: #{Chef::Config[:knife][:hp_tenant_id]}")
|
75
73
|
Chef::Log.debug("hp_auth_uri: #{locate_config_value(:hp_auth_uri)}")
|
76
|
-
Chef::Log.debug("
|
74
|
+
Chef::Log.debug("hp_access_key: #{locate_config_value(:hp_access_key)}")
|
75
|
+
Chef::Log.debug("hp_secret_key: #{locate_config_value(:hp_secret_key)}")
|
76
|
+
Chef::Log.debug("hp_tenant_id: #{locate_config_value(:hp_tenant_id)}")
|
77
|
+
Chef::Log.debug("hp_avl_zone: #{region()}")
|
77
78
|
@connection ||= begin
|
78
79
|
connection = Fog::Compute.new(
|
79
80
|
:provider => 'HP',
|
80
|
-
:
|
81
|
-
:
|
82
|
-
:
|
81
|
+
:version => :v2,
|
82
|
+
:hp_auth_uri => locate_config_value(:hp_auth_uri),
|
83
|
+
:hp_access_key => locate_config_value(:hp_access_key),
|
84
|
+
:hp_secret_key => locate_config_value(:hp_secret_key),
|
85
|
+
:hp_tenant_id => locate_config_value(:hp_tenant_id),
|
86
|
+
:hp_avl_zone => region()
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def netconnection
|
92
|
+
Chef::Log.debug("hp_auth_uri: #{locate_config_value(:hp_auth_uri)}")
|
93
|
+
Chef::Log.debug("hp_access_key: #{locate_config_value(:hp_access_key)}")
|
94
|
+
Chef::Log.debug("hp_secret_key: #{locate_config_value(:hp_secret_key)}")
|
95
|
+
Chef::Log.debug("hp_tenant_id: #{locate_config_value(:hp_tenant_id)}")
|
96
|
+
Chef::Log.debug("hp_avl_zone: #{region()}")
|
97
|
+
@connection ||= begin
|
98
|
+
connection = Fog::HP::Network.new(
|
83
99
|
:hp_auth_uri => locate_config_value(:hp_auth_uri),
|
84
|
-
:
|
100
|
+
:hp_access_key => locate_config_value(:hp_access_key),
|
101
|
+
:hp_secret_key => locate_config_value(:hp_secret_key),
|
102
|
+
:hp_tenant_id => locate_config_value(:hp_tenant_id),
|
103
|
+
:hp_avl_zone => region()
|
85
104
|
)
|
86
105
|
end
|
87
106
|
end
|
@@ -103,7 +122,7 @@ class Chef
|
|
103
122
|
keys.each do |k|
|
104
123
|
pretty_key = k.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(hp)/i) ? w.upcase : w.capitalize }
|
105
124
|
if Chef::Config[:knife][k].nil?
|
106
|
-
errors << "You did not
|
125
|
+
errors << "You did not provide a valid '#{pretty_key}' value."
|
107
126
|
end
|
108
127
|
end
|
109
128
|
|
@@ -112,19 +131,17 @@ class Chef
|
|
112
131
|
end
|
113
132
|
end
|
114
133
|
|
115
|
-
def
|
134
|
+
def region()
|
116
135
|
case locate_config_value(:hp_avl_zone)
|
117
|
-
when '
|
118
|
-
|
119
|
-
|
120
|
-
return 'az-2.region-a.geo-1'
|
136
|
+
when 'us-west'
|
137
|
+
Chef::Log.debug("hp_avl_zone: us-west->'region-a.geo-1'")
|
138
|
+
return 'region-a.geo-1'
|
121
139
|
else
|
122
|
-
|
140
|
+
Chef::Log.debug("hp_avl_zone: us-east->'region-b.geo-1'")
|
141
|
+
return 'region-b.geo-1'
|
123
142
|
end
|
124
143
|
end
|
125
144
|
|
126
145
|
end
|
127
146
|
end
|
128
147
|
end
|
129
|
-
|
130
|
-
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2012
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -33,16 +33,16 @@ class Chef
|
|
33
33
|
flavor_list = [
|
34
34
|
ui.color('ID', :bold),
|
35
35
|
ui.color('Name', :bold),
|
36
|
-
ui.color('
|
36
|
+
ui.color('VCPUs', :bold),
|
37
37
|
ui.color('RAM', :bold),
|
38
38
|
ui.color('Disk', :bold),
|
39
39
|
]
|
40
|
-
connection.flavors.sort_by
|
41
|
-
flavor_list << flavor
|
42
|
-
flavor_list << flavor
|
43
|
-
flavor_list << flavor.
|
44
|
-
flavor_list << "#{flavor
|
45
|
-
flavor_list << "#{flavor
|
40
|
+
connection.list_flavors_detail.body['flavors'].sort_by { |h| h['name'] }.each do |flavor|
|
41
|
+
flavor_list << flavor['id']
|
42
|
+
flavor_list << flavor['name']
|
43
|
+
flavor_list << flavor['vcpus'].to_s
|
44
|
+
flavor_list << "#{flavor['ram'].to_s} MB"
|
45
|
+
flavor_list << "#{flavor['disk'].to_s} GB"
|
46
46
|
end
|
47
47
|
puts ui.list(flavor_list, :uneven_columns_across, 5)
|
48
48
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2013
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2013-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -32,25 +32,27 @@ class Chef
|
|
32
32
|
|
33
33
|
group_list = [
|
34
34
|
ui.color('Name', :bold),
|
35
|
+
ui.color('Direction', :bold),
|
36
|
+
ui.color('Type', :bold),
|
35
37
|
ui.color('Protocol', :bold),
|
36
|
-
ui.color('
|
37
|
-
ui.color('
|
38
|
-
ui.color('CIDR', :bold),
|
38
|
+
ui.color('Port Range', :bold),
|
39
|
+
ui.color('Remote', :bold),
|
39
40
|
ui.color('Description', :bold),
|
40
41
|
]
|
41
|
-
|
42
|
-
group.
|
43
|
-
unless rule['
|
42
|
+
netconnection.security_groups.sort_by(&:name).each do |group|
|
43
|
+
group.security_group_rules.each do |rule|
|
44
|
+
unless rule['protocol'].nil?
|
44
45
|
group_list << group.name
|
45
|
-
group_list << rule['
|
46
|
-
group_list << rule['
|
47
|
-
group_list << rule['
|
48
|
-
group_list << rule['
|
46
|
+
group_list << rule['direction']
|
47
|
+
group_list << rule['ethertype']
|
48
|
+
group_list << rule['protocol']
|
49
|
+
group_list << "#{rule['port_range_min']}-#{rule['port_range_max']}"
|
50
|
+
group_list << rule['remote_ip_prefix']
|
49
51
|
group_list << group.description
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
53
|
-
puts ui.list(group_list, :uneven_columns_across,
|
55
|
+
puts ui.list(group_list, :uneven_columns_across, 7)
|
54
56
|
end
|
55
57
|
end
|
56
58
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2012
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 Chef Software, Inc.
|
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
|
+
class HpNetworkList < Knife
|
22
|
+
|
23
|
+
include Knife::HpBase
|
24
|
+
|
25
|
+
banner "knife hp group list (options)"
|
26
|
+
|
27
|
+
require 'chef/knife/hp_base'
|
28
|
+
|
29
|
+
banner "knife hp network list (options)"
|
30
|
+
|
31
|
+
def run
|
32
|
+
|
33
|
+
validate!
|
34
|
+
|
35
|
+
net_list = [
|
36
|
+
ui.color('Name', :bold),
|
37
|
+
ui.color('ID', :bold),
|
38
|
+
ui.color('Tenant', :bold),
|
39
|
+
ui.color('External', :bold),
|
40
|
+
ui.color('Shared', :bold),
|
41
|
+
]
|
42
|
+
netconnection.list_networks.body['networks'].sort_by { |h| h['name'] }.each do |network|
|
43
|
+
net_list << network['name']
|
44
|
+
net_list << network['id']
|
45
|
+
net_list << network['tenant_id']
|
46
|
+
net_list << network['router:external'].to_s
|
47
|
+
net_list << network['shared'].to_s
|
48
|
+
end
|
49
|
+
puts ui.list(net_list, :uneven_columns_across, 5)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2012-
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -56,6 +56,13 @@ class Chef
|
|
56
56
|
:description => "The HP Cloud Key Pair ID",
|
57
57
|
:proc => Proc.new { |key| Chef::Config[:knife][:hp_ssh_key_id] = key }
|
58
58
|
|
59
|
+
option :availability_zone,
|
60
|
+
:short => "-Z Zone",
|
61
|
+
:long => "--hp-zone Zone",
|
62
|
+
:default => "az1",
|
63
|
+
:description => "Your HP Cloud Availability Zone within your Region (az1/az2/az3)",
|
64
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:hp_avl_zone] = key }
|
65
|
+
|
59
66
|
option :chef_node_name,
|
60
67
|
:short => "-N NAME",
|
61
68
|
:long => "--node-name NAME",
|
@@ -117,11 +124,20 @@ class Chef
|
|
117
124
|
:boolean => true,
|
118
125
|
:default => true
|
119
126
|
|
120
|
-
option :
|
121
|
-
:long =>
|
122
|
-
:description =>
|
123
|
-
:
|
124
|
-
|
127
|
+
option :networks,
|
128
|
+
:long => "--networks NETWORK_1,NETWORK_2,NETWORK_3",
|
129
|
+
:description => "Comma separated list of the name(s) of the network(s) for the server to attach",
|
130
|
+
:proc => Proc.new { |networks| networks.split(',') }
|
131
|
+
|
132
|
+
option :floating_ip,
|
133
|
+
:short => "-a [IP]",
|
134
|
+
:long => "--floating-ip [IP]",
|
135
|
+
:default => "-1",
|
136
|
+
:description => "Request to associate a floating IP address to server. Assumes IPs have been allocated to the project. Specifying IP is optional."
|
137
|
+
|
138
|
+
option :bootstrap_network,
|
139
|
+
:long => '--bootstrap-network NAME',
|
140
|
+
:description => "Specify network for bootstrapping."
|
125
141
|
|
126
142
|
def tcp_test_ssh(hostname)
|
127
143
|
tcp_socket = TCPSocket.new(hostname, 22)
|
@@ -159,120 +175,158 @@ class Chef
|
|
159
175
|
Chef::Log.debug("Flavor #{locate_config_value(:flavor)}")
|
160
176
|
Chef::Log.debug("Image #{locate_config_value(:image)}")
|
161
177
|
Chef::Log.debug("Group(s) #{config[:security_groups]}")
|
178
|
+
Chef::Log.debug("Availability Zone #{locate_config_value(:availability_zone)}")
|
179
|
+
Chef::Log.debug("Requested Floating IP #{locate_config_value(:floating_ip)}")
|
162
180
|
Chef::Log.debug("Key Pair #{Chef::Config[:knife][:hp_ssh_key_id]}")
|
163
181
|
|
164
182
|
server_def = {
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
183
|
+
:name => node_name,
|
184
|
+
:flavor_id => locate_config_value(:flavor),
|
185
|
+
:image_id => locate_config_value(:image),
|
186
|
+
:security_groups => config[:security_groups],
|
187
|
+
:availability_zone => locate_config_value(:availability_zone),
|
188
|
+
:key_name => Chef::Config[:knife][:hp_ssh_key_id]
|
189
|
+
}
|
190
|
+
unless locate_config_value(:networks).nil?
|
191
|
+
server_def[:nics] = locate_config_value(:networks).map do |nic|
|
192
|
+
nic_id = { 'net_id' => nic }
|
193
|
+
nic_id
|
194
|
+
end
|
195
|
+
end
|
196
|
+
Chef::Log.debug("server_def is: #{server_def}")
|
197
|
+
|
198
|
+
server = connection.servers.create(server_def)
|
199
|
+
|
200
|
+
msg_pair("Instance Name", server.name)
|
201
|
+
msg_pair("Instance ID", server.id)
|
202
|
+
msg_pair("Availability zone", server.availability_zone)
|
203
|
+
|
204
|
+
print "\n#{ui.color("Waiting for server", :magenta)}"
|
205
|
+
|
206
|
+
# wait for it to be ready to do stuff
|
207
|
+
server.wait_for { print "."; ready? }
|
208
|
+
|
209
|
+
puts("\n")
|
210
|
+
|
211
|
+
msg_pair("Flavor", server.flavor.name)
|
212
|
+
msg_pair("Image", server.image.name)
|
213
|
+
msg_pair("Security Group(s)", server.security_groups.collect {|x| x['name']}.join(", "))
|
214
|
+
msg_pair("SSH Key Pair", server.key_name)
|
215
|
+
|
216
|
+
Chef::Log.debug("Addresses #{server.addresses}")
|
217
|
+
|
218
|
+
floating_address = locate_config_value(:floating_ip)
|
219
|
+
Chef::Log.debug("Floating IP Address requested #{floating_address}")
|
220
|
+
unless (floating_address == '-1') # no floating IP requested
|
221
|
+
addresses = connection.addresses
|
222
|
+
# floating requested without value
|
223
|
+
if floating_address.nil?
|
224
|
+
free_floating = addresses.find_index { |a| a.fixed_ip.nil? }
|
225
|
+
if free_floating.nil? # no free floating IP found
|
226
|
+
ui.error("Unable to assign a Floating IP from allocated IPs.")
|
227
|
+
exit 1
|
228
|
+
else
|
229
|
+
floating_address = addresses[free_floating].ip
|
230
|
+
end
|
231
|
+
end
|
232
|
+
connection.associate_address(server.id, floating_address)
|
233
|
+
msg_pair("Floating IP Address", floating_address)
|
234
|
+
end
|
235
|
+
|
236
|
+
bootstrap_network = locate_config_value(:bootstrap_network)
|
237
|
+
|
238
|
+
unless bootstrap_network.nil?
|
239
|
+
Chef::Log.debug("Bootstrap specified network: #{bootstrap_network}")
|
240
|
+
bootstrap_ip_address = primary_network_ip_address(server.addresses, bootstrap_network)
|
241
|
+
else
|
242
|
+
if (floating_address == '-1') # no float requested
|
243
|
+
Chef::Log.debug("Bootstrap first network: #{server.addresses.first[0]}")
|
244
|
+
bootstrap_ip_address = server.addresses.first[1][0]['addr']
|
245
|
+
else
|
246
|
+
Chef::Log.debug("Bootstrap floating IP address: #{floating_address}")
|
247
|
+
bootstrap_ip_address = floating_address
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
print "\n#{ui.color("Waiting for sshd on #{bootstrap_ip_address}", :magenta)}"
|
252
|
+
|
253
|
+
# hack to ensure the nodes have had time to spin up
|
254
|
+
print(".")
|
255
|
+
sleep 30
|
256
|
+
print(".")
|
257
|
+
|
258
|
+
print(".") until tcp_test_ssh(bootstrap_ip_address) {
|
259
|
+
sleep @initial_sleep_delay ||= 10
|
260
|
+
puts("done")
|
261
|
+
}
|
262
|
+
|
263
|
+
bootstrap_for_node(server, bootstrap_ip_address).run
|
264
|
+
|
265
|
+
puts "\n"
|
266
|
+
msg_pair("Instance ID", server.id)
|
267
|
+
msg_pair("Instance Name", server.name)
|
268
|
+
msg_pair("Flavor", server.flavor.name)
|
269
|
+
msg_pair("Image", server.image.name)
|
270
|
+
msg_pair("Security Group(s)", server.security_groups.collect {|x| x['name']}.join(", "))
|
271
|
+
msg_pair("SSH Key Pair", server.key_name)
|
272
|
+
server.addresses.each do |name,addr|
|
273
|
+
msg_pair("Network", name)
|
274
|
+
msg_pair(" IP Address", addr[0]['addr'])
|
275
|
+
end
|
276
|
+
msg_pair("Environment", config[:environment] || '_default')
|
277
|
+
msg_pair("Run List", config[:run_list].join(', '))
|
200
278
|
end
|
201
279
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
msg_pair("SSH Key Pair", server.key_name)
|
223
|
-
msg_pair("Public IP Address", server.public_ip_address)
|
224
|
-
msg_pair("Private IP Address", server.private_ip_address)
|
225
|
-
msg_pair("Environment", config[:environment] || '_default')
|
226
|
-
msg_pair("Run List", config[:run_list].join(', '))
|
227
|
-
end
|
280
|
+
def bootstrap_for_node(server, bootstrap_ip_address)
|
281
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
282
|
+
bootstrap.name_args = bootstrap_ip_address
|
283
|
+
bootstrap.config[:run_list] = config[:run_list]
|
284
|
+
bootstrap.config[:ssh_user] = config[:ssh_user]
|
285
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
286
|
+
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
287
|
+
bootstrap.config[:chef_node_name] = server.name
|
288
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
289
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
290
|
+
bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
|
291
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
292
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
293
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
294
|
+
bootstrap.config[:environment] = config[:environment]
|
295
|
+
# let ohai know we're using OpenStack
|
296
|
+
Chef::Config[:knife][:hints] ||= {}
|
297
|
+
Chef::Config[:knife][:hints]['hp'] ||= {}
|
298
|
+
bootstrap
|
299
|
+
end
|
228
300
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
bootstrap.config[:run_list] = config[:run_list]
|
233
|
-
bootstrap.config[:ssh_user] = config[:ssh_user]
|
234
|
-
bootstrap.config[:identity_file] = config[:identity_file]
|
235
|
-
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
236
|
-
bootstrap.config[:chef_node_name] = server.name
|
237
|
-
bootstrap.config[:prerelease] = config[:prerelease]
|
238
|
-
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
239
|
-
bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
|
240
|
-
bootstrap.config[:distro] = locate_config_value(:distro)
|
241
|
-
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
242
|
-
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
243
|
-
bootstrap.config[:environment] = config[:environment]
|
244
|
-
bootstrap
|
245
|
-
end
|
301
|
+
def flavor
|
302
|
+
@flavor ||= connection.flavors.get(locate_config_value(:flavor))
|
303
|
+
end
|
246
304
|
|
247
|
-
|
248
|
-
|
249
|
-
|
305
|
+
def image
|
306
|
+
@image ||= connection.images.get(locate_config_value(:image))
|
307
|
+
end
|
250
308
|
|
251
|
-
|
252
|
-
@image ||= connection.images.get(locate_config_value(:image))
|
253
|
-
end
|
309
|
+
def validate!
|
254
310
|
|
255
|
-
|
311
|
+
super([:image, :flavor, :hp_access_key, :hp_secret_key, :hp_tenant_id])
|
256
312
|
|
257
|
-
|
313
|
+
if flavor.nil?
|
314
|
+
ui.error("You have not provided a valid flavor ID. Please note the options for this value are -f or --flavor.")
|
315
|
+
exit 1
|
316
|
+
end
|
258
317
|
|
259
|
-
|
260
|
-
|
261
|
-
|
318
|
+
if image.nil?
|
319
|
+
ui.error("You have not provided a valid image ID. Please note the options for this value are -I or --image.")
|
320
|
+
exit 1
|
321
|
+
end
|
262
322
|
end
|
263
323
|
|
264
|
-
if
|
265
|
-
|
266
|
-
|
324
|
+
#generate a random name if chef_node_name is empty
|
325
|
+
def get_node_name(chef_node_name)
|
326
|
+
return chef_node_name unless chef_node_name.nil?
|
327
|
+
#lazy uuids
|
328
|
+
chef_node_name = "hp-"+rand.to_s.split('.')[1]
|
267
329
|
end
|
268
330
|
end
|
269
|
-
|
270
|
-
#generate a random name if chef_node_name is empty
|
271
|
-
def get_node_name(chef_node_name)
|
272
|
-
return chef_node_name unless chef_node_name.nil?
|
273
|
-
#lazy uuids
|
274
|
-
chef_node_name = "hp-"+rand.to_s.split('.')[1]
|
275
|
-
end
|
276
331
|
end
|
277
332
|
end
|
278
|
-
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2012
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -64,31 +64,22 @@ class Chef
|
|
64
64
|
@name_args.each do |instance_id|
|
65
65
|
begin
|
66
66
|
server = connection.servers.get(instance_id)
|
67
|
-
addresses = connection.addresses
|
68
67
|
|
69
68
|
msg_pair("Instance ID", server.id)
|
70
69
|
msg_pair("Instance Name", server.name)
|
71
|
-
msg_pair("Flavor", server.flavor
|
72
|
-
msg_pair("Image", server.image
|
73
|
-
msg_pair("
|
74
|
-
|
70
|
+
msg_pair("Flavor", server.flavor.name)
|
71
|
+
msg_pair("Image", server.image.name)
|
72
|
+
msg_pair("Availability Zone", server.availability_zone)
|
73
|
+
server.addresses.each do |name,addr|
|
74
|
+
msg_pair("Network", name)
|
75
|
+
msg_pair(" IP Address", addr[0]['addr'])
|
76
|
+
end
|
75
77
|
|
76
78
|
puts "\n"
|
77
79
|
confirm("Do you really want to delete this server")
|
78
80
|
|
79
81
|
server.destroy
|
80
82
|
|
81
|
-
#HP doesn't free allocated IPs when servers are deleted
|
82
|
-
#if the address is a floating, it's the first entry in the private addresses(?)
|
83
|
-
float = server.addresses['private'][0]['addr']
|
84
|
-
Chef::Log.debug("hp_server_delete: float:#{float}")
|
85
|
-
addresses.each do |address|
|
86
|
-
if !(address.fixed_ip.nil?) && (address.fixed_ip == float)
|
87
|
-
msg_pair("Deleted Floating IP Address", float)
|
88
|
-
address.destroy
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
83
|
ui.warn("Deleted server #{server.id}")
|
93
84
|
|
94
85
|
if config[:purge]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
|
-
# Author:: Matt Ray (<matt@
|
3
|
-
# Copyright:: Copyright (c) 2012
|
2
|
+
# Author:: Matt Ray (<matt@getchef.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012-2014 Chef Software, Inc.
|
4
4
|
# License:: Apache License, Version 2.0
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -34,32 +34,25 @@ class Chef
|
|
34
34
|
server_list = [
|
35
35
|
ui.color('Instance ID', :bold),
|
36
36
|
ui.color('Name', :bold),
|
37
|
-
ui.color('Public IP', :bold),
|
38
|
-
ui.color('Private IP', :bold),
|
39
37
|
ui.color('Flavor', :bold),
|
40
38
|
ui.color('Image', :bold),
|
39
|
+
ui.color('Zone', :bold),
|
41
40
|
ui.color('Key Pair', :bold),
|
42
41
|
ui.color('State', :bold)
|
43
42
|
]
|
44
|
-
connection.servers.
|
43
|
+
connection.list_servers_detail.body['servers'].sort_by { |h| h['name'] }.each do |server|
|
45
44
|
Chef::Log.debug("Server: #{server.to_yaml}")
|
46
|
-
|
47
|
-
|
48
|
-
server_list << server
|
49
|
-
server_list << server
|
50
|
-
server_list <<
|
51
|
-
server_list <<
|
52
|
-
server_list << server
|
53
|
-
if server.image
|
54
|
-
server_list << server.image['id']
|
55
|
-
else
|
56
|
-
server_list << ""
|
57
|
-
end
|
58
|
-
server_list << (server.key_name or "")
|
45
|
+
|
46
|
+
server_list << server['id']
|
47
|
+
server_list << server['name']
|
48
|
+
server_list << server['flavor']['id']
|
49
|
+
server_list << server['image']['id']
|
50
|
+
server_list << server['OS-EXT-AZ:availability_zone']
|
51
|
+
server_list << (server['key_name'] or "")
|
59
52
|
server_list << begin
|
60
|
-
state = server.
|
53
|
+
state = server['status'].downcase
|
61
54
|
case state
|
62
|
-
when 'shutting-down','terminated','stopping','stopped','active(deleting)','build(deleting)'
|
55
|
+
when 'shutting-down','terminated','stopping','stopped','active(deleting)','build(deleting)','error'
|
63
56
|
ui.color(state, :red)
|
64
57
|
when 'pending','build(scheduling)','build(spawning)','build(networking)'
|
65
58
|
ui.color(state, :yellow)
|
@@ -68,11 +61,9 @@ class Chef
|
|
68
61
|
end
|
69
62
|
end
|
70
63
|
end
|
71
|
-
puts ui.list(server_list, :uneven_columns_across,
|
64
|
+
puts ui.list(server_list, :uneven_columns_across, 7)
|
72
65
|
|
73
66
|
end
|
74
67
|
end
|
75
68
|
end
|
76
69
|
end
|
77
|
-
|
78
|
-
|
data/lib/knife-hp/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-hp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2014-05-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: fog
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 1.
|
22
|
+
version: '1.20'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,7 +27,7 @@ dependencies:
|
|
27
27
|
requirements:
|
28
28
|
- - ! '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: 1.
|
30
|
+
version: '1.20'
|
31
31
|
- !ruby/object:Gem::Dependency
|
32
32
|
name: chef
|
33
33
|
requirement: !ruby/object:Gem::Requirement
|
@@ -35,7 +35,7 @@ dependencies:
|
|
35
35
|
requirements:
|
36
36
|
- - ! '>='
|
37
37
|
- !ruby/object:Gem::Version
|
38
|
-
version: 0
|
38
|
+
version: '11.0'
|
39
39
|
type: :runtime
|
40
40
|
prerelease: false
|
41
41
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
requirements:
|
44
44
|
- - ! '>='
|
45
45
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0
|
46
|
+
version: '11.0'
|
47
47
|
description: HP Cloud Services Cloud support for Chef's Knife command
|
48
48
|
email:
|
49
49
|
- adam@opscode.com
|
@@ -65,6 +65,7 @@ files:
|
|
65
65
|
- lib/chef/knife/hp_flavor_list.rb
|
66
66
|
- lib/chef/knife/hp_group_list.rb
|
67
67
|
- lib/chef/knife/hp_image_list.rb
|
68
|
+
- lib/chef/knife/hp_network_list.rb
|
68
69
|
- lib/chef/knife/hp_server_create.rb
|
69
70
|
- lib/chef/knife/hp_server_delete.rb
|
70
71
|
- lib/chef/knife/hp_server_list.rb
|
@@ -89,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
90
|
version: '0'
|
90
91
|
requirements: []
|
91
92
|
rubyforge_project:
|
92
|
-
rubygems_version: 1.8.
|
93
|
+
rubygems_version: 1.8.23.2
|
93
94
|
signing_key:
|
94
95
|
specification_version: 3
|
95
96
|
summary: HP Cloud Services Cloud support for Chef's Knife command
|