knife-linode 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/README.md +137 -0
- data/knife-linode.gemspec +10 -7
- data/lib/chef/knife/linode_base.rb +2 -2
- data/lib/chef/knife/linode_server_create.rb +51 -10
- data/lib/chef/knife/linode_server_delete.rb +53 -6
- data/lib/chef/knife/linode_server_reboot.rb +1 -2
- data/lib/knife-linode/version.rb +1 -1
- data/spec/chef/knife/linode_datacenter_list_spec.rb +31 -0
- data/spec/chef/knife/linode_flavor_list_spec.rb +31 -0
- data/spec/chef/knife/linode_server_create_spec.rb +216 -0
- data/spec/chef/knife/linode_server_list_spec.rb +46 -0
- data/spec/spec_helper.rb +6 -0
- metadata +74 -10
- data/README.rdoc +0 -85
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
## Knife Linode
|
2
|
+
|
3
|
+
#### Please bare with me while I get this plugin cleaned up! -[jesseadams](https://github.com/jesseadams) 07/28/2014
|
4
|
+
#### Update 08/11/2014: Lots of big changes just got merged in. Please test master and submit issues/pull requests as necessary. Thanks!
|
5
|
+
|
6
|
+
### Description
|
7
|
+
|
8
|
+
This is the official Opscode Knife plugin for Linode. This plugin gives knife
|
9
|
+
the ability to create, bootstrap, and manage Linode instances.
|
10
|
+
|
11
|
+
In-depth usage instructions can be found on the [Chef Docs](http://docs.opscode.com/plugin_knife_linode.html).
|
12
|
+
|
13
|
+
### Requirements
|
14
|
+
|
15
|
+
* Chef 11.8.x or higher
|
16
|
+
|
17
|
+
### Installation
|
18
|
+
|
19
|
+
This plugin is distributed as a Ruby Gem. To install it, run:
|
20
|
+
|
21
|
+
gem install knife-linode
|
22
|
+
|
23
|
+
Or use bundler:
|
24
|
+
|
25
|
+
gem 'knife-linode', '~> 0.3'
|
26
|
+
|
27
|
+
Depending on your system's configuration, you may need to run this command
|
28
|
+
with root privileges.
|
29
|
+
|
30
|
+
### Configuration
|
31
|
+
|
32
|
+
In order to communicate with the Linode API you will have to tell Knife about
|
33
|
+
your Linode API Key. The easiest way to accomplish this is to create some
|
34
|
+
entries in your `knife.rb` file:
|
35
|
+
|
36
|
+
knife[:linode_api_key] = "Your Linode API Key"
|
37
|
+
|
38
|
+
If your knife.rb file will be checked into a SCM system (ie readable by
|
39
|
+
others) you may want to read the values from environment variables:
|
40
|
+
|
41
|
+
knife[:linode_api_key] = "#{ENV['LINODE_API_KEY']}"
|
42
|
+
|
43
|
+
You also have the option of passing your Linode API Key into the individual
|
44
|
+
knife subcommands using the `-A` (or `--linode-api-key`) command option
|
45
|
+
|
46
|
+
# Provision a new 1 GB 64 bit Ubuntu 14.04 Linode in the Dallas, TX datacenter
|
47
|
+
knife linode server create -r 'role[webserver]' --linode-image 124 --linode-datacenter 2 --linode-flavor 1 --linode-node-name YOUR_LINODE_NODE_NAME
|
48
|
+
|
49
|
+
Additionally the following options may be set in your `knife.rb`:
|
50
|
+
|
51
|
+
* linode_flavor
|
52
|
+
* linode_image
|
53
|
+
* linode_kernel
|
54
|
+
* ssh_password
|
55
|
+
* bootstrap_version
|
56
|
+
* distro
|
57
|
+
* template_file
|
58
|
+
|
59
|
+
## Sub Commands
|
60
|
+
|
61
|
+
This plugin provides the following Knife subcommands. Specific command
|
62
|
+
options can be found by invoking the subcommand with a `--help` flag
|
63
|
+
|
64
|
+
### knife linode server create
|
65
|
+
|
66
|
+
Provisions a new server in Linode and then perform a Chef bootstrap (using the
|
67
|
+
SSH protocol). The goal of the bootstrap is to get Chef installed on the
|
68
|
+
target system so it can run Chef Client with a Chef Server. The main
|
69
|
+
assumption is a baseline OS installation exists (provided by the
|
70
|
+
provisioning). It is primarily intended for Chef Client systems that talk to a
|
71
|
+
Chef server. By default the server is bootstrapped using the
|
72
|
+
[ubuntu10.04-gems](https://github.com/opscode/chef/blob/master/chef/lib/chef/k
|
73
|
+
nife/bootstrap/ubuntu10.04-gems.erb) template. This can be overridden using
|
74
|
+
the `-d` or `--template-file` command options.
|
75
|
+
|
76
|
+
### knife linode server delete
|
77
|
+
|
78
|
+
Deletes an existing server in the currently configured Linode account.
|
79
|
+
**PLEASE NOTE** - this does not delete the associated node and client objects
|
80
|
+
from the Chef server.
|
81
|
+
|
82
|
+
### knife linode server list
|
83
|
+
|
84
|
+
Outputs a list of all servers in the currently configured Linode account.
|
85
|
+
**PLEASE NOTE** - this shows all instances associated with the account, some
|
86
|
+
of which may not be currently managed by the Chef server.
|
87
|
+
|
88
|
+
### knife linode server reboot
|
89
|
+
|
90
|
+
Reboots an existing server in the currently configured Linode account.
|
91
|
+
|
92
|
+
### knife linode datacenter list
|
93
|
+
|
94
|
+
View a list of available data centers, listed by data center ID and location.
|
95
|
+
|
96
|
+
### knife linode flavor list
|
97
|
+
|
98
|
+
View a list of servers from the Linode environment, listed by ID, name, RAM,
|
99
|
+
disk, and Price.
|
100
|
+
|
101
|
+
### knife linode image list
|
102
|
+
|
103
|
+
View a list of images from the Linode environment, listed by ID, name, bits,
|
104
|
+
and image size.
|
105
|
+
|
106
|
+
### knife linode kernel list
|
107
|
+
|
108
|
+
View a a list of available kernels, listed by ID and name.
|
109
|
+
|
110
|
+
### knife linode stackscript list
|
111
|
+
|
112
|
+
View a list of Linode StackScripts that are currently being used.
|
113
|
+
|
114
|
+
## License
|
115
|
+
|
116
|
+
Apache License, Version 2.0
|
117
|
+
|
118
|
+
Original Author: Adam Jacob (<adam@opscode.com>)
|
119
|
+
|
120
|
+
Copyright (c) 2009-2014 Opscode, Inc.
|
121
|
+
|
122
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
123
|
+
use this file except in compliance with the License. You may obtain a copy of
|
124
|
+
the License at
|
125
|
+
|
126
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
127
|
+
|
128
|
+
Unless required by applicable law or agreed to in writing, software
|
129
|
+
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
130
|
+
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
131
|
+
License for the specific language governing permissions and limitations under
|
132
|
+
the License.
|
133
|
+
|
134
|
+
## Maintainers
|
135
|
+
|
136
|
+
* Jesse R. Adams ([jesseadams](https://github.com/jesseadams))
|
137
|
+
* You?
|
data/knife-linode.gemspec
CHANGED
@@ -5,18 +5,21 @@ require "knife-linode/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "knife-linode"
|
7
7
|
s.version = Knife::Linode::VERSION
|
8
|
-
s.
|
9
|
-
s.
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.summary = "Linode Support for Chef's Knife Command"
|
8
|
+
s.authors = ['Adam Jacob', 'Seth Chisamore', 'Lamont Granquist']
|
9
|
+
s.email = ['adam@opscode.com', 'schisamo@opscode.com', 'lamont@opscode.com']
|
10
|
+
s.homepage = 'http://wiki.opscode.com/display/chef'
|
11
|
+
s.summary = "Linode Support for Chef's Knife Command"
|
13
12
|
s.description = s.summary
|
14
|
-
s.extra_rdoc_files = [
|
13
|
+
s.extra_rdoc_files = ['README.md', 'LICENSE']
|
15
14
|
|
16
15
|
s.files = `git ls-files`.split("\n")
|
17
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
-
s.add_dependency "fog", "~> 1.0"
|
20
18
|
s.require_paths = ["lib"]
|
21
19
|
|
20
|
+
s.add_runtime_dependency "fog", "~> 1.0"
|
21
|
+
s.add_runtime_dependency "chef", "~> 11.8"
|
22
|
+
|
23
|
+
s.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
s.add_development_dependency "rubocop", "~> 0.24"
|
22
25
|
end
|
@@ -55,7 +55,7 @@ class Chef
|
|
55
55
|
|
56
56
|
def locate_config_value(key)
|
57
57
|
key = key.to_sym
|
58
|
-
Chef::Config[:knife][key]
|
58
|
+
config[key] || Chef::Config[:knife][key]
|
59
59
|
end
|
60
60
|
|
61
61
|
def msg_pair(label, value, color=:cyan)
|
@@ -70,7 +70,7 @@ class Chef
|
|
70
70
|
keys.each do |k|
|
71
71
|
pretty_key = k.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(api)/i) ? w.upcase : w.capitalize }
|
72
72
|
if Chef::Config[:knife][k].nil?
|
73
|
-
errors << "You did not
|
73
|
+
errors << "You did not provide a valid '#{pretty_key}' value."
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -43,28 +43,28 @@ class Chef
|
|
43
43
|
:long => "--linode-flavor FLAVOR",
|
44
44
|
:description => "The flavor of server",
|
45
45
|
:proc => Proc.new { |f| Chef::Config[:knife][:linode_flavor] = f },
|
46
|
-
:default => 1
|
46
|
+
:default => 1 # Linode 1024
|
47
47
|
|
48
48
|
option :linode_image,
|
49
49
|
:short => "-I IMAGE",
|
50
50
|
:long => "--linode-image IMAGE",
|
51
51
|
:description => "The image for the server",
|
52
52
|
:proc => Proc.new { |i| Chef::Config[:knife][:linode_image] = i },
|
53
|
-
:default =>
|
53
|
+
:default => 99 # Ubuntu 12.04 LTS
|
54
54
|
|
55
55
|
option :linode_kernel,
|
56
56
|
:short => "-k KERNEL",
|
57
57
|
:long => "--linode-kernel KERNEL",
|
58
58
|
:description => "The kernel for the server",
|
59
59
|
:proc => Proc.new { |i| Chef::Config[:knife][:linode_kernel] = i },
|
60
|
-
:default => 138
|
60
|
+
:default => 138 # Latest 64 bit
|
61
61
|
|
62
62
|
option :linode_datacenter,
|
63
63
|
:short => "-D DATACENTER",
|
64
64
|
:long => "--linode-datacenter DATACENTER",
|
65
65
|
:description => "The datacenter for the server",
|
66
66
|
:proc => Proc.new { |i| Chef::Config[:knife][:linode_datacenter] = i },
|
67
|
-
:default => 3
|
67
|
+
:default => 3 # Fremont, CA, USA
|
68
68
|
|
69
69
|
option :linode_node_name,
|
70
70
|
:short => "-L NAME",
|
@@ -111,7 +111,7 @@ class Chef
|
|
111
111
|
:long => "--distro DISTRO",
|
112
112
|
:description => "Bootstrap a distro using a template",
|
113
113
|
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
|
114
|
-
:default => "
|
114
|
+
:default => "chef-full"
|
115
115
|
|
116
116
|
option :template_file,
|
117
117
|
:long => "--template-file TEMPLATE",
|
@@ -126,11 +126,37 @@ class Chef
|
|
126
126
|
:proc => lambda { |o| o.split(/[\s,]+/) },
|
127
127
|
:default => []
|
128
128
|
|
129
|
-
option :
|
130
|
-
:
|
131
|
-
:
|
129
|
+
option :json_attributes,
|
130
|
+
:short => "-j JSON",
|
131
|
+
:long => "--json-attributes JSON",
|
132
|
+
:description => "A JSON string to be added to the first run of chef-client",
|
133
|
+
:proc => lambda { |o| JSON.parse(o) }
|
134
|
+
|
135
|
+
option :host_key_verify,
|
136
|
+
:long => "--[no-]host-key-verify",
|
137
|
+
:description => "Verify host key, enabled by default",
|
132
138
|
:boolean => true,
|
133
|
-
:default =>
|
139
|
+
:default => true
|
140
|
+
|
141
|
+
Chef::Config[:knife][:hints] ||= {"linode" => {}}
|
142
|
+
option :hint,
|
143
|
+
:long => "--hint HINT_NAME[=HINT_FILE]",
|
144
|
+
:description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
|
145
|
+
:proc => Proc.new { |h|
|
146
|
+
name, path = h.split("=")
|
147
|
+
Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
|
148
|
+
}
|
149
|
+
|
150
|
+
option :secret,
|
151
|
+
:short => "-s SECRET",
|
152
|
+
:long => "--secret ",
|
153
|
+
:description => "The secret key to use to encrypt data bag item values",
|
154
|
+
:proc => Proc.new { |s| Chef::Config[:knife][:secret] = s }
|
155
|
+
|
156
|
+
option :secret_file,
|
157
|
+
:long => "--secret-file SECRET_FILE",
|
158
|
+
:description => "A file containing the secret key to use to encrypt data bag item values",
|
159
|
+
:proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf }
|
134
160
|
|
135
161
|
def tcp_test_ssh(hostname)
|
136
162
|
Chef::Log.debug("testing ssh connection to #{hostname}")
|
@@ -215,6 +241,16 @@ class Chef
|
|
215
241
|
puts("done")
|
216
242
|
}
|
217
243
|
|
244
|
+
Chef::Config[:knife][:hints]['linode'] ||= Hash.new
|
245
|
+
Chef::Config[:knife][:hints]['linode'].merge!({
|
246
|
+
'server_id' => server.id.to_s,
|
247
|
+
'datacenter_id' => locate_config_value(:linode_datacenter),
|
248
|
+
'flavor_id' => locate_config_value(:linode_flavor),
|
249
|
+
'image_id' => locate_config_value(:linode_image),
|
250
|
+
'kernel_id' => locate_config_value(:linode_kernel),
|
251
|
+
'ip_addresses' => server.ips.map(&:ip)})
|
252
|
+
|
253
|
+
msg_pair("JSON Attributes", config[:json_attributes]) unless !config[:json_attributes] || config[:json_attributes].empty?
|
218
254
|
bootstrap_for_node(server,fqdn).run
|
219
255
|
end
|
220
256
|
|
@@ -228,11 +264,16 @@ class Chef
|
|
228
264
|
bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.id
|
229
265
|
bootstrap.config[:prerelease] = config[:prerelease]
|
230
266
|
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
267
|
+
bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
|
231
268
|
bootstrap.config[:distro] = locate_config_value(:distro)
|
232
269
|
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
233
270
|
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
234
271
|
bootstrap.config[:environment] = config[:environment]
|
235
|
-
bootstrap.config[:
|
272
|
+
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
273
|
+
bootstrap.config[:secret] = locate_config_value(:secret)
|
274
|
+
bootstrap.config[:secret_file] = locate_config_value(:secret_file)
|
275
|
+
bootstrap.config[:private_ip] = server.ips.reject{ |ip| ip.public }.first.ip
|
276
|
+
bootstrap.config[:public_ip] = server.public_ip_address
|
236
277
|
bootstrap
|
237
278
|
end
|
238
279
|
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
2
|
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
4
3
|
# Author:: Lamont Granquist (<lamont@opscode.com>)
|
@@ -19,14 +18,47 @@
|
|
19
18
|
#
|
20
19
|
|
21
20
|
require 'chef/knife/linode_base'
|
22
|
-
|
21
|
+
|
22
|
+
# These two are needed for the '--purge' deletion case
|
23
|
+
require 'chef/node'
|
24
|
+
require 'chef/api_client'
|
25
|
+
|
23
26
|
class Chef
|
24
27
|
class Knife
|
25
28
|
class LinodeServerDelete < Chef::Knife
|
26
29
|
|
27
30
|
include Knife::LinodeBase
|
28
31
|
|
29
|
-
banner "knife linode server delete (options)"
|
32
|
+
banner "knife linode server delete LINODE_ID|LINODE_LABEL (options)"
|
33
|
+
|
34
|
+
option :purge,
|
35
|
+
:short => "-P",
|
36
|
+
:long => "--purge",
|
37
|
+
:boolean => true,
|
38
|
+
:default => false,
|
39
|
+
:description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the Linode node itself. Assumes node and client have the same name as the server (if not, add the '--node-name' option)."
|
40
|
+
# :description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the Linode node itself. The '--node-name' option also must be set to specify the Chef node and client to be removed."
|
41
|
+
|
42
|
+
option :chef_node_name,
|
43
|
+
:short => "-N NAME",
|
44
|
+
:long => "--node-name NAME",
|
45
|
+
:description => "The name of the node and client to delete, if it differs from the server name. Only has meaning when used with the '--purge' option."
|
46
|
+
# :description => "The name of the node and client to delete. Only has meaning when used with the '--purge' option."
|
47
|
+
|
48
|
+
# Extracted from Chef::Knife.delete_object, because it has a
|
49
|
+
# confirmation step built in... By specifying the '--purge'
|
50
|
+
# flag (and also explicitly confirming the server destruction!)
|
51
|
+
# the user is already making their intent known. It is not
|
52
|
+
# necessary to make them confirm two more times.
|
53
|
+
def destroy_item(klass, name, type_name)
|
54
|
+
begin
|
55
|
+
object = klass.load(name)
|
56
|
+
object.destroy
|
57
|
+
ui.warn("Deleted #{type_name} #{name}")
|
58
|
+
rescue Net::HTTPServerException
|
59
|
+
ui.warn("Could not find a #{type_name} named #{name} to delete!")
|
60
|
+
end
|
61
|
+
end
|
30
62
|
|
31
63
|
def run
|
32
64
|
|
@@ -35,7 +67,10 @@ class Chef
|
|
35
67
|
@name_args.each do |linode_id|
|
36
68
|
|
37
69
|
begin
|
38
|
-
server = connection.servers.
|
70
|
+
server = connection.servers.detect do |s|
|
71
|
+
s.id.to_s == linode_id || s.name == linode_id
|
72
|
+
end
|
73
|
+
delete_id = server.id
|
39
74
|
|
40
75
|
msg_pair("Linode ID", server.id.to_s)
|
41
76
|
msg_pair("Name", server.name)
|
@@ -45,9 +80,21 @@ class Chef
|
|
45
80
|
puts "\n"
|
46
81
|
confirm("Do you really want to delete this server")
|
47
82
|
|
48
|
-
connection.servers.get(
|
83
|
+
connection.servers.get(delete_id).destroy
|
84
|
+
|
85
|
+
ui.warn("Deleted server #{delete_id}")
|
49
86
|
|
50
|
-
|
87
|
+
if config[:purge]
|
88
|
+
if config[:chef_node_name]
|
89
|
+
thing_to_delete = config[:chef_node_name]
|
90
|
+
else
|
91
|
+
thing_to_delete = server.name
|
92
|
+
end
|
93
|
+
destroy_item(Chef::Node, thing_to_delete, "node")
|
94
|
+
destroy_item(Chef::ApiClient, thing_to_delete, "client")
|
95
|
+
else
|
96
|
+
ui.warn("Corresponding node and client for the #{linode_id} server were not deleted and remain registered with the Chef Server")
|
97
|
+
end
|
51
98
|
rescue Fog::Compute::Linode::NotFound
|
52
99
|
ui.error("Could not locate server '#{linode_id}'.")
|
53
100
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
2
|
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
4
3
|
# Author:: Lamont Granquist (<lamont@opscode.com>)
|
@@ -26,7 +25,7 @@ class Chef
|
|
26
25
|
|
27
26
|
include Knife::LinodeBase
|
28
27
|
|
29
|
-
banner "knife linode server reboot (options)"
|
28
|
+
banner "knife linode server reboot LINODE_ID (options)"
|
30
29
|
|
31
30
|
def run
|
32
31
|
|
data/lib/knife-linode/version.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'linode_datacenter_list'
|
3
|
+
|
4
|
+
describe Chef::Knife::LinodeDatacenterList do
|
5
|
+
subject { Chef::Knife::LinodeDatacenterList.new }
|
6
|
+
|
7
|
+
let(:api_key) { 'FAKE_API_KEY' }
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
Chef::Knife::LinodeDatacenterList.load_deps
|
11
|
+
Chef::Config[:knife][:linode_api_key] = api_key
|
12
|
+
allow(subject).to receive(:puts)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#run" do
|
16
|
+
it "should validate the Linode config keys exist" do
|
17
|
+
expect(subject).to receive(:validate!)
|
18
|
+
subject.run
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should output the column headers" do
|
22
|
+
expect(subject).to receive(:puts).with(/^ID\s+Location\s*$/)
|
23
|
+
subject.run
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should output the datacenter locations" do
|
27
|
+
expect(subject).to receive(:puts).with(/(?:Newark|Tokyo|Dallas)/)
|
28
|
+
subject.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'linode_flavor_list'
|
3
|
+
|
4
|
+
describe Chef::Knife::LinodeFlavorList do
|
5
|
+
subject { Chef::Knife::LinodeFlavorList.new }
|
6
|
+
|
7
|
+
let(:api_key) { 'FAKE_API_KEY' }
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
Chef::Knife::LinodeFlavorList.load_deps
|
11
|
+
Chef::Config[:knife][:linode_api_key] = api_key
|
12
|
+
allow(subject).to receive(:puts)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#run" do
|
16
|
+
it "should validate the Linode config keys exist" do
|
17
|
+
expect(subject).to receive(:validate!)
|
18
|
+
subject.run
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should output the column headers" do
|
22
|
+
expect(subject).to receive(:puts).with(/^ID\s+Name\s+RAM\s+Disk\s+Price\s*$/)
|
23
|
+
subject.run
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should output a list of the available Linode flavors" do
|
27
|
+
expect(subject).to receive(:puts).with(/\bLinode \d+\b/)
|
28
|
+
subject.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'linode_server_create'
|
3
|
+
|
4
|
+
class MockSocket < BasicSocket
|
5
|
+
def initialize; end
|
6
|
+
def gets; end
|
7
|
+
def close; end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Chef::Knife::LinodeServerCreate do
|
11
|
+
subject { Chef::Knife::LinodeServerCreate.new }
|
12
|
+
|
13
|
+
let(:api_key) { 'FAKE_API_KEY' }
|
14
|
+
let(:mock_socket) { MockSocket.new }
|
15
|
+
|
16
|
+
before :each do
|
17
|
+
Chef::Knife::LinodeServerCreate.load_deps
|
18
|
+
Chef::Config[:knife][:linode_api_key] = api_key
|
19
|
+
allow(subject).to receive(:puts)
|
20
|
+
allow(subject).to receive(:print)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#tcp_test_ssh" do
|
24
|
+
let(:empty_block) { lambda {} }
|
25
|
+
|
26
|
+
before :each do
|
27
|
+
allow(TCPSocket).to receive(:new).and_return(mock_socket)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should open a socket to the host on port 22" do
|
31
|
+
hostname = "foo.example.com"
|
32
|
+
expect(IO).to receive(:select).with([mock_socket], anything(), anything(), anything()).and_return([mock_socket])
|
33
|
+
expect(TCPSocket).to receive(:new).with(hostname, 22).and_return(mock_socket)
|
34
|
+
subject.tcp_test_ssh(hostname, &empty_block)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return true if the socket is listening" do
|
38
|
+
allow(IO).to receive(:select).with([mock_socket], anything(), anything(), anything()).and_return([mock_socket])
|
39
|
+
expect(subject.tcp_test_ssh("foo.example.com", &empty_block)).to be_truthy
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return false if the socket is NOT listening" do
|
43
|
+
allow(IO).to receive(:select).with([mock_socket], anything(), anything(), anything()).and_return(nil)
|
44
|
+
expect(subject.tcp_test_ssh("foo.example.com", &empty_block)).to be_falsey
|
45
|
+
end
|
46
|
+
|
47
|
+
# TODO: Add more specs for behavior of rescue blocks
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#run" do
|
51
|
+
let(:mock_bootstrap) {
|
52
|
+
double("Chef::Knife::Bootstrap").tap do |mb|
|
53
|
+
allow(mb).to receive(:run)
|
54
|
+
allow(mb).to receive(:name_args=)
|
55
|
+
allow(mb).to receive(:config).and_return({})
|
56
|
+
end
|
57
|
+
}
|
58
|
+
|
59
|
+
let(:mock_server) {
|
60
|
+
double("Fog::Compute::Linode::Server").tap do |ms|
|
61
|
+
allow(ms).to receive(:ips).and_return([mock_ip("1.2.3.4")])
|
62
|
+
allow(ms).to receive(:id).and_return(42)
|
63
|
+
allow(ms).to receive(:name).and_return("Test Linode")
|
64
|
+
allow(ms).to receive(:status).and_return(1)
|
65
|
+
allow(ms).to receive(:public_ip_address)
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
let(:mock_servers) {
|
70
|
+
double("Fog::Collection").tap do |ms|
|
71
|
+
allow(subject.connection).to receive(:servers).and_return(ms)
|
72
|
+
end
|
73
|
+
}
|
74
|
+
|
75
|
+
before :each do
|
76
|
+
allow(Chef::Knife::Bootstrap).to receive(:new).and_return(mock_bootstrap)
|
77
|
+
allow(subject).to receive(:tcp_test_ssh).and_return(true)
|
78
|
+
allow(mock_servers).to receive(:create).and_return(mock_server)
|
79
|
+
allow(subject.connection).to receive(:servers).and_return(mock_servers)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should validate the Linode config keys exist" do
|
83
|
+
expect(subject).to receive(:validate!)
|
84
|
+
subject.run
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should call #create on the servers collection with the correct params" do
|
88
|
+
skip 'fails - arg variable does not exist'
|
89
|
+
|
90
|
+
configure_chef(subject)
|
91
|
+
|
92
|
+
expect(mock_servers).to receive(:create) { |server|
|
93
|
+
server.each do |k,v|
|
94
|
+
case k
|
95
|
+
when :data_center, :flavor, :image, :kernel
|
96
|
+
expect(arg[k].id.to_i).to eq(v.id)
|
97
|
+
else
|
98
|
+
expect(arg[k]).to eq(v)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
}.and_return(mock_server)
|
102
|
+
|
103
|
+
subject.run
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should create a Chef::Knife::Bootstrap instance" do
|
107
|
+
expect(Chef::Knife::Bootstrap).to receive(:new)
|
108
|
+
subject.run
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should set the bootstrap name_args to the Linode's public IP" do
|
112
|
+
ips = %w( 1.2.3.4 192.168.1.1 ).map { |ip| mock_ip(ip) }
|
113
|
+
allow(mock_server).to receive(:ips).and_return(ips)
|
114
|
+
expect(mock_bootstrap).to receive(:name_args=).with([ips.first.ip])
|
115
|
+
|
116
|
+
subject.run
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should set the bootstrap config correctly" do
|
120
|
+
mock_config = {}
|
121
|
+
allow(mock_bootstrap).to receive(:config).and_return(mock_config)
|
122
|
+
|
123
|
+
chef_config = configure_chef(subject)
|
124
|
+
|
125
|
+
params = [
|
126
|
+
:run_list, :ssh_user, :identity_file, :ssh_password, :chef_node_name,
|
127
|
+
:prerelease, :bootstrap_version, :distro, :use_sudo, :template_file,
|
128
|
+
:environment, :no_host_key_verify
|
129
|
+
]
|
130
|
+
|
131
|
+
subject.run
|
132
|
+
|
133
|
+
params.each do |param|
|
134
|
+
case param
|
135
|
+
when :use_sudo
|
136
|
+
expect(mock_config[:use_sudo]).to be_falsey
|
137
|
+
else
|
138
|
+
expectation = chef_config[param]
|
139
|
+
actual = mock_config[param]
|
140
|
+
|
141
|
+
if actual != expectation
|
142
|
+
$stderr.puts "#{param}: #{actual.inspect} should have been #{expectation.inspect}"
|
143
|
+
end
|
144
|
+
|
145
|
+
expect(actual).to eq(expectation)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def mock_ip(ip)
|
153
|
+
OpenStruct.new(:ip => ip)
|
154
|
+
end
|
155
|
+
|
156
|
+
def configure_chef(subject)
|
157
|
+
connection = subject.connection
|
158
|
+
server = {
|
159
|
+
:data_center => connection.data_centers.first,
|
160
|
+
:flavor => connection.flavors.first,
|
161
|
+
:image => connection.images.first,
|
162
|
+
:kernel => connection.kernels.first,
|
163
|
+
:type => "ext3",
|
164
|
+
:payment_terms => 1,
|
165
|
+
:stack_script => nil,
|
166
|
+
:name => "test_node",
|
167
|
+
:password => nil,
|
168
|
+
}
|
169
|
+
|
170
|
+
chef_config = {
|
171
|
+
:ssh_user => "root",
|
172
|
+
:run_list => [],
|
173
|
+
:identity_file => "~/.ssh/id_dsa",
|
174
|
+
:chef_node_name => "test_node",
|
175
|
+
:environment => "_test",
|
176
|
+
}
|
177
|
+
|
178
|
+
server_params = {}
|
179
|
+
server.each do |k,v|
|
180
|
+
case k
|
181
|
+
when :data_center
|
182
|
+
server_params[:datacenter] = v.id
|
183
|
+
when :data_center, :flavor, :image, :kernel
|
184
|
+
server_params[k] = v.id
|
185
|
+
else
|
186
|
+
server_params[k] = v
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# setup the config before we call #run
|
191
|
+
server_params.each do |k,v|
|
192
|
+
case k
|
193
|
+
when :name
|
194
|
+
Chef::Config[:knife][:linode_node_name] = v
|
195
|
+
when :password
|
196
|
+
Chef::Config[:knife][:ssh_password] = v
|
197
|
+
when :type, :payment_terms, :stack_script
|
198
|
+
# these are hard-coded in the plugin, so don't add them to the Config
|
199
|
+
when :name
|
200
|
+
Chef::Config[:knife][:linode_node_name] = v
|
201
|
+
Chef::Config[:knife][:chef_node_name] = v
|
202
|
+
when :datacenter, :flavor, :image, :kernel
|
203
|
+
Chef::Config[:knife]["linode_#{k}".to_sym] = v
|
204
|
+
else
|
205
|
+
Chef::Config[:knife][k] = v
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
cli_config = subject.config.dup
|
210
|
+
chef_config.each do |k,v|
|
211
|
+
cli_config[k] = v
|
212
|
+
end
|
213
|
+
allow(subject).to receive(:config).and_return(cli_config)
|
214
|
+
#cli_config.merge(Chef::Config[:knife].dup)
|
215
|
+
cli_config
|
216
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'linode_server_list'
|
3
|
+
|
4
|
+
describe Chef::Knife::LinodeServerList do
|
5
|
+
subject { Chef::Knife::LinodeServerList.new }
|
6
|
+
|
7
|
+
let(:api_key) { 'FAKE_API_KEY' }
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
Chef::Knife::LinodeServerList.load_deps
|
11
|
+
Chef::Config[:knife][:linode_api_key] = api_key
|
12
|
+
allow(subject).to receive(:puts)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#run" do
|
16
|
+
it "should validate the Linode config keys exist" do
|
17
|
+
expect(subject).to receive(:validate!)
|
18
|
+
subject.run
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should output the column headers" do
|
22
|
+
expect(subject).to receive(:puts).with(/^Linode ID\s+Name\s+IPs\s+Status\s+Backups\s+Datacenter\s*$/)
|
23
|
+
subject.run
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should output a list of the server labels" do
|
27
|
+
expect(subject).to receive(:puts).with(/\btest_\d+\b/)
|
28
|
+
subject.run
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should output a list of the server IPs" do
|
32
|
+
expect(subject).to receive(:puts).with(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/)
|
33
|
+
subject.run
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should output the running state of the servers" do
|
37
|
+
expect(subject).to receive(:puts).with(/\bRunning\b/)
|
38
|
+
subject.run
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should output the datacenter location of the servers" do
|
42
|
+
expect(subject).to receive(:puts).with(/\bNewark, NJ, USA\b/)
|
43
|
+
subject.run
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-linode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2014-08-16 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: fog
|
18
|
-
requirement:
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ~>
|
@@ -23,7 +23,60 @@ dependencies:
|
|
23
23
|
version: '1.0'
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
|
-
version_requirements:
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '1.0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: chef
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '11.8'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '11.8'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '3.0'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: rubocop
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0.24'
|
72
|
+
type: :development
|
73
|
+
prerelease: false
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0.24'
|
27
80
|
description: Linode Support for Chef's Knife Command
|
28
81
|
email:
|
29
82
|
- adam@opscode.com
|
@@ -32,13 +85,14 @@ email:
|
|
32
85
|
executables: []
|
33
86
|
extensions: []
|
34
87
|
extra_rdoc_files:
|
35
|
-
- README.
|
88
|
+
- README.md
|
36
89
|
- LICENSE
|
37
90
|
files:
|
38
91
|
- .gitignore
|
92
|
+
- .travis.yml
|
39
93
|
- Gemfile
|
40
94
|
- LICENSE
|
41
|
-
- README.
|
95
|
+
- README.md
|
42
96
|
- Rakefile
|
43
97
|
- knife-linode.gemspec
|
44
98
|
- lib/chef/knife/linode_base.rb
|
@@ -52,6 +106,11 @@ files:
|
|
52
106
|
- lib/chef/knife/linode_server_reboot.rb
|
53
107
|
- lib/chef/knife/linode_stackscript_list.rb
|
54
108
|
- lib/knife-linode/version.rb
|
109
|
+
- spec/chef/knife/linode_datacenter_list_spec.rb
|
110
|
+
- spec/chef/knife/linode_flavor_list_spec.rb
|
111
|
+
- spec/chef/knife/linode_server_create_spec.rb
|
112
|
+
- spec/chef/knife/linode_server_list_spec.rb
|
113
|
+
- spec/spec_helper.rb
|
55
114
|
homepage: http://wiki.opscode.com/display/chef
|
56
115
|
licenses: []
|
57
116
|
post_install_message:
|
@@ -61,19 +120,24 @@ require_paths:
|
|
61
120
|
required_ruby_version: !ruby/object:Gem::Requirement
|
62
121
|
none: false
|
63
122
|
requirements:
|
64
|
-
- -
|
123
|
+
- - '>='
|
65
124
|
- !ruby/object:Gem::Version
|
66
125
|
version: '0'
|
67
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
127
|
none: false
|
69
128
|
requirements:
|
70
|
-
- -
|
129
|
+
- - '>='
|
71
130
|
- !ruby/object:Gem::Version
|
72
131
|
version: '0'
|
73
132
|
requirements: []
|
74
133
|
rubyforge_project:
|
75
|
-
rubygems_version: 1.8.
|
134
|
+
rubygems_version: 1.8.25
|
76
135
|
signing_key:
|
77
136
|
specification_version: 3
|
78
137
|
summary: Linode Support for Chef's Knife Command
|
79
|
-
test_files:
|
138
|
+
test_files:
|
139
|
+
- spec/chef/knife/linode_datacenter_list_spec.rb
|
140
|
+
- spec/chef/knife/linode_flavor_list_spec.rb
|
141
|
+
- spec/chef/knife/linode_server_create_spec.rb
|
142
|
+
- spec/chef/knife/linode_server_list_spec.rb
|
143
|
+
- spec/spec_helper.rb
|
data/README.rdoc
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
= Knife Linode
|
2
|
-
|
3
|
-
= DESCRIPTION:
|
4
|
-
|
5
|
-
This is the official Opscode Knife plugin for Linode. This plugin gives knife the ability to create, bootstrap, and manage Linode instances.
|
6
|
-
|
7
|
-
= INSTALLATION:
|
8
|
-
|
9
|
-
Be sure you are running the latest version Chef. Versions earlier than 0.10.0 don't support plugins:
|
10
|
-
|
11
|
-
gem install chef
|
12
|
-
|
13
|
-
This plugin is distributed as a Ruby Gem. To install it, run:
|
14
|
-
|
15
|
-
gem install knife-linode
|
16
|
-
|
17
|
-
Depending on your system's configuration, you may need to run this command with root privileges.
|
18
|
-
|
19
|
-
= CONFIGURATION:
|
20
|
-
|
21
|
-
In order to communicate with the Linode API you will have to tell Knife about your Linode API Key. The easiest way to accomplish this is to create some entries in your <tt>knife.rb</tt> file:
|
22
|
-
|
23
|
-
knife[:linode_api_key] = "Your Linode API Key"
|
24
|
-
|
25
|
-
If your knife.rb file will be checked into a SCM system (ie readable by others) you may want to read the values from environment variables:
|
26
|
-
|
27
|
-
knife[:linode_api_key] = "#{ENV['LINODE_API_KEY']}"
|
28
|
-
|
29
|
-
You also have the option of passing your Linode API Key into the individual knife subcommands using the <tt>-A</tt> (or <tt>--linode-api-key</tt>) command option
|
30
|
-
|
31
|
-
# provision a new m1.small Ubuntu 10.04 webserver # FIXME: change from EC2 to linode
|
32
|
-
knife ec2 server create 'role[webserver]' -I ami-7000f019 -f m1.small -A 'Your AWS Access Key ID' -K "Your AWS Secret Access Key"
|
33
|
-
|
34
|
-
Additionally the following options may be set in your `knife.rb`:
|
35
|
-
|
36
|
-
* linode_flavor
|
37
|
-
* linode_image
|
38
|
-
* linode_kernel
|
39
|
-
* ssh_password
|
40
|
-
* bootstrap_version
|
41
|
-
* distro
|
42
|
-
* template_file
|
43
|
-
|
44
|
-
= SUBCOMMANDS:
|
45
|
-
|
46
|
-
This plugin provides the following Knife subcommands. Specific command options can be found by invoking the subcommand with a <tt>--help</tt> flag
|
47
|
-
|
48
|
-
== knife linode server create
|
49
|
-
|
50
|
-
Provisions a new server in Linode and then perform a Chef bootstrap (using the SSH protocol). The goal of the bootstrap is to get Chef installed on the target system so it can run Chef Client with a Chef Server. The main assumption is a baseline OS installation exists (provided by the provisioning). It is primarily intended for Chef Client systems that talk to a Chef server. By default the server is bootstrapped using the {ubuntu10.04-gems}[https://github.com/opscode/chef/blob/master/chef/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb] template. This can be overridden using the <tt>-d</tt> or <tt>--template-file</tt> command options.
|
51
|
-
|
52
|
-
== knife linode server delete
|
53
|
-
|
54
|
-
Deletes an existing server in the currently configured Linode account. <b>PLEASE NOTE</b> - this does not delete the associated node and client objects from the Chef server.
|
55
|
-
|
56
|
-
== knife linode server list
|
57
|
-
|
58
|
-
Outputs a list of all servers in the currently configured Linode account. <b>PLEASE NOTE</b> - this shows all instances associated with the account, some of which may not be currently managed by the Chef server.
|
59
|
-
|
60
|
-
== knife linode server reboot
|
61
|
-
|
62
|
-
== knife linode datacenter list
|
63
|
-
== knife linode flavor list
|
64
|
-
== knife linode image list
|
65
|
-
== knife linode kernel list
|
66
|
-
== knife linode stackscript list
|
67
|
-
|
68
|
-
= LICENSE:
|
69
|
-
|
70
|
-
Author:: Adam Jacob (<adam@opscode.com>)
|
71
|
-
Copyright:: Copyright (c) 2009-2011 Opscode, Inc.
|
72
|
-
License:: Apache License, Version 2.0
|
73
|
-
|
74
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
75
|
-
you may not use this file except in compliance with the License.
|
76
|
-
You may obtain a copy of the License at
|
77
|
-
|
78
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
79
|
-
|
80
|
-
Unless required by applicable law or agreed to in writing, software
|
81
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
82
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
83
|
-
See the License for the specific language governing permissions and
|
84
|
-
limitations under the License.
|
85
|
-
|