lobot 1.0.pre → 2.0.0pre

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