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 CHANGED
@@ -1,2 +1,3 @@
1
1
  pkg/*
2
2
  Gemfile.lock
3
+ .rspec
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 2.0.0
3
+ script:
4
+ # - bundle exec rubocop .
5
+ - bundle exec rspec --color --format progress
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.has_rdoc = true
9
- s.authors = ["Adam Jacob","Seth Chisamore", "Lamont Granquist"]
10
- s.email = ["adam@opscode.com","schisamo@opscode.com", "lamont@opscode.com"]
11
- s.homepage = "http://wiki.opscode.com/display/chef"
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 = ["README.rdoc", "LICENSE" ]
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] || config[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 provided a valid '#{pretty_key}' value."
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 => 93
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 => "ubuntu10.04-gems"
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 :no_host_key_verify,
130
- :long => "--no-host-key-verify",
131
- :description => "Disable host key verification",
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 => false
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[:no_host_key_verify] = config[:no_host_key_verify]
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.get(linode_id)
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(linode_id).destroy
83
+ connection.servers.get(delete_id).destroy
84
+
85
+ ui.warn("Deleted server #{delete_id}")
49
86
 
50
- ui.warn("Deleted server #{linode_id}")
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
 
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module Linode
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
6
  end
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ require 'fog'
3
+
4
+ Fog.mock!
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib', 'chef', 'knife'))
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.2.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: 2012-05-09 00:00:00.000000000Z
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: &70126029154420 !ruby/object:Gem::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: *70126029154420
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.rdoc
88
+ - README.md
36
89
  - LICENSE
37
90
  files:
38
91
  - .gitignore
92
+ - .travis.yml
39
93
  - Gemfile
40
94
  - LICENSE
41
- - README.rdoc
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.17
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
-