knife-softlayer 0.4.0 → 0.4.1

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 +5 -13
  2. data/.gitignore +19 -19
  3. data/.travis.yml +20 -22
  4. data/CHANGELOG.md +19 -19
  5. data/CONTRIBUTING.md +31 -31
  6. data/CONTRIBUTORS.md +2 -2
  7. data/Gemfile +4 -4
  8. data/LICENSE.txt +72 -72
  9. data/README.md +83 -83
  10. data/Rakefile +37 -37
  11. data/docs/cla-corporate.md +133 -133
  12. data/docs/cla-individual.md +84 -84
  13. data/examples/datacenter.md +94 -94
  14. data/examples/flavor.md +58 -58
  15. data/examples/global_ip.md +22 -22
  16. data/examples/image.md +34 -34
  17. data/examples/key_pair.md +56 -56
  18. data/examples/server.md +82 -81
  19. data/examples/vlan.md +85 -85
  20. data/knife-softlayer.gemspec +39 -39
  21. data/lib/chef/knife/flavor/base.rb +52 -52
  22. data/lib/chef/knife/softlayer.rb +14 -14
  23. data/lib/chef/knife/softlayer_base.rb +112 -112
  24. data/lib/chef/knife/softlayer_datacenter_list.rb +28 -28
  25. data/lib/chef/knife/softlayer_datacenter_show.rb +45 -45
  26. data/lib/chef/knife/softlayer_delete.rb +6 -6
  27. data/lib/chef/knife/softlayer_flavor_list.rb +53 -53
  28. data/lib/chef/knife/softlayer_global_ip_list.rb +34 -34
  29. data/lib/chef/knife/softlayer_image_list.rb +26 -26
  30. data/lib/chef/knife/softlayer_key_pair_create.rb +37 -37
  31. data/lib/chef/knife/softlayer_key_pair_list.rb +28 -28
  32. data/lib/chef/knife/softlayer_list.rb +6 -6
  33. data/lib/chef/knife/softlayer_server_create.rb +459 -453
  34. data/lib/chef/knife/softlayer_server_destroy.rb +146 -146
  35. data/lib/chef/knife/softlayer_server_list.rb +27 -27
  36. data/lib/chef/knife/softlayer_server_relaunch.rb +208 -208
  37. data/lib/chef/knife/softlayer_vlan_create.rb +37 -37
  38. data/lib/chef/knife/softlayer_vlan_list.rb +28 -28
  39. data/lib/chef/knife/softlayer_vlan_show.rb +40 -40
  40. data/lib/knife-softlayer/version.rb +12 -12
  41. data/spec/spec_helper.rb +21 -21
  42. data/spec/unit/softlayer_base_spec.rb +26 -26
  43. data/spec/unit/softlayer_server_create_spec.rb +105 -105
  44. data/spec/unit/softlayer_server_destroy_spec.rb +47 -47
  45. metadata +29 -29
@@ -1,34 +1,34 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
- #
7
-
8
- require 'chef/knife/softlayer_base'
9
-
10
- class Chef
11
- class Knife
12
- class SoftlayerGlobalIpList < Knife
13
-
14
- include Knife::SoftlayerBase
15
-
16
- banner 'knife softlayer global ip list (options)'
17
-
18
- def run
19
- $stdout.sync = true
20
-
21
- if connection(:network).get_global_ip_records.body.empty?
22
- puts ui.color("No global ip addresses found.", :green)
23
- else
24
- puts ui.color("This operation can take several minutes. ", :yellow)
25
- table_data = connection(:network).ips.map do |ip|
26
- {:address => ip.address, :destination => ip.destination_ip.respond_to?(:address) ? ip.destination_ip.address : 'NOT ROUTED'} if ip.global?
27
- end.compact
28
- puts Formatador.display_table(table_data, [:address, :destination])
29
- end
30
- end
31
-
32
- end
33
- end
34
- end
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
+ #
7
+
8
+ require 'chef/knife/softlayer_base'
9
+
10
+ class Chef
11
+ class Knife
12
+ class SoftlayerGlobalIpList < Knife
13
+
14
+ include Knife::SoftlayerBase
15
+
16
+ banner 'knife softlayer global ip list (options)'
17
+
18
+ def run
19
+ $stdout.sync = true
20
+
21
+ if connection(:network).get_global_ip_records.body.empty?
22
+ puts ui.color("No global ip addresses found.", :green)
23
+ else
24
+ puts ui.color("This operation can take several minutes. ", :yellow)
25
+ table_data = connection(:network).ips.map do |ip|
26
+ {:address => ip.address, :destination => ip.destination_ip.respond_to?(:address) ? ip.destination_ip.address : 'NOT ROUTED'} if ip.global?
27
+ end.compact
28
+ puts Formatador.display_table(table_data, [:address, :destination])
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -1,26 +1,26 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
- #
7
-
8
- require 'chef/knife/softlayer_base'
9
-
10
- class Chef
11
- class Knife
12
- class SoftlayerImageList < Knife
13
-
14
- include Knife::SoftlayerBase
15
-
16
- banner 'knife softlayer image list'
17
-
18
- def run
19
- $stdout.sync = true
20
- table_data = connection(:compute).images.map { |i| {:id => i.id, :name => i.name, :access => i.public? ? 'PUBLIC' : 'PRIVATE' } }
21
- puts Formatador.display_table(table_data, [:id, :access, :name,])
22
- end
23
-
24
- end
25
- end
26
- end
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
+ #
7
+
8
+ require 'chef/knife/softlayer_base'
9
+
10
+ class Chef
11
+ class Knife
12
+ class SoftlayerImageList < Knife
13
+
14
+ include Knife::SoftlayerBase
15
+
16
+ banner 'knife softlayer image list'
17
+
18
+ def run
19
+ $stdout.sync = true
20
+ table_data = connection(:compute).images.map { |i| {:id => i.id, :name => i.name, :access => i.public? ? 'PUBLIC' : 'PRIVATE' } }
21
+ puts Formatador.display_table(table_data, [:id, :access, :name,])
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -1,37 +1,37 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
- #
7
-
8
- require 'chef/knife/softlayer_base'
9
-
10
- class Chef
11
- class Knife
12
- class SoftlayerKeyPairCreate < Knife
13
-
14
- include Knife::SoftlayerBase
15
-
16
- banner 'knife softlayer key pair create'
17
-
18
- def run
19
- $stdout.sync = true
20
- opts = {
21
- :label => ui.ask_question("Enter the label for this key pair: "),
22
- :key => ui.ask("Enter path to the public key: ", lambda{ |answer| IO.read(answer) })
23
- }
24
-
25
- key_pair = connection(:compute).key_pairs.create(opts)
26
-
27
- if !!key_pair
28
- puts "#{ui.color("Key pair successfully created. Provisioning may take a few minutes to complete.", :green)}"
29
- puts "#{ui.color("Key pair ID is: ", :green)} #{key_pair.id}"
30
- else
31
- puts "#{ui.color("Encountered a problem verifying key pair creation. Please try again.", :yellow)}"
32
- end
33
- end
34
-
35
- end
36
- end
37
- end
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
+ #
7
+
8
+ require 'chef/knife/softlayer_base'
9
+
10
+ class Chef
11
+ class Knife
12
+ class SoftlayerKeyPairCreate < Knife
13
+
14
+ include Knife::SoftlayerBase
15
+
16
+ banner 'knife softlayer key pair create'
17
+
18
+ def run
19
+ $stdout.sync = true
20
+ opts = {
21
+ :label => ui.ask_question("Enter the label for this key pair: "),
22
+ :key => ui.ask("Enter path to the public key: ", lambda{ |answer| IO.read(answer) })
23
+ }
24
+
25
+ key_pair = connection(:compute).key_pairs.create(opts)
26
+
27
+ if !!key_pair
28
+ puts "#{ui.color("Key pair successfully created. Provisioning may take a few minutes to complete.", :green)}"
29
+ puts "#{ui.color("Key pair ID is: ", :green)} #{key_pair.id}"
30
+ else
31
+ puts "#{ui.color("Encountered a problem verifying key pair creation. Please try again.", :yellow)}"
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -1,28 +1,28 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
- #
7
-
8
- require 'chef/knife/softlayer_base'
9
-
10
- class Chef
11
- class Knife
12
- class SoftlayerKeyPairList < Knife
13
-
14
- include Knife::SoftlayerBase
15
-
16
- banner 'knife softlayer key pair list'
17
-
18
- def run
19
- $stdout.sync = true
20
- table_data = connection(:compute).key_pairs.map do |kp|
21
- {:id => kp.id, :label => kp.label, :create_date => kp.create_date, :modify_date => kp.modify_date }
22
- end
23
- puts Formatador.display_table(table_data, [:id, :label, :create_date, :modify_date])
24
- end
25
-
26
- end
27
- end
28
- end
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
+ #
7
+
8
+ require 'chef/knife/softlayer_base'
9
+
10
+ class Chef
11
+ class Knife
12
+ class SoftlayerKeyPairList < Knife
13
+
14
+ include Knife::SoftlayerBase
15
+
16
+ banner 'knife softlayer key pair list'
17
+
18
+ def run
19
+ $stdout.sync = true
20
+ table_data = connection(:compute).key_pairs.map do |kp|
21
+ {:id => kp.id, :label => kp.label, :create_date => kp.create_date, :modify_date => kp.modify_date }
22
+ end
23
+ puts Formatador.display_table(table_data, [:id, :label, :create_date, :modify_date])
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -1,6 +1,6 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
- #
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
+ #
@@ -1,453 +1,459 @@
1
- #
2
- # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
- # © Copyright IBM Corporation 2014.
4
- #
5
- # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
- #
7
-
8
- require 'chef/knife/softlayer_base'
9
-
10
- class Chef
11
- class Knife
12
- class SoftlayerServerCreateError < StandardError; end
13
- class SoftlayerServerCreate < Knife
14
-
15
- attr_reader :cci
16
-
17
- include Knife::SoftlayerBase
18
-
19
- banner 'knife softlayer server create (options)'
20
-
21
- option :flavor,
22
- :long => '--flavor FLAVOR',
23
- :short => '-f FLAVOR',
24
- :description => 'Pre-configured packages of computing resources. See `knife softlayer flavor list` for details.'
25
-
26
- option :hostname,
27
- :long => '--hostname VALUE',
28
- :short => '-H VALUE',
29
- :description => 'The hostname SoftLayer will assign to the VM instance.'
30
-
31
- option :domain,
32
- :long => '--domain VALUE',
33
- :short => '-D VALUE',
34
- :description => 'The FQDN SoftLayer will assign to the VM instance.',
35
- :default => Chef::Config[:knife][:softlayer_default_domain]
36
-
37
- option :cores,
38
- :long => '--cores VALUE',
39
- :short => '-C VALUE',
40
- :description => 'The number of virtual cores SoftLayer will assign to the VM instance.'
41
-
42
- option :os_code,
43
- :long => '--os-code VALUE',
44
- :short => '-O VALUE',
45
- :description => 'A valid SoftLayer operating system code. See `knife softlayer flavor list --all` for a list of valid codes.'
46
-
47
- option :ram,
48
- :long => '--ram VALUE',
49
- :short => '-R VALUE',
50
- :description => 'The number of virtual cores SoftLayer will assign to the VM instance.'
51
-
52
-
53
- option :block_storage,
54
- :long => '--block-storage VALUE',
55
- :short => '-B VALUE',
56
- :description => 'The size in GB of the block storage devices (disks) for this instance. Specify 1 - 5 entries in a comma separated list following the format "dev:size". Example: "0:25,2:500" would be a 25GB volume on device 0 (the root partition) and a 100GB volume on on device 2. [NOTE: SoftLayer VMs always reserve device 1 for a swap device.] ',
57
- :proc => Proc.new { |devs| devs.split(',').map{ |dev| device, capacity = dev.split(':'); {"device"=>device, "diskImage"=>{"capacity"=>capacity}} } }
58
-
59
- option :nic,
60
- :long => '--network-interface-speed VALUE',
61
- :short => '-n VALUE',
62
- :description => 'The maximum speed of the public NIC available to the instance.',
63
- :default => 10
64
-
65
- option :bill_monthly,
66
- :long => '--bill-monthly',
67
- :description => 'Flag to bill monthly instead of hourly, minimum charge of one month.',
68
- :boolean => true,
69
- :default => false
70
-
71
- option :vlan,
72
- :long => '--vlan VLAN-ID',
73
- :description => 'Internal SoftLayer ID of the public VLAN into which the compute instance should be placed.'
74
-
75
- option :private_vlan,
76
- :long => '--private-vlan VLAN-ID',
77
- :description => 'Internal SoftLayer ID of the private VLAN into which the compute instance should be placed.'
78
-
79
- option :image_id,
80
- :long => '--image-id IMAGE-ID',
81
- :description => 'Internal SoftLayer uuid specifying the image template from which the compute instance should be booted.'
82
-
83
- option :private_network_only,
84
- :long => '--private-network-only',
85
- :description => 'Flag to be passed when the compute instance should have no public facing network interface.',
86
- :boolean => true
87
-
88
- option :use_private_network,
89
- :long => '--use-private-network',
90
- :description => 'Flag to be passwed when bootstrap is preferred over the private network.',
91
- :boolean => true
92
-
93
- option :from_file,
94
- :long => '--from-file PATH',
95
- :description => 'Path to JSON file containing arguments for provisoning and bootstrap.'
96
-
97
- #option :single_tenant,
98
- # :long => '--single-tenant',
99
- # :description => 'Create a VM with a dedicated physical host.',
100
- # :boolean => true,
101
- # :default => false
102
-
103
- option :local_storage,
104
- :long => '--local-storage',
105
- :description => 'Force local block storage instead of SAN storage.',
106
- :boolean => true,
107
- :default => false
108
-
109
- option :datacenter,
110
- :long => '--datacenter VALUE',
111
- :description => 'Create a VM in a particular datacenter.',
112
- :default => Chef::Config[:knife][:softlayer_default_datacenter]
113
-
114
- option :tags,
115
- :short => "-T T=V[,T=V,...]",
116
- :long => "--tags Tag=Value[,Tag=Value...]",
117
- :description => "The tags for this server",
118
- :proc => Proc.new { |tags| tags.split(',') }
119
-
120
- option :chef_node_name,
121
- :short => "-N NAME",
122
- :long => "--node-name NAME",
123
- :description => "The Chef node name for your new node",
124
- :proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
125
-
126
- option :ssh_user,
127
- :short => "-x USERNAME",
128
- :long => "--ssh-user USERNAME",
129
- :description => "The ssh username",
130
- :default => "root"
131
-
132
- option :ssh_password,
133
- :short => "-P PASSWORD",
134
- :long => "--ssh-password PASSWORD",
135
- :description => "The ssh password"
136
-
137
- option :ssh_keys,
138
- :short => "-S KEY",
139
- :long => "--ssh-keys KEY",
140
- :description => "The ssh keys for the SoftLayer Virtual Guest environment. Accepts a space separated list of integers.",
141
- :proc => Proc.new { |ssh_keys| ssh_keys.split(' ').map { |k| {:id => k}} }
142
-
143
- option :ssh_port,
144
- :short => "-p PORT",
145
- :long => "--ssh-port PORT",
146
- :description => "The ssh port",
147
- :default => "22",
148
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
149
-
150
- option :ssh_gateway,
151
- :short => "-w GATEWAY",
152
- :long => "--ssh-gateway GATEWAY",
153
- :description => "The ssh gateway server",
154
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
155
-
156
- option :identity_file,
157
- :short => "-i IDENTITY_FILE",
158
- :long => "--identity-file IDENTITY_FILE",
159
- :description => "The SSH identity file used for authentication"
160
-
161
- option :prerelease,
162
- :long => "--prerelease",
163
- :description => "Install the pre-release chef gems"
164
-
165
- option :bootstrap_version,
166
- :long => "--bootstrap-version VERSION",
167
- :description => "The version of Chef to install",
168
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
169
-
170
- option :bootstrap_proxy,
171
- :long => "--bootstrap-proxy PROXY_URL",
172
- :description => "The proxy server for the node being bootstrapped",
173
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
174
-
175
- option :distro,
176
- :short => "-d DISTRO",
177
- :long => "--distro DISTRO",
178
- :description => "Bootstrap a distro using a template; default is 'chef-full'",
179
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
180
- :default => "chef-full"
181
-
182
- option :template_file,
183
- :long => "--template-file TEMPLATE",
184
- :description => "Full path to location of template to use",
185
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
186
- :default => false
187
-
188
- option :run_list,
189
- :short => "-r RUN_LIST",
190
- :long => "--run-list RUN_LIST",
191
- :description => "Comma separated list of roles/recipes to apply",
192
- :proc => lambda { |o| o.split(/[\s,]+/) }
193
-
194
- option :secret,
195
- :short => "-s SECRET",
196
- :long => "--secret ",
197
- :description => "The secret key to use to encrypt data bag item values",
198
- :proc => lambda { |s| Chef::Config[:knife][:secret] = s }
199
-
200
- option :secret_file,
201
- :long => "--secret-file SECRET_FILE",
202
- :description => "A file containing the secret key to use to encrypt data bag item values",
203
- :proc => lambda { |sf| Chef::Config[:knife][:secret_file] = sf }
204
-
205
- option :json_attributes,
206
- :short => "-j JSON",
207
- :long => "--json-attributes JSON",
208
- :description => "A JSON string to be added to the first run of chef-client",
209
- :proc => lambda { |o| JSON.parse(o) }
210
-
211
- option :host_key_verify,
212
- :long => "--[no-]host-key-verify",
213
- :description => "Verify host key, enabled by default.",
214
- :boolean => true,
215
- :default => true
216
-
217
- option :bootstrap_protocol,
218
- :long => "--bootstrap-protocol protocol",
219
- :description => "protocol to bootstrap windows servers. options: winrm/ssh",
220
- :proc => Proc.new { |key| Chef::Config[:knife][:bootstrap_protocol] = key },
221
- :default => nil
222
-
223
- option :fqdn,
224
- :long => "--fqdn FQDN",
225
- :description => "Pre-defined FQDN",
226
- :proc => Proc.new { |key| Chef::Config[:knife][:fqdn] = key },
227
- :default => nil
228
-
229
- option :assign_global_ip,
230
- :long => "--assign-global-ip IpAdress",
231
- :description => "Assign an existing SoftLayer Global IP address.",
232
- :default => nil
233
-
234
- option :new_global_ip,
235
- :long => "--new-global-ip VERSION",
236
- :description => "Order a new SoftLayer Global IP address and assign it to the instance."
237
-
238
- option :hint,
239
- :long => "--hint HINT_NAME[=HINT_FILE]",
240
- :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
241
- :proc => Proc.new { |h|
242
- Chef::Config[:knife][:hints] ||= {}
243
- name, path = h.split("=")
244
- Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
245
- }
246
-
247
- require 'chef/knife/bootstrap'
248
- # Make the base bootstrap options available on topo bootstrap
249
- self.options = (Chef::Knife::Bootstrap.options).merge(self.options)
250
-
251
- ##
252
- # Run the procedure to create a SoftLayer VM and bootstrap it.
253
- # @return [nil]
254
- def run
255
- $stdout.sync = true
256
- config.merge!(slurp_from_file(config[:from_file])) if config[:from_file]
257
-
258
- # TODO: Windows support.
259
- raise SoftlayerServerCreateError, "#{ui.color("Windows VMs not currently supported.", :red)}" if config[:os_code] =~ /^WIN_/
260
- raise SoftlayerServerCreateError, "#{ui.color("identity file (-i) option is incompatible with password (-P) option required.", :red)}" if !!config[:identity_file] and !!config[:ssh_password]
261
- raise SoftlayerServerCreateError, "#{ui.color("--new-global-ip value must be 'v4' or 'v6'.", :red)}" if config[:new_global_ip] and !config[:new_global_ip].to_s.match(/^v[4,6]$/i)
262
-
263
- # TODO: create a pre-launch method for clean up tasks.
264
- # TODO: create a pre-launch method for clean up tasks.
265
- config[:vlan] = config[:vlan].to_i if config[:vlan]
266
- config[:private_vlan] = config[:private_vlan].to_i if config[:private_vlan]
267
- Fog.credentials[:private_key_path] = config[:identity_file] if config[:identity_file]
268
- # TODO: create a pre-launch method for clean up tasks.
269
- # TODO: create a pre-launch method for clean up tasks.
270
-
271
- opts = {
272
- :flavor => :flavor_id,
273
- :hostname => :name,
274
- :domain => nil,
275
- :cores => :cpu,
276
- :os_code => nil,
277
- :ram => nil,
278
- :block_storage => :disk,
279
- :local_storage => :ephemeral_storage,
280
- :datacenter => nil,
281
- :ssh_keys => :key_pairs,
282
- :vlan => nil,
283
- :private_vlan => nil,
284
- :image_id => nil,
285
- :private_network_only => nil,
286
- #:tags => nil
287
- }
288
-
289
-
290
- opts.keys.each do |opt|
291
- if opts[opt].nil?
292
- opts[opt] = config[opt]
293
- else
294
- opts[opts.delete(opt)] = config[opt] # clever shit like this is why I like programming :-]
295
- end
296
- end
297
-
298
- # FIXME: make the above deal with nested opts and get rid of this one-off
299
- opts[:network_components] = [ {:speed => config[:nic]} ] if !!config[:nic]
300
-
301
- opts.delete_if { |k,v| v.nil? }
302
-
303
- puts ui.color("Launching SoftLayer VM, this may take a few minutes.", :green)
304
- instance = connection.servers.create(opts)
305
- if config[:private_network_only] || config[:use_private_network]
306
- instance.ssh_ip_address = Proc.new {|server| server.private_ip_address }
307
- end
308
- progress Proc.new { instance.wait_for { ready? and sshable? } }
309
- putc("\n")
310
-
311
- if config[:tags]
312
- puts ui.color("Applying tags to SoftLayer instance.", :green)
313
- progress Proc.new { instance.add_tags(config[:tags]) }
314
- putc("\n")
315
- end
316
-
317
-
318
- if config[:new_global_ip] || config[:assign_global_ip]
319
- if config[:new_global_ip] # <— the value of this will be v4 or v6
320
- begin
321
- puts ui.color('Provisioning new Global IP' + config[:new_global_ip].downcase + ', this may take a few minutes.', :green)
322
- create_global_ip = Proc.new do
323
- existing_records = connection(:network).get_global_ip_records.body
324
- connection(:network).send('create_new_global_ip' + config[:new_global_ip].downcase) or raise SoftlayerServerCreateError, "Unable to create new Global IP Address.";
325
- sleep 20 # if we look for the new record too quickly it won't be there yet...
326
- new_record_global_id = (connection(:network).get_global_ip_records.body - existing_records).reduce['id']
327
- connection(:network).ips.select { |ip| ip.global_id == new_record_global_id }.reduce
328
- end
329
- global_ip = progress(create_global_ip) or raise SoftlayerServerCreateError, "Error encountered creating Global IP Address."
330
- puts ui.color('Global IP Address successfully created.', :green)
331
- rescue SoftlayerServerCreateError => e
332
- puts ui.color('We have encountered a problem ordering the requested global IP. The transaction may not have completed.', :red)
333
- puts ui.color(e.message, :yellow)
334
- end
335
- end
336
-
337
- if config[:assign_global_ip]
338
- case config[:assign_global_ip].to_s
339
- #ipv4
340
- when /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/
341
- global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
342
- #ipv6
343
- when /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
344
- global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
345
- else
346
- raise SoftlayerServerCreateError, "--assign-global-ip value must be valid IPv4 or IPv6 address"
347
- end
348
- global_ip or raise SoftlayerServerCreateError, "Global IP address not found. Please check the address or id supplied and try again."
349
- global_ip.reload
350
- end
351
-
352
- route_global_ip = Proc.new do
353
- puts ui.color('Routing Global IP Address to Instance.', :green)
354
- global_ip.route(connection(:network).ips.by_address(instance.public_ip_address)) or raise SoftlayerServerCreateError, "Global IP address failed to route."
355
- puts ui.color('Global IP Address has been assigned.', :green)
356
- puts ui.color('Global IP Address will not function without networking rules on the endpoint operating system. See http://knowledgelayer.softlayer.com/learning/global-ip-addresses for details.', :yellow)
357
- end
358
- progress(route_global_ip)
359
-
360
- end
361
-
362
- puts ui.color('Bootstrapping Chef node, this may take a few minutes.', :green)
363
- linux_bootstrap(instance).run
364
-
365
- puts ui.color("Applying tags to Chef node.", :green)
366
- progress apply_tags(instance)
367
-
368
- end
369
-
370
- # @param [Hash] instance
371
- # @return [Chef::Knife::Bootstrap]
372
- def linux_bootstrap(instance)
373
- bootstrap = Chef::Knife::Bootstrap.new
374
- instance.ssh_ip_address = instance.private_ip_address if config[:private_network_only]
375
- bootstrap.name_args = [instance.ssh_ip_address]
376
- bootstrap.config[:ssh_user] = config[:ssh_user]
377
- bootstrap.config[:ssh_password] = config[:ssh_password] if config[:ssh_password]
378
- bootstrap.config[:identity_file] = config[:identity_file] if config[:identity_file]
379
- bootstrap.config[:ssh_port] = config[:ssh_port]
380
- bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
381
- bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || instance.id
382
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
383
- bootstrap.config[:host_key_verify] = config[:host_key_verify]
384
- shared_bootstrap(bootstrap)
385
- end
386
-
387
- # @param [Chef::Knife::Bootstrap] bootstrap
388
- # @return [Chef::Knife::Bootstrap]
389
- def shared_bootstrap(bootstrap)
390
- bootstrap.config[:run_list] = config[:run_list]
391
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
392
- bootstrap.config[:distro] = locate_config_value(:distro)
393
- bootstrap.config[:template_file] = locate_config_value(:template_file)
394
- bootstrap.config[:environment] = locate_config_value(:environment)
395
- bootstrap.config[:prerelease] = config[:prerelease]
396
- bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
397
- bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
398
- bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
399
- bootstrap.config[:secret] = locate_config_value(:secret)
400
- bootstrap.config[:secret_file] = locate_config_value(:secret_file)
401
- bootstrap.config[:tags] = locate_config_value(:tags)
402
- bootstrap.config[:fqdn] = locate_config_value(:fqdn)
403
- Chef::Config[:knife][:hints] ||= {}
404
- Chef::Config[:knife][:hints]['softlayer'] ||= {}
405
- bootstrap
406
- end
407
-
408
- def windows_bootstrap(server, fqdn)
409
- # TODO: Windows support....
410
- end
411
-
412
- def progress(proc)
413
- t = Thread.new { Thread.current[:output] = proc.call }
414
- i = 0
415
- while t.alive?
416
- sleep 0.5
417
- putc('.')
418
- i += 1
419
- putc("\n") if i == 76
420
- i = 0 if i == 76
421
- end
422
- putc("\n")
423
- t.join
424
- t[:output]
425
- end
426
-
427
- def slurp_from_file(path)
428
- args = JSON.parse(IO.read(path))
429
- args.keys.each { |key| args[key.gsub('-', '_').to_sym] = args.delete(key) }
430
- # TODO: Something less ugly than the empty rescue block below. The :proc Procs/Lambdas aren't idempotent...
431
- args.keys.each { |key| begin; args[key] = options[key][:proc] ? options[key][:proc].call(args[key]) : args[key]; rescue; end }
432
- args
433
- end
434
-
435
- def apply_tags(instance)
436
- Proc.new do
437
- chef = Chef::Search::Query.new
438
- chef.search('node', "name:#{locate_config_value(:chef_node_name) || instance.id}") do |n|
439
- config[:tags] = [] if config[:tags].nil? # we're going to tag every Chef node with the SL id no matter what
440
- config[:tags] << "slid=#{instance.id}"
441
- config[:tags].each do |tag|
442
- n.tag(tag)
443
- end
444
- n.save
445
- end
446
- end
447
- end
448
-
449
- end
450
- end
451
- end
452
-
453
-
1
+ #
2
+ # Author:: Matt Eldridge (<matt.eldridge@us.ibm.com>)
3
+ # © Copyright IBM Corporation 2014.
4
+ #
5
+ # LICENSE: Apache 2.0 (http://www.apache.org/licenses/)
6
+ #
7
+
8
+ require 'chef/knife/softlayer_base'
9
+
10
+ class Chef
11
+ class Knife
12
+ class SoftlayerServerCreateError < StandardError; end
13
+ class SoftlayerServerCreate < Knife
14
+
15
+ attr_reader :cci
16
+
17
+ include Knife::SoftlayerBase
18
+
19
+ banner 'knife softlayer server create (options)'
20
+
21
+ option :flavor,
22
+ :long => '--flavor FLAVOR',
23
+ :short => '-f FLAVOR',
24
+ :description => 'Pre-configured packages of computing resources. See `knife softlayer flavor list` for details.'
25
+
26
+ option :hostname,
27
+ :long => '--hostname VALUE',
28
+ :short => '-H VALUE',
29
+ :description => 'The hostname SoftLayer will assign to the VM instance.'
30
+
31
+ option :domain,
32
+ :long => '--domain VALUE',
33
+ :short => '-D VALUE',
34
+ :description => 'The FQDN SoftLayer will assign to the VM instance.',
35
+ :default => Chef::Config[:knife][:softlayer_default_domain]
36
+
37
+ option :cores,
38
+ :long => '--cores VALUE',
39
+ :short => '-C VALUE',
40
+ :description => 'The number of virtual cores SoftLayer will assign to the VM instance.'
41
+
42
+ option :os_code,
43
+ :long => '--os-code VALUE',
44
+ :short => '-O VALUE',
45
+ :description => 'A valid SoftLayer operating system code. See `knife softlayer flavor list --all` for a list of valid codes.'
46
+
47
+ option :ram,
48
+ :long => '--ram VALUE',
49
+ :short => '-R VALUE',
50
+ :description => 'The number of virtual cores SoftLayer will assign to the VM instance.'
51
+
52
+
53
+ option :block_storage,
54
+ :long => '--block-storage VALUE',
55
+ :short => '-B VALUE',
56
+ :description => 'The size in GB of the block storage devices (disks) for this instance. Specify 1 - 5 entries in a comma separated list following the format "dev:size". Example: "0:25,2:500" would be a 25GB volume on device 0 (the root partition) and a 100GB volume on on device 2. [NOTE: SoftLayer VMs always reserve device 1 for a swap device.] ',
57
+ :proc => Proc.new { |devs| devs.split(',').map{ |dev| device, capacity = dev.split(':'); {"device"=>device, "diskImage"=>{"capacity"=>capacity}} } }
58
+
59
+ option :nic,
60
+ :long => '--network-interface-speed VALUE',
61
+ :short => '-n VALUE',
62
+ :description => 'The maximum speed of the public NIC available to the instance.',
63
+ :default => 10
64
+
65
+ option :bill_monthly,
66
+ :long => '--bill-monthly',
67
+ :description => 'Flag to bill monthly instead of hourly, minimum charge of one month.',
68
+ :boolean => true,
69
+ :default => false
70
+
71
+ option :vlan,
72
+ :long => '--vlan VLAN-ID',
73
+ :description => 'Internal SoftLayer ID of the public VLAN into which the compute instance should be placed.'
74
+
75
+ option :private_vlan,
76
+ :long => '--private-vlan VLAN-ID',
77
+ :description => 'Internal SoftLayer ID of the private VLAN into which the compute instance should be placed.'
78
+
79
+ option :image_id,
80
+ :long => '--image-id IMAGE-ID',
81
+ :description => 'Internal SoftLayer uuid specifying the image template from which the compute instance should be booted.'
82
+
83
+ option :private_network_only,
84
+ :long => '--private-network-only',
85
+ :description => 'Flag to be passed when the compute instance should have no public facing network interface.',
86
+ :boolean => true
87
+
88
+ option :use_private_network,
89
+ :long => '--use-private-network',
90
+ :description => 'Flag to be passwed when bootstrap is preferred over the private network.',
91
+ :boolean => true
92
+
93
+ option :from_file,
94
+ :long => '--from-file PATH',
95
+ :description => 'Path to JSON file containing arguments for provisoning and bootstrap.'
96
+
97
+ #option :single_tenant,
98
+ # :long => '--single-tenant',
99
+ # :description => 'Create a VM with a dedicated physical host.',
100
+ # :boolean => true,
101
+ # :default => false
102
+
103
+ option :local_storage,
104
+ :long => '--local-storage',
105
+ :description => 'Force local block storage instead of SAN storage.',
106
+ :boolean => true,
107
+ :default => false
108
+
109
+ option :datacenter,
110
+ :long => '--datacenter VALUE',
111
+ :description => 'Create a VM in a particular datacenter.',
112
+ :default => Chef::Config[:knife][:softlayer_default_datacenter]
113
+
114
+ option :tags,
115
+ :short => "-T T=V[,T=V,...]",
116
+ :long => "--tags Tag=Value[,Tag=Value...]",
117
+ :description => "The tags for this server",
118
+ :proc => Proc.new { |tags| tags.split(',') }
119
+
120
+ option :chef_node_name,
121
+ :short => "-N NAME",
122
+ :long => "--node-name NAME",
123
+ :description => "The Chef node name for your new node",
124
+ :proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
125
+
126
+ option :ssh_user,
127
+ :short => "-x USERNAME",
128
+ :long => "--ssh-user USERNAME",
129
+ :description => "The ssh username",
130
+ :default => "root"
131
+
132
+ option :ssh_password,
133
+ :short => "-P PASSWORD",
134
+ :long => "--ssh-password PASSWORD",
135
+ :description => "The ssh password"
136
+
137
+ option :ssh_keys,
138
+ :short => "-S KEY",
139
+ :long => "--ssh-keys KEY",
140
+ :description => "The ssh keys for the SoftLayer Virtual Guest environment. Accepts a space separated list of integers.",
141
+ :proc => Proc.new { |ssh_keys| ssh_keys.split(' ').map { |k| {:id => k}} }
142
+
143
+ option :ssh_port,
144
+ :short => "-p PORT",
145
+ :long => "--ssh-port PORT",
146
+ :description => "The ssh port",
147
+ :default => "22",
148
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
149
+
150
+ option :ssh_gateway,
151
+ :short => "-w GATEWAY",
152
+ :long => "--ssh-gateway GATEWAY",
153
+ :description => "The ssh gateway server",
154
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
155
+
156
+ option :identity_file,
157
+ :short => "-i IDENTITY_FILE",
158
+ :long => "--identity-file IDENTITY_FILE",
159
+ :description => "The SSH identity file used for authentication"
160
+
161
+ option :prerelease,
162
+ :long => "--prerelease",
163
+ :description => "Install the pre-release chef gems"
164
+
165
+ option :bootstrap_version,
166
+ :long => "--bootstrap-version VERSION",
167
+ :description => "The version of Chef to install",
168
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
169
+
170
+ option :bootstrap_proxy,
171
+ :long => "--bootstrap-proxy PROXY_URL",
172
+ :description => "The proxy server for the node being bootstrapped",
173
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
174
+
175
+ option :distro,
176
+ :short => "-d DISTRO",
177
+ :long => "--distro DISTRO",
178
+ :description => "Bootstrap a distro using a template; default is 'chef-full'",
179
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
180
+ :default => "chef-full"
181
+
182
+ option :template_file,
183
+ :long => "--template-file TEMPLATE",
184
+ :description => "Full path to location of template to use",
185
+ :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
186
+ :default => false
187
+
188
+ option :run_list,
189
+ :short => "-r RUN_LIST",
190
+ :long => "--run-list RUN_LIST",
191
+ :description => "Comma separated list of roles/recipes to apply",
192
+ :proc => lambda { |o| o.split(/[\s,]+/) }
193
+
194
+ option :secret,
195
+ :short => "-s SECRET",
196
+ :long => "--secret ",
197
+ :description => "The secret key to use to encrypt data bag item values",
198
+ :proc => lambda { |s| Chef::Config[:knife][:secret] = s }
199
+
200
+ option :secret_file,
201
+ :long => "--secret-file SECRET_FILE",
202
+ :description => "A file containing the secret key to use to encrypt data bag item values",
203
+ :proc => lambda { |sf| Chef::Config[:knife][:secret_file] = sf }
204
+
205
+ option :json_attributes,
206
+ :short => "-j JSON",
207
+ :long => "--json-attributes JSON",
208
+ :description => "A JSON string to be added to the first run of chef-client",
209
+ :proc => lambda { |o| JSON.parse(o) }
210
+
211
+ option :host_key_verify,
212
+ :long => "--[no-]host-key-verify",
213
+ :description => "Verify host key, enabled by default.",
214
+ :boolean => true,
215
+ :default => true
216
+
217
+ option :bootstrap_protocol,
218
+ :long => "--bootstrap-protocol protocol",
219
+ :description => "protocol to bootstrap windows servers. options: winrm/ssh",
220
+ :proc => Proc.new { |key| Chef::Config[:knife][:bootstrap_protocol] = key },
221
+ :default => nil
222
+
223
+ option :fqdn,
224
+ :long => "--fqdn FQDN",
225
+ :description => "Pre-defined FQDN",
226
+ :proc => Proc.new { |key| Chef::Config[:knife][:fqdn] = key },
227
+ :default => nil
228
+
229
+ option :assign_global_ip,
230
+ :long => "--assign-global-ip IpAdress",
231
+ :description => "Assign an existing SoftLayer Global IP address.",
232
+ :default => nil
233
+
234
+ option :new_global_ip,
235
+ :long => "--new-global-ip VERSION",
236
+ :description => "Order a new SoftLayer Global IP address and assign it to the instance."
237
+
238
+ option :hint,
239
+ :long => "--hint HINT_NAME[=HINT_FILE]",
240
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
241
+ :proc => Proc.new { |h|
242
+ Chef::Config[:knife][:hints] ||= {}
243
+ name, path = h.split("=")
244
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
245
+ }
246
+
247
+ option :user_data,
248
+ :short => "-u USERDATA",
249
+ :long => "--user-data USERDATA",
250
+ :description => "Optional user data to pass on to SoftLayer compute instance"
251
+
252
+ require 'chef/knife/bootstrap'
253
+ # Make the base bootstrap options available on topo bootstrap
254
+ self.options = (Chef::Knife::Bootstrap.options).merge(self.options)
255
+
256
+ ##
257
+ # Run the procedure to create a SoftLayer VM and bootstrap it.
258
+ # @return [nil]
259
+ def run
260
+ $stdout.sync = true
261
+ config.merge!(slurp_from_file(config[:from_file])) if config[:from_file]
262
+
263
+ # TODO: Windows support.
264
+ raise SoftlayerServerCreateError, "#{ui.color("Windows VMs not currently supported.", :red)}" if config[:os_code] =~ /^WIN_/
265
+ raise SoftlayerServerCreateError, "#{ui.color("identity file (-i) option is incompatible with password (-P) option required.", :red)}" if !!config[:identity_file] and !!config[:ssh_password]
266
+ raise SoftlayerServerCreateError, "#{ui.color("--new-global-ip value must be 'v4' or 'v6'.", :red)}" if config[:new_global_ip] and !config[:new_global_ip].to_s.match(/^v[4,6]$/i)
267
+
268
+ # TODO: create a pre-launch method for clean up tasks.
269
+ # TODO: create a pre-launch method for clean up tasks.
270
+ config[:vlan] = config[:vlan].to_i if config[:vlan]
271
+ config[:private_vlan] = config[:private_vlan].to_i if config[:private_vlan]
272
+ Fog.credentials[:private_key_path] = config[:identity_file] if config[:identity_file]
273
+ # TODO: create a pre-launch method for clean up tasks.
274
+ # TODO: create a pre-launch method for clean up tasks.
275
+
276
+ opts = {
277
+ :flavor => :flavor_id,
278
+ :hostname => :name,
279
+ :domain => nil,
280
+ :cores => :cpu,
281
+ :os_code => nil,
282
+ :ram => nil,
283
+ :block_storage => :disk,
284
+ :local_storage => :ephemeral_storage,
285
+ :datacenter => nil,
286
+ :ssh_keys => :key_pairs,
287
+ :vlan => nil,
288
+ :private_vlan => nil,
289
+ :image_id => nil,
290
+ :private_network_only => nil,
291
+ #:tags => nil,
292
+ :user_data => nil
293
+ }
294
+
295
+
296
+ opts.keys.each do |opt|
297
+ if opts[opt].nil?
298
+ opts[opt] = config[opt]
299
+ else
300
+ opts[opts.delete(opt)] = config[opt] # clever shit like this is why I like programming :-]
301
+ end
302
+ end
303
+
304
+ # FIXME: make the above deal with nested opts and get rid of this one-off
305
+ opts[:network_components] = [ {:speed => config[:nic]} ] if !!config[:nic]
306
+
307
+ opts.delete_if { |k,v| v.nil? }
308
+
309
+ puts ui.color("Launching SoftLayer VM, this may take a few minutes.", :green)
310
+ instance = connection.servers.create(opts)
311
+ if config[:private_network_only] || config[:use_private_network]
312
+ instance.ssh_ip_address = Proc.new {|server| server.private_ip_address }
313
+ end
314
+ progress Proc.new { instance.wait_for { ready? and sshable? } }
315
+ putc("\n")
316
+
317
+ if config[:tags]
318
+ puts ui.color("Applying tags to SoftLayer instance.", :green)
319
+ progress Proc.new { instance.add_tags(config[:tags]) }
320
+ putc("\n")
321
+ end
322
+
323
+
324
+ if config[:new_global_ip] || config[:assign_global_ip]
325
+ if config[:new_global_ip] # <— the value of this will be v4 or v6
326
+ begin
327
+ puts ui.color('Provisioning new Global IP' + config[:new_global_ip].downcase + ', this may take a few minutes.', :green)
328
+ create_global_ip = Proc.new do
329
+ existing_records = connection(:network).get_global_ip_records.body
330
+ connection(:network).send('create_new_global_ip' + config[:new_global_ip].downcase) or raise SoftlayerServerCreateError, "Unable to create new Global IP Address.";
331
+ sleep 20 # if we look for the new record too quickly it won't be there yet...
332
+ new_record_global_id = (connection(:network).get_global_ip_records.body - existing_records).reduce['id']
333
+ connection(:network).ips.select { |ip| ip.global_id == new_record_global_id }.reduce
334
+ end
335
+ global_ip = progress(create_global_ip) or raise SoftlayerServerCreateError, "Error encountered creating Global IP Address."
336
+ puts ui.color('Global IP Address successfully created.', :green)
337
+ rescue SoftlayerServerCreateError => e
338
+ puts ui.color('We have encountered a problem ordering the requested global IP. The transaction may not have completed.', :red)
339
+ puts ui.color(e.message, :yellow)
340
+ end
341
+ end
342
+
343
+ if config[:assign_global_ip]
344
+ case config[:assign_global_ip].to_s
345
+ #ipv4
346
+ when /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/
347
+ global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
348
+ #ipv6
349
+ when /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/
350
+ global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
351
+ else
352
+ raise SoftlayerServerCreateError, "--assign-global-ip value must be valid IPv4 or IPv6 address"
353
+ end
354
+ global_ip or raise SoftlayerServerCreateError, "Global IP address not found. Please check the address or id supplied and try again."
355
+ global_ip.reload
356
+ end
357
+
358
+ route_global_ip = Proc.new do
359
+ puts ui.color('Routing Global IP Address to Instance.', :green)
360
+ global_ip.route(connection(:network).ips.by_address(instance.public_ip_address)) or raise SoftlayerServerCreateError, "Global IP address failed to route."
361
+ puts ui.color('Global IP Address has been assigned.', :green)
362
+ puts ui.color('Global IP Address will not function without networking rules on the endpoint operating system. See http://knowledgelayer.softlayer.com/learning/global-ip-addresses for details.', :yellow)
363
+ end
364
+ progress(route_global_ip)
365
+
366
+ end
367
+
368
+ puts ui.color('Bootstrapping Chef node, this may take a few minutes.', :green)
369
+ linux_bootstrap(instance).run
370
+
371
+ puts ui.color("Applying tags to Chef node.", :green)
372
+ progress apply_tags(instance)
373
+
374
+ end
375
+
376
+ # @param [Hash] instance
377
+ # @return [Chef::Knife::Bootstrap]
378
+ def linux_bootstrap(instance)
379
+ bootstrap = Chef::Knife::Bootstrap.new
380
+ instance.ssh_ip_address = instance.private_ip_address if config[:private_network_only]
381
+ bootstrap.name_args = [instance.ssh_ip_address]
382
+ bootstrap.config[:ssh_user] = config[:ssh_user]
383
+ bootstrap.config[:ssh_password] = config[:ssh_password] if config[:ssh_password]
384
+ bootstrap.config[:identity_file] = config[:identity_file] if config[:identity_file]
385
+ bootstrap.config[:ssh_port] = config[:ssh_port]
386
+ bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
387
+ bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || instance.id
388
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
389
+ bootstrap.config[:host_key_verify] = config[:host_key_verify]
390
+ shared_bootstrap(bootstrap)
391
+ end
392
+
393
+ # @param [Chef::Knife::Bootstrap] bootstrap
394
+ # @return [Chef::Knife::Bootstrap]
395
+ def shared_bootstrap(bootstrap)
396
+ bootstrap.config[:run_list] = config[:run_list]
397
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
398
+ bootstrap.config[:distro] = locate_config_value(:distro)
399
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
400
+ bootstrap.config[:environment] = locate_config_value(:environment)
401
+ bootstrap.config[:prerelease] = config[:prerelease]
402
+ bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
403
+ bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
404
+ bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
405
+ bootstrap.config[:secret] = locate_config_value(:secret)
406
+ bootstrap.config[:secret_file] = locate_config_value(:secret_file)
407
+ bootstrap.config[:tags] = locate_config_value(:tags)
408
+ bootstrap.config[:fqdn] = locate_config_value(:fqdn)
409
+ Chef::Config[:knife][:hints] ||= {}
410
+ Chef::Config[:knife][:hints]['softlayer'] ||= {}
411
+ bootstrap
412
+ end
413
+
414
+ def windows_bootstrap(server, fqdn)
415
+ # TODO: Windows support....
416
+ end
417
+
418
+ def progress(proc)
419
+ t = Thread.new { Thread.current[:output] = proc.call }
420
+ i = 0
421
+ while t.alive?
422
+ sleep 0.5
423
+ putc('.')
424
+ i += 1
425
+ putc("\n") if i == 76
426
+ i = 0 if i == 76
427
+ end
428
+ putc("\n")
429
+ t.join
430
+ t[:output]
431
+ end
432
+
433
+ def slurp_from_file(path)
434
+ args = JSON.parse(IO.read(path))
435
+ args.keys.each { |key| args[key.gsub('-', '_').to_sym] = args.delete(key) }
436
+ # TODO: Something less ugly than the empty rescue block below. The :proc Procs/Lambdas aren't idempotent...
437
+ args.keys.each { |key| begin; args[key] = options[key][:proc] ? options[key][:proc].call(args[key]) : args[key]; rescue; end }
438
+ args
439
+ end
440
+
441
+ def apply_tags(instance)
442
+ Proc.new do
443
+ chef = Chef::Search::Query.new
444
+ chef.search('node', "name:#{locate_config_value(:chef_node_name) || instance.id}") do |n|
445
+ config[:tags] = [] if config[:tags].nil? # we're going to tag every Chef node with the SL id no matter what
446
+ config[:tags] << "slid=#{instance.id}"
447
+ config[:tags].each do |tag|
448
+ n.tag(tag)
449
+ end
450
+ n.save
451
+ end
452
+ end
453
+ end
454
+
455
+ end
456
+ end
457
+ end
458
+
459
+