knife-softlayer 0.4.0 → 0.4.1

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