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.
Files changed (46) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +0 -1
  3. data/Guardfile +10 -0
  4. data/README.md +52 -72
  5. data/Rakefile +2 -6
  6. data/Vagrantfile +15 -0
  7. data/bin/lobot +7 -0
  8. data/chef/cookbooks/pivotal_ci/.gitignore +4 -0
  9. data/chef/cookbooks/pivotal_ci/Gemfile +3 -0
  10. data/chef/cookbooks/pivotal_ci/README.md +3 -0
  11. data/chef/cookbooks/pivotal_ci/attributes/git.rb +1 -1
  12. data/chef/cookbooks/pivotal_ci/attributes/jenkins.rb +2 -3
  13. data/chef/cookbooks/pivotal_ci/attributes/nginx.rb +1 -2
  14. data/chef/cookbooks/pivotal_ci/attributes/ssl.rb +1 -1
  15. data/chef/cookbooks/pivotal_ci/files/default/tests/minitest/default_test.rb +37 -0
  16. data/chef/cookbooks/pivotal_ci/metadata.rb +10 -0
  17. data/chef/cookbooks/pivotal_ci/recipes/id_rsa.rb +5 -2
  18. data/chef/cookbooks/pivotal_ci/recipes/jenkins.rb +1 -1
  19. data/chef/cookbooks/pivotal_ci/recipes/jenkins_config.rb +10 -17
  20. data/chef/cookbooks/pivotal_ci/recipes/nginx.rb +2 -0
  21. data/chef/cookbooks/pivotal_ci/templates/default/jenkins-job-config.xml.erb +11 -3
  22. data/chef/cookbooks/pivotal_ci/templates/default/nginx-htaccess.erb +3 -3
  23. data/chef/cookbooks/pivotal_ci/templates/default/org.jenkinsci.plugins.xvfb.XvfbBuildWrapper.xml.erb +11 -0
  24. data/chef/cookbooks/pivotal_ci/test/kitchen/Kitchenfile +4 -0
  25. data/chef/cookbooks/pivotal_ci/test/kitchen/cookbooks/pivotal_ci_test/attributes/default.rb +3 -0
  26. data/chef/cookbooks/pivotal_ci/test/kitchen/cookbooks/pivotal_ci_test/metadata.rb +10 -0
  27. data/chef/cookbooks/pivotal_ci/test/kitchen/cookbooks/pivotal_ci_test/recipes/default.rb +41 -0
  28. data/lib/generators/lobot/templates/soloistrc +2 -1
  29. data/lib/lobot.rb +1 -12
  30. data/lib/lobot/amazon.rb +91 -0
  31. data/lib/lobot/cli.rb +148 -0
  32. data/lib/lobot/config.rb +80 -0
  33. data/lib/lobot/jenkins.rb +17 -0
  34. data/lib/lobot/sobo.rb +35 -0
  35. data/lib/lobot/tasks/ci.rake +0 -178
  36. data/lib/lobot/version.rb +1 -1
  37. data/lobot.gemspec +19 -15
  38. data/script/bootstrap_server.sh +4 -1
  39. data/spec/lib/lobot/amazon_spec.rb +127 -0
  40. data/spec/lib/lobot/cli_spec.rb +212 -0
  41. data/spec/lib/lobot/config_spec.rb +95 -0
  42. data/spec/lib/lobot/jenkins_spec.rb +14 -0
  43. data/spec/spec_helper.rb +5 -10
  44. metadata +87 -31
  45. data/chef/cookbooks/pivotal_ci/libraries/ci_config.rb +0 -2
  46. data/spec/install_spec.rb +0 -80
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Lobot
2
- VERSION = "1.0.pre"
2
+ VERSION = "2.0.0pre"
3
3
  end
@@ -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}/*`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
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('fog', '>=0.9.0')
23
- s.add_dependency('capistrano')
24
- s.add_dependency('capistrano-ext')
25
- s.add_dependency('rvm')
26
- s.add_dependency('rvm-capistrano')
27
- s.add_dependency('nokogiri', '>=1.4.4')
28
- s.add_dependency('ci_reporter', '>=1.7.0')
29
- s.add_dependency('headless')
30
- s.add_development_dependency('cucumber')
31
- s.add_development_dependency('rspec')
32
- s.add_development_dependency('generator_spec')
33
- s.add_development_dependency('rails')
34
- s.add_development_dependency('jasmine')
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
@@ -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 ! dpkg --get-selections | grep "^$package\s" > /dev/null
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