knife-server 0.1.0

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.
@@ -0,0 +1,89 @@
1
+ module Knife
2
+ module Server
3
+ class Ec2SecurityGroup
4
+ def initialize(connection, ui)
5
+ @aws = connection
6
+ @ui = ui
7
+ end
8
+
9
+ def configure_chef_server_group(group_name, options = {})
10
+ group = find_or_create(group_name, options)
11
+
12
+ ip_permissions.each do |p|
13
+ if permission_exists?(group, p)
14
+ @ui.msg "Inbound security group rule " +
15
+ "#{p[:proto]}(#{p[:from]} -> #{p[:to]}) exists"
16
+ else
17
+ @ui.msg "Creating inbound security group rule for " +
18
+ "#{p[:proto]}(#{p[:from]} -> #{p[:to]})"
19
+ options = permission_options(group, p)
20
+ @aws.authorize_security_group_ingress(group.name, options)
21
+ end
22
+ end
23
+ end
24
+
25
+ def find_or_create(name, options = {})
26
+ group = @aws.security_groups.find { |g| g.name == name }
27
+
28
+ if group.nil?
29
+ @ui.msg "Creating EC2 security group '#{name}'"
30
+ @aws.create_security_group(name, options[:description])
31
+ group = @aws.security_groups.find { |g| g.name == name }
32
+ else
33
+ @ui.msg "EC2 security group '#{name}' exists"
34
+ end
35
+
36
+ group
37
+ end
38
+
39
+ private
40
+
41
+ def ip_permissions
42
+ [ {:proto => 'icmp', :from => -1, :to => -1},
43
+ {:proto => 'tcp', :from => 0, :to => 65535},
44
+ {:proto => 'udp', :from => 0, :to => 65535},
45
+ {:proto => 'tcp', :from => 22, :to => 22, :cidr => '0.0.0.0/0'},
46
+ {:proto => 'tcp', :from => 443, :to => 443, :cidr => '0.0.0.0/0'},
47
+ {:proto => 'tcp', :from => 444, :to => 444, :cidr => '0.0.0.0/0'},
48
+ ].freeze
49
+ end
50
+
51
+ def permission_exists?(group, perm)
52
+ group.ip_permissions.find do |p|
53
+ p['ipProtocol'] == perm[:proto] &&
54
+ p['fromPort'] == perm[:from] &&
55
+ p['toPort'] == perm[:to]
56
+ end
57
+ end
58
+
59
+ def permission_options(group, opts)
60
+ options = {
61
+ 'IpPermissions' => [
62
+ {
63
+ 'IpProtocol' => opts[:proto],
64
+ 'FromPort' => opts[:from],
65
+ 'ToPort' => opts[:to]
66
+ }
67
+ ]
68
+ }
69
+
70
+ if opts[:cidr]
71
+ options['IpPermissions'].first['IpRanges'] = [
72
+ {
73
+ 'CidrIp' => opts[:cidr]
74
+ }
75
+ ]
76
+ else
77
+ options['IpPermissions'].first['Groups'] = [
78
+ {
79
+ 'GroupName' => group.name,
80
+ 'UserId' => group.owner_id
81
+ }
82
+ ]
83
+ end
84
+
85
+ options
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,34 @@
1
+ require 'net/ssh'
2
+
3
+ module Knife
4
+ module Server
5
+ class SSH
6
+ DEFAULT_OPTIONS = { :user => "root", :port => "22" }.freeze
7
+
8
+ def initialize(params)
9
+ options = DEFAULT_OPTIONS.merge(params)
10
+
11
+ @host = options.delete(:host)
12
+ @user = options.delete(:user)
13
+ @options = options
14
+ end
15
+
16
+ def exec!(cmd)
17
+ if @user == "root"
18
+ full_cmd = cmd
19
+ else
20
+ full_cmd = [
21
+ %[sudo USER=root HOME="$(getent passwd root | cut -d : -f 6)"],
22
+ %[bash -c '#{cmd}']
23
+ ].join(" ")
24
+ end
25
+
26
+ result = ""
27
+ Net::SSH.start(@host, @user, @options) do |ssh|
28
+ result = ssh.exec!(full_cmd)
29
+ end
30
+ result
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Knife
2
+ module Server
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,259 @@
1
+ require 'chef/knife/server_bootstrap_ec2'
2
+ require 'chef/knife/ec2_server_create'
3
+ require 'fog'
4
+ require 'net/ssh'
5
+ require 'fakefs/spec_helpers'
6
+
7
+ describe Chef::Knife::ServerBootstrapEc2 do
8
+ include FakeFS::SpecHelpers
9
+
10
+ before do
11
+ Chef::Log.logger = Logger.new(StringIO.new)
12
+ @knife = Chef::Knife::ServerBootstrapEc2.new
13
+ @stdout = StringIO.new
14
+ @knife.ui.stub!(:stdout).and_return(@stdout)
15
+ @stderr = StringIO.new
16
+ @knife.ui.stub!(:stderr).and_return(@stderr)
17
+ @knife.config[:chef_node_name] = "yakky"
18
+ end
19
+
20
+ let(:connection) { mock(Fog::Compute::AWS) }
21
+
22
+ describe "#ec2_bootstrap" do
23
+ before do
24
+ @knife.config[:chef_node_name] = "shave.yak"
25
+ @knife.config[:ssh_user] = "jdoe"
26
+ @knife.config[:ssh_port] = "2222"
27
+ @knife.config[:identity_file] = "~/.ssh/mykey_dsa"
28
+ @knife.config[:security_groups] = %w{x y z}
29
+ @knife.config[:tags] = %w{tag1=val1 tag2=val2}
30
+ @knife.config[:distro] = "distro-praha"
31
+ @knife.config[:ebs_size] = "42"
32
+ @knife.config[:webui_password] = "daweb"
33
+ @knife.config[:amqp_password] = "queueitup"
34
+
35
+ ENV['_SPEC_WEBUI_PASSWORD'] = ENV['WEBUI_PASSWORD']
36
+ ENV['_SPEC_AMQP_PASSWORD'] = ENV['AMQP_PASSWORD']
37
+ end
38
+
39
+ after do
40
+ ENV['WEBUI_PASSWORD'] = ENV.delete('_SPEC_WEBUI_PASSWORD')
41
+ ENV['AMQP_PASSWORD'] = ENV.delete('_SPEC_AMQP_PASSWORD')
42
+ end
43
+
44
+ let(:bootstrap) { @knife.ec2_bootstrap }
45
+
46
+ it "returns an Ec2ServerCreate instance" do
47
+ bootstrap.should be_a(Chef::Knife::Ec2ServerCreate)
48
+ end
49
+
50
+ it "configs the bootstrap's chef_node_name" do
51
+ bootstrap.config[:chef_node_name].should eq("shave.yak")
52
+ end
53
+
54
+ it "configs the bootstrap's ssh_user" do
55
+ bootstrap.config[:ssh_user].should eq("jdoe")
56
+ end
57
+
58
+ it "configs the bootstrap's ssh_port" do
59
+ bootstrap.config[:ssh_port].should eq("2222")
60
+ end
61
+
62
+ it "configs the bootstrap's identity_file" do
63
+ bootstrap.config[:identity_file].should eq("~/.ssh/mykey_dsa")
64
+ end
65
+
66
+ it "configs the bootstrap's security_groups" do
67
+ bootstrap.config[:security_groups].should eq(["x", "y", "z"])
68
+ end
69
+
70
+ it "configs the bootstrap's ebs_size" do
71
+ bootstrap.config[:ebs_size].should eq("42")
72
+ end
73
+
74
+ it "configs the bootstrap's tags" do
75
+ bootstrap.config[:tags].should include("tag1=val1")
76
+ bootstrap.config[:tags].should include("tag2=val2")
77
+ end
78
+
79
+ it "adds Role=chef_server to the bootstrap's tags" do
80
+ bootstrap.config[:tags].should include("Role=chef_server")
81
+ end
82
+
83
+ it "configs the bootstrap's distro" do
84
+ bootstrap.config[:distro].should eq("distro-praha")
85
+ end
86
+
87
+ it "configs the bootstrap's distro to chef-server-debian by default" do
88
+ @knife.config.delete(:distro)
89
+
90
+ bootstrap.config[:distro].should eq("chef-server-debian")
91
+ end
92
+
93
+ it "configs the bootstrap's distro value driven off platform value" do
94
+ @knife.config.delete(:distro)
95
+ @knife.config[:platform] = "freebsd"
96
+
97
+ bootstrap.config[:distro].should eq("chef-server-freebsd")
98
+ end
99
+
100
+ it "configs the bootstrap's ENV with the webui password" do
101
+ bootstrap
102
+ ENV['WEBUI_PASSWORD'].should eq("daweb")
103
+ end
104
+
105
+ it "configs the bootstrap's ENV with the amqp password" do
106
+ bootstrap
107
+ ENV['AMQP_PASSWORD'].should eq("queueitup")
108
+ end
109
+ end
110
+
111
+ describe "#ec2_connection" do
112
+ before do
113
+ @before_config = Hash.new
114
+ @before_config[:knife] = Hash.new
115
+ [:aws_access_key_id, :aws_secret_access_key, :region].each do |attr|
116
+ @before_config[:knife][attr] = Chef::Config[:knife][attr]
117
+ end
118
+
119
+ Chef::Config[:knife][:aws_access_key_id] = "key"
120
+ Chef::Config[:knife][:aws_secret_access_key] = "secret"
121
+ Chef::Config[:knife][:region] = "hell-south-666"
122
+ end
123
+
124
+ after do
125
+ [:aws_access_key_id, :aws_secret_access_key, :region].each do |attr|
126
+ Chef::Config[:knife][attr] = @before_config[:knife][attr]
127
+ end
128
+ end
129
+
130
+ it "constructs a connection" do
131
+ Fog::Compute.should_receive(:new).with({
132
+ :provider => 'AWS',
133
+ :aws_access_key_id => 'key',
134
+ :aws_secret_access_key => 'secret',
135
+ :region => 'hell-south-666'
136
+ })
137
+
138
+ @knife.ec2_connection
139
+ end
140
+ end
141
+
142
+ describe "#server_dns_name" do
143
+ before do
144
+ @knife.config[:chef_node_name] = 'shavemy.yak'
145
+ @knife.stub(:ec2_connection) { connection }
146
+ end
147
+
148
+ context "when server is found" do
149
+ before do
150
+ connection.stub(:servers) { [server] }
151
+ end
152
+
153
+ let(:server) do
154
+ stub(:dns_name => 'blahblah.aws.compute.com', :state => "running",
155
+ :tags => {'Name' => 'shavemy.yak', 'Role' => 'chef_server'})
156
+ end
157
+
158
+ it "returns the provisioned dns name" do
159
+ @knife.server_dns_name.should eq('blahblah.aws.compute.com')
160
+ end
161
+
162
+ it "ignores terminated instances" do
163
+ server.stub(:state) { "terminated" }
164
+ @knife.server_dns_name.should be_nil
165
+ end
166
+ end
167
+
168
+ context "when server is not found" do
169
+ before do
170
+ connection.stub(:servers) { [] }
171
+ end
172
+
173
+ it "returns nil" do
174
+ @knife.server_dns_name.should be_nil
175
+ end
176
+ end
177
+ end
178
+
179
+ describe "#run" do
180
+ before do
181
+ @before_config = Hash.new
182
+ [:node_name, :client_key].each do |attr|
183
+ @before_config[attr] = Chef::Config[attr]
184
+ end
185
+ Chef::Config[:node_name] = "smithers"
186
+ Chef::Config[:client_key] = "/var/tmp/myclientkey.pem"
187
+
188
+ @knife.config[:security_groups] = ["mygroup"]
189
+ @knife.config[:validation_key] = "/var/tmp/validation.pem"
190
+ @knife.config[:ssh_port] = "2345"
191
+ @knife.config[:identity_file] = "~/.ssh/mykey_dsa"
192
+ @knife.stub(:ec2_connection) { connection }
193
+ @knife.stub(:server_dns_name) { "grapes.wrath" }
194
+ Chef::Knife::Ec2ServerCreate.stub(:new) { bootstrap }
195
+ Knife::Server::Ec2SecurityGroup.stub(:new) { security_group }
196
+ Knife::Server::SSH.stub(:new) { ssh }
197
+ Knife::Server::Credentials.stub(:new) { credentials }
198
+ security_group.stub(:configure_chef_server_group)
199
+ credentials.stub(:install_validation_key)
200
+ credentials.stub(:create_root_client)
201
+ end
202
+
203
+ after do
204
+ [:node_name, :client_key].each do |attr|
205
+ Chef::Config[attr] = @before_config[attr]
206
+ end
207
+ end
208
+
209
+ let(:bootstrap) { stub(:run => true, :config => Hash.new) }
210
+ let(:security_group) { stub }
211
+ let(:ssh) { stub }
212
+ let(:credentials) { stub.as_null_object }
213
+
214
+ it "exits if node_name option is missing" do
215
+ def @knife.exit(code) ; end
216
+ @knife.config.delete(:chef_node_name)
217
+
218
+ @knife.should_receive(:exit)
219
+ @knife.run
220
+ end
221
+
222
+ it "configures the ec2 security group" do
223
+ Knife::Server::Ec2SecurityGroup.should_receive(:new).
224
+ with(connection, @knife.ui)
225
+ security_group.should_receive(:configure_chef_server_group).
226
+ with('mygroup', :description => 'mygroup group')
227
+
228
+ @knife.run
229
+ end
230
+
231
+ it "bootstraps an ec2 server" do
232
+ bootstrap.should_receive(:run)
233
+ @knife.run
234
+ end
235
+
236
+ it "installs a new validation.pem key from the server" do
237
+ Knife::Server::SSH.should_receive(:new).
238
+ with({ :host => "grapes.wrath", :user => "root", :port => "2345" })
239
+ Knife::Server::Credentials.should_receive(:new).
240
+ with(ssh, "/etc/chef/validation.pem")
241
+ credentials.should_receive(:install_validation_key)
242
+
243
+ @knife.run
244
+ end
245
+
246
+ it "create a root client key" do
247
+ credentials.should_receive(:create_root_client)
248
+
249
+ @knife.run
250
+ end
251
+
252
+ it "installs a client key" do
253
+ credentials.should_receive(:install_client_key).
254
+ with("smithers", "/var/tmp/myclientkey.pem")
255
+
256
+ @knife.run
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,104 @@
1
+ require 'knife/server/credentials'
2
+ require 'fakefs/spec_helpers'
3
+
4
+ describe Knife::Server::Credentials do
5
+ include FakeFS::SpecHelpers
6
+
7
+ let(:ssh) { stub("SSH Client") }
8
+ let(:validation_key_path) { "/tmp/validation.pem" }
9
+ let(:client_key_path) { "/tmp/client.pem" }
10
+
11
+ subject do
12
+ Knife::Server::Credentials.new(ssh, validation_key_path)
13
+ end
14
+
15
+ before do
16
+ FileUtils.mkdir_p(File.dirname(validation_key_path))
17
+ FileUtils.mkdir_p(File.dirname(client_key_path))
18
+ File.new(validation_key_path, "wb") { |f| f.write("thekey") }
19
+ File.new(client_key_path, "wb") { |f| f.write("clientkey") }
20
+ end
21
+
22
+ describe "#install_validation_key" do
23
+ before do
24
+ ssh.stub(:exec!).with("cat /etc/chef/validation.pem") { "newkey" }
25
+ end
26
+
27
+ it "creates a backup of the existing validation key file" do
28
+ original = File.open("/tmp/validation.pem", "rb") { |f| f.read }
29
+ subject.install_validation_key("old")
30
+ backup = File.open("/tmp/validation.old.pem", "rb") { |f| f.read }
31
+
32
+ original.should eq(backup)
33
+ end
34
+
35
+ it "skips backup file creation if validation key file does not exist" do
36
+ FileUtils.rm_f(validation_key_path)
37
+ subject.install_validation_key("old")
38
+
39
+ File.exists?("/tmp/validation.old.pem").should_not be_true
40
+ end
41
+
42
+ it "copies the key back from the server into validation key file" do
43
+ subject.install_validation_key("old")
44
+ key_str = File.open("/tmp/validation.pem", "rb") { |f| f.read }
45
+
46
+ key_str.should eq("newkey")
47
+ end
48
+ end
49
+
50
+ describe "#create_root_client" do
51
+ it "creates an initial client key on the server" do
52
+ ssh.should_receive(:exec!).with([
53
+ 'knife configure --initial --server-url http://127.0.0.1:4000',
54
+ '--user root --repository "" --defaults --yes'
55
+ ].join(" "))
56
+
57
+ subject.create_root_client
58
+ end
59
+ end
60
+
61
+ describe "#install_client_key" do
62
+ before do
63
+ ssh.stub(:exec!)
64
+ ssh.stub(:exec!).with("cat /tmp/chef-client-bob.pem") { "bobkey" }
65
+ end
66
+
67
+ it "creates a user client key on the server" do
68
+ ssh.should_receive(:exec!).with([
69
+ "knife client create bob --admin",
70
+ "--file /tmp/chef-client-bob.pem --disable-editing",
71
+ ].join(" "))
72
+
73
+ subject.install_client_key("bob", client_key_path)
74
+ end
75
+
76
+ it "creates a backup of the existing client key file" do
77
+ original = File.open("/tmp/client.pem", "rb") { |f| f.read }
78
+ subject.install_client_key("bob", client_key_path, "old")
79
+ backup = File.open("/tmp/client.old.pem", "rb") { |f| f.read }
80
+
81
+ original.should eq(backup)
82
+ end
83
+
84
+ it "skips backup file creation if client key file does not exist" do
85
+ FileUtils.rm_f(client_key_path)
86
+ subject.install_client_key("bob", client_key_path, "old")
87
+
88
+ File.exists?("/tmp/client.old.pem").should_not be_true
89
+ end
90
+
91
+ it "copies the key back from the server into client key file" do
92
+ subject.install_client_key("bob", client_key_path, "old")
93
+ key_str = File.open("/tmp/client.pem", "rb") { |f| f.read }
94
+
95
+ key_str.should eq("bobkey")
96
+ end
97
+
98
+ it "removes the user client key from the server" do
99
+ ssh.should_receive(:exec!).with("rm -f /tmp/chef-client-bob.pem")
100
+
101
+ subject.install_client_key("bob", client_key_path)
102
+ end
103
+ end
104
+ end