engineyard-hudson 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +47 -0
- data/History.md +6 -0
- data/README.md +74 -0
- data/Rakefile +19 -0
- data/bin/ey-hudson +7 -0
- data/engineyard-hudson.gemspec +29 -0
- data/features/install.feature +57 -0
- data/features/server.feature +12 -0
- data/features/step_definitions/common_steps.rb +191 -0
- data/features/step_definitions/fixture_project_steps.rb +14 -0
- data/features/step_definitions/hudson_steps.rb +3 -0
- data/features/support/common.rb +37 -0
- data/features/support/env.rb +13 -0
- data/features/support/matchers.rb +10 -0
- data/fixtures/cookbooks/main/recipes/default.rb +1 -0
- data/fixtures/cookbooks/redis/recipes/default.rb +0 -0
- data/fixtures/projects/rails/Gemfile +3 -0
- data/fixtures/projects/rails/Gemfile.lock +10 -0
- data/fixtures/projects/rails/Rakefile +4 -0
- data/lib/engineyard-hudson.rb +4 -0
- data/lib/engineyard-hudson/cli.rb +44 -0
- data/lib/engineyard-hudson/cli/install.rb +62 -0
- data/lib/engineyard-hudson/cli/install/templates/attributes.rb.erb +17 -0
- data/lib/engineyard-hudson/cli/install/templates/cookbooks/main/attributes/recipe.rb +3 -0
- data/lib/engineyard-hudson/cli/install/templates/cookbooks/main/definitions/ey_cloud_report.rb +6 -0
- data/lib/engineyard-hudson/cli/install/templates/cookbooks/main/libraries/ruby_block.rb +40 -0
- data/lib/engineyard-hudson/cli/install/templates/cookbooks/main/libraries/run_for_app.rb +12 -0
- data/lib/engineyard-hudson/cli/install/templates/recipes.rb +83 -0
- metadata +213 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -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)
|
data/History.md
ADDED
data/README.md
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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"]
|
data/bin/ey-hudson
ADDED
@@ -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,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 @@
|
|
1
|
+
require_recipe 'redis'
|
File without changes
|
@@ -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,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,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
|