knife-openstack 0.10.0 → 1.0.0.rc1

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +6 -0
  4. data/CHANGELOG.md +20 -22
  5. data/Gemfile +11 -1
  6. data/README.md +6 -4
  7. data/Rakefile +33 -0
  8. data/knife-openstack.gemspec +7 -8
  9. data/lib/chef/knife/cloud/openstack_server_create_options.rb +75 -0
  10. data/lib/chef/knife/cloud/openstack_service.rb +62 -0
  11. data/lib/chef/knife/cloud/openstack_service_options.rb +50 -0
  12. data/lib/chef/knife/openstack_flavor_list.rb +37 -51
  13. data/lib/chef/knife/openstack_group_list.rb +38 -45
  14. data/lib/chef/knife/openstack_helpers.rb +30 -0
  15. data/lib/chef/knife/openstack_image_list.rb +42 -59
  16. data/lib/chef/knife/openstack_network_list.rb +25 -21
  17. data/lib/chef/knife/openstack_server_create.rb +166 -452
  18. data/lib/chef/knife/openstack_server_delete.rb +26 -106
  19. data/lib/chef/knife/openstack_server_list.rb +37 -59
  20. data/lib/chef/knife/openstack_server_show.rb +57 -0
  21. data/lib/knife-openstack/version.rb +1 -1
  22. data/spec/functional/flavor_list_func_spec.rb +45 -0
  23. data/spec/functional/group_list_func_spec.rb +67 -0
  24. data/spec/functional/image_list_func_spec.rb +51 -0
  25. data/spec/functional/network_list_func_spec.rb +44 -0
  26. data/spec/functional/server_create_func_spec.rb +118 -0
  27. data/spec/functional/server_delete_func_spec.rb +84 -0
  28. data/spec/functional/server_list_func_spec.rb +95 -0
  29. data/spec/functional/server_show_func_spec.rb +46 -0
  30. data/spec/integration/cleanup.rb +91 -0
  31. data/spec/integration/config/environment.yml.sample +13 -0
  32. data/spec/integration/openstack_spec.rb +618 -0
  33. data/spec/spec_helper.rb +126 -0
  34. data/spec/unit/openstack_flavor_list_spec.rb +30 -0
  35. data/spec/unit/openstack_group_list_spec.rb +43 -0
  36. data/spec/unit/openstack_image_list_spec.rb +32 -0
  37. data/spec/unit/openstack_network_list_spec.rb +39 -0
  38. data/spec/unit/openstack_server_create_spec.rb +344 -182
  39. data/spec/unit/openstack_server_delete_spec.rb +43 -0
  40. data/spec/unit/openstack_server_list_spec.rb +32 -0
  41. data/spec/unit/openstack_server_show_spec.rb +42 -0
  42. data/spec/unit/openstack_service_spec.rb +89 -0
  43. data/spec/unit/validate_spec.rb +55 -0
  44. metadata +95 -51
  45. data/lib/chef/knife/openstack_base.rb +0 -182
@@ -1,57 +1,50 @@
1
- #
2
- # Author:: Matt Ray (<matt@getchef.com>)
3
- # Copyright:: Copyright (c) 2013-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
- require 'chef/knife/openstack_base'
1
+ require 'chef/knife/cloud/list_resource_command'
2
+ require 'chef/knife/openstack_helpers'
3
+ require 'chef/knife/cloud/openstack_service_options'
20
4
 
21
5
  class Chef
22
6
  class Knife
23
- class OpenstackGroupList < Knife
24
-
25
- include Knife::OpenstackBase
26
-
27
- banner "knife openstack group list (options)"
7
+ class Cloud
8
+ class OpenstackGroupList < ResourceListCommand
9
+ include OpenstackHelpers
10
+ include OpenstackServiceOptions
28
11
 
29
- def run
12
+ banner "knife openstack group list (options)"
30
13
 
31
- validate!
14
+ def query_resource
15
+ begin
16
+ @service.connection.security_groups
17
+ rescue Excon::Errors::BadRequest => e
18
+ response = Chef::JSONCompat.from_json(e.response.body)
19
+ ui.fatal("Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}")
20
+ raise e
21
+ end
22
+ end
32
23
 
33
- group_list = [
34
- ui.color('Name', :bold),
35
- ui.color('Protocol', :bold),
36
- ui.color('From', :bold),
37
- ui.color('To', :bold),
38
- ui.color('CIDR', :bold),
39
- ui.color('Description', :bold),
40
- ]
41
- connection.security_groups.sort_by(&:name).each do |group|
42
- group.security_group_rules.each do |rule|
43
- unless rule.ip_protocol.nil?
44
- group_list << group.name
45
- group_list << rule.ip_protocol
46
- group_list << rule.from_port.to_s
47
- group_list << rule.to_port.to_s
48
- group_list << rule.ip_range['cidr']
49
- group_list << group.description
24
+ def list(security_groups)
25
+ group_list = [
26
+ ui.color('Name', :bold),
27
+ ui.color('Protocol', :bold),
28
+ ui.color('From', :bold),
29
+ ui.color('To', :bold),
30
+ ui.color('CIDR', :bold),
31
+ ui.color('Description', :bold),
32
+ ]
33
+ security_groups.sort_by(&:name).each do |group|
34
+ group.security_group_rules.each do |rule|
35
+ unless rule.ip_protocol.nil?
36
+ group_list << group.name
37
+ group_list << rule.ip_protocol
38
+ group_list << rule.from_port.to_s
39
+ group_list << rule.to_port.to_s
40
+ group_list << rule.ip_range['cidr']
41
+ group_list << group.description
42
+ end
50
43
  end
51
44
  end
45
+ puts ui.list(group_list, :uneven_columns_across, 6)
52
46
  end
53
- puts ui.list(group_list, :uneven_columns_across, 6)
54
47
  end
55
48
  end
56
49
  end
57
- end
50
+ end
@@ -0,0 +1,30 @@
1
+ require 'chef/knife/cloud/openstack_service_options'
2
+
3
+ class Chef
4
+ class Knife
5
+ class Cloud
6
+ module OpenstackHelpers
7
+
8
+ def primary_private_ip_address(addresses)
9
+ primary_network_ip_address(addresses, 'private')
10
+ end
11
+
12
+ def primary_public_ip_address(addresses)
13
+ primary_network_ip_address(addresses, 'public')
14
+ end
15
+
16
+ def primary_network_ip_address(addresses, network_name)
17
+ return addresses[network_name].last['addr'] if addresses[network_name] && !addresses[network_name].empty?
18
+ end
19
+
20
+ def create_service_instance
21
+ OpenstackService.new
22
+ end
23
+
24
+ def validate!
25
+ super(:openstack_username, :openstack_password, :openstack_auth_url)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,71 +1,54 @@
1
- #
2
- # Author:: Seth Chisamore (<schisamo@getchef.com>)
3
- # Author:: Matt Ray (<matt@getchef.com>)
4
- # Copyright:: Copyright (c) 2011-2014 Chef Software, Inc.
5
- # License:: Apache License, Version 2.0
6
- #
7
- # Licensed under the Apache License, Version 2.0 (the "License");
8
- # you may not use this file except in compliance with the License.
9
- # You may obtain a copy of the License at
10
- #
11
- # http://www.apache.org/licenses/LICENSE-2.0
12
- #
13
- # Unless required by applicable law or agreed to in writing, software
14
- # distributed under the License is distributed on an "AS IS" BASIS,
15
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
- # See the License for the specific language governing permissions and
17
- # limitations under the License.
18
- #
1
+ # Author:: Prabhu Das (<prabhu.das@clogeny.com>)
2
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
19
3
 
20
- require 'chef/knife/openstack_base'
4
+ require 'chef/knife/cloud/list_resource_command'
5
+ require 'chef/knife/openstack_helpers'
6
+ require 'chef/knife/cloud/openstack_service_options'
21
7
 
22
8
  class Chef
23
9
  class Knife
24
- class OpenstackImageList < Knife
25
-
26
- include Knife::OpenstackBase
27
-
28
- banner "knife openstack image list (options)"
29
-
30
- option :disable_filter,
31
- :long => "--disable-filter",
32
- :description => "Disable filtering of the image list. Currently filters names ending with 'initrd' or 'kernel'",
33
- :boolean => true,
34
- :default => false
35
-
36
- def run
10
+ class Cloud
11
+ class OpenstackImageList < ResourceListCommand
12
+ include OpenstackHelpers
13
+ include OpenstackServiceOptions
14
+
15
+ banner "knife openstack image list (options)"
16
+
17
+ option :disable_filter,
18
+ :long => "--disable-filter",
19
+ :description => "Disable filtering of the image list. Currently filters names ending with 'initrd' or 'kernel'",
20
+ :boolean => true,
21
+ :default => false
22
+
23
+ def before_exec_command
24
+ #set resource_filters
25
+ if !config[:disable_filter]
26
+ @resource_filters = [{:attribute => 'name', :regex => /initrd$|kernel$|loader$|virtual$|vmlinuz$/}]
27
+ end
28
+ #set columns_with_info map
29
+ @columns_with_info = [
30
+ {:label => 'Name', :key => 'name'},
31
+ {:label => 'ID', :key => 'id'},
32
+ {:label => 'Snapshot', :key => 'metadata', :value_callback => method(:is_image_snapshot)}
33
+ ]
34
+ @sort_by_field = "name"
35
+ end
37
36
 
38
- validate!
37
+ def query_resource
38
+ @service.list_images
39
+ end
39
40
 
40
- image_list = [
41
- ui.color('Name', :bold),
42
- ui.color('ID', :bold),
43
- ui.color('Snapshot', :bold),
44
- ]
45
- begin
46
- connection.images.sort_by do |image|
47
- [image.name.to_s.downcase, image.id].compact
48
- end.each do |image|
49
- unless ((image.name =~ /initrd$|kernel$|loader$|virtual$|vmlinuz$/) &&
50
- !config[:disable_filter])
51
- image_list << image.name
52
- image_list << image.id
53
- snapshot = 'no'
54
- image.metadata.each do |datum|
55
- if (datum.key == 'image_type') && (datum.value == 'snapshot')
56
- snapshot = 'yes'
57
- end
58
- end
59
- image_list << snapshot
41
+ def is_image_snapshot(metadata)
42
+ snapshot = 'no'
43
+ metadata.each do |datum|
44
+ if (datum.key == 'image_type') && (datum.value == 'snapshot')
45
+ snapshot = 'yes'
60
46
  end
61
47
  end
62
- rescue Excon::Errors::BadRequest => e
63
- response = Chef::JSONCompat.from_json(e.response.body)
64
- ui.fatal("Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}")
65
- raise e
48
+ snapshot
66
49
  end
67
- puts ui.list(image_list, :uneven_columns_across, 3)
50
+
68
51
  end
69
52
  end
70
53
  end
71
- end
54
+ end
@@ -1,31 +1,35 @@
1
- require 'chef/knife/openstack_base'
1
+ # Author:: Prabhu Das (<prabhu.das@clogeny.com>)
2
+ # Copyright:: Copyright (c) 2014 Chef Software, Inc.
3
+
4
+ require 'chef/knife/cloud/list_resource_command'
5
+ require 'chef/knife/openstack_helpers'
6
+ require 'chef/knife/cloud/openstack_service_options'
2
7
 
3
8
  class Chef
4
9
  class Knife
5
- class OpenstackNetworkList < Knife
6
-
7
- include Knife::OpenstackBase
8
-
9
- banner "knife openstack network list (options)"
10
+ class Cloud
11
+ class OpenstackNetworkList < ResourceListCommand
12
+ include OpenstackHelpers
13
+ include OpenstackServiceOptions
10
14
 
11
- def run
15
+ banner "knife openstack network list (options)"
12
16
 
13
- validate!
17
+ def before_exec_command
18
+ #set columns_with_info map
19
+ @columns_with_info = [
20
+ {:label => 'Name', :key => 'name'},
21
+ {:label => 'ID', :key => 'id'},
22
+ {:label => 'Tenant', :key => 'tenant_id'},
23
+ {:label => 'Shared', :key => 'shared'}
24
+ ]
25
+ @sort_by_field = "name"
26
+ end
14
27
 
15
- net_list = [
16
- ui.color('Name', :bold),
17
- ui.color('ID', :bold),
18
- ui.color('Tenant', :bold),
19
- ui.color('Shared', :bold),
20
- ]
21
- network.networks.all.sort_by(&:name).each do |network|
22
- net_list << network.name
23
- net_list << network.id
24
- net_list << network.tenant_id
25
- net_list << network.shared.to_s
28
+ def query_resource
29
+ @service.list_networks
26
30
  end
27
- puts ui.list(net_list, :uneven_columns_across, 4)
31
+
28
32
  end
29
33
  end
30
34
  end
31
- end
35
+ end
@@ -18,500 +18,214 @@
18
18
  # limitations under the License.
19
19
  #
20
20
 
21
- require 'chef/knife/openstack_base'
22
- require 'chef/knife/winrm_base'
21
+ require 'chef/knife/cloud/server/create_command'
22
+ require 'chef/knife/openstack_helpers'
23
+ require 'chef/knife/cloud/openstack_server_create_options'
24
+ require 'chef/knife/cloud/openstack_service'
25
+ require 'chef/knife/cloud/openstack_service_options'
26
+ require 'chef/knife/cloud/exceptions'
23
27
 
24
28
  class Chef
25
29
  class Knife
26
- class OpenstackServerCreate < Knife
27
- include Knife::OpenstackBase
28
- include Chef::Knife::WinrmBase
29
-
30
- deps do
31
- require 'fog'
32
- require 'readline'
33
- require 'chef/json_compat'
34
- require 'chef/knife/bootstrap'
35
- Chef::Knife::Bootstrap.load_deps
36
- end
37
-
38
- banner "knife openstack server create (options)"
39
-
40
- attr_accessor :initial_sleep_delay
41
-
42
- option :flavor,
43
- :short => "-f FLAVOR",
44
- :long => "--flavor FLAVOR",
45
- :description => "The flavor name or ID of server (m1.small, m1.medium, etc)",
46
- :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
47
-
48
- option :image,
49
- :short => "-I IMAGE",
50
- :long => "--image IMAGE",
51
- :description => "A regexp matching an image name or an image ID for the server",
52
- :proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
53
-
54
- option :security_groups,
55
- :short => "-G X,Y,Z",
56
- :long => "--groups X,Y,Z",
57
- :description => "The security groups for this server",
58
- :default => ["default"],
59
- :proc => Proc.new { |groups| groups.split(',') }
60
-
61
- md = {}
62
- option :metadata,
63
- :short => "-M X=1",
64
- :long => "--metadata X=1",
65
- :description => "Metadata information for this server (may pass multiple times)",
66
- :proc => Proc.new { |data| md.merge!({data.split('=')[0]=>data.split('=')[1]}) }
67
-
68
- option :chef_node_name,
69
- :short => "-N NAME",
70
- :long => "--node-name NAME",
71
- :description => "The Chef node name for your new node"
72
-
73
- option :network_ids,
74
- :long => "--network-ids NETWORK_ID_1,NETWORK_ID_2,NETWORK_ID_3",
75
- :description => "Comma separated list of the UUID(s) of the network(s) for the server to attach",
76
- :proc => Proc.new { |networks| networks.split(',') }
77
-
78
- option :floating_ip,
79
- :short => "-a [IP]",
80
- :long => "--floating-ip [IP]",
81
- :default => "-1",
82
- :description => "Request to associate a floating IP address to the new OpenStack node. Assumes IPs have been allocated to the project. Specific IP is optional."
83
-
84
- option :bootstrap_network,
85
- :long => '--bootstrap-network NAME',
86
- :default => 'public',
87
- :description => "Specify network for bootstrapping. Default is 'public'."
88
-
89
- option :network,
90
- :long => "--no-network",
91
- :boolean => true,
92
- :default => true,
93
- :description => "Use first available network for bootstrapping if 'public' and 'private' are unavailable."
94
-
95
- option :private_network,
96
- :long => "--private-network",
97
- :description => "Use the private IP for bootstrapping rather than the public IP",
98
- :boolean => true,
99
- :default => false
100
-
101
- option :ssh_key_name,
102
- :short => "-S KEY",
103
- :long => "--ssh-key KEY",
104
- :description => "The OpenStack SSH keypair id",
105
- :proc => Proc.new { |key| Chef::Config[:knife][:openstack_ssh_key_id] = key }
106
-
107
- option :ssh_port,
108
- :short => "-p PORT",
109
- :long => "--ssh-port PORT",
110
- :description => "The ssh port",
111
- :default => "22",
112
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
113
-
114
- option :ssh_user,
115
- :short => "-x USERNAME",
116
- :long => "--ssh-user USERNAME",
117
- :description => "The ssh username",
118
- :default => "root"
119
-
120
- option :ssh_password,
121
- :short => "-P PASSWORD",
122
- :long => "--ssh-password PASSWORD",
123
- :description => "The ssh password"
124
-
125
- option :identity_file,
126
- :short => "-i IDENTITY_FILE",
127
- :long => "--identity-file IDENTITY_FILE",
128
- :description => "The SSH identity file used for authentication"
129
-
130
- option :prerelease,
131
- :long => "--prerelease",
132
- :description => "Install the pre-release chef gems"
133
-
134
- option :bootstrap_version,
135
- :long => "--bootstrap-version VERSION",
136
- :description => "The version of Chef to install",
137
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
138
-
139
- option :distro,
140
- :short => "-d DISTRO",
141
- :long => "--distro DISTRO",
142
- :description => "Bootstrap a distro using a template; default is 'chef-full'",
143
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
144
- :default => "chef-full"
145
-
146
- option :template_file,
147
- :long => "--template-file TEMPLATE",
148
- :description => "Full path to location of template to use",
149
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
150
- :default => false
151
-
152
- option :run_list,
153
- :short => "-r RUN_LIST",
154
- :long => "--run-list RUN_LIST",
155
- :description => "Comma separated list of roles/recipes to apply",
156
- :proc => lambda { |o| o.split(/[\s,]+/) },
157
- :default => []
30
+ class Cloud
31
+ class OpenstackServerCreate < ServerCreateCommand
32
+ include OpenstackHelpers
33
+ include OpenstackServerCreateOptions
34
+ include OpenstackServiceOptions
35
+
36
+
37
+ banner "knife openstack server create (options)"
38
+
39
+ def before_exec_command
40
+ super
41
+ # setup the create options
42
+ @create_options = {
43
+ :server_def => {
44
+ #servers require a name, knife-cloud generates the chef_node_name
45
+ :name => config[:chef_node_name],
46
+ :image_ref => service.get_image(locate_config_value(:image)).id,
47
+ :flavor_ref => service.get_flavor(locate_config_value(:flavor)).id,
48
+ :security_groups => locate_config_value(:openstack_security_groups),
49
+ :availability_zone => locate_config_value(:availability_zone),
50
+ :metadata => locate_config_value(:metadata),
51
+ :key_name => locate_config_value(:openstack_ssh_key_id)
52
+ },
53
+ :server_create_timeout => locate_config_value(:server_create_timeout)
54
+ }
55
+
56
+ @create_options[:server_def].merge!({:user_data => locate_config_value(:user_data)}) if locate_config_value(:user_data)
57
+ @create_options[:server_def].merge!({:nics => locate_config_value(:network_ids).map { |nic| nic_id = { 'net_id' => nic }}}) if locate_config_value(:network_ids)
58
+
59
+ Chef::Log.debug("Create server params - server_def = #{@create_options[:server_def]}")
60
+ #set columns_with_info map
61
+ @columns_with_info = [
62
+ {:label => 'Instance ID', :key => 'id'},
63
+ {:label => 'Name', :key => 'name'},
64
+ {:label => 'Public IP', :key => 'addresses', :value_callback => method(:primary_public_ip_address)},
65
+ {:label => 'Private IP', :key => 'addresses', :value_callback => method(:primary_private_ip_address)},
66
+ {:label => 'Flavor', :key => 'flavor', :value_callback => method(:get_id)},
67
+ {:label => 'Image', :key => 'image', :value_callback => method(:get_id)},
68
+ {:label => 'Keypair', :key => 'key_name'},
69
+ {:label => 'State', :key => 'state'},
70
+ {:label => 'Availability Zone', :key => 'availability_zone'}
71
+ ]
72
+ end
158
73
 
159
- option :host_key_verify,
160
- :long => "--[no-]host-key-verify",
161
- :description => "Verify host key, enabled by default",
162
- :boolean => true,
163
- :default => true
74
+ def get_id(value)
75
+ value['id']
76
+ end
164
77
 
165
- option :bootstrap_protocol,
166
- :long => "--bootstrap-protocol protocol",
167
- :description => "Protocol to bootstrap Windows servers. options: winrm",
168
- :default => nil
78
+ # Setup the floating ip after server creation.
79
+ def after_exec_command
80
+ Chef::Log.debug("Addresses #{server.addresses}")
81
+ msg_pair("Public IP Address", primary_public_ip_address(server.addresses)) if primary_public_ip_address(server.addresses)
82
+ msg_pair("Private IP Address", primary_private_ip_address(server.addresses)) if primary_private_ip_address(server.addresses)
83
+
84
+ floating_address = locate_config_value(:openstack_floating_ip)
85
+ bind_ip = primary_network_ip_address(server.addresses,server.addresses.keys[0])
86
+ Chef::Log.debug("Floating IP Address requested #{floating_address}")
87
+ unless (floating_address == '-1') #no floating IP requested
88
+ addresses = service.connection.addresses
89
+ #floating requested without value
90
+ if floating_address.nil?
91
+ free_floating = addresses.find_index {|a| a.fixed_ip.nil?}
92
+ begin
93
+ if free_floating.nil? #no free floating IP found
94
+ error_message = "Unable to assign a Floating IP from allocated IPs."
95
+ ui.fatal(error_message)
96
+ raise CloudExceptions::ServerSetupError, error_message
97
+ else
98
+ floating_address = addresses[free_floating].ip
99
+ end
100
+ rescue CloudExceptions::ServerSetupError => e
101
+ cleanup_on_failure
102
+ raise e
103
+ end
104
+ end
169
105
 
170
- option :bootstrap_proxy,
171
- :long => "--bootstrap-proxy PROXY_URL",
172
- :description => "The proxy server for the node being bootstrapped",
173
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_proxy] = v }
106
+ # Pull the port_id for the associate_floating_ip
107
+ port_id = @service.network.list_ports[:body].flatten.flatten[1]["id"]
108
+ fixed_ip_address = @service.network.list_ports[:body].flatten.flatten[1]["fixed_ips"].flatten[0]["ip_address"]
174
109
 
175
- option :server_create_timeout,
176
- :long => "--server-create-timeout timeout",
177
- :description => "How long to wait until the server is ready; default is 600 seconds",
178
- :default => 600,
179
- :proc => Proc.new { |v| Chef::Config[:knife][:server_create_timeouts] = v }
110
+ floating_ip_id = get_floating_ip_id(floating_address)
111
+ # Associate the floating ip via the neutron/network api
112
+ @service.network.associate_floating_ip(floating_ip_id, port_id, options = {:fixed_ip_address => fixed_ip_address })
180
113
 
181
- option :first_boot_attributes,
182
- :short => "-j JSON_ATTRIBS",
183
- :long => "--json-attributes JSON_ATTRIBS",
184
- :description => "A JSON string to be added to the first run of chef-client",
185
- :proc => lambda { |o| JSON.parse(o) },
186
- :default => {}
114
+ #a bit of a hack, but server.reload takes a long time
115
+ (server.addresses['public'] ||= []) << {"version"=>4,"addr"=>floating_address}
116
+ msg_pair("Floating IP Address", floating_address)
117
+ end
187
118
 
188
- option :user_data,
189
- :long => "--user-data USER_DATA",
190
- :description => "The file path containing user data information for this server",
191
- :proc => Proc.new { |user_data| open(user_data) { |f| f.read } }
119
+ Chef::Log.debug("Addresses #{server.addresses}")
120
+ Chef::Log.debug("Public IP Address actual: #{primary_public_ip_address(server.addresses)}") if primary_public_ip_address(server.addresses)
192
121
 
193
- def tcp_test_ssh(hostname, port)
194
- tcp_socket = TCPSocket.new(hostname, port)
195
- readable = IO.select([tcp_socket], nil, nil, 5)
196
- if readable
197
- Chef::Log.debug("sshd accepting connections on #{hostname} port #{port}, banner is #{tcp_socket.gets}")
198
- yield
199
- true
200
- else
201
- false
122
+ msg_pair("Private IP Address", primary_private_ip_address(server.addresses)) if primary_private_ip_address(server.addresses)
123
+ super
202
124
  end
203
- rescue Errno::ETIMEDOUT
204
- false
205
- rescue Errno::EPERM
206
- false
207
- rescue Errno::ECONNREFUSED
208
- sleep 2
209
- false
210
- rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
211
- sleep 2
212
- false
213
- rescue Errno::ENETUNREACH
214
- sleep 2
215
- false
216
- ensure
217
- tcp_socket && tcp_socket.close
218
- end
219
125
 
220
- def tcp_test_winrm(hostname, port)
221
- TCPSocket.new(hostname, port)
222
- return true
223
- rescue SocketError
224
- sleep 2
225
- false
226
- rescue Errno::ETIMEDOUT
227
- false
228
- rescue Errno::EPERM
229
- false
230
- rescue Errno::ECONNREFUSED
231
- sleep 2
232
- false
233
- rescue Errno::EHOSTUNREACH
234
- sleep 2
235
- false
236
- rescue Errno::ENETUNREACH
237
- sleep 2
238
- false
239
- end
126
+ def before_bootstrap
127
+ super
240
128
 
241
- def load_winrm_deps
242
- require 'winrm'
243
- require 'em-winrm'
244
- require 'chef/knife/bootstrap_windows_winrm'
245
- require 'chef/knife/core/windows_bootstrap_context'
246
- require 'chef/knife/winrm'
247
- end
248
- def run
249
- $stdout.sync = true
129
+ # Use SSH password either specified from command line or from openstack server instance
130
+ config[:ssh_password] = locate_config_value(:ssh_password) || server.password unless config[:openstack_ssh_key_id]
250
131
 
251
- validate!
252
- if locate_config_value(:bootstrap_protocol) == 'winrm'
253
- load_winrm_deps
254
- else
255
- # workaround for KNIFE-296 winrm values stomping on ssh values
256
- # unchanged ssh_user and changed winrm_user, override ssh_user
257
- if locate_config_value(:ssh_user).eql?(options[:ssh_user][:default]) &&
258
- !locate_config_value(:winrm_user).eql?(options[:winrm_user][:default])
259
- config[:ssh_user] = locate_config_value(:winrm_user)
260
- end
261
- # unchanged ssh_port and changed winrm_port, override ssh_port
262
- if locate_config_value(:ssh_port).eql?(options[:ssh_port][:default]) &&
263
- !locate_config_value(:winrm_port).eql?(options[:winrm_port][:default])
264
- config[:ssh_port] = locate_config_value(:winrm_port)
265
- end
266
- # unset ssh_password and set winrm_password, override ssh_password
267
- if locate_config_value(:ssh_password).nil? &&
268
- !locate_config_value(:winrm_password).nil?
269
- config[:ssh_password] = locate_config_value(:winrm_password)
270
- end
271
- # unset identity_file and set kerberos_keytab_file, override identity_file
272
- if locate_config_value(:identity_file).nil? &&
273
- !locate_config_value(:kerberos_keytab_file).nil?
274
- config[:identity_file] = locate_config_value(:kerberos_keytab_file)
275
- end
276
- end
277
- # servers require a name, generate one if not passed
278
- node_name = get_node_name(config[:chef_node_name])
132
+ # private_network means bootstrap_network = 'private'
133
+ config[:bootstrap_network] = 'private' if config[:private_network]
279
134
 
280
- # define the server to be created
281
- server_def = {
282
- :name => node_name,
283
- :image_ref => image.id,
284
- :flavor_ref => flavor.id,
285
- :security_groups => locate_config_value(:security_groups),
286
- :availability_zone => locate_config_value(:availability_zone),
287
- :metadata => locate_config_value(:metadata),
288
- :key_name => locate_config_value(:openstack_ssh_key_id)
289
- }
290
- server_def[:user_data] = locate_config_value(:user_data) unless locate_config_value(:user_data).nil?
291
- unless locate_config_value(:network_ids).nil?
292
- server_def[:nics] = locate_config_value(:network_ids).map do |nic|
293
- nic_id = { 'net_id' => nic }
294
- nic_id
135
+ # Which IP address to bootstrap
136
+ unless config[:network] # --no-network
137
+ bootstrap_ip_address = primary_public_ip_address(server.addresses) ||
138
+ primary_private_ip_address(server.addresses) ||
139
+ server.addresses.first[1][0]['addr']
140
+ Chef::Log.debug("No Bootstrap Network: #{config[:bootstrap_network]}")
141
+ else
142
+ bootstrap_ip_address = primary_network_ip_address(server.addresses, config[:bootstrap_network])
143
+ Chef::Log.debug("Bootstrap Network: #{config[:bootstrap_network]}")
295
144
  end
296
- end
297
- Chef::Log.debug("server_def is: #{server_def}")
298
145
 
299
- Chef::Log.debug("Name #{node_name}")
300
- Chef::Log.debug("Availability Zone #{locate_config_value(:availability_zone)}")
301
- Chef::Log.debug("Image #{locate_config_value(:image)}")
302
- Chef::Log.debug("Flavor #{locate_config_value(:flavor)}")
303
- Chef::Log.debug("Requested Floating IP #{locate_config_value(:floating_ip)}")
304
- Chef::Log.debug("Security Groups #{locate_config_value(:security_groups)}")
305
- Chef::Log.debug("User Data #{locate_config_value(:user_data)}")
306
- Chef::Log.debug("Metadata #{locate_config_value(:metadata)}")
307
- Chef::Log.debug("Creating server #{server_def}")
308
-
309
- begin
310
- server = connection.servers.create(server_def)
311
- rescue Excon::Errors::BadRequest => e
312
- response = Chef::JSONCompat.from_json(e.response.body)
313
- if response['badRequest']['code'] == 400
314
- if response['badRequest']['message'] =~ /Invalid flavorRef/
315
- ui.fatal("Bad request (400): Invalid flavor specified: #{server_def[:flavor_ref]}")
316
- exit 1
317
- else
318
- ui.fatal("Bad request (400): #{response['badRequest']['message']}")
319
- exit 1
320
- end
321
- else
322
- ui.fatal("Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}")
323
- raise e
146
+ Chef::Log.debug("Bootstrap IP Address: #{bootstrap_ip_address}")
147
+ if bootstrap_ip_address.nil?
148
+ error_message = "No IP address available for bootstrapping."
149
+ ui.error(error_message)
150
+ raise CloudExceptions::BootstrapError, error_message
324
151
  end
152
+ config[:bootstrap_ip_address] = bootstrap_ip_address
325
153
  end
326
154
 
327
- msg_pair("Instance Name", server.name)
328
- msg_pair("Instance ID", server.id)
329
- msg_pair("Availability zone", server.availability_zone)
330
-
331
- print "\n#{ui.color("Waiting for server", :magenta)}"
332
-
333
- # wait for it to be ready to do stuff
334
- server.wait_for(Integer(locate_config_value(:server_create_timeout))) { print "."; ready? }
335
-
336
- puts("\n")
155
+ def validate_params!
156
+ # set param vm_name to a random value if the name is not set by the user (plugin)
157
+ config[:chef_node_name] = get_node_name(locate_config_value(:chef_node_name), locate_config_value(:chef_node_name_prefix))
337
158
 
338
- msg_pair("Flavor", server.flavor['id'])
339
- msg_pair("Image", server.image['id'])
340
- msg_pair("SSH Identity File", config[:identity_file])
341
- msg_pair("SSH Keypair", server.key_name) if server.key_name
342
- msg_pair("SSH Password", server.password) if (server.password && !server.key_name)
159
+ errors = []
343
160
 
344
- Chef::Log.debug("Addresses #{server.addresses}")
345
- msg_pair("Public IP Address", primary_public_ip_address(server.addresses)) if primary_public_ip_address(server.addresses)
346
- msg_pair("Private IP Address", primary_private_ip_address(server.addresses)) if primary_private_ip_address(server.addresses)
347
-
348
- floating_address = locate_config_value(:floating_ip)
349
- Chef::Log.debug("Floating IP Address requested #{floating_address}")
350
- unless (floating_address == '-1') # no floating IP requested
351
- addresses = connection.addresses
352
- # floating requested without value
353
- if floating_address.nil?
354
- free_floating = addresses.find_index { |a| a.fixed_ip.nil? }
355
- if free_floating.nil? # no free floating IP found
356
- ui.error("Unable to assign a Floating IP from allocated IPs.")
357
- exit 1
358
- else
359
- floating_address = addresses[free_floating].ip
161
+ if locate_config_value(:bootstrap_protocol) == 'winrm'
162
+ if locate_config_value(:winrm_password).nil?
163
+ errors << "You must provide Winrm Password."
360
164
  end
165
+ elsif locate_config_value(:bootstrap_protocol) != 'ssh'
166
+ errors << "You must provide a valid bootstrap protocol. options [ssh/winrm]. For linux type images, options [ssh]"
361
167
  end
362
- server.associate_address(floating_address)
363
- # bit of a hack, but server.reload takes a long time
364
- (server.addresses['public'] ||= []) << { "version" => 4, "addr" => floating_address }
365
- msg_pair("Floating IP Address", floating_address)
366
- end
367
-
368
- Chef::Log.debug("Addresses #{server.addresses}")
369
- Chef::Log.debug("Public IP Address actual: #{primary_public_ip_address(server.addresses)}") if primary_public_ip_address(server.addresses)
370
168
 
371
- # private_network means bootstrap_network = 'private'
372
- config[:bootstrap_network] = 'private' if config[:private_network]
373
-
374
- unless config[:network] # --no-network
375
- bootstrap_ip_address = primary_public_ip_address(server.addresses) ||
376
- primary_private_ip_address(server.addresses) ||
377
- server.addresses[1][0]['addr']
378
- Chef::Log.debug("No Bootstrap Network: #{config[:bootstrap_network]}")
379
- else
380
- bootstrap_ip_address = primary_network_ip_address(server.addresses, config[:bootstrap_network])
381
- Chef::Log.debug("Bootstrap Network: #{config[:bootstrap_network]}")
169
+ errors << "You must provide --image-os-type option [windows/linux]" if ! (%w(windows linux).include?(locate_config_value(:image_os_type)))
170
+ error_message = ""
171
+ raise CloudExceptions::ValidationError, error_message if errors.each{|e| ui.error(e); error_message = "#{error_message} #{e}."}.any?
382
172
  end
383
173
 
384
- Chef::Log.debug("Bootstrap IP Address: #{bootstrap_ip_address}")
385
- if bootstrap_ip_address.nil?
386
- ui.error("No IP address available for bootstrapping.")
387
- exit 1
174
+ def is_image_valid?
175
+ service.get_image(locate_config_value(:image)).nil? ? false : true
388
176
  end
389
177
 
390
- if locate_config_value(:bootstrap_protocol) == 'winrm'
391
- print "\n#{ui.color("Waiting for winrm", :magenta)}"
392
- print(".") until tcp_test_winrm(bootstrap_ip_address, locate_config_value(:winrm_port))
393
- bootstrap_for_windows_node(server, bootstrap_ip_address).run
394
- else
395
- Chef::Log.debug("Waiting for sshd on IP address: #{bootstrap_ip_address} and port: #{locate_config_value(:ssh_port)}")
396
- print "\n#{ui.color("Waiting for sshd", :magenta)}"
397
- print(".") until tcp_test_ssh(bootstrap_ip_address, locate_config_value(:ssh_port)) {
398
- sleep @initial_sleep_delay ||= 10
399
- puts("done")
400
- }
401
- bootstrap_for_node(server, bootstrap_ip_address).run
402
- end
403
- puts "\n"
404
- msg_pair("Instance Name", server.name)
405
- msg_pair("Instance ID", server.id)
406
- msg_pair("Flavor", server.flavor['id'])
407
- msg_pair("Image", server.image['id'])
408
- msg_pair("SSH Keypair", server.key_name) if server.key_name
409
- msg_pair("SSH Password", server.password) if (server.password && !server.key_name)
410
- server.addresses.each do |name,addr|
411
- msg_pair("Network", name)
412
- msg_pair(" IP Address", addr[0]['addr'])
178
+ def is_flavor_valid?
179
+ service.get_flavor(locate_config_value(:flavor)).nil? ? false : true
413
180
  end
414
- msg_pair("Environment", config[:environment] || '_default')
415
- msg_pair("Run List", config[:run_list].join(', '))
416
- end
417
-
418
- def bootstrap_for_windows_node(server, bootstrap_ip_address)
419
- bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
420
- bootstrap.name_args = [bootstrap_ip_address]
421
- bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) || 'Administrator'
422
- bootstrap.config[:winrm_password] = locate_config_value(:winrm_password)
423
- bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
424
- bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
425
- bootstrap_common_params(bootstrap, server.name)
426
- end
427
181
 
428
- def bootstrap_common_params(bootstrap, server_name)
429
- bootstrap.config[:chef_node_name] = config[:chef_node_name] || server_name
430
- bootstrap.config[:run_list] = config[:run_list]
431
- bootstrap.config[:first_boot_attributes] = config[:first_boot_attributes]
432
- bootstrap.config[:prerelease] = config[:prerelease]
433
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
434
- bootstrap.config[:distro] = locate_config_value(:distro)
435
- bootstrap.config[:template_file] = locate_config_value(:template_file)
436
- bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
437
- bootstrap.config[:environment] = config[:environment]
438
- bootstrap.config[:encrypted_data_bag_secret] = config[:encrypted_data_bag_secret]
439
- bootstrap.config[:encrypted_data_bag_secret_file] = config[:encrypted_data_bag_secret_file]
440
- # let ohai know we're using OpenStack
441
- Chef::Config[:knife][:hints] ||= {}
442
- Chef::Config[:knife][:hints]['openstack'] ||= {}
443
- bootstrap
444
- end
445
-
446
- def bootstrap_for_node(server, bootstrap_ip_address)
447
- bootstrap = Chef::Knife::Bootstrap.new
448
- bootstrap.name_args = [bootstrap_ip_address]
449
- bootstrap.config[:ssh_user] = config[:ssh_user]
450
- bootstrap.config[:ssh_password] = config[:ssh_password] || server.password unless config[:ssh_key_name]
451
- bootstrap.config[:ssh_port] = config[:ssh_port]
452
- bootstrap.config[:identity_file] = config[:identity_file]
453
- bootstrap.config[:host_key_verify] = config[:host_key_verify]
454
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
455
- bootstrap_common_params(bootstrap, server.name)
456
- end
457
-
458
- def flavor
459
- @flavor ||= connection.flavors.find{|f| f.name == locate_config_value(:flavor) || f.id == locate_config_value(:flavor) }
460
- end
182
+ def is_floating_ip_valid?
183
+ address = locate_config_value(:openstack_floating_ip)
461
184
 
462
- def image
463
- @image ||= connection.images.find{|img| img.name =~ /#{locate_config_value(:image)}/ || img.id == locate_config_value(:image) }
464
- end
465
-
466
- def is_floating_ip_valid
467
- address = locate_config_value(:floating_ip)
468
- if address == '-1' # no floating IP requested
469
- return true
470
- end
471
- addresses = connection.addresses
472
- return false if addresses.empty? # no floating IPs
473
- # floating requested without value
474
- if address.nil?
475
- if addresses.find_index { |a| a.fixed_ip.nil? }
185
+ if address == '-1' # no floating IP requested
476
186
  return true
477
- else
478
- return false # no floating IPs available
479
187
  end
480
- else
481
- # floating requested with value
482
- if addresses.find_index { |a| a.ip == address }
483
- return true
188
+
189
+ addresses = service.connection.addresses
190
+ return false if addresses.empty? # no floating IPs
191
+ # floating requested without value
192
+ if address.nil?
193
+ if addresses.find_index { |a| a.fixed_ip.nil? }
194
+ return true
195
+ else
196
+ return false # no floating IPs available
197
+ end
484
198
  else
485
- return false # requested floating IP does not exist
199
+ # floating requested with value
200
+ if addresses.find_index { |a| a.ip == address }
201
+ return true
202
+ else
203
+ return false # requested floating IP does not exist
204
+ end
486
205
  end
487
206
  end
488
- end
489
-
490
- def validate!
491
- super([:image, :openstack_username, :openstack_password, :openstack_auth_url])
492
-
493
- if flavor.nil?
494
- ui.error("You have not provided a valid flavor ID. Please note the options for this value are -f or --flavor.")
495
- exit 1
496
- end
497
207
 
498
- if image.nil?
499
- ui.error("You have not provided a valid image ID. Please note the options for this value are -I or --image.")
500
- exit 1
208
+ def post_connection_validations
209
+ errors = []
210
+ errors << "You have not provided a valid image ID. Please note the options for this value are -I or --image." if !is_image_valid?
211
+ errors << "You have not provided a valid flavor ID. Please note the options for this value are -f or --flavor." if !is_flavor_valid?
212
+ errors << "You have either requested an invalid floating IP address or none are available." if !is_floating_ip_valid?
213
+ error_message = ""
214
+ raise CloudExceptions::ValidationError, error_message if errors.each{|e| ui.error(e); error_message = "#{error_message} #{e}."}.any?
501
215
  end
502
216
 
503
- if !is_floating_ip_valid
504
- ui.error("You have either requested an invalid floating IP address or none are available.")
505
- exit 1
217
+ def get_floating_ip_id(floating_address)
218
+ # required for this method to work
219
+ floating_ip_id = -1
220
+ # Figure out the id for the port that the floating ip you requested
221
+ @service.network.list_floating_ips[:body]["floatingips"].each do |x|
222
+ if x["floating_ip_address"] == floating_address
223
+ floating_ip_id = x["id"]
224
+ end
225
+ end
226
+ return floating_ip_id
506
227
  end
507
228
  end
508
-
509
- # generate a random name if chef_node_name is empty
510
- def get_node_name(chef_node_name)
511
- return chef_node_name unless chef_node_name.nil?
512
- # lazy uuids
513
- chef_node_name = "os-" + rand.to_s.split('.')[1]
514
- end
515
229
  end
516
230
  end
517
231
  end