engineyard-hudson 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ .DS_Store
5
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in engineyard-hudson.gemspec
4
+ gemspec
@@ -0,0 +1,47 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ engineyard-hudson (0.0.1)
5
+ thor (~> 0.14.3)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ awesome_print (0.2.1)
11
+ builder (2.1.2)
12
+ cucumber (0.9.3)
13
+ builder (~> 2.1.2)
14
+ diff-lcs (~> 1.1.2)
15
+ gherkin (~> 2.2.9)
16
+ json (~> 1.4.6)
17
+ term-ansicolor (~> 1.0.5)
18
+ diff-lcs (1.1.2)
19
+ gherkin (2.2.9)
20
+ json (~> 1.4.6)
21
+ term-ansicolor (~> 1.0.5)
22
+ json (1.4.6)
23
+ rake (0.8.7)
24
+ rspec (2.0.1)
25
+ rspec-core (~> 2.0.1)
26
+ rspec-expectations (~> 2.0.1)
27
+ rspec-mocks (~> 2.0.1)
28
+ rspec-core (2.0.1)
29
+ rspec-expectations (2.0.1)
30
+ diff-lcs (>= 1.1.2)
31
+ rspec-mocks (2.0.1)
32
+ rspec-core (~> 2.0.1)
33
+ rspec-expectations (~> 2.0.1)
34
+ term-ansicolor (1.0.5)
35
+ thor (0.14.3)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ awesome_print
42
+ cucumber (~> 0.9.3)
43
+ engineyard-hudson!
44
+ json (~> 1.4.0)
45
+ rake (~> 0.8.7)
46
+ rspec (~> 2.0.1)
47
+ thor (~> 0.14.3)
@@ -0,0 +1,6 @@
1
+ # History
2
+
3
+ ## 0.1.0 - 2010-10-30
4
+
5
+ * Initial 'ey-hudson install .' command
6
+ * 'ey-hudson server' shows 'Coming soon'
@@ -0,0 +1,74 @@
1
+ # Easier to do CI than not to.
2
+
3
+ Run your continuous integration (CI) tests against your Engine Yard AppCloud environments - the exact same configuration you are using in production!
4
+
5
+ You're developing on OS X or Windows, deploying to Engine Yard AppCloud (Gentoo/Linux), and you're running your CI on your local machine or a spare Ubuntu machine in the corner of the office, or ... you're not running CI at all?
6
+
7
+ It's a nightmare. It was for me. [Hudson CI](http://hudson-ci.org/), `engineyard-hudson` and the [hudson](http://github.com/cowboyd/hudson.rb) CLI projects now make CI easier to do than not to. A few commands and your Rails applications' tests are automatically running, no additional setup, and its the same environment you are deploying your Rails applications (Engine Yard AppCloud). Sweet.
8
+
9
+ ## Installation
10
+
11
+ gem install engineyard-hudson
12
+
13
+ ## Assumptions
14
+
15
+ It is assumed you are familiar with the `engineyard` CLI gem.
16
+
17
+ You **do not** need to be familiar with custom chef recipes. Just follow the simple commands. Easy peasy.
18
+
19
+ ## Warning
20
+
21
+ In the very first release of `engineyard-hudson`, there is no support for authentication/authorization of Hudson CI.
22
+
23
+ ## Hosting Hudson CI on Engine Yard AppCloud
24
+
25
+ Instructions *coming soon*.
26
+
27
+ Hosting Hudson CI on Engine Yard AppCloud is optional. It can be hosted anywhere. You need the following information about your Hudson CI:
28
+
29
+ * Hudson CI host (& port)
30
+ * Hudson CI's user's public key (probably at /home/hudson/.ssh/id_rsa.pub)
31
+ * Hudson CI's user's private key path (probably /home/hudson/.ssh/id_rsa)
32
+
33
+ ## Running your tests in Hudson against Engine Yard AppCloud
34
+
35
+ After a couple manual steps, you will have your applications tests running.
36
+
37
+ $ cd /my/project
38
+ $ ey-hudson install .
39
+
40
+ Finally:
41
+ * edit cookbooks/hudson_slave/attributes/default.rb as necessary.
42
+ * run: ey recipes upload # use --environment(-e) & --account(-c)
43
+ * run: ey recipes apply # to select environment
44
+ * Boot your environment if not already booted.
45
+
46
+ Done! Either visit your Hudson CI site or use `hudson list` to see the status of your projects being tested.
47
+
48
+ ## Automatically triggering job builds
49
+
50
+ In Hudson CI, a "job" is one of your projects. Each time it runs your tests, it is called a "build".
51
+
52
+ It is often desirable to have your SCM trigger Hudson CI to run your job build whenever you push new code.
53
+
54
+ ### GitHub Service Hooks
55
+
56
+ * Go to the "Admin" section of your GitHub project
57
+ * Click "Service Hooks"
58
+ * Click "Post-Receive URLs"
59
+ * Enter the URL `http://HUDSON-CI-URL/job/APP-NAME/build`
60
+ * Click "Update Settings"
61
+
62
+ You can also use the "Test Hook" link to test this is wired up correctly.
63
+
64
+ ### CLI
65
+
66
+ Using the `hudson` CLI:
67
+
68
+ hudson build APP-NAME
69
+
70
+ ### Curl
71
+
72
+ You are triggering the build via a GET call to an URL endpoint. So you can also use `curl`:
73
+
74
+ curl http://HUDSON-CI-URL/job/APP-NAME/build
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ namespace :cucumber do
5
+ require 'cucumber/rake/task'
6
+ Cucumber::Rake::Task.new(:wip, 'Run features that are being worked on') do |t|
7
+ t.cucumber_opts = "--tags @wip"
8
+ end
9
+ Cucumber::Rake::Task.new(:ok, 'Run features that should be working') do |t|
10
+ t.cucumber_opts = "--tags ~@wip"
11
+ end
12
+ task :all => [:ok, :wip]
13
+ end
14
+
15
+ desc 'Alias for cucumber:ok'
16
+ task :cucumber => 'cucumber:ok'
17
+
18
+ desc "Start test server; Run cucumber:ok; Kill Test Server;"
19
+ task :default => ["cucumber"]
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+ require 'engineyard-hudson'
5
+ require 'engineyard-hudson/cli'
6
+
7
+ Engineyard::Hudson::CLI.start
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "engineyard-hudson"
5
+ s.version = '0.1.0'
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ["Dr Nic Williams"]
8
+ s.email = ["drnicwilliams@gmail.com"]
9
+ s.homepage = "http://github.com/engineyard/engineyard-hudson"
10
+ s.summary = %q{Easier to do CI than not to. Use Hudson CI on Engine Yard AppCloud.}
11
+ s.description = %q{Run your continuous integration (CI) tests against your Engine Yard AppCloud environments - the exact same configuration you are using in production!}
12
+
13
+ s.rubyforge_project = "engineyard-hudson"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency("thor", ["~> 0.14.3"])
21
+ s.add_dependency("engineyard", ["~> 1.3.3"])
22
+ # s.add_dependency("hudson", ["~> 0.3.0.beta"])
23
+
24
+ s.add_development_dependency("rake", ["~> 0.8.7"])
25
+ s.add_development_dependency("cucumber", ["~> 0.9.3"])
26
+ s.add_development_dependency("rspec", ["~> 2.0.1"])
27
+ s.add_development_dependency("json", ["~>1.4.0"])
28
+ s.add_development_dependency("awesome_print")
29
+ end
@@ -0,0 +1,57 @@
1
+ Feature: Managing a rails project as a Hudson CI job on AppCloud
2
+ I want to build/test my project in the same environment I run in Engine Yard AppCloud
3
+
4
+ Scenario: Setup first project as a slave for Hudson
5
+ Given I have an environment "hudson" on account "drnic" on AppCloud
6
+ And I have an environment "my_app_ci" on account "drnic" on AppCloud
7
+ And I am in the "rails" project folder
8
+ When I run local executable "ey-hudson" with arguments "install ."
9
+ Then file "cookbooks/hudson_slave/attributes/default.rb" is created
10
+ And file "cookbooks/hudson_slave/recipes/default.rb" is created
11
+ And file "cookbooks/main/recipes/default.rb" is created
12
+ And file "cookbooks/main/libraries/ruby_block.rb" is created
13
+ And I should see exactly
14
+ """
15
+ create cookbooks
16
+ create cookbooks/main/attributes/recipe.rb
17
+ create cookbooks/main/definitions/ey_cloud_report.rb
18
+ create cookbooks/main/libraries/ruby_block.rb
19
+ create cookbooks/main/libraries/run_for_app.rb
20
+ create cookbooks/hudson_slave/attributes/default.rb
21
+ create cookbooks/hudson_slave/recipes/default.rb
22
+ create cookbooks/main/recipes/default.rb
23
+
24
+ Finally:
25
+ * edit cookbooks/hudson_slave/attributes/default.rb as necessary.
26
+ * run: ey recipes upload # use --environment(-e) & --account(-c)
27
+ * run: ey recipes apply # to select environment
28
+ * Boot your environment if not already booted.
29
+ When the recipe completes, your project will commence its first build on Hudson CI.
30
+ """
31
+
32
+ Scenario: Setup project with existing cookbooks as a slave for Hudson
33
+ Given I have an environment "hudson" on account "drnic" on AppCloud
34
+ And I have an environment "my_app_ci" on account "drnic" on AppCloud
35
+ And I am in the "rails" project folder
36
+ And I already have cookbooks installed
37
+ When I run local executable "ey-hudson" with arguments "install ."
38
+ Then file "cookbooks/hudson_slave/attributes/default.rb" is created
39
+ And file "cookbooks/hudson_slave/recipes/default.rb" is created
40
+ And file "cookbooks/main/recipes/default.rb" is created
41
+ And file "cookbooks/redis/recipes/default.rb" is created
42
+ And I should see exactly
43
+ """
44
+ create cookbooks/hudson_slave/attributes/default.rb
45
+ create cookbooks/hudson_slave/recipes/default.rb
46
+ append cookbooks/main/recipes/default.rb
47
+
48
+ Finally:
49
+ * edit cookbooks/hudson_slave/attributes/default.rb as necessary.
50
+ * run: ey recipes upload # use --environment(-e) & --account(-c)
51
+ * run: ey recipes apply # to select environment
52
+ * Boot your environment if not already booted.
53
+ When the recipe completes, your project will commence its first build on Hudson CI.
54
+ """
55
+
56
+
57
+
@@ -0,0 +1,12 @@
1
+ @wip
2
+ Feature: Managing ey hudson server
3
+ I want to setup and manage a Hudson CI server hosted on Engine Yard AppCloud
4
+
5
+ Scenario: Setup new Hudson CI server on AppCloud
6
+ Given I have an environment "hudson" on account "drnic" on AppCloud
7
+ And I am in the "rails" project folder
8
+ When I run local executable "ey-hudson" with arguments "server -e hudson -c drnic"
9
+ Then I should see "Hudson CI server recipe installed."
10
+
11
+
12
+
@@ -0,0 +1,191 @@
1
+ Given /^this project is active project folder/ do
2
+ @active_project_folder = File.expand_path(File.dirname(__FILE__) + "/../..")
3
+ end
4
+
5
+ Given /^env variable \$([\w_]+) set to( project path|) "(.*)"/ do |env_var, path, value|
6
+ in_project_folder {
7
+ value = File.expand_path(value)
8
+ } unless path.empty?
9
+ ENV[env_var] = value
10
+ end
11
+
12
+ Given /"(.*)" folder is deleted/ do |folder|
13
+ in_project_folder { FileUtils.rm_rf folder }
14
+ end
15
+
16
+ When /^I invoke "(.*)" generator with arguments "(.*)"$/ do |generator, arguments|
17
+ @stdout = StringIO.new
18
+ in_project_folder do
19
+ if Object.const_defined?("APP_ROOT")
20
+ APP_ROOT.replace(FileUtils.pwd)
21
+ else
22
+ APP_ROOT = FileUtils.pwd
23
+ end
24
+ run_generator(generator, arguments.split(' '), SOURCES, :stdout => @stdout)
25
+ end
26
+ File.open(File.join(@tmp_root, "generator.out"), "w") do |f|
27
+ @stdout.rewind
28
+ f << @stdout.read
29
+ end
30
+ end
31
+
32
+ When /^I run executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
33
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
34
+ in_project_folder do
35
+ system "#{executable.inspect} #{arguments} > #{@stdout.inspect} 2> #{@stdout.inspect}"
36
+ end
37
+ end
38
+
39
+ When /^I run project executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
40
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
41
+ in_project_folder do
42
+ system "ruby -rubygems #{executable.inspect} #{arguments} > #{@stdout.inspect} 2> #{@stdout.inspect}"
43
+ end
44
+ end
45
+
46
+ When /^I run local executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
47
+ @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
48
+ executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable))
49
+ in_project_folder do
50
+ system "ruby -rubygems #{executable.inspect} #{arguments} > #{@stdout.inspect} 2> #{@stdout.inspect}"
51
+ end
52
+ end
53
+
54
+ When /^I invoke task "rake (.*)"/ do |task|
55
+ @stdout = File.expand_path(File.join(@tmp_root, "tests.out"))
56
+ in_project_folder do
57
+ system "bundle exec rake #{task} --trace > #{@stdout.inspect} 2> #{@stdout.inspect}"
58
+ end
59
+ end
60
+
61
+ Then /^folder "(.*)" (is|is not) created/ do |folder, is|
62
+ in_project_folder do
63
+ File.exists?(folder).should(is == 'is' ? be_true : be_false)
64
+ end
65
+ end
66
+
67
+ Then /^file "(.*)" (is|is not) created/ do |file, is|
68
+ in_project_folder do
69
+ File.exists?(file).should(is == 'is' ? be_true : be_false)
70
+ end
71
+ end
72
+
73
+ Then /^file with name matching "(.*)" is created/ do |pattern|
74
+ in_project_folder do
75
+ Dir[pattern].should_not be_empty
76
+ end
77
+ end
78
+
79
+ Then /^file "(.*)" contents (does|does not) match \/(.*)\// do |file, does, regex|
80
+ in_project_folder do
81
+ actual_output = File.read(file)
82
+ (does == 'does') ?
83
+ actual_output.should(match(/#{regex}/)) :
84
+ actual_output.should_not(match(/#{regex}/))
85
+ end
86
+ end
87
+
88
+ Then /gem file "(.*)" and generated file "(.*)" should be the same/ do |gem_file, project_file|
89
+ File.exists?(gem_file).should be_true
90
+ File.exists?(project_file).should be_true
91
+ gem_file_contents = File.read(File.dirname(__FILE__) + "/../../#{gem_file}")
92
+ project_file_contents = File.read(File.join(@active_project_folder, project_file))
93
+ project_file_contents.should == gem_file_contents
94
+ end
95
+
96
+ Then /^(does|does not) invoke generator "(.*)"$/ do |does_invoke, generator|
97
+ actual_output = get_command_output
98
+ does_invoke == "does" ?
99
+ actual_output.should(match(/dependency\s+#{generator}/)) :
100
+ actual_output.should_not(match(/dependency\s+#{generator}/))
101
+ end
102
+
103
+ Then /help options "(.*)" and "(.*)" are displayed/ do |opt1, opt2|
104
+ actual_output = get_command_output
105
+ actual_output.should match(/#{opt1}/)
106
+ actual_output.should match(/#{opt2}/)
107
+ end
108
+
109
+ Then /^I should see "([^\"]*)"$/ do |text|
110
+ actual_output = get_command_output
111
+ actual_output.should contain(text)
112
+ end
113
+
114
+ Then /^I should not see "([^\"]*)"$/ do |text|
115
+ actual_output =
116
+ actual_output.should_not contain(text)
117
+ end
118
+
119
+ Then /^I should see$/ do |text|
120
+ actual_output = get_command_output
121
+ actual_output.should contain(text)
122
+ end
123
+
124
+ Then /^I should not see$/ do |text|
125
+ actual_output = get_command_output
126
+ actual_output.should_not contain(text)
127
+ end
128
+
129
+ Then /^I should see exactly$/ do |text|
130
+ actual_output = get_command_output
131
+ actual_output.should == text
132
+ end
133
+
134
+ Then /^I should see all (\d+) tests pass/ do |expected_test_count|
135
+ expected = %r{^#{expected_test_count} tests, \d+ assertions, 0 failures, 0 errors}
136
+ actual_output = get_command_output
137
+ actual_output.should match(expected)
138
+ end
139
+
140
+ Then /^I should see all (\d+) examples pass/ do |expected_test_count|
141
+ expected = %r{^#{expected_test_count} examples?, 0 failures}
142
+ actual_output = get_command_output
143
+ actual_output.should match(expected)
144
+ end
145
+
146
+ Then /^yaml file "(.*)" contains (\{.*\})/ do |file, yaml|
147
+ in_project_folder do
148
+ yaml = eval yaml
149
+ YAML.load(File.read(file)).should == yaml
150
+ end
151
+ end
152
+
153
+ Then /^Rakefile can display tasks successfully/ do
154
+ @stdout = File.expand_path(File.join(@tmp_root, "rakefile.out"))
155
+ in_project_folder do
156
+ system "rake -T > #{@stdout.inspect} 2> #{@stdout.inspect}"
157
+ end
158
+ actual_output = get_command_output
159
+ actual_output.should match(/^rake\s+\w+\s+#\s.*/)
160
+ end
161
+
162
+ Then /^task "rake (.*)" is executed successfully/ do |task|
163
+ @stdout.should_not be_nil
164
+ actual_output = get_command_output
165
+ actual_output.should_not match(/^Don't know how to build task '#{task}'/)
166
+ actual_output.should_not match(/Error/i)
167
+ end
168
+
169
+ Then /^gem spec key "(.*)" contains \/(.*)\// do |key, regex|
170
+ in_project_folder do
171
+ gem_file = Dir["pkg/*.gem"].first
172
+ gem_spec = Gem::Specification.from_yaml(`gem spec #{gem_file}`)
173
+ spec_value = gem_spec.send(key.to_sym)
174
+ spec_value.to_s.should match(/#{regex}/)
175
+ end
176
+ end
177
+
178
+ Then /^the file "([^\"]*)" is a valid gemspec$/ do |filename|
179
+ spec = eval(File.read(filename))
180
+ spec.validate
181
+ end
182
+
183
+ When /^I create a new node with the following options on "http:\/\/(.+?):(\d+)":$/ do |host, port, table|
184
+ options = table.raw.inject({}) do |options, (key, value)|
185
+ options[(key.to_sym rescue key) || key] = value
186
+ options
187
+ end
188
+
189
+ Hudson::Api.setup_base_url(:host => host, :port => port.to_i)
190
+ Hudson::Api.add_node(options)
191
+ end
@@ -0,0 +1,14 @@
1
+ Given /^I am in the "([^\"]*)" project folder$/ do |project|
2
+ project_folder = File.expand_path(File.join(@fixtures_path, "projects", project))
3
+ in_tmp_folder do
4
+ FileUtils.cp_r(project_folder, project)
5
+ setup_active_project_folder(project)
6
+ end
7
+ end
8
+
9
+ Given /^I already have cookbooks installed$/ do
10
+ cookbooks_folder = File.expand_path(File.join(@fixtures_path, "cookbooks"))
11
+ in_project_folder do
12
+ FileUtils.cp_r(cookbooks_folder, ".")
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ Given /^I have an environment "([^"]*)" on account "([^"]*)" on AppCloud$/ do |env, account|
2
+
3
+ end
@@ -0,0 +1,37 @@
1
+ module CommonHelpers
2
+ def get_command_output
3
+ strip_color_codes(File.read(@stdout)).chomp
4
+ end
5
+
6
+ def strip_color_codes(text)
7
+ text.gsub(/\e\[\d+m/, '')
8
+ end
9
+
10
+ def in_tmp_folder(&block)
11
+ FileUtils.chdir(@tmp_root, &block)
12
+ end
13
+
14
+ def in_project_folder(&block)
15
+ project_folder = @active_project_folder || @tmp_root
16
+ FileUtils.chdir(project_folder, &block)
17
+ end
18
+
19
+ def in_home_folder(&block)
20
+ FileUtils.chdir(@home_path, &block)
21
+ end
22
+
23
+ def force_local_lib_override(project_name = @project_name)
24
+ rakefile = File.read(File.join(project_name, 'Rakefile'))
25
+ File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
26
+ f << "$:.unshift('#{@lib_path}')\n"
27
+ f << rakefile
28
+ end
29
+ end
30
+
31
+ def setup_active_project_folder project_name
32
+ @active_project_folder = File.join(@tmp_root, project_name)
33
+ @project_name = project_name
34
+ end
35
+ end
36
+
37
+ World(CommonHelpers)
@@ -0,0 +1,13 @@
1
+ $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../../lib'))
2
+ require 'engineyard-hudson'
3
+ require 'bundler/setup'
4
+
5
+ Before do
6
+ @tmp_root = File.dirname(__FILE__) + "/../../tmp"
7
+ @home_path = File.expand_path(File.join(@tmp_root, "home"))
8
+ @lib_path = File.expand_path(File.dirname(__FILE__) + "/../../lib")
9
+ @fixtures_path = File.expand_path(File.dirname(__FILE__) + "/../../fixtures")
10
+ FileUtils.rm_rf @tmp_root
11
+ FileUtils.mkdir_p @home_path
12
+ ENV['HOME'] = @home_path
13
+ end
@@ -0,0 +1,10 @@
1
+
2
+ module Matchers
3
+ RSpec::Matchers.define :contain do |expected_text|
4
+ match do |text|
5
+ text.index expected_text
6
+ end
7
+ end
8
+ end
9
+
10
+ World(Matchers)
@@ -0,0 +1 @@
1
+ require_recipe 'redis'
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.rb"
2
+
3
+ gem "rake"
@@ -0,0 +1,10 @@
1
+ GEM
2
+ remote: http://rubygems.rb/
3
+ specs:
4
+ rake (0.8.7)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ rake
@@ -0,0 +1,4 @@
1
+ desc "Default task runs tests"
2
+ task :default do
3
+ puts "Tests ran successfully!"
4
+ end
@@ -0,0 +1,4 @@
1
+ module Engineyard
2
+ module Hudson
3
+ end
4
+ end
@@ -0,0 +1,44 @@
1
+ require 'thor'
2
+
3
+ module Engineyard
4
+ module Hudson
5
+ class CLI < Thor
6
+
7
+ def self.common_options
8
+ method_option :environment, :type => :string, :aliases => %w(-e),
9
+ :desc => "Environment in which to deploy this application", :required => true
10
+ method_option :account, :type => :string, :aliases => %w(-c),
11
+ :desc => "Name of the account you want to deploy in"
12
+ end
13
+
14
+ desc "install PROJECT_PATH", "Install Hudson node/slave recipes into your project."
15
+ def install(project_path)
16
+ require 'engineyard-hudson/cli/install'
17
+ Engineyard::Hudson::Install.start(ARGV[1..-1])
18
+ end
19
+
20
+ desc "server", "Setup a Hudson CI server on AppCloud."
21
+ def server
22
+ shell.say "Coming soon!", :green
23
+ end
24
+
25
+ desc "version", "show version information"
26
+ def version
27
+ shell.say Engineyard::Hudson::VERSION
28
+ end
29
+
30
+ map "-v" => :version, "--version" => :version, "-h" => :help, "--help" => :help
31
+
32
+ private
33
+ def display(text)
34
+ shell.say text
35
+ exit
36
+ end
37
+
38
+ def error(text)
39
+ shell.say "ERROR: #{text}", :red
40
+ exit
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,62 @@
1
+ require 'thor/group'
2
+
3
+ module Engineyard
4
+ module Hudson
5
+ class Install < Thor::Group
6
+ include Thor::Actions
7
+
8
+ argument :project_path
9
+
10
+ def self.source_root
11
+ File.join(File.dirname(__FILE__), "install", "templates")
12
+ end
13
+
14
+ def install_cookbooks
15
+ file = "cookbooks/main/recipes/default.rb"
16
+ unless File.exists?(File.join(destination_root, "cookbooks/main/recipes/default.rb"))
17
+ directory "cookbooks"
18
+ end
19
+ end
20
+
21
+ def attributes
22
+ template "attributes.rb.erb", "cookbooks/hudson_slave/attributes/default.rb"
23
+ end
24
+
25
+ def recipe
26
+ copy_file "recipes.rb", "cookbooks/hudson_slave/recipes/default.rb"
27
+ end
28
+
29
+ def enable_recipe
30
+ file = "cookbooks/main/recipes/default.rb"
31
+ enable_cmd = "\nrequire_recipe 'hudson_slave'"
32
+ if File.exists?(file_path = File.join(destination_root, file))
33
+ append_file file, enable_cmd
34
+ else
35
+ create_file file, enable_cmd
36
+ end
37
+ end
38
+
39
+ # README:
40
+ # Finally:
41
+ # * edit cookbooks/hudson_slave/attributes/default.rb as necessary
42
+ # * run: ey recipes upload
43
+ # * run: ey recipes apply
44
+ # * Boot your environment if not already booted.
45
+ # When the recipe completes, your project will commence its first build on Hudson CI.
46
+ def readme
47
+ say ""
48
+ say "Finally:"
49
+ say "* edit "; say "cookbooks/hudson_slave/attributes/default.rb ", :yellow; say "as necessary."
50
+ say "* run: "; say "ey recipes upload ", :green; say "# use --environment(-e) & --account(-c)"
51
+ say "* run: "; say "ey recipes apply ", :green; say "# to select environment"
52
+ say "* "; say "Boot your environment ", :yellow; say "if not already booted."
53
+ say "When the recipe completes, your project will commence its first build on Hudson CI."
54
+ end
55
+
56
+ private
57
+ def say(msg, color = nil)
58
+ color ? shell.say(msg, color) : shell.say(msg)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ #
2
+ # Cookbook Name:: hudson_slave
3
+ # Recipe:: default
4
+ #
5
+
6
+ hudson_slave({
7
+ :master => {
8
+ :host => "ec2-174-129-24-134.compute-1.amazonaws.com",
9
+ :port => 80,
10
+ :public_key => "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6AWDDDJcsIrY0KA99KPg+UmSjxjPz7+Eu9mO5GaSNn0vvVdsgrgjkh+35AS9k8Gn/DPaQJoNih+DpY5ZHsuY1zlvnvvk+hsCUHOATngARNs6yQMf2IrQqf38SlBPJ/xjt4oopLyqZuZ59xbFMFa0Yr/B7cCpxNpeIMCbwmc8YOtztOG1ZazlxB6eMTwp1V25TxFPh3PqUz9s37NmBEhkRiEyiJzlDSrKwz2y+77VWztQByM30lYAEXc5GwJD1LTaQwlv/thjhwveAzKLIpxzC5TbUjii7L+4iJF/JrjtXAEYmkegXj6lGBpRIdwXTYWMm3jG6gG+MV2nfWmocDzg3Q==",
11
+ :master_key_location => "/home/hudson/.ssh/id_rsa"
12
+ },
13
+ :gem => {
14
+ :install => "hudson --pre",
15
+ :version => "hudson-0.3.0.beta.13"
16
+ }
17
+ })
@@ -0,0 +1,3 @@
1
+ recipes('main')
2
+ owner_name(@attribute[:users].first[:username])
3
+ owner_pass(@attribute[:users].first[:password])
@@ -0,0 +1,6 @@
1
+ define :ey_cloud_report do
2
+ execute "reporting for #{params[:name]}" do
3
+ command "ey-enzyme --report '#{params[:message]}'"
4
+ epic_fail true
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+
2
+ class Chef
3
+ class Resource
4
+ class RubyBlock < Chef::Resource
5
+ def initialize(name, collection=nil, node=nil)
6
+ super(name, collection, node)
7
+ @resource_name = :ruby_block
8
+ @action = :create
9
+ @allowed_actions.push(:create)
10
+ end
11
+
12
+ def block(&block)
13
+ if block
14
+ @block = block
15
+ else
16
+ @block
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ class Chef
25
+ class Provider
26
+ class RubyBlock < Chef::Provider
27
+ def load_current_resource
28
+ Chef::Log.debug(@new_resource.inspect)
29
+ true
30
+ end
31
+
32
+ def action_create
33
+ @new_resource.block.call
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ Chef::Platform.platforms[:default].merge! :ruby_block => Chef::Provider::RubyBlock
40
+
@@ -0,0 +1,12 @@
1
+ class Chef
2
+ class Recipe
3
+ def run_for_app(*apps, &block)
4
+ apps.map! {|a| a.to_s }
5
+ node[:applications].map{|k,v| [k,v] }.sort_by {|a,b| a }.each do |name, app_data|
6
+ if apps.include?(name)
7
+ block.call(name, app_data)
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,83 @@
1
+ #
2
+ # Cookbook Name:: hudson_slave
3
+ # Recipe:: default
4
+ #
5
+
6
+ env_name = node[:environment][:name]
7
+ username = node[:users].first[:username]
8
+
9
+ if ['solo','app_master'].include?(node[:instance_role]) && env_name =~ /_(ci|hudson_slave)$/
10
+ # gem_package "hudson" do
11
+ # source "http://gemcutter.org"
12
+ # version "0.3.0.beta.3"
13
+ # action :install
14
+ # end
15
+
16
+ execute "install_hudson_in_resin" do
17
+ command "/usr/local/ey_resin/ruby/bin/gem install #{node[:hudson_slave][:gem][:install]}"
18
+ not_if { FileTest.directory?("/usr/local/ey_resin/ruby/gems/1.8/gems/#{node[:hudson_slave][:gem][:version]}") }
19
+ end
20
+
21
+ ruby_block "authorize_hudson_master_key" do
22
+ authorized_keys = "/home/#{node[:users].first[:username]}/.ssh/authorized_keys"
23
+ block do
24
+ File.open(authorized_keys, "a") do |f|
25
+ f.puts node[:hudson_slave][:master][:public_key]
26
+ end
27
+ end
28
+ not_if "grep '#{node[:hudson_slave][:master][:public_key]}' #{authorized_keys}"
29
+ end
30
+
31
+ execute "setup-git-config-for-tagging" do
32
+ command %Q{ sudo su #{username} -c "git config --global user.email 'you@example.com' && git config --global user.name 'You are Special'" }
33
+ not_if %Q{ sudo su #{username} -c "git config user.email" }
34
+ end
35
+
36
+ ruby_block "add-slave-to-master" do
37
+ block do
38
+ Gem.clear_paths
39
+ require "hudson"
40
+ require "hudson/config"
41
+
42
+ Hudson::Api.setup_base_url(node[:hudson_slave][:master])
43
+
44
+ # Tell master about this slave
45
+ Hudson::Api.add_node(
46
+ :name => env_name,
47
+ :description => "Automatically added by Engine Yard AppCloud for environment #{env_name}",
48
+ :slave_host => node[:engineyard][:environment][:instances].first[:public_hostname],
49
+ :slave_user => username,
50
+ :executors => [node[:applications].size, 1].max,
51
+ :label => node[:applications].keys.join(" ")
52
+ )
53
+ end
54
+ action :create
55
+ end
56
+
57
+ ruby_block "tell-master-about-new-jobs" do
58
+ block do
59
+ begin
60
+ job_names = Hudson::Api.summary["jobs"].map {|job| job["name"]} # TODO Hudson::Api.job_names
61
+ app_names = node[:applications].keys
62
+ apps_to_add = app_names - job_names
63
+
64
+ # Tell server about each application
65
+ apps_to_add.each do |app_name|
66
+ data = node[:applications][app_name]
67
+
68
+ job_config = Hudson::JobConfigBuilder.new("rails") do |c|
69
+ c.scm = data[:repository_name]
70
+ c.assigned_node = app_name
71
+ end
72
+
73
+ Hudson::Api.create_job(app_name, job_config)
74
+ Hudson::Api.build_job(app_name)
75
+ end
76
+ rescue Errno::ECONNREFUSED, Errno::EAFNOSUPPORT
77
+ raise Exception, "No connection available to the Hudson server (#{Hudson::Api.base_uri})."
78
+ end
79
+ end
80
+ action :create
81
+ end
82
+
83
+ end
metadata ADDED
@@ -0,0 +1,213 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: engineyard-hudson
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Dr Nic Williams
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-30 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: thor
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 33
30
+ segments:
31
+ - 0
32
+ - 14
33
+ - 3
34
+ version: 0.14.3
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: engineyard
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 29
46
+ segments:
47
+ - 1
48
+ - 3
49
+ - 3
50
+ version: 1.3.3
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: rake
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 49
62
+ segments:
63
+ - 0
64
+ - 8
65
+ - 7
66
+ version: 0.8.7
67
+ type: :development
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: cucumber
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 61
78
+ segments:
79
+ - 0
80
+ - 9
81
+ - 3
82
+ version: 0.9.3
83
+ type: :development
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: rspec
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 13
94
+ segments:
95
+ - 2
96
+ - 0
97
+ - 1
98
+ version: 2.0.1
99
+ type: :development
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: json
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ hash: 7
110
+ segments:
111
+ - 1
112
+ - 4
113
+ - 0
114
+ version: 1.4.0
115
+ type: :development
116
+ version_requirements: *id006
117
+ - !ruby/object:Gem::Dependency
118
+ name: awesome_print
119
+ prerelease: false
120
+ requirement: &id007 !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ hash: 3
126
+ segments:
127
+ - 0
128
+ version: "0"
129
+ type: :development
130
+ version_requirements: *id007
131
+ description: Run your continuous integration (CI) tests against your Engine Yard AppCloud environments - the exact same configuration you are using in production!
132
+ email:
133
+ - drnicwilliams@gmail.com
134
+ executables:
135
+ - ey-hudson
136
+ extensions: []
137
+
138
+ extra_rdoc_files: []
139
+
140
+ files:
141
+ - .gitignore
142
+ - Gemfile
143
+ - Gemfile.lock
144
+ - History.md
145
+ - README.md
146
+ - Rakefile
147
+ - bin/ey-hudson
148
+ - engineyard-hudson.gemspec
149
+ - features/install.feature
150
+ - features/server.feature
151
+ - features/step_definitions/common_steps.rb
152
+ - features/step_definitions/fixture_project_steps.rb
153
+ - features/step_definitions/hudson_steps.rb
154
+ - features/support/common.rb
155
+ - features/support/env.rb
156
+ - features/support/matchers.rb
157
+ - fixtures/cookbooks/main/recipes/default.rb
158
+ - fixtures/cookbooks/redis/recipes/default.rb
159
+ - fixtures/projects/rails/Gemfile
160
+ - fixtures/projects/rails/Gemfile.lock
161
+ - fixtures/projects/rails/Rakefile
162
+ - lib/engineyard-hudson.rb
163
+ - lib/engineyard-hudson/cli.rb
164
+ - lib/engineyard-hudson/cli/install.rb
165
+ - lib/engineyard-hudson/cli/install/templates/attributes.rb.erb
166
+ - lib/engineyard-hudson/cli/install/templates/cookbooks/main/attributes/recipe.rb
167
+ - lib/engineyard-hudson/cli/install/templates/cookbooks/main/definitions/ey_cloud_report.rb
168
+ - lib/engineyard-hudson/cli/install/templates/cookbooks/main/libraries/ruby_block.rb
169
+ - lib/engineyard-hudson/cli/install/templates/cookbooks/main/libraries/run_for_app.rb
170
+ - lib/engineyard-hudson/cli/install/templates/recipes.rb
171
+ has_rdoc: true
172
+ homepage: http://github.com/engineyard/engineyard-hudson
173
+ licenses: []
174
+
175
+ post_install_message:
176
+ rdoc_options: []
177
+
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ none: false
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ hash: 3
186
+ segments:
187
+ - 0
188
+ version: "0"
189
+ required_rubygems_version: !ruby/object:Gem::Requirement
190
+ none: false
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ hash: 3
195
+ segments:
196
+ - 0
197
+ version: "0"
198
+ requirements: []
199
+
200
+ rubyforge_project: engineyard-hudson
201
+ rubygems_version: 1.3.7
202
+ signing_key:
203
+ specification_version: 3
204
+ summary: Easier to do CI than not to. Use Hudson CI on Engine Yard AppCloud.
205
+ test_files:
206
+ - features/install.feature
207
+ - features/server.feature
208
+ - features/step_definitions/common_steps.rb
209
+ - features/step_definitions/fixture_project_steps.rb
210
+ - features/step_definitions/hudson_steps.rb
211
+ - features/support/common.rb
212
+ - features/support/env.rb
213
+ - features/support/matchers.rb