lobot 1.0.pre → 2.0.0pre
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.
- 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
|