knife-softlayer 0.4.5 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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