knife-softlayer 0.4.5 → 0.4.6

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 +4 -4
  2. data/.gitignore +19 -19
  3. data/.travis.yml +20 -20
  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 -82
  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 +462 -458
  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 +27 -27
@@ -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', :account => i.account_id } }
21
- puts Formatador.display_table(table_data, [:id, :access, :name, :account])
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', :account => i.account_id } }
21
+ puts Formatador.display_table(table_data, [:id, :access, :name, :account])
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,458 +1,462 @@
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 => nil
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(' ') }
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
- puts ui.color("Launching SoftLayer VM, this may take a few minutes.", :green)
309
- instance = connection.servers.create(opts)
310
- if config[:private_network_only] || config[:use_private_network]
311
- instance.ssh_ip_address = Proc.new {|server| server.private_ip_address }
312
- end
313
- progress Proc.new { instance.wait_for { ready? and sshable? } }
314
- putc("\n")
315
-
316
- if config[:tags]
317
- puts ui.color("Applying tags to SoftLayer instance.", :green)
318
- progress Proc.new { instance.add_tags(config[:tags]) }
319
- putc("\n")
320
- end
321
-
322
-
323
- if config[:new_global_ip] || config[:assign_global_ip]
324
- if config[:new_global_ip] # <— the value of this will be v4 or v6
325
- begin
326
- puts ui.color('Provisioning new Global IP' + config[:new_global_ip].downcase + ', this may take a few minutes.', :green)
327
- create_global_ip = Proc.new do
328
- existing_records = connection(:network).get_global_ip_records.body
329
- connection(:network).send('create_new_global_ip' + config[:new_global_ip].downcase) or raise SoftlayerServerCreateError, "Unable to create new Global IP Address.";
330
- sleep 20 # if we look for the new record too quickly it won't be there yet...
331
- new_record_global_id = (connection(:network).get_global_ip_records.body - existing_records).reduce['id']
332
- connection(:network).ips.select { |ip| ip.global_id == new_record_global_id }.reduce
333
- end
334
- global_ip = progress(create_global_ip) or raise SoftlayerServerCreateError, "Error encountered creating Global IP Address."
335
- puts ui.color('Global IP Address successfully created.', :green)
336
- rescue SoftlayerServerCreateError => e
337
- puts ui.color('We have encountered a problem ordering the requested global IP. The transaction may not have completed.', :red)
338
- puts ui.color(e.message, :yellow)
339
- end
340
- end
341
-
342
- if config[:assign_global_ip]
343
- case config[:assign_global_ip].to_s
344
- #ipv4
345
- when /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/
346
- global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
347
- #ipv6
348
- 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*$/
349
- global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
350
- else
351
- raise SoftlayerServerCreateError, "--assign-global-ip value must be valid IPv4 or IPv6 address"
352
- end
353
- global_ip or raise SoftlayerServerCreateError, "Global IP address not found. Please check the address or id supplied and try again."
354
- global_ip.reload
355
- end
356
-
357
- route_global_ip = Proc.new do
358
- puts ui.color('Routing Global IP Address to Instance.', :green)
359
- global_ip.route(connection(:network).ips.by_address(instance.public_ip_address)) or raise SoftlayerServerCreateError, "Global IP address failed to route."
360
- puts ui.color('Global IP Address has been assigned.', :green)
361
- 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)
362
- end
363
- progress(route_global_ip)
364
-
365
- end
366
-
367
- puts ui.color('Bootstrapping Chef node, this may take a few minutes.', :green)
368
- linux_bootstrap(instance).run
369
-
370
- puts ui.color("Applying tags to Chef node.", :green)
371
- progress apply_tags(instance)
372
-
373
- end
374
-
375
- # @param [Hash] instance
376
- # @return [Chef::Knife::Bootstrap]
377
- def linux_bootstrap(instance)
378
- bootstrap = Chef::Knife::Bootstrap.new
379
- instance.ssh_ip_address = instance.private_ip_address if config[:private_network_only]
380
- bootstrap.name_args = [instance.ssh_ip_address]
381
- bootstrap.config[:ssh_user] = config[:ssh_user]
382
- bootstrap.config[:ssh_password] = config[:ssh_password] if config[:ssh_password]
383
- bootstrap.config[:identity_file] = config[:identity_file] if config[:identity_file]
384
- bootstrap.config[:ssh_port] = config[:ssh_port]
385
- bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
386
- bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || instance.id
387
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
388
- bootstrap.config[:host_key_verify] = config[:host_key_verify]
389
- shared_bootstrap(bootstrap)
390
- end
391
-
392
- # @param [Chef::Knife::Bootstrap] bootstrap
393
- # @return [Chef::Knife::Bootstrap]
394
- def shared_bootstrap(bootstrap)
395
- bootstrap.config[:run_list] = config[:run_list]
396
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
397
- bootstrap.config[:distro] = locate_config_value(:distro)
398
- bootstrap.config[:template_file] = locate_config_value(:template_file)
399
- bootstrap.config[:environment] = locate_config_value(:environment)
400
- bootstrap.config[:prerelease] = config[:prerelease]
401
- bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
402
- bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
403
- bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
404
- bootstrap.config[:secret] = locate_config_value(:secret)
405
- bootstrap.config[:secret_file] = locate_config_value(:secret_file)
406
- bootstrap.config[:tags] = locate_config_value(:tags)
407
- bootstrap.config[:fqdn] = locate_config_value(:fqdn)
408
- Chef::Config[:knife][:hints] ||= {}
409
- Chef::Config[:knife][:hints]['softlayer'] ||= {}
410
- bootstrap
411
- end
412
-
413
- def windows_bootstrap(server, fqdn)
414
- # TODO: Windows support....
415
- end
416
-
417
- def progress(proc)
418
- t = Thread.new { Thread.current[:output] = proc.call }
419
- i = 0
420
- while t.alive?
421
- sleep 0.5
422
- putc('.')
423
- i += 1
424
- putc("\n") if i == 76
425
- i = 0 if i == 76
426
- end
427
- putc("\n")
428
- t.join
429
- t[:output]
430
- end
431
-
432
- def slurp_from_file(path)
433
- args = JSON.parse(IO.read(path))
434
- args.keys.each { |key| args[key.gsub('-', '_').to_sym] = args.delete(key) }
435
- # TODO: Something less ugly than the empty rescue block below. The :proc Procs/Lambdas aren't idempotent...
436
- args.keys.each { |key| begin; args[key] = options[key][:proc] ? options[key][:proc].call(args[key]) : args[key]; rescue; end }
437
- args
438
- end
439
-
440
- def apply_tags(instance)
441
- Proc.new do
442
- chef = Chef::Search::Query.new
443
- chef.search('node', "name:#{locate_config_value(:chef_node_name) || instance.id}") do |n|
444
- config[:tags] = [] if config[:tags].nil? # we're going to tag every Chef node with the SL id no matter what
445
- config[:tags] << "slid=#{instance.id}"
446
- config[:tags].each do |tag|
447
- n.tag(tag)
448
- end
449
- n.save
450
- end
451
- end
452
- end
453
-
454
- end
455
- end
456
- end
457
-
458
-
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 => nil
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(' ') }
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
+ option :wait_for_timeout,
253
+ :short => "-t VALUE",
254
+ :long => "--wait-for-timeout VALUE",
255
+ :description => "Timeout for provisioning proccess",
256
+ :default => 600
257
+
258
+ require 'chef/knife/bootstrap'
259
+ # Make the base bootstrap options available on topo bootstrap
260
+ self.options = (Chef::Knife::Bootstrap.options).merge(self.options)
261
+
262
+ ##
263
+ # Run the procedure to create a SoftLayer VM and bootstrap it.
264
+ # @return [nil]
265
+ def run
266
+ $stdout.sync = true
267
+ config.merge!(slurp_from_file(config[:from_file])) if config[:from_file]
268
+
269
+ # TODO: Windows support.
270
+ raise SoftlayerServerCreateError, "#{ui.color("Windows VMs not currently supported.", :red)}" if config[:os_code] =~ /^WIN_/
271
+ raise SoftlayerServerCreateError, "#{ui.color("identity file (-i) option is incompatible with password (-P) option required.", :red)}" if !!config[:identity_file] and !!config[:ssh_password]
272
+ 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)
273
+
274
+ # TODO: create a pre-launch method for clean up tasks.
275
+ # TODO: create a pre-launch method for clean up tasks.
276
+ config[:vlan] = config[:vlan].to_i if config[:vlan]
277
+ config[:private_vlan] = config[:private_vlan].to_i if config[:private_vlan]
278
+ Fog.credentials[:private_key_path] = config[:identity_file] if config[:identity_file]
279
+ # TODO: create a pre-launch method for clean up tasks.
280
+ # TODO: create a pre-launch method for clean up tasks.
281
+
282
+ opts = {
283
+ :flavor => :flavor_id,
284
+ :hostname => :name,
285
+ :domain => nil,
286
+ :cores => :cpu,
287
+ :os_code => nil,
288
+ :ram => nil,
289
+ :block_storage => :disk,
290
+ :local_storage => :ephemeral_storage,
291
+ :datacenter => nil,
292
+ :ssh_keys => :key_pairs,
293
+ :vlan => nil,
294
+ :private_vlan => nil,
295
+ :image_id => nil,
296
+ :private_network_only => nil,
297
+ #:tags => nil,
298
+ :user_data => nil
299
+ }
300
+
301
+
302
+ opts.keys.each do |opt|
303
+ if opts[opt].nil?
304
+ opts[opt] = config[opt]
305
+ else
306
+ opts[opts.delete(opt)] = config[opt] # clever shit like this is why I like programming :-]
307
+ end
308
+ end
309
+
310
+ # FIXME: make the above deal with nested opts and get rid of this one-off
311
+ opts[:network_components] = [ {"speed" => config[:nic]} ] if !!config[:nic]
312
+
313
+ opts.delete_if { |k,v| v.nil? }
314
+ puts ui.color("Launching SoftLayer VM, this may take a few minutes.", :green)
315
+ instance = connection.servers.create(opts)
316
+ if config[:private_network_only] || config[:use_private_network]
317
+ instance.ssh_ip_address = Proc.new {|server| server.private_ip_address }
318
+ end
319
+ progress Proc.new { instance.wait_for(timeout=config[:wait_for_timeout]) { ready? and sshable? } }
320
+ putc("\n")
321
+
322
+ if config[:tags]
323
+ puts ui.color("Applying tags to SoftLayer instance.", :green)
324
+ progress Proc.new { instance.add_tags(config[:tags]) }
325
+ putc("\n")
326
+ end
327
+
328
+
329
+ if config[:new_global_ip] || config[:assign_global_ip]
330
+ if config[:new_global_ip] # <— the value of this will be v4 or v6
331
+ begin
332
+ puts ui.color('Provisioning new Global IP' + config[:new_global_ip].downcase + ', this may take a few minutes.', :green)
333
+ create_global_ip = Proc.new do
334
+ existing_records = connection(:network).get_global_ip_records.body
335
+ connection(:network).send('create_new_global_ip' + config[:new_global_ip].downcase) or raise SoftlayerServerCreateError, "Unable to create new Global IP Address.";
336
+ sleep 20 # if we look for the new record too quickly it won't be there yet...
337
+ new_record_global_id = (connection(:network).get_global_ip_records.body - existing_records).reduce['id']
338
+ connection(:network).ips.select { |ip| ip.global_id == new_record_global_id }.reduce
339
+ end
340
+ global_ip = progress(create_global_ip) or raise SoftlayerServerCreateError, "Error encountered creating Global IP Address."
341
+ puts ui.color('Global IP Address successfully created.', :green)
342
+ rescue SoftlayerServerCreateError => e
343
+ puts ui.color('We have encountered a problem ordering the requested global IP. The transaction may not have completed.', :red)
344
+ puts ui.color(e.message, :yellow)
345
+ end
346
+ end
347
+
348
+ if config[:assign_global_ip]
349
+ case config[:assign_global_ip].to_s
350
+ #ipv4
351
+ when /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/
352
+ global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
353
+ #ipv6
354
+ 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*$/
355
+ global_ip = connection(:network).ips.by_address(config[:assign_global_ip])
356
+ else
357
+ raise SoftlayerServerCreateError, "--assign-global-ip value must be valid IPv4 or IPv6 address"
358
+ end
359
+ global_ip or raise SoftlayerServerCreateError, "Global IP address not found. Please check the address or id supplied and try again."
360
+ global_ip.reload
361
+ end
362
+
363
+ route_global_ip = Proc.new do
364
+ puts ui.color('Routing Global IP Address to Instance.', :green)
365
+ global_ip.route(connection(:network).ips.by_address(instance.public_ip_address)) or raise SoftlayerServerCreateError, "Global IP address failed to route."
366
+ puts ui.color('Global IP Address has been assigned.', :green)
367
+ 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)
368
+ end
369
+ progress(route_global_ip)
370
+
371
+ end
372
+
373
+ puts ui.color('Bootstrapping Chef node, this may take a few minutes.', :green)
374
+ linux_bootstrap(instance).run
375
+
376
+ puts ui.color("Applying tags to Chef node.", :green)
377
+ progress apply_tags(instance)
378
+
379
+ end
380
+
381
+ # @param [Hash] instance
382
+ # @return [Chef::Knife::Bootstrap]
383
+ def linux_bootstrap(instance)
384
+ bootstrap = Chef::Knife::Bootstrap.new
385
+ instance.ssh_ip_address = instance.private_ip_address if config[:private_network_only]
386
+ bootstrap.name_args = [instance.ssh_ip_address]
387
+ bootstrap.config[:ssh_user] = config[:ssh_user]
388
+ bootstrap.config[:ssh_password] = config[:ssh_password] if config[:ssh_password]
389
+ bootstrap.config[:identity_file] = config[:identity_file] if config[:identity_file]
390
+ bootstrap.config[:ssh_port] = config[:ssh_port]
391
+ bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
392
+ bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || instance.id
393
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
394
+ bootstrap.config[:host_key_verify] = config[:host_key_verify]
395
+ shared_bootstrap(bootstrap)
396
+ end
397
+
398
+ # @param [Chef::Knife::Bootstrap] bootstrap
399
+ # @return [Chef::Knife::Bootstrap]
400
+ def shared_bootstrap(bootstrap)
401
+ bootstrap.config[:run_list] = config[:run_list]
402
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
403
+ bootstrap.config[:distro] = locate_config_value(:distro)
404
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
405
+ bootstrap.config[:environment] = locate_config_value(:environment)
406
+ bootstrap.config[:prerelease] = config[:prerelease]
407
+ bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
408
+ bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
409
+ bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
410
+ bootstrap.config[:secret] = locate_config_value(:secret)
411
+ bootstrap.config[:secret_file] = locate_config_value(:secret_file)
412
+ bootstrap.config[:tags] = locate_config_value(:tags)
413
+ bootstrap.config[:fqdn] = locate_config_value(:fqdn)
414
+ Chef::Config[:knife][:hints] ||= {}
415
+ Chef::Config[:knife][:hints]['softlayer'] ||= {}
416
+ bootstrap
417
+ end
418
+
419
+ def windows_bootstrap(server, fqdn)
420
+ # TODO: Windows support....
421
+ end
422
+
423
+ def progress(proc)
424
+ t = Thread.new { Thread.current[:output] = proc.call }
425
+ i = 0
426
+ while t.alive?
427
+ sleep 0.5
428
+ putc('.')
429
+ i += 1
430
+ putc("\n") if i == 76
431
+ i = 0 if i == 76
432
+ end
433
+ putc("\n")
434
+ t.join
435
+ t[:output]
436
+ end
437
+
438
+ def slurp_from_file(path)
439
+ args = JSON.parse(IO.read(path))
440
+ args.keys.each { |key| args[key.gsub('-', '_').to_sym] = args.delete(key) }
441
+ # TODO: Something less ugly than the empty rescue block below. The :proc Procs/Lambdas aren't idempotent...
442
+ args.keys.each { |key| begin; args[key] = options[key][:proc] ? options[key][:proc].call(args[key]) : args[key]; rescue; end }
443
+ args
444
+ end
445
+
446
+ def apply_tags(instance)
447
+ Proc.new do
448
+ chef = Chef::Search::Query.new
449
+ chef.search('node', "name:#{locate_config_value(:chef_node_name) || instance.id}") do |n|
450
+ config[:tags] = [] if config[:tags].nil? # we're going to tag every Chef node with the SL id no matter what
451
+ config[:tags] << "slid=#{instance.id}"
452
+ config[:tags].each do |tag|
453
+ n.tag(tag)
454
+ end
455
+ n.save
456
+ end
457
+ end
458
+ end
459
+
460
+ end
461
+ end
462
+ end