knife-softlayer 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/CHANGELOG.md +11 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +72 -0
- data/README.md +64 -0
- data/Rakefile +37 -0
- data/docs/cla-corporate.md +133 -0
- data/docs/cla-individual.md +84 -0
- data/knife-softlayer.gemspec +39 -0
- data/knife-softlayer.html +464 -0
- data/lib/chef/knife/flavor/base.rb +177 -0
- data/lib/chef/knife/softlayer.rb +14 -0
- data/lib/chef/knife/softlayer_base.rb +167 -0
- data/lib/chef/knife/softlayer_delete.rb +6 -0
- data/lib/chef/knife/softlayer_flavor_list.rb +44 -0
- data/lib/chef/knife/softlayer_list.rb +6 -0
- data/lib/chef/knife/softlayer_server_create.rb +343 -0
- data/lib/chef/knife/softlayer_server_destroy.rb +124 -0
- data/lib/knife-softlayer/version.rb +12 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/unit/softlayer_server_create_spec.rb +136 -0
- data/spec/unit/softlayer_server_destroy_spec.rb +71 -0
- metadata +232 -0
@@ -0,0 +1,177 @@
|
|
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'
|
9
|
+
|
10
|
+
class Chef
|
11
|
+
class Knife
|
12
|
+
module SoftlayerFlavorBase
|
13
|
+
|
14
|
+
##
|
15
|
+
# Return the flavors table or the options table.
|
16
|
+
# @param [Boolean] show_all
|
17
|
+
# @return [Hash]
|
18
|
+
def table_info(show_all=false)
|
19
|
+
if show_all
|
20
|
+
options_table
|
21
|
+
else
|
22
|
+
flavors_table
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Build table of all VM configuration options.
|
28
|
+
# @return [Hash]
|
29
|
+
def options_table
|
30
|
+
columns = [
|
31
|
+
ui.color("| CORES", :bold, :white, :green),
|
32
|
+
ui.color("| RAM", :bold, :white, :green),
|
33
|
+
ui.color("| DISK", :bold, :white, :green),
|
34
|
+
ui.color("| OS", :bold, :white, :green),
|
35
|
+
ui.color("| NETWORK [MBS]", :bold, :white, :green),
|
36
|
+
ui.color("| DATACENTER", :bold, :white, :green)
|
37
|
+
]
|
38
|
+
|
39
|
+
opts = connection.getCreateObjectOptions
|
40
|
+
cpu = opts['processors']
|
41
|
+
ram = opts['memory']
|
42
|
+
disk = opts['blockDevices'].sort_by{|d| d['itemPrice']['item']['description'] unless d['itemPrice'].nil? }
|
43
|
+
os = opts['operatingSystems']
|
44
|
+
net = opts['networkComponents']
|
45
|
+
datacenter = opts['datacenters']
|
46
|
+
|
47
|
+
i = 0
|
48
|
+
until i >= opts.keys.map{|key| opts[key].count }.sort.last do
|
49
|
+
columns << (cpu[i].nil? ? '| ' : '| ' + cpu[i]['itemPrice']['item']['description'])
|
50
|
+
columns << (ram[i].nil? ? '| ' : '| ' + ram[i]['template']['maxMemory'].to_s + " [#{ram[i]['itemPrice']['item']['description']}]")
|
51
|
+
columns << (disk[i].nil? ? '| ' : '| ' + disk[i]['itemPrice']['item']['description'])
|
52
|
+
columns << (os[i].nil? ? '| ' : '| ' + os[i]['template']['operatingSystemReferenceCode'])
|
53
|
+
columns << (net[i].nil? ? '| ' : '| ' + net[i]['template']['networkComponents'].first['maxSpeed'].to_s)
|
54
|
+
columns << (datacenter[i].nil? ? '| ' : '| ' + datacenter[i]['template']['datacenter']['name'])
|
55
|
+
i+=1
|
56
|
+
end
|
57
|
+
columns
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Build the VM "flavor" table.
|
62
|
+
# @return [Hash]
|
63
|
+
def flavors_table
|
64
|
+
# We don't have "flavors" actually, you can just pick from the menu.
|
65
|
+
# Let's shim in place the standard openstack flavors so people can get started without making loads of decisions.
|
66
|
+
|
67
|
+
columns = [
|
68
|
+
ui.color("| FLAVOR", :bold, :green),
|
69
|
+
ui.color("| CORES", :bold, :green),
|
70
|
+
ui.color("| RAM", :bold, :green),
|
71
|
+
ui.color("| DISK", :bold, :green)
|
72
|
+
]
|
73
|
+
|
74
|
+
# tiny
|
75
|
+
columns << "| tiny"
|
76
|
+
columns << "| 1"
|
77
|
+
columns << "| 1024"
|
78
|
+
columns << "| 25GB [LOCAL]"
|
79
|
+
|
80
|
+
# small
|
81
|
+
columns << "| small"
|
82
|
+
columns << "| 2"
|
83
|
+
columns << "| 2048"
|
84
|
+
columns << "| 100GB [LOCAL]"
|
85
|
+
|
86
|
+
|
87
|
+
# medium
|
88
|
+
columns << "| medium"
|
89
|
+
columns << "| 4"
|
90
|
+
columns << "| 4096"
|
91
|
+
columns << "| 150GB [LOCAL]"
|
92
|
+
|
93
|
+
|
94
|
+
# large
|
95
|
+
columns << "| large"
|
96
|
+
columns << "| 8"
|
97
|
+
columns << "| 8192"
|
98
|
+
columns << "| 200GB [LOCAL]"
|
99
|
+
|
100
|
+
|
101
|
+
# xlarge
|
102
|
+
columns << "| xlarge"
|
103
|
+
columns << "| 16"
|
104
|
+
columns << "| 16384"
|
105
|
+
columns << "| 300GB [LOCAL]"
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.load_flavor(flavor)
|
109
|
+
self.send(flavor.to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
##
|
116
|
+
# Set options for a "tiny" instance.
|
117
|
+
# @return [Hash]
|
118
|
+
def self.tiny
|
119
|
+
{
|
120
|
+
'startCpus' => 1,
|
121
|
+
'maxMemory' => 1024,
|
122
|
+
'localDiskFlag' => true,
|
123
|
+
'blockDevices' => [{'device' => 0, 'diskImage' => {'capacity' => 25 } }]
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Set options for a "small" instance.
|
129
|
+
# @return [Hash]
|
130
|
+
def self.small
|
131
|
+
{
|
132
|
+
'startCpus' => 2,
|
133
|
+
'maxMemory' => 2048,
|
134
|
+
'localDiskFlag' => true,
|
135
|
+
'blockDevices' => [{'device' => 0, 'diskImage' => {'capacity' => 100 } }]
|
136
|
+
}
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# Set options for a "medium" instance.
|
141
|
+
# @return [Hash]
|
142
|
+
def self.medium
|
143
|
+
{
|
144
|
+
'startCpus' => 4,
|
145
|
+
'maxMemory' => 4096,
|
146
|
+
'localDiskFlag' => true,
|
147
|
+
'blockDevices' => [{'device' => 0, 'diskImage' => {'capacity' => 25 } },{'device' => 2, 'diskImage' => {'capacity' => 150 } }]
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Set options for a "large" instance.
|
153
|
+
# @return [Hash]
|
154
|
+
def self.large
|
155
|
+
{
|
156
|
+
'startCpus' => 8,
|
157
|
+
'maxMemory' => 8192,
|
158
|
+
'localDiskFlag' => true,
|
159
|
+
'blockDevices' => [{'device' => 0, 'diskImage' => {'capacity' => 25 } },{'device' => 2, 'diskImage' => {'capacity' => 200 } }]
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Set options for an "xlarge" instance.
|
165
|
+
# @return [Hash]
|
166
|
+
def self.xlarge
|
167
|
+
{
|
168
|
+
'startCpus' => 16,
|
169
|
+
'maxMemory' => 16384,
|
170
|
+
'localDiskFlag' => true,
|
171
|
+
'blockDevices' => [{'device' => 0, 'diskImage' => {'capacity' => 25 } },{'device' => 2, 'diskImage' => {'capacity' => 300 } }]
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,14 @@
|
|
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 "knife-softlayer/version"
|
9
|
+
|
10
|
+
module Knife
|
11
|
+
module Softlayer
|
12
|
+
# Your code goes here...
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,167 @@
|
|
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'
|
9
|
+
|
10
|
+
class Chef
|
11
|
+
class Knife
|
12
|
+
module SoftlayerBase
|
13
|
+
|
14
|
+
# :nodoc:
|
15
|
+
def self.included(includer)
|
16
|
+
includer.class_eval do
|
17
|
+
|
18
|
+
deps do
|
19
|
+
require 'softlayer_api'
|
20
|
+
require 'readline'
|
21
|
+
require 'chef/json_compat'
|
22
|
+
require 'net/ssh'
|
23
|
+
require 'net/ssh/multi'
|
24
|
+
end
|
25
|
+
|
26
|
+
option :softlayer_credential_file,
|
27
|
+
:long => "--softlayer-credential-file FILE",
|
28
|
+
:description => "File containing SoftLayer credentials as used by `softlayer_api` Ruby gem.",
|
29
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:softlayer_credential_file] = key }
|
30
|
+
|
31
|
+
option :softlayer_username,
|
32
|
+
:short => "-U USERNAME",
|
33
|
+
:long => "--softlayer-username KEY",
|
34
|
+
:description => "Your SoftLayer Username",
|
35
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:softlayer_access_key_id] = key }
|
36
|
+
|
37
|
+
option :softlayer_api_key,
|
38
|
+
:short => "-K SECRET",
|
39
|
+
:long => "--softlayer-api-key SECRET",
|
40
|
+
:description => "Your SoftLayer API Key",
|
41
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:softlayer_secret_access_key] = key }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Returns a connection to a SoftLayer API Service Endpoint.
|
47
|
+
# @param [Symbol] service
|
48
|
+
# @return [SoftLayer::Service]
|
49
|
+
def connection(service=:cci)
|
50
|
+
SoftLayer::Service.new(
|
51
|
+
SoftlayerBase.send(service),
|
52
|
+
:username => Chef::Config[:knife][:softlayer_username],
|
53
|
+
:api_key => Chef::Config[:knife][:softlayer_api_key]
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Returns identifier string for the SoftLayer Virtual Guest service.
|
59
|
+
# @return [String]
|
60
|
+
def self.cci
|
61
|
+
'SoftLayer_Virtual_Guest'
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Returns identifier string for the SoftLayer Product Package service.
|
66
|
+
# @return [String]
|
67
|
+
def self.package
|
68
|
+
'SoftLayer_Product_Package'
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# Returns identifier string for the SoftLayer Product Order service.
|
73
|
+
# @return [String]
|
74
|
+
def self.order
|
75
|
+
'SoftLayer_Product_Order'
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# Returns identifier string for the SoftLayer Subnet Ordering service.
|
80
|
+
# @return [String]
|
81
|
+
def self.subnet
|
82
|
+
'SoftLayer_Container_Product_Order_Network_Subnet'
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Returns identifier string for the SoftLayer User Account service.
|
87
|
+
# @return [String]
|
88
|
+
def self.account
|
89
|
+
'SoftLayer_Account'
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Returns identifier string for the SoftLayer Global IP service.
|
94
|
+
# @return [String]
|
95
|
+
def self.global_ip
|
96
|
+
'SoftLayer_Network_Subnet_IpAddress_Global'
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Returns id of a particular SoftLayer ordering package.
|
101
|
+
# @return [String]
|
102
|
+
def self.non_server_package_id
|
103
|
+
0 # this package contains everything that isn't a server on the SoftLayer API
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Queries the SoftLayer API and returns the "category code" required for ordering a Global IPv4 address.
|
108
|
+
# @return [Integer]
|
109
|
+
def self.global_ipv4_cat_code
|
110
|
+
SoftLayer::Service.new(
|
111
|
+
SoftlayerBase.send(:package),
|
112
|
+
:username => Chef::Config[:knife][:softlayer_username],
|
113
|
+
:api_key => Chef::Config[:knife][:softlayer_api_key]
|
114
|
+
).object_with_id(non_server_package_id).object_mask('isRequired', 'itemCategory').getConfiguration.map do |item|
|
115
|
+
item['itemCategory']['id'] if item['itemCategory']['categoryCode'] == 'global_ipv4'
|
116
|
+
end.compact.first
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Queries the SoftLayer API and returns the "price code" required for ordering a Global IPv4 address.
|
121
|
+
# @return [Integer]
|
122
|
+
def self.global_ipv4_price_code
|
123
|
+
SoftLayer::Service.new(
|
124
|
+
SoftlayerBase.send(:package),
|
125
|
+
:username => Chef::Config[:knife][:softlayer_username],
|
126
|
+
:api_key => Chef::Config[:knife][:softlayer_api_key]
|
127
|
+
).object_with_id(non_server_package_id).object_mask('id', 'item.description', 'categories.id').getItemPrices.map do |item|
|
128
|
+
item['id'] if item['categories'][0]['id'] == SoftlayerBase.global_ipv4_cat_code
|
129
|
+
end.compact.first
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Constructs an order required for purchasing a Global IPv4 address.
|
134
|
+
# @return [Hash]
|
135
|
+
def self.build_global_ipv4_order
|
136
|
+
{
|
137
|
+
"complexType" => SoftlayerBase.subnet,
|
138
|
+
"packageId" => non_server_package_id,
|
139
|
+
"prices" => [{"id"=>SoftlayerBase.global_ipv4_price_code}],
|
140
|
+
"quantity" => 1
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Locates a config value.
|
146
|
+
# @param [String] key
|
147
|
+
# @return [String]
|
148
|
+
def locate_config_value(key)
|
149
|
+
key = key.to_sym
|
150
|
+
config[key] || Chef::Config[:knife][key]
|
151
|
+
end
|
152
|
+
|
153
|
+
##
|
154
|
+
# A CLI output formatting wrapper.
|
155
|
+
# @param [String] label
|
156
|
+
# @param [String] value
|
157
|
+
# @param [Symbol] color
|
158
|
+
# @return [String]
|
159
|
+
def msg_pair(label, value, color=:cyan)
|
160
|
+
if value && !value.to_s.empty?
|
161
|
+
puts "#{ui.color(label, color)}: #{value}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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
|
+
require 'chef/knife/flavor/base'
|
10
|
+
|
11
|
+
class Chef
|
12
|
+
class Knife
|
13
|
+
class SoftlayerFlavorList < Knife
|
14
|
+
|
15
|
+
include Knife::SoftlayerBase
|
16
|
+
include Knife::SoftlayerFlavorBase
|
17
|
+
|
18
|
+
banner 'knife softlayer flavor list (options)'
|
19
|
+
|
20
|
+
option :all,
|
21
|
+
:short => "-a",
|
22
|
+
:long => "--all",
|
23
|
+
:description => "Display all available configuration options for launching an instance.",
|
24
|
+
:default => false
|
25
|
+
|
26
|
+
##
|
27
|
+
# Run the procedure to list softlayer VM flavors or display all available options.
|
28
|
+
# @return [nil]
|
29
|
+
def run
|
30
|
+
table_data = table_info(config[:all])
|
31
|
+
if config[:all]
|
32
|
+
puts ui.list(table_data, :columns_across, 6)
|
33
|
+
msg = "These options can be used in place of 'flavors'; See `knife softlayer server create --help` for details.\n"
|
34
|
+
else
|
35
|
+
puts ui.list(table_data, :columns_across, 4)
|
36
|
+
msg = "'flavors' provided here for convenience; SoftLayer allows you to choose a configuration a la carte.\nFor a full list of available instance options use --all with the `knife softlayer flavor list` subcommand."
|
37
|
+
end
|
38
|
+
puts ui.color("\nNOTICE: ", :yellow)
|
39
|
+
puts ui.color(msg)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,343 @@
|
|
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
|
+
require 'chef/knife/flavor/base'
|
10
|
+
|
11
|
+
class Chef
|
12
|
+
class Knife
|
13
|
+
class SoftlayerServerCreateError < StandardError; end
|
14
|
+
class SoftlayerServerCreate < Knife
|
15
|
+
|
16
|
+
attr_reader :cci
|
17
|
+
|
18
|
+
include Knife::SoftlayerBase
|
19
|
+
|
20
|
+
banner 'knife softlayer server create (options)'
|
21
|
+
|
22
|
+
option :flavor,
|
23
|
+
:long => '--flavor FLAVOR',
|
24
|
+
:short => '-f FLAVOR',
|
25
|
+
:description => 'Pre-configured packages of computing resources. See `knife softlayer flavor list` for details.'
|
26
|
+
|
27
|
+
option :hostname,
|
28
|
+
:long => '--hostname VALUE',
|
29
|
+
:short => '-H VALUE',
|
30
|
+
:description => 'The hostname SoftLayer will assign to the VM instance.'
|
31
|
+
|
32
|
+
option :domain,
|
33
|
+
:long => '--domain VALUE',
|
34
|
+
:short => '-D VALUE',
|
35
|
+
:description => 'The FQDN SoftLayer will assign to the VM instance.',
|
36
|
+
:default => 'example.com'
|
37
|
+
|
38
|
+
option :cores,
|
39
|
+
:long => '--cores VALUE',
|
40
|
+
:short => '-C VALUE',
|
41
|
+
:description => 'The number of virtual cores SoftLayer will assign to the VM instance.',
|
42
|
+
:default => 1
|
43
|
+
|
44
|
+
option :os_code,
|
45
|
+
:long => '--os-code VALUE',
|
46
|
+
:short => '-O VALUE',
|
47
|
+
:description => 'A valid SoftLayer operating system code. See `knife softlayer flavor list --all` for a list of valid codes.',
|
48
|
+
:default => 'UBUNTU_LATEST'
|
49
|
+
|
50
|
+
option :ram,
|
51
|
+
:long => '--ram VALUE',
|
52
|
+
:short => '-R VALUE',
|
53
|
+
:description => 'The number of virtual cores SoftLayer will assign to the VM instance.',
|
54
|
+
:default => 1024
|
55
|
+
|
56
|
+
option :block_storage,
|
57
|
+
:long => '--block-storage VALUE',
|
58
|
+
:short => '-B VALUE',
|
59
|
+
: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.] ',
|
60
|
+
:default => '0:25'
|
61
|
+
|
62
|
+
option :nic,
|
63
|
+
:long => '--network-interface-speed VALUE',
|
64
|
+
:short => '-n VALUE',
|
65
|
+
:description => 'The maximum speed of the public NIC available to the instance.',
|
66
|
+
:default => 10
|
67
|
+
|
68
|
+
option :bill_monthly,
|
69
|
+
:long => '--bill-monthly',
|
70
|
+
:description => 'Flag to bill monthly instead of hourly, minimum charge of one month.',
|
71
|
+
:default => false
|
72
|
+
|
73
|
+
option :single_tenant,
|
74
|
+
:long => '--single-tenant',
|
75
|
+
:description => 'Create a CCI VM with a dedicated physical host.',
|
76
|
+
:boolean => true,
|
77
|
+
:default => false
|
78
|
+
|
79
|
+
option :san_storage,
|
80
|
+
:long => '--san-storage',
|
81
|
+
:description => 'Create a CCI VM with SAN based block storage [disk].',
|
82
|
+
:boolean => true,
|
83
|
+
:default => false
|
84
|
+
|
85
|
+
option :datacenter,
|
86
|
+
:long => '--datacenter VALUE',
|
87
|
+
:description => 'Create a CCI VI in a particular datacenter.'
|
88
|
+
|
89
|
+
option :tags,
|
90
|
+
:short => "-T T=V[,T=V,...]",
|
91
|
+
:long => "--tags Tag=Value[,Tag=Value...]",
|
92
|
+
:description => "The tags for this server",
|
93
|
+
:proc => Proc.new { |tags| tags.split(',') }
|
94
|
+
|
95
|
+
option :chef_node_name,
|
96
|
+
:short => "-N NAME",
|
97
|
+
:long => "--node-name NAME",
|
98
|
+
:description => "The Chef node name for your new node",
|
99
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
|
100
|
+
|
101
|
+
|
102
|
+
option :ssh_user,
|
103
|
+
:short => "-x USERNAME",
|
104
|
+
:long => "--ssh-user USERNAME",
|
105
|
+
:description => "The ssh username",
|
106
|
+
:default => "root"
|
107
|
+
|
108
|
+
option :ssh_password,
|
109
|
+
:short => "-P PASSWORD",
|
110
|
+
:long => "--ssh-password PASSWORD",
|
111
|
+
:description => "The ssh password"
|
112
|
+
|
113
|
+
option :ssh_port,
|
114
|
+
:short => "-p PORT",
|
115
|
+
:long => "--ssh-port PORT",
|
116
|
+
:description => "The ssh port",
|
117
|
+
:default => "22",
|
118
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
|
119
|
+
|
120
|
+
option :ssh_gateway,
|
121
|
+
:short => "-w GATEWAY",
|
122
|
+
:long => "--ssh-gateway GATEWAY",
|
123
|
+
:description => "The ssh gateway server",
|
124
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
|
125
|
+
|
126
|
+
option :identity_file,
|
127
|
+
:short => "-i IDENTITY_FILE",
|
128
|
+
:long => "--identity-file IDENTITY_FILE",
|
129
|
+
:description => "The SSH identity file used for authentication"
|
130
|
+
|
131
|
+
option :prerelease,
|
132
|
+
:long => "--prerelease",
|
133
|
+
:description => "Install the pre-release chef gems"
|
134
|
+
|
135
|
+
option :bootstrap_version,
|
136
|
+
:long => "--bootstrap-version VERSION",
|
137
|
+
:description => "The version of Chef to install",
|
138
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
139
|
+
|
140
|
+
option :bootstrap_proxy,
|
141
|
+
:long => "--bootstrap-proxy PROXY_URL",
|
142
|
+
:description => "The proxy server for the node being bootstrapped",
|
143
|
+
:proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
|
144
|
+
|
145
|
+
option :distro,
|
146
|
+
:short => "-d DISTRO",
|
147
|
+
:long => "--distro DISTRO",
|
148
|
+
:description => "Bootstrap a distro using a template; default is 'chef-full'",
|
149
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
|
150
|
+
:default => "chef-full"
|
151
|
+
|
152
|
+
option :template_file,
|
153
|
+
:long => "--template-file TEMPLATE",
|
154
|
+
:description => "Full path to location of template to use",
|
155
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
156
|
+
:default => false
|
157
|
+
|
158
|
+
option :run_list,
|
159
|
+
:short => "-r RUN_LIST",
|
160
|
+
:long => "--run-list RUN_LIST",
|
161
|
+
:description => "Comma separated list of roles/recipes to apply",
|
162
|
+
:proc => lambda { |o| o.split(/[\s,]+/) }
|
163
|
+
|
164
|
+
option :secret,
|
165
|
+
:short => "-s SECRET",
|
166
|
+
:long => "--secret ",
|
167
|
+
:description => "The secret key to use to encrypt data bag item values",
|
168
|
+
:proc => lambda { |s| Chef::Config[:knife][:secret] = s }
|
169
|
+
|
170
|
+
option :secret_file,
|
171
|
+
:long => "--secret-file SECRET_FILE",
|
172
|
+
:description => "A file containing the secret key to use to encrypt data bag item values",
|
173
|
+
:proc => lambda { |sf| Chef::Config[:knife][:secret_file] = sf }
|
174
|
+
|
175
|
+
option :json_attributes,
|
176
|
+
:short => "-j JSON",
|
177
|
+
:long => "--json-attributes JSON",
|
178
|
+
:description => "A JSON string to be added to the first run of chef-client",
|
179
|
+
:proc => lambda { |o| JSON.parse(o) }
|
180
|
+
|
181
|
+
option :host_key_verify,
|
182
|
+
:long => "--[no-]host-key-verify",
|
183
|
+
:description => "Verify host key, enabled by default.",
|
184
|
+
:boolean => true,
|
185
|
+
:default => true
|
186
|
+
|
187
|
+
option :bootstrap_protocol,
|
188
|
+
:long => "--bootstrap-protocol protocol",
|
189
|
+
:description => "protocol to bootstrap windows servers. options: winrm/ssh",
|
190
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:bootstrap_protocol] = key },
|
191
|
+
:default => nil
|
192
|
+
|
193
|
+
option :fqdn,
|
194
|
+
:long => "--fqdn FQDN",
|
195
|
+
:description => "Pre-defined FQDN",
|
196
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:fqdn] = key },
|
197
|
+
:default => nil
|
198
|
+
|
199
|
+
option :assign_global_ip,
|
200
|
+
:long => "--assign-global-ip IpAdress",
|
201
|
+
:description => "Assign an existing SoftLayer Global IP address.",
|
202
|
+
:default => nil
|
203
|
+
|
204
|
+
option :new_global_ip,
|
205
|
+
:long => "--new-global-ip",
|
206
|
+
:description => "Order a new SoftLayer Global IP address and assign it to the instance."
|
207
|
+
|
208
|
+
option :hint,
|
209
|
+
:long => "--hint HINT_NAME[=HINT_FILE]",
|
210
|
+
:description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
|
211
|
+
:proc => Proc.new { |h|
|
212
|
+
Chef::Config[:knife][:hints] ||= {}
|
213
|
+
name, path = h.split("=")
|
214
|
+
Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
|
215
|
+
}
|
216
|
+
|
217
|
+
##
|
218
|
+
# Run the procedure to create a SoftLayer VM and bootstrap it.
|
219
|
+
# @return [nil]
|
220
|
+
def run
|
221
|
+
|
222
|
+
$stdout.sync = true
|
223
|
+
|
224
|
+
config[:os_code] =~ /^WIN_/ and raise SoftlayerServerCreateError, "#{ui.color("Windows VMs not currently supported.", :red)}"
|
225
|
+
|
226
|
+
if config[:flavor]
|
227
|
+
@template = SoftlayerFlavorBase.load_flavor(config[:flavor])
|
228
|
+
else
|
229
|
+
@template = {}
|
230
|
+
@template['startCpus'] = config[:cores]
|
231
|
+
@template['maxMemory'] = config[:ram]
|
232
|
+
@template['localDiskFlag'] = !config[:san_storage]
|
233
|
+
@template['blockDevices'] = config[:block_storage].split(',').map do |i|
|
234
|
+
dev, cap = i.split(':')
|
235
|
+
{'device' => dev, 'diskImage' => {'capacity' => cap } }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
@template['complexType'] = SoftlayerBase.cci
|
240
|
+
@template['hostname'] = config[:hostname]
|
241
|
+
@template['domain'] = config[:domain]
|
242
|
+
@template['dedicatedAccountHostOnlyFlag'] = config[:single_tenant]
|
243
|
+
@template['operatingSystemReferenceCode'] = config[:os_code]
|
244
|
+
@template['hourlyBillingFlag'] = config[:bill_monthly]
|
245
|
+
@template['networkComponents'] = [{ 'maxSpeed' => config[:nic]}]
|
246
|
+
|
247
|
+
@template['datacenter'] = { 'name' => config[:datacenter] } if config[:datacenter]
|
248
|
+
|
249
|
+
@response = connection.createObject(@template)
|
250
|
+
|
251
|
+
puts ui.color("Launching SoftLayer CCI, this may take a few minutes.", :green)
|
252
|
+
|
253
|
+
begin
|
254
|
+
@cci = connection.object_mask('mask.operatingSystem.passwords.password').object_with_id(@response['id']).getObject
|
255
|
+
sleep 1
|
256
|
+
putc('.')
|
257
|
+
end while @cci['operatingSystem'].nil? or @cci['operatingSystem']['passwords'].empty?
|
258
|
+
|
259
|
+
linux_bootstrap(@cci).run
|
260
|
+
|
261
|
+
if config[:new_global_ip] || config[:assign_global_ip]
|
262
|
+
if config[:new_global_ip]
|
263
|
+
begin
|
264
|
+
order = SoftlayerBase.build_global_ipv4_order
|
265
|
+
response = connection(:order).placeOrder(order)
|
266
|
+
global_ip_id = response['placedOrder']['account']['globalIpv4Records'].first['id']
|
267
|
+
|
268
|
+
if global_ip_id
|
269
|
+
puts ui.color('Global IP Address successfully created.', :green)
|
270
|
+
else
|
271
|
+
raise 'Unable to find Global IP Address ID. Address not created.'
|
272
|
+
end
|
273
|
+
rescue Exception => e
|
274
|
+
puts ui.color('We have encountered a problem ordering the requested global IP. The transaction may not have completed.', :red)
|
275
|
+
puts ui.color(e.message, :yellow)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
if config[:assign_global_ip]
|
280
|
+
global_ip_id = connection(:account).object_mask('ipAddress').getGlobalIpv4Records.map do |global_ip|
|
281
|
+
global_ip['id'] if global_ip['ipAddress']['ipAddress'] == config[:assign_global_ip]
|
282
|
+
end.compact.first
|
283
|
+
if global_ip_id
|
284
|
+
puts ui.color('Global IP Address ID found.', :green)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
puts ui.color('Assigning Global IP Address to Instance.', :green)
|
289
|
+
connection(:global_ip).object_with_id(global_ip_id).route(@cci['primaryIpAddress'])
|
290
|
+
|
291
|
+
puts ui.color('Global IP Address has been assigned.', :green)
|
292
|
+
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)
|
293
|
+
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
# @param [Hash] cci
|
299
|
+
# @return [Chef::Knife::Bootstrap]
|
300
|
+
def linux_bootstrap(cci)
|
301
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
302
|
+
bootstrap.name_args = [cci['primaryIpAddress']]
|
303
|
+
bootstrap.config[:ssh_user] = config[:ssh_user]
|
304
|
+
bootstrap.config[:ssh_password] = cci['operatingSystem']['passwords'].first['password']
|
305
|
+
bootstrap.config[:ssh_port] = config[:ssh_port]
|
306
|
+
bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
|
307
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
308
|
+
bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || cci['id']
|
309
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
310
|
+
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
311
|
+
shared_bootstrap(bootstrap)
|
312
|
+
end
|
313
|
+
|
314
|
+
# @param [Chef::Knife::Bootstrap] bootstrap
|
315
|
+
# @return [Chef::Knife::Bootstrap]
|
316
|
+
def shared_bootstrap(bootstrap)
|
317
|
+
bootstrap.config[:run_list] = config[:run_list]
|
318
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
319
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
320
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
321
|
+
bootstrap.config[:environment] = locate_config_value(:environment)
|
322
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
323
|
+
bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
|
324
|
+
bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:encrypted_data_bag_secret)
|
325
|
+
bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:encrypted_data_bag_secret_file)
|
326
|
+
bootstrap.config[:secret] = locate_config_value(:secret)
|
327
|
+
bootstrap.config[:secret_file] = locate_config_value(:secret_file)
|
328
|
+
bootstrap.config[:tags] = locate_config_value(:tags)
|
329
|
+
bootstrap.config[:fqdn] = locate_config_value(:fqdn)
|
330
|
+
Chef::Config[:knife][:hints] ||= {}
|
331
|
+
Chef::Config[:knife][:hints]['softlayer'] ||= {}
|
332
|
+
bootstrap
|
333
|
+
end
|
334
|
+
|
335
|
+
def windows_bootstrap(server, fqdn)
|
336
|
+
# TODO: Windows support....
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
|