knife-server 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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