lobot 1.0.pre → 2.0.0pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +0 -1
- data/Guardfile +10 -0
- data/README.md +52 -72
- data/Rakefile +2 -6
- data/Vagrantfile +15 -0
- data/bin/lobot +7 -0
- data/chef/cookbooks/pivotal_ci/.gitignore +4 -0
- data/chef/cookbooks/pivotal_ci/Gemfile +3 -0
- data/chef/cookbooks/pivotal_ci/README.md +3 -0
- data/chef/cookbooks/pivotal_ci/attributes/git.rb +1 -1
- data/chef/cookbooks/pivotal_ci/attributes/jenkins.rb +2 -3
- data/chef/cookbooks/pivotal_ci/attributes/nginx.rb +1 -2
- data/chef/cookbooks/pivotal_ci/attributes/ssl.rb +1 -1
- data/chef/cookbooks/pivotal_ci/files/default/tests/minitest/default_test.rb +37 -0
- data/chef/cookbooks/pivotal_ci/metadata.rb +10 -0
- data/chef/cookbooks/pivotal_ci/recipes/id_rsa.rb +5 -2
- data/chef/cookbooks/pivotal_ci/recipes/jenkins.rb +1 -1
- data/chef/cookbooks/pivotal_ci/recipes/jenkins_config.rb +10 -17
- data/chef/cookbooks/pivotal_ci/recipes/nginx.rb +2 -0
- data/chef/cookbooks/pivotal_ci/templates/default/jenkins-job-config.xml.erb +11 -3
- data/chef/cookbooks/pivotal_ci/templates/default/nginx-htaccess.erb +3 -3
- data/chef/cookbooks/pivotal_ci/templates/default/org.jenkinsci.plugins.xvfb.XvfbBuildWrapper.xml.erb +11 -0
- data/chef/cookbooks/pivotal_ci/test/kitchen/Kitchenfile +4 -0
- data/chef/cookbooks/pivotal_ci/test/kitchen/cookbooks/pivotal_ci_test/attributes/default.rb +3 -0
- data/chef/cookbooks/pivotal_ci/test/kitchen/cookbooks/pivotal_ci_test/metadata.rb +10 -0
- data/chef/cookbooks/pivotal_ci/test/kitchen/cookbooks/pivotal_ci_test/recipes/default.rb +41 -0
- data/lib/generators/lobot/templates/soloistrc +2 -1
- data/lib/lobot.rb +1 -12
- data/lib/lobot/amazon.rb +91 -0
- data/lib/lobot/cli.rb +148 -0
- data/lib/lobot/config.rb +80 -0
- data/lib/lobot/jenkins.rb +17 -0
- data/lib/lobot/sobo.rb +35 -0
- data/lib/lobot/tasks/ci.rake +0 -178
- data/lib/lobot/version.rb +1 -1
- data/lobot.gemspec +19 -15
- data/script/bootstrap_server.sh +4 -1
- data/spec/lib/lobot/amazon_spec.rb +127 -0
- data/spec/lib/lobot/cli_spec.rb +212 -0
- data/spec/lib/lobot/config_spec.rb +95 -0
- data/spec/lib/lobot/jenkins_spec.rb +14 -0
- data/spec/spec_helper.rb +5 -10
- metadata +87 -31
- data/chef/cookbooks/pivotal_ci/libraries/ci_config.rb +0 -2
- data/spec/install_spec.rb +0 -80
data/lib/lobot/sobo.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
module Lobot
|
4
|
+
class Sobo
|
5
|
+
attr_reader :ip, :key
|
6
|
+
attr_writer :timeout
|
7
|
+
|
8
|
+
def initialize(ip, key)
|
9
|
+
@ip = ip
|
10
|
+
@key = key
|
11
|
+
end
|
12
|
+
|
13
|
+
def timeout
|
14
|
+
@timeout || 10000
|
15
|
+
end
|
16
|
+
|
17
|
+
def exec(command)
|
18
|
+
Net::SSH.start(ip, "ubuntu", :keys => [key], :timeout => timeout) do |ssh|
|
19
|
+
ssh.exec(command)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def exec!(command)
|
24
|
+
output = nil
|
25
|
+
Net::SSH.start(ip, "ubuntu", :keys => [key], :timeout => timeout) do |ssh|
|
26
|
+
output = ssh.exec!(command)
|
27
|
+
end
|
28
|
+
output
|
29
|
+
end
|
30
|
+
|
31
|
+
def upload(from, to, opts = "--exclude .git")
|
32
|
+
system("rsync -avz --delete #{from} ubuntu@#{ip}:#{to} #{opts}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/lobot/tasks/ci.rake
CHANGED
@@ -1,130 +1,4 @@
|
|
1
1
|
namespace :ci do
|
2
|
-
desc "Spin up CI server on amazon"
|
3
|
-
task :create_server do
|
4
|
-
require 'fog'
|
5
|
-
require 'yaml'
|
6
|
-
require 'socket'
|
7
|
-
|
8
|
-
aws_conf_location = File.join(Dir.pwd, 'config', 'ci.yml')
|
9
|
-
aws_conf = YAML.load_file(aws_conf_location)
|
10
|
-
aws_credentials = aws_conf['credentials']
|
11
|
-
ec2_server_access = aws_conf['ec2_server_access']
|
12
|
-
server_config = aws_conf['server']
|
13
|
-
|
14
|
-
security_group_name = server_config['security_group']
|
15
|
-
|
16
|
-
aws_connection = Fog::Compute.new(
|
17
|
-
:provider => aws_credentials['provider'],
|
18
|
-
:aws_access_key_id => aws_credentials['aws_access_key_id'],
|
19
|
-
:aws_secret_access_key => aws_credentials['aws_secret_access_key']
|
20
|
-
)
|
21
|
-
|
22
|
-
security_group = aws_connection.security_groups.get(security_group_name)
|
23
|
-
|
24
|
-
if security_group.nil?
|
25
|
-
puts "Could not find security group named '#{security_group_name}'. Creating..."
|
26
|
-
security_group = aws_connection.security_groups.new(:name => security_group_name, :description => 'ci servers group auto-created by lobot')
|
27
|
-
security_group.save
|
28
|
-
security_group.reload
|
29
|
-
p security_group
|
30
|
-
end
|
31
|
-
|
32
|
-
PORTS_TO_OPEN = [22, 443, 80, 8111 ] + (9000...9010).to_a
|
33
|
-
PORTS_TO_OPEN.each do |port|
|
34
|
-
is_in_security_group = !!security_group.ip_permissions.detect{|group| (group['fromPort']..group['toPort']).include?(port) && group['ipRanges'].detect{|range| range["cidrIp"]== "0.0.0.0/0" } }
|
35
|
-
|
36
|
-
unless is_in_security_group
|
37
|
-
puts "Allowing port #{port} into '#{security_group_name}' security group"
|
38
|
-
security_group.authorize_port_range(port..port)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
ec2_key_pair_name = ec2_server_access['key_pair_name'] || "ci"
|
43
|
-
public_key_local_path = File.expand_path("#{ec2_server_access['id_rsa_path']}.pub")
|
44
|
-
|
45
|
-
current_key_pair = aws_connection.key_pairs.get(ec2_key_pair_name)
|
46
|
-
if current_key_pair
|
47
|
-
puts "Using existing '#{ec2_key_pair_name}' keypair"
|
48
|
-
else
|
49
|
-
raise "Unable to upload keypair, missing #{public_key_local_path}!" unless File.exist?(public_key_local_path)
|
50
|
-
puts "Creating '#{ec2_key_pair_name}' keypair, uploading #{public_key_local_path} to aws"
|
51
|
-
|
52
|
-
aws_connection.key_pairs.new(
|
53
|
-
:name => ec2_key_pair_name,
|
54
|
-
:public_key => File.read("#{public_key_local_path}")
|
55
|
-
).save
|
56
|
-
end
|
57
|
-
|
58
|
-
number_of_servers = aws_connection.servers.select{ |server| server.state == 'running' }.length
|
59
|
-
puts "you have #{number_of_servers} server(s) already running in this account" if number_of_servers > 0
|
60
|
-
|
61
|
-
puts "Launching server... (this costs money until you stop it)"
|
62
|
-
server = aws_connection.servers.create(
|
63
|
-
:image_id => 'ami-a29943cb',
|
64
|
-
:flavor_id => server_config['flavor_id'] || 'c1.medium',
|
65
|
-
:key_name => ec2_key_pair_name,
|
66
|
-
:groups => [security_group_name]
|
67
|
-
)
|
68
|
-
|
69
|
-
unless aws_conf['server']['elastic_ip'] =~ /\d.\.\d.\.\d.\.\d./
|
70
|
-
elastic_ip = aws_connection.addresses.create
|
71
|
-
aws_conf['server']['elastic_ip'] = elastic_ip.public_ip
|
72
|
-
puts "Allocated elastic IP address #{aws_conf['server']['elastic_ip']}"
|
73
|
-
end
|
74
|
-
|
75
|
-
server.wait_for { ready? }
|
76
|
-
|
77
|
-
aws_connection.associate_address(server.id, aws_conf['server']['elastic_ip'])
|
78
|
-
|
79
|
-
socket = false
|
80
|
-
Timeout.timeout(180) do
|
81
|
-
print "Server booted, waiting for SSH to come up on #{aws_conf['server']['elastic_ip']}: "
|
82
|
-
until socket
|
83
|
-
begin
|
84
|
-
Timeout.timeout(5) do
|
85
|
-
socket = TCPSocket.open(aws_conf['server']['elastic_ip'], 22)
|
86
|
-
end
|
87
|
-
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Timeout::Error, Errno::EHOSTUNREACH
|
88
|
-
end
|
89
|
-
putc "."
|
90
|
-
sleep 1
|
91
|
-
end
|
92
|
-
end
|
93
|
-
puts ""
|
94
|
-
|
95
|
-
puts "Server is ready:"
|
96
|
-
p server
|
97
|
-
|
98
|
-
puts "Writing server instance_id(#{server.id}) and elastic IP(#{aws_conf['server']['elastic_ip']}) to ci.yml"
|
99
|
-
aws_conf["server"].merge!("instance_id" => server.id)
|
100
|
-
|
101
|
-
f = File.open(aws_conf_location, "w")
|
102
|
-
f.write(aws_conf.to_yaml)
|
103
|
-
f.close
|
104
|
-
end
|
105
|
-
|
106
|
-
desc "terminate the CI Server and release IP"
|
107
|
-
task :destroy_server do
|
108
|
-
puts "Terminating the CI Server and releasing IP..."
|
109
|
-
require 'fog'
|
110
|
-
require 'yaml'
|
111
|
-
require 'socket'
|
112
|
-
|
113
|
-
aws_conf_location = File.join(Dir.pwd, 'config', 'ci.yml')
|
114
|
-
aws_conf = YAML.load_file(aws_conf_location)
|
115
|
-
aws_credentials = aws_conf['credentials']
|
116
|
-
server_config = aws_conf['server']
|
117
|
-
|
118
|
-
aws_connection = Fog::Compute.new(
|
119
|
-
:provider => aws_credentials['provider'],
|
120
|
-
:aws_access_key_id => aws_credentials['aws_access_key_id'],
|
121
|
-
:aws_secret_access_key => aws_credentials['aws_secret_access_key']
|
122
|
-
)
|
123
|
-
|
124
|
-
aws_connection.release_address(aws_conf['server']['elastic_ip'])
|
125
|
-
aws_connection.servers.new(:id => server_config['instance_id']).destroy
|
126
|
-
end
|
127
|
-
|
128
2
|
desc "stop(suspend) the CI Server"
|
129
3
|
task :stop_server do
|
130
4
|
puts "Stopping (suspending) the CI Server..."
|
@@ -170,25 +44,6 @@ namespace :ci do
|
|
170
44
|
aws_connection.associate_address(server_config['instance_id'], server_config['elastic_ip']) if server_config['elastic_ip']
|
171
45
|
end
|
172
46
|
|
173
|
-
desc "open the CI interface in a browser"
|
174
|
-
task :open do
|
175
|
-
aws_conf_location = File.join(Dir.pwd, 'config', 'ci.yml')
|
176
|
-
aws_conf = YAML.load_file(aws_conf_location)
|
177
|
-
server_config = aws_conf['server']
|
178
|
-
exec "open https://#{server_config['elastic_ip']}"
|
179
|
-
end
|
180
|
-
|
181
|
-
desc "ssh to CI"
|
182
|
-
task :ssh do
|
183
|
-
aws_conf_location = File.join(Dir.pwd, 'config', 'ci.yml')
|
184
|
-
aws_conf = YAML.load_file(aws_conf_location)
|
185
|
-
server_config = aws_conf['server']
|
186
|
-
ssh_port = server_config['ssh_port'] || 22
|
187
|
-
cmd = "ssh -i #{aws_conf['ec2_server_access']['id_rsa_path']} ubuntu@#{server_config['elastic_ip']} -p #{ssh_port}"
|
188
|
-
puts cmd
|
189
|
-
exec cmd
|
190
|
-
end
|
191
|
-
|
192
47
|
desc "Get build status"
|
193
48
|
task :status do
|
194
49
|
require 'nokogiri'
|
@@ -235,37 +90,4 @@ namespace :ci do
|
|
235
90
|
puts "EC2 instance information not available. Did you run rake ci:server_start?"
|
236
91
|
end
|
237
92
|
end
|
238
|
-
|
239
|
-
desc "Run a command with a virtual frame buffer"
|
240
|
-
task :headlessly, :command do |task, args|
|
241
|
-
# headless is your friend on linux - http://www.aentos.com/blog/easy-setup-your-cucumber-scenarios-using-headless-gem-run-selenium-your-ci-server
|
242
|
-
begin
|
243
|
-
Headless
|
244
|
-
rescue NameError
|
245
|
-
puts "Headless not available, did you add it to your Gemfile?"
|
246
|
-
exit 1
|
247
|
-
end
|
248
|
-
unless args[:command]
|
249
|
-
puts "Usage: rake ci:headlessly[command] <additional options>"
|
250
|
-
exit 1
|
251
|
-
end
|
252
|
-
exit_code = 1
|
253
|
-
Headless.ly(:display => 42) do |headless|
|
254
|
-
begin
|
255
|
-
command = args[:command].gsub(/^['"](.*)['"]$/, "\\1")
|
256
|
-
system(command)
|
257
|
-
exit_code = $?.exitstatus
|
258
|
-
ensure
|
259
|
-
headless.destroy
|
260
|
-
end
|
261
|
-
end
|
262
|
-
exit exit_code
|
263
|
-
end
|
264
|
-
|
265
|
-
#aliases
|
266
|
-
desc "maybe"
|
267
|
-
task "server:create" => "ci:create_server"
|
268
|
-
task "server:start" => "ci:start_server"
|
269
|
-
task "server:destroy" => "ci:destroy_server"
|
270
|
-
task "server:stop" => "ci:stop_server"
|
271
93
|
end
|
data/lib/lobot/version.rb
CHANGED
data/lobot.gemspec
CHANGED
@@ -15,21 +15,25 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.rubyforge_project = "lobot"
|
16
16
|
|
17
17
|
s.files = `git ls-files`.split("\n") + `cd chef/travis-cookbooks && git ls-files`.split("\n").map { |f| "chef/travis-cookbooks/#{f}" }
|
18
|
-
s.test_files = `git ls-files -- {test,spec,features}
|
19
|
-
s.executables = `git ls-files -- bin
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
|
22
|
-
s.add_dependency
|
23
|
-
s.add_dependency
|
24
|
-
s.add_dependency
|
25
|
-
s.add_dependency
|
26
|
-
s.add_dependency
|
27
|
-
|
28
|
-
s.
|
29
|
-
s.
|
30
|
-
s.add_development_dependency
|
31
|
-
s.add_development_dependency
|
32
|
-
s.add_development_dependency
|
33
|
-
s.add_development_dependency
|
34
|
-
s.add_development_dependency
|
22
|
+
s.add_dependency "fog", "~> 1.6"
|
23
|
+
s.add_dependency "ci_reporter", "~> 1.7"
|
24
|
+
s.add_dependency "thor"
|
25
|
+
s.add_dependency "hashie"
|
26
|
+
s.add_dependency "net-ssh"
|
27
|
+
|
28
|
+
s.add_development_dependency "godot"
|
29
|
+
s.add_development_dependency "rspec"
|
30
|
+
s.add_development_dependency "jasmine"
|
31
|
+
s.add_development_dependency "guard-rspec"
|
32
|
+
s.add_development_dependency "guard-bundler"
|
33
|
+
s.add_development_dependency "test-kitchen"
|
34
|
+
s.add_development_dependency "pry"
|
35
|
+
|
36
|
+
s.add_development_dependency "terminal-notifier-guard"
|
37
|
+
s.add_development_dependency "rb-fsevent"
|
38
|
+
s.add_development_dependency "vagrant", "~> 1.0"
|
35
39
|
end
|
data/script/bootstrap_server.sh
CHANGED
@@ -2,11 +2,14 @@
|
|
2
2
|
|
3
3
|
set -e
|
4
4
|
|
5
|
+
sudo apt-get update > /dev/null
|
6
|
+
|
5
7
|
packages="git build-essential openssl libreadline6 libreadline6-dev libreadline5 curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev autoconf libc6-dev libncurses5-dev automake libtool bison subversion pkg-config"
|
8
|
+
selections=`dpkg --get-selections`
|
6
9
|
|
7
10
|
for package in $packages
|
8
11
|
do
|
9
|
-
if !
|
12
|
+
if ! echo "$selections" | grep "^$package\s" > /dev/null
|
10
13
|
then
|
11
14
|
to_install="$to_install $package"
|
12
15
|
fi
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lobot::Amazon, :slow => true do
|
4
|
+
let(:tempdir) { Dir.mktmpdir }
|
5
|
+
let(:amazon) { Lobot::Amazon.new(ENV["EC2_KEY"], ENV["EC2_SECRET"]) }
|
6
|
+
let(:fog) { amazon.send(:fog) }
|
7
|
+
|
8
|
+
describe "#create_security_group" do
|
9
|
+
context "when there is no existing security group" do
|
10
|
+
it "creates a security group" do
|
11
|
+
amazon.create_security_group("totally_not_a_honeypot")
|
12
|
+
amazon.security_groups.map(&:name).should include "totally_not_a_honeypot"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when the security group already exists" do
|
17
|
+
before { amazon.create_security_group("bart_police") }
|
18
|
+
|
19
|
+
it "does not complain" do
|
20
|
+
expect { amazon.create_security_group("bart_police") }.not_to raise_error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#open_port" do
|
26
|
+
before { amazon.create_security_group("bag_of_weasels") }
|
27
|
+
|
28
|
+
let(:group) { amazon.security_groups.get("bag_of_weasels") }
|
29
|
+
|
30
|
+
def includes_port?(permissions, ports)
|
31
|
+
permissions.any? { |p| (p["fromPort"]..p["toPort"]).include?(80) }
|
32
|
+
end
|
33
|
+
|
34
|
+
it "opens a port for business" do
|
35
|
+
group.ip_permissions.should_not include "80"
|
36
|
+
amazon.open_port("bag_of_weasels", 80)
|
37
|
+
includes_port?(group.reload.ip_permissions, 80).should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
it "takes a bunch of ports" do
|
41
|
+
amazon.open_port("bag_of_weasels", 22, 443)
|
42
|
+
includes_port?(group.reload.ip_permissions, 22).should be_true
|
43
|
+
includes_port?(group.reload.ip_permissions, 443).should be_true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "#add_key_pair" do
|
48
|
+
let(:key_pair_path) { "#{tempdir}/supernuts" }
|
49
|
+
|
50
|
+
before do
|
51
|
+
system "ssh-keygen -q -f #{key_pair_path} -P ''"
|
52
|
+
amazon.delete_key_pair("is_supernuts")
|
53
|
+
end
|
54
|
+
|
55
|
+
it "uploads the key" do
|
56
|
+
amazon.add_key_pair("is_supernuts", "#{key_pair_path}.pub")
|
57
|
+
amazon.key_pairs.map(&:name).should include "is_supernuts"
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when the key is already there" do
|
61
|
+
before { amazon.add_key_pair("is_supernuts", "#{key_pair_path}.pub") }
|
62
|
+
|
63
|
+
it "doesn't reupload" do
|
64
|
+
expect do
|
65
|
+
amazon.add_key_pair("is_supernuts", "#{key_pair_path}.pub")
|
66
|
+
end.not_to raise_error
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "things which launch instances" do
|
72
|
+
let(:key_pair_path) { "#{tempdir}/cookie" }
|
73
|
+
|
74
|
+
before do
|
75
|
+
system "ssh-keygen -q -f #{key_pair_path} -P ''"
|
76
|
+
amazon.add_key_pair("eating_my_cookie", "#{key_pair_path}.pub")
|
77
|
+
amazon.create_security_group("chump_of_change")
|
78
|
+
end
|
79
|
+
|
80
|
+
let(:freshly_launched_server) { amazon.launch_server("eating_my_cookie", "chump_of_change", "t1.micro") }
|
81
|
+
|
82
|
+
describe "#launch_instance" do
|
83
|
+
it "creates an instance" do
|
84
|
+
expect { freshly_launched_server }.to change { amazon.servers.reload.count }.by(1)
|
85
|
+
|
86
|
+
freshly_launched_server.availability_zone.should == "us-east-1a"
|
87
|
+
freshly_launched_server.flavor_id.should == "t1.micro"
|
88
|
+
freshly_launched_server.tags.should == {"lobot"=>Lobot::VERSION, "Name"=>"Lobot"}
|
89
|
+
freshly_launched_server.key_name.should == "eating_my_cookie"
|
90
|
+
freshly_launched_server.groups.should == ["chump_of_change"]
|
91
|
+
freshly_launched_server.public_ip_address.should == amazon.elastic_ip_address.public_ip
|
92
|
+
|
93
|
+
freshly_launched_server.destroy
|
94
|
+
amazon.elastic_ip_address.destroy
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#destroy_ec2" do
|
99
|
+
let!(:server_ip) { freshly_launched_server.public_ip_address }
|
100
|
+
|
101
|
+
it "stops all the instances" do
|
102
|
+
expect do
|
103
|
+
amazon.destroy_ec2
|
104
|
+
end.to change { freshly_launched_server.reload.state }.from("running")
|
105
|
+
fog.addresses.get(server_ip).should_not be
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#elastic_ip_address" do
|
111
|
+
it "allocates an ip address" do
|
112
|
+
expect { amazon.elastic_ip_address }.to change { fog.addresses.reload.count }.by(1)
|
113
|
+
amazon.elastic_ip_address.public_ip.should =~ /\d+\.\d+\.\d+\.\d+/
|
114
|
+
amazon.elastic_ip_address.destroy
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#release_elastic_ip" do
|
119
|
+
let!(:elastic_ip) { amazon.elastic_ip_address }
|
120
|
+
|
121
|
+
it "releases the ip" do
|
122
|
+
expect do
|
123
|
+
amazon.release_elastic_ip(elastic_ip.public_ip)
|
124
|
+
end.to change { fog.addresses.reload.count }.by(-1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lobot::CLI do
|
4
|
+
let(:tempfile) do
|
5
|
+
Tempfile.new('lobot-config').tap do |file|
|
6
|
+
file.write YAML.dump({})
|
7
|
+
file.close
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:lobot_config) { Lobot::Config.new(:aws_key => ENV["EC2_KEY"], :aws_secret => ENV["EC2_SECRET"]) }
|
12
|
+
let(:cli) { subject }
|
13
|
+
let(:sobo) { Lobot::Sobo.new(lobot_config.master, lobot_config.server_ssh_key) }
|
14
|
+
|
15
|
+
before do
|
16
|
+
cli.stub(:lobot_config).and_return(lobot_config)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#ssh" do
|
20
|
+
it "starts an ssh session to the lobot host" do
|
21
|
+
cli.should_receive(:exec).with("ssh -i #{cli.lobot_config.server_ssh_key} ubuntu@#{cli.lobot_config.master} -p #{cli.lobot_config.ssh_port}")
|
22
|
+
cli.ssh
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#open" do
|
27
|
+
let(:lobot_config) do
|
28
|
+
Lobot::Config.new(:node_attributes => {
|
29
|
+
:nginx => {
|
30
|
+
:basic_auth_user => "ci",
|
31
|
+
:basic_auth_password => "secret"
|
32
|
+
}
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
it "opens a web browser with the lobot page" do
|
37
|
+
cli.should_receive(:exec).with("open https://#{cli.lobot_config.node_attributes.nginx.basic_auth_user}:#{cli.lobot_config.node_attributes.nginx.basic_auth_password}@#{cli.lobot_config.master}/")
|
38
|
+
cli.open
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#add_build" do
|
43
|
+
let(:name) { "bob" }
|
44
|
+
let(:repository) { "http://github.com/mkocher/soloist.git" }
|
45
|
+
let(:branch) { "master" }
|
46
|
+
let(:command) { "script/ci_build.sh" }
|
47
|
+
|
48
|
+
it "adds a build to the node attributes" do
|
49
|
+
cli.add_build(name, repository, branch, command)
|
50
|
+
lobot_config.node_attributes.jenkins.builds.should =~ [{
|
51
|
+
"name" => "bob",
|
52
|
+
"repository" => "http://github.com/mkocher/soloist.git",
|
53
|
+
"command" => "script/ci_build.sh",
|
54
|
+
"branch" => "master"
|
55
|
+
}]
|
56
|
+
end
|
57
|
+
|
58
|
+
it "does not add a build twice with identical parameters" do
|
59
|
+
cli.add_build(name, repository, branch, command)
|
60
|
+
cli.add_build(name, repository, branch, command)
|
61
|
+
lobot_config.node_attributes.jenkins.builds.should =~ [{
|
62
|
+
"name" => "bob",
|
63
|
+
"repository" => "http://github.com/mkocher/soloist.git",
|
64
|
+
"command" => "script/ci_build.sh",
|
65
|
+
"branch" => "master"
|
66
|
+
}]
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with persisted configuration data" do
|
70
|
+
let(:lobot_config) { Lobot::Config.from_file(tempfile.path) }
|
71
|
+
|
72
|
+
def builds
|
73
|
+
cli.lobot_config.reload.node_attributes.jenkins.builds
|
74
|
+
end
|
75
|
+
|
76
|
+
it "persists a build" do
|
77
|
+
cli.add_build(name, repository, branch, command)
|
78
|
+
builds.should_not be_nil
|
79
|
+
builds.should_not be_empty
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
shared_examples_for "a start command that updates known_hosts" do
|
85
|
+
let(:key) { "AAAAB3NzaC1yc2EAAAADAQABAAABAQDjhJ/xZCgVhq9Xk+3DKJZ6tcgyIHcIXKSzu6Z/EK1uykyHeP/i7CwwKgiAv7lAV7B4UiUMHUm2nEiguog9VtYc6mc0g1N829lnuMhPRyOTb0SSYTNEN7Uuwy10cuq3Rd/9QAdxNV/voQW3Rl60BFzZvzp8UxJzCXFT1NmB+0W45X7Ypstv0oVV/EdyJJUuoPijQ097A4kHt6KUThKzxhagh1UrVTCE6eccscxuuRPX3yCEf8cUaVrKtuSE3vZnBcmSOY92zA4NV/YdJYNPIrKyCvWb/R+nC4R0pQNqv1gSEqPT51wYxKnvmIPFGntKaJSN2qmMlvs/AlFnFOeUsUFN" }
|
86
|
+
|
87
|
+
def known_hosts_contents
|
88
|
+
File.read(File.expand_path('~/.ssh/known_hosts'))
|
89
|
+
end
|
90
|
+
|
91
|
+
it "clears out the entry in knownhosts as this is a new box but the ip may be recycled" do
|
92
|
+
system "echo '#{ip_address} ssh-rsa #{key}' >> ~/.ssh/known_hosts"
|
93
|
+
action
|
94
|
+
known_hosts_contents.should_not include(key)
|
95
|
+
known_hosts_contents.should include(ip_address)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "doesn't mess with other entries" do
|
99
|
+
expect { action }.not_to change { known_hosts_contents }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "#create", :slow => true do
|
104
|
+
it "launches an instance and associates elastic ip" do
|
105
|
+
cli.lobot_config.instance_size = 't1.micro'
|
106
|
+
expect { cli.create }.to change { lobot_config.master }.from(nil)
|
107
|
+
expect { cli.destroy_ec2 }.to change { lobot_config.master }.to(nil)
|
108
|
+
end
|
109
|
+
|
110
|
+
context "with a fake amazon" do
|
111
|
+
let(:ip_address) { "192.168.33.10" }
|
112
|
+
let(:server) { double("server", :public_ip_address => ip_address).as_null_object }
|
113
|
+
let(:amazon) { double("AMZN", :launch_server => server).as_null_object }
|
114
|
+
|
115
|
+
before { cli.stub(:amazon).and_return(amazon) }
|
116
|
+
|
117
|
+
def action
|
118
|
+
cli.create
|
119
|
+
end
|
120
|
+
|
121
|
+
it_behaves_like "a start command that updates known_hosts"
|
122
|
+
|
123
|
+
context "with a custom instance size", :slow => false do
|
124
|
+
before { cli.lobot_config.instance_size = 'really_big_instance' }
|
125
|
+
|
126
|
+
it "launches the instance with the configured instance size" do
|
127
|
+
amazon.should_receive(:launch_server).with(anything, anything, 'really_big_instance')
|
128
|
+
cli.create
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "#create_vagrant" do
|
135
|
+
before do
|
136
|
+
File.open(tempfile.path, "w") do |f|
|
137
|
+
f.write(YAML.dump(
|
138
|
+
"node_attributes" => {
|
139
|
+
"nginx" => {
|
140
|
+
"basic_auth_user" => "ci",
|
141
|
+
"basic_auth_password" => "secret"
|
142
|
+
}
|
143
|
+
}
|
144
|
+
))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def lobot_config
|
149
|
+
Lobot::Config.from_file(tempfile.path)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "starts a virtual machine" do
|
153
|
+
cli.create_vagrant
|
154
|
+
Godot.wait('192.168.33.10', 22).should be
|
155
|
+
end
|
156
|
+
|
157
|
+
it "updates the config master ip address" do
|
158
|
+
expect { cli.create_vagrant }.to change { lobot_config.master }.to('192.168.33.10')
|
159
|
+
end
|
160
|
+
|
161
|
+
context "known_hosts" do
|
162
|
+
let(:ip_address) { "192.168.33.10" }
|
163
|
+
|
164
|
+
def action
|
165
|
+
cli.create_vagrant
|
166
|
+
end
|
167
|
+
|
168
|
+
it_behaves_like "a start command that updates known_hosts"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe "#bootstrap", :slow => true do
|
173
|
+
before { cli.create_vagrant }
|
174
|
+
|
175
|
+
it "installs all necessary packages, installs rvm and sets up the user" do
|
176
|
+
cli.bootstrap
|
177
|
+
sobo.exec!("dpkg --get-selections").should include("libncurses5-dev")
|
178
|
+
sobo.exec!("ls /usr/local/rvm/").should_not be_empty
|
179
|
+
sobo.exec!("groups ubuntu").should include("rvm")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe "#chef", :slow => true do
|
184
|
+
let(:name) { "Bob" }
|
185
|
+
let(:repository) { "http://github.com/mkocher/soloist.git" }
|
186
|
+
let(:branch) { "master" }
|
187
|
+
let(:command) { "exit 0" }
|
188
|
+
|
189
|
+
let(:godot) { Godot.new(cli.master_server.ip, 8080) }
|
190
|
+
let(:jenkins) { Lobot::Jenkins.new(lobot_config) }
|
191
|
+
|
192
|
+
before do
|
193
|
+
cli.create_vagrant
|
194
|
+
cli.bootstrap
|
195
|
+
cli.add_build(name, repository, branch, command)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "runs chef" do
|
199
|
+
cli.lobot_config.recipes = ["pivotal_ci::jenkins", "pivotal_ci::id_rsa", "pivotal_ci::git_config", "sysctl", "pivotal_ci::jenkins_config"]
|
200
|
+
cli.chef
|
201
|
+
|
202
|
+
sobo.exec!("ls /var/lib/").should include "jenkins"
|
203
|
+
sobo.exec!("grep 'kernel.shmmax=' /etc/sysctl.conf").should_not be_empty
|
204
|
+
sobo.exec!("sudo cat /var/lib/jenkins/.ssh/id_rsa").should == File.read(lobot_config.github_ssh_key)
|
205
|
+
|
206
|
+
godot.wait!
|
207
|
+
godot.match!(/Bob/, 'api/json')
|
208
|
+
|
209
|
+
jenkins.jobs.first.name.should == "Bob"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|