knife-softlayer 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|