bosh-workspace 0.9.0.rc3 → 0.9.0.rc4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +30 -1
- data/Rakefile +51 -0
- data/lib/bosh/workspace.rb +1 -0
- data/lib/bosh/workspace/git_remote_url.rb +22 -0
- data/lib/bosh/workspace/helpers/git_credentials_helper.rb +16 -1
- data/lib/bosh/workspace/release.rb +3 -1
- data/lib/bosh/workspace/schemas/releases.rb +5 -4
- data/lib/bosh/workspace/version.rb +1 -1
- data/spec/assets/foo-boshrelease-repo-subdir.zip +0 -0
- data/spec/git_remote_url_spec.rb +39 -0
- data/spec/helpers/git_credentials_helper_spec.rb +26 -1
- data/spec/release_spec.rb +37 -1
- data/spec/schemas/releases_spec.rb +12 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a974316196c63756eb13d118715adee4cb23a54
|
4
|
+
data.tar.gz: f683bafb845d0d1f6fe837f0c99d3d9508c9d445
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94646d03fe1942ca890a41f1e43487d6f7fedcecb6fb16605f48d6fd34abbc78d3771bf6d5cc9750066ca5a4cbd6804d25d9bb736a7b7e4d111a47c7a2168df9
|
7
|
+
data.tar.gz: d88d409fdc34da7cc2b8bcf3112f89fd9e0a7871512e33a7f6f8f85f512b678a263dcd51051032bc2bdac9c437b835483d54fae58d3c703c942bc5ab8eeb2c6d
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Bosh workspace
|
2
|
-
[](https://travis-ci.org/cloudfoundry-incubator/bosh-workspace) [](https://codeclimate.com/github/rkoster/bosh-workspace) [](https://codeclimate.com/github/rkoster/bosh-workspace) [](https://gemnasium.com/cloudfoundry-incubator/bosh-workspace)
|
2
|
+
[](https://travis-ci.org/cloudfoundry-incubator/bosh-workspace) [](https://codeclimate.com/github/rkoster/bosh-workspace) [](https://codeclimate.com/github/rkoster/bosh-workspace) [](https://gemnasium.com/cloudfoundry-incubator/bosh-workspace) [](https://waffle.io/cloudfoundry-incubator/bosh-workspace)
|
3
3
|
|
4
4
|
|
5
5
|
This is a `bosh` cli plugin for creating reproducible and upgradable deployments.
|
@@ -95,6 +95,35 @@ git add . && git commit -m "Added cf-warden deployment"
|
|
95
95
|
Congratulations you should now have a running Cloud Foundry.
|
96
96
|
For further reference on how to start using it go to the [bosh-lite documentation](https://github.com/cloudfoundry/bosh-lite#try-your-cloud-foundry-deployment).
|
97
97
|
|
98
|
+
### Using private boshreleases
|
99
|
+
When using a boshrelease from a location which requires authentication
|
100
|
+
a `.credentials.yml` file is required, located at the root of your boshworkspace.
|
101
|
+
Two types of authentication are supported: `username/password` and `sshkey`.
|
102
|
+
|
103
|
+
Example `.credentials.yml` file:
|
104
|
+
```yaml
|
105
|
+
- url: https://github.com/example/top-secret-boshrelease.git
|
106
|
+
username: foo
|
107
|
+
password: bar
|
108
|
+
- url: ssh://git@github.com/example/super-secret-boshrelease.git
|
109
|
+
private_key: |
|
110
|
+
-----BEGIN RSA PRIVATE KEY-----
|
111
|
+
MIICXAIBAAKBgQDHFr+KICms+tuT1OXJwhCUmR2dKVy7psa8xzElSyzqx7oJyfJ1
|
112
|
+
JZyOzToj9T5SfTIq396agbHJWVfYphNahvZ/7uMXqHxf+ZH9BL1gk9Y6kCnbM5R6
|
113
|
+
0gfwjyW1/dQPjOzn9N394zd2FJoFHwdq9Qs0wBugspULZVNRxq7veq/fzwIDAQAB
|
114
|
+
AoGBAJ8dRTQFhIllbHx4GLbpTQsWXJ6w4hZvskJKCLM/o8R4n+0W45pQ1xEiYKdA
|
115
|
+
Z/DRcnjltylRImBD8XuLL8iYOQSZXNMb1h3g5/UGbUXLmCgQLOUUlnYt34QOQm+0
|
116
|
+
KvUqfMSFBbKMsYBAoQmNdTHBaz3dZa8ON9hh/f5TT8u0OWNRAkEA5opzsIXv+52J
|
117
|
+
duc1VGyX3SwlxiE2dStW8wZqGiuLH142n6MKnkLU4ctNLiclw6BZePXFZYIK+AkE
|
118
|
+
xQ+k16je5QJBAN0TIKMPWIbbHVr5rkdUqOyezlFFWYOwnMmw/BKa1d3zp54VP/P8
|
119
|
+
+5aQ2d4sMoKEOfdWH7UqMe3FszfYFvSu5KMCQFMYeFaaEEP7Jn8rGzfQ5HQd44ek
|
120
|
+
lQJqmq6CE2BXbY/i34FuvPcKU70HEEygY6Y9d8J3o6zQ0K9SYNu+pcXt4lkCQA3h
|
121
|
+
jJQQe5uEGJTExqed7jllQ0khFJzLMx0K6tj0NeeIzAaGCQz13oo2sCdeGRHO4aDh
|
122
|
+
HH6Qlq/6UOV5wP8+GAcCQFgRCcB+hrje8hfEEefHcFpyKH+5g1Eu1k0mLrxK2zd+
|
123
|
+
4SlotYRHgPCEubokb2S1zfZDWIXW3HmggnGgM949TlY=
|
124
|
+
-----END RSA PRIVATE KEY-----
|
125
|
+
```
|
126
|
+
|
98
127
|
## Experimental
|
99
128
|
### dns support
|
100
129
|
Dns support can be enabled by adding a `domain_name` property to your deployment.
|
data/Rakefile
CHANGED
@@ -1,5 +1,56 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
|
+
require "archive/zip"
|
4
|
+
require "rugged"
|
3
5
|
|
4
6
|
task :default => :spec
|
5
7
|
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
namespace :git_assets do
|
10
|
+
desc "Extract git assets used in specs to tmp/git_assets"
|
11
|
+
task :extract do
|
12
|
+
FileUtils.rm_rf(tmp_workdir) if File.exist?(tmp_workdir)
|
13
|
+
git_spec_assets.each do |name, file|
|
14
|
+
target = File.join(tmp_workdir, name)
|
15
|
+
Dir.mktmpdir(name) do |tmp_dir|
|
16
|
+
Archive::Zip.extract(file, tmp_dir)
|
17
|
+
Rugged::Repository.clone_at(tmp_dir, target)
|
18
|
+
end
|
19
|
+
puts "Extracted #{name}.zip"
|
20
|
+
end
|
21
|
+
puts "All git assets have been extracted into: #{tmp_workdir}"
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Update git assets with changes made in tmp/git_assets"
|
25
|
+
task :update do
|
26
|
+
tmp_workdirs.each do |name, dir|
|
27
|
+
archive = File.join(git_spec_assets_dir, "#{name}.zip")
|
28
|
+
Archive::Zip.archive(archive, dir)
|
29
|
+
puts "Updated #{name}.zip"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def tmp_workdir
|
34
|
+
File.join(project_root, "tmp/git_assets")
|
35
|
+
end
|
36
|
+
|
37
|
+
def tmp_workdirs
|
38
|
+
Dir["#{tmp_workdir}/*"].map do |file|
|
39
|
+
[ File.basename(file, ".*"), File.join(file, ".git") ]
|
40
|
+
end.to_h
|
41
|
+
end
|
42
|
+
|
43
|
+
def git_spec_assets_dir
|
44
|
+
File.join(project_root, "spec/assets")
|
45
|
+
end
|
46
|
+
|
47
|
+
def git_spec_assets
|
48
|
+
Dir[File.join(git_spec_assets_dir, "/*repo*.zip")].map do |file|
|
49
|
+
[ File.basename(file, ".*"), file ]
|
50
|
+
end.to_h
|
51
|
+
end
|
52
|
+
|
53
|
+
def project_root
|
54
|
+
File.dirname(__FILE__)
|
55
|
+
end
|
56
|
+
end
|
data/lib/bosh/workspace.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
class GitRemoteUrl
|
3
|
+
def initialize(url)
|
4
|
+
@url = url
|
5
|
+
end
|
6
|
+
|
7
|
+
def protocol()
|
8
|
+
case @url
|
9
|
+
when /^git:/
|
10
|
+
return :git
|
11
|
+
when /^https:/
|
12
|
+
return :https
|
13
|
+
when /^http:/
|
14
|
+
return :http
|
15
|
+
when /(@.+:|^ssh:)/
|
16
|
+
return :ssh
|
17
|
+
else
|
18
|
+
raise "Unsupported protocol for remote git url: #{@url}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -16,11 +16,13 @@ module Bosh::Workspace
|
|
16
16
|
def fetch_and_checkout(repo)
|
17
17
|
url = repo.remotes['origin'].url
|
18
18
|
repo.fetch('origin', REFSPEC, connection_options_for(repo, url))
|
19
|
-
repo.
|
19
|
+
commit = repo.references['refs/remotes/origin/HEAD'].resolve.target_id
|
20
|
+
repo.checkout commit, strategy: :force
|
20
21
|
end
|
21
22
|
|
22
23
|
def connection_options_for(repo, url)
|
23
24
|
return {} if check_connection(repo, url)
|
25
|
+
validate_url_protocol_support!(url)
|
24
26
|
|
25
27
|
options = { credentials: require_credetials_for(url) }
|
26
28
|
unless check_connection(repo, url, options)
|
@@ -67,6 +69,19 @@ module Bosh::Workspace
|
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
72
|
+
def validate_url_protocol_support!(url)
|
73
|
+
protocol = GitRemoteUrl.new(url).protocol
|
74
|
+
case protocol
|
75
|
+
when :git
|
76
|
+
err("Somthing is wrong, the git protocol does not support authentication")
|
77
|
+
when :https, :ssh
|
78
|
+
unless Rugged.features.include? protocol
|
79
|
+
say("Please reinstall Rugged gem with #{protocol} support: http://git.io/veiyJ")
|
80
|
+
err("Rugged requires #{protocol} support for: #{url}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
70
85
|
def git_credentials_file
|
71
86
|
File.join work_dir, '.credentials.yml'
|
72
87
|
end
|
@@ -5,6 +5,7 @@ module Bosh::Workspace
|
|
5
5
|
def initialize(release, releases_dir)
|
6
6
|
@name = release["name"]
|
7
7
|
@ref = release["ref"]
|
8
|
+
@path = release["path"]
|
8
9
|
@spec_version = release["version"].to_s
|
9
10
|
@git_url = release["git"]
|
10
11
|
@repo_dir = File.join(releases_dir, @name)
|
@@ -46,7 +47,8 @@ module Bosh::Workspace
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def releases_dir
|
49
|
-
new_style_repo ? "releases/#{@name}" : "releases"
|
50
|
+
dir = new_style_repo ? "releases/#{@name}" : "releases"
|
51
|
+
@path ? File.join(@path, dir) : dir
|
50
52
|
end
|
51
53
|
|
52
54
|
def releases_tree
|
@@ -4,10 +4,11 @@ module Bosh::Workspace
|
|
4
4
|
def validate(object)
|
5
5
|
Membrane::SchemaParser.parse do
|
6
6
|
[{
|
7
|
-
"name"
|
8
|
-
"version"
|
9
|
-
optional("
|
10
|
-
optional("
|
7
|
+
"name" => String,
|
8
|
+
"version" => ReleaseVersion.new,
|
9
|
+
optional("path") => enum(String),
|
10
|
+
optional("ref") => enum(String),
|
11
|
+
optional("git") => String,
|
11
12
|
}]
|
12
13
|
end.validate object
|
13
14
|
end
|
Binary file
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
describe GitRemoteUrl do
|
3
|
+
describe '.protocol' do
|
4
|
+
subject { GitRemoteUrl.new(url) }
|
5
|
+
|
6
|
+
context 'git protocol' do
|
7
|
+
let(:url) { "git://example.com/foo" }
|
8
|
+
its(:protocol) { is_expected.to eq(:git) }
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'https protocol' do
|
12
|
+
let(:url) { "https://example.com/foo" }
|
13
|
+
its(:protocol) { is_expected.to eq(:https) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'http protocol' do
|
17
|
+
let(:url) { "http://example.com/foo" }
|
18
|
+
its(:protocol) { is_expected.to eq(:http) }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'ssh protocol style 1' do
|
22
|
+
let(:url) { "foo@example.com:foo" }
|
23
|
+
its(:protocol) { is_expected.to eq(:ssh) }
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'ssh protocol style 2' do
|
27
|
+
let(:url) { "ssh://foo@example.com/foo" }
|
28
|
+
its(:protocol) { is_expected.to eq(:ssh) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'unsupported protocol' do
|
32
|
+
let(:url) { "foo://foo@example.com/foo" }
|
33
|
+
it 'raises' do
|
34
|
+
expect { subject.protocol() }.to raise_error /unsupported protocol/i
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -15,6 +15,8 @@ module Bosh::Workspace
|
|
15
15
|
let(:dir) { File.join(work_dir, '.releases', 'foo') }
|
16
16
|
let(:repo) { instance_double 'Rugged::Repository' }
|
17
17
|
let(:remote) { instance_double 'Rugged::Remote', url: url }
|
18
|
+
let(:protocol) { :http }
|
19
|
+
let(:git_url) { instance_double 'GitRemoteURL', protocol: protocol }
|
18
20
|
|
19
21
|
before do
|
20
22
|
allow(Rugged::Repository).to receive(:new).with(dir).and_return(repo)
|
@@ -22,9 +24,12 @@ module Bosh::Workspace
|
|
22
24
|
allow(repo).to receive_message_chain("remotes.[]").and_return(remote)
|
23
25
|
allow(repo).to receive_message_chain("remotes.create_anonymous")
|
24
26
|
.with(url).and_return(remote)
|
27
|
+
allow(repo).to receive_message_chain("references.[].resolve.target_id")
|
28
|
+
.and_return(:commit_id)
|
25
29
|
allow(remote).to receive(:check_connection).with(:fetch, Hash)
|
26
30
|
.and_return(!auth_required, credentials_auth_valid)
|
27
|
-
allow(repo).to receive(:checkout).with(
|
31
|
+
allow(repo).to receive(:checkout).with(:commit_id, strategy: :force)
|
32
|
+
allow(GitRemoteUrl).to receive(:new).and_return(git_url)
|
28
33
|
end
|
29
34
|
|
30
35
|
def expect_no_credentials
|
@@ -95,6 +100,26 @@ module Bosh::Workspace
|
|
95
100
|
.with(url).and_return(creds_hash)
|
96
101
|
end
|
97
102
|
|
103
|
+
context "without supported protocol" do
|
104
|
+
before do
|
105
|
+
expect(Rugged).to receive(:features).and_return([])
|
106
|
+
end
|
107
|
+
|
108
|
+
let(:protocol) { :https }
|
109
|
+
|
110
|
+
it "raises" do
|
111
|
+
expect { subject }.to raise_error /rugged requires https/i
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context "with git protocol" do
|
116
|
+
let(:protocol) { :git }
|
117
|
+
|
118
|
+
it "raises" do
|
119
|
+
expect { subject }.to raise_error /not support authentication/i
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
98
123
|
context "with sshkey" do
|
99
124
|
let(:creds_hash) { { private_key: "foobarkey" } }
|
100
125
|
|
data/spec/release_spec.rb
CHANGED
@@ -18,7 +18,6 @@ describe Bosh::Workspace::Release do
|
|
18
18
|
|
19
19
|
describe "#update_repo" do
|
20
20
|
subject do
|
21
|
-
# puts `cd #{File.join(releases_dir, name)} && git checkout origin/HEAD && git log`
|
22
21
|
Dir[File.join(releases_dir, name, "releases/**/foo*.yml")].to_s
|
23
22
|
end
|
24
23
|
|
@@ -258,4 +257,41 @@ describe Bosh::Workspace::Release do
|
|
258
257
|
its(:version) { should eq version.to_s }
|
259
258
|
end
|
260
259
|
end
|
260
|
+
|
261
|
+
context "given a release which is located in a subfolder" do
|
262
|
+
let(:repo) { extracted_asset_dir("foo", "foo-boshrelease-repo-subdir.zip") }
|
263
|
+
let(:release_data) do
|
264
|
+
{ "name" => name, "version" => version, "git" => repo, "path" => "release" }
|
265
|
+
end
|
266
|
+
|
267
|
+
describe "#update_repo" do
|
268
|
+
subject do
|
269
|
+
Dir[File.join(releases_dir, name, "release/releases/**/*.yml")].to_s
|
270
|
+
end
|
271
|
+
|
272
|
+
context "latest version" do
|
273
|
+
before { release.update_repo }
|
274
|
+
|
275
|
+
let(:version) { "latest" }
|
276
|
+
|
277
|
+
it "checks out repo" do
|
278
|
+
expect(subject).to match(/release\/releases\/foo-12.yml/)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "attributes" do
|
284
|
+
let(:version) { "12" }
|
285
|
+
subject { release }
|
286
|
+
its(:name) { should eq name }
|
287
|
+
its(:git_url) { should eq repo }
|
288
|
+
its(:repo_dir) { should match(/\/#{name}$/) }
|
289
|
+
its(:manifest) { should match "release/releases/#{name}-#{version}.yml$" }
|
290
|
+
its(:name_version) { should eq "#{name}/#{version}" }
|
291
|
+
its(:version) { should eq version }
|
292
|
+
its(:manifest_file) do
|
293
|
+
should match(/\/release\/releases\/#{name}-#{version}.yml$/)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
261
297
|
end
|
@@ -1,7 +1,13 @@
|
|
1
1
|
module Bosh::Workspace::Schemas
|
2
2
|
describe Releases do
|
3
3
|
let(:release) do
|
4
|
-
{
|
4
|
+
{
|
5
|
+
"name" => "foo",
|
6
|
+
"version" => 1,
|
7
|
+
"path" => "release",
|
8
|
+
"ref" => "cec3ec1",
|
9
|
+
"git" => "example.com/git.git"
|
10
|
+
}
|
5
11
|
end
|
6
12
|
|
7
13
|
subject { Releases.new.validate(releases) }
|
@@ -32,5 +38,10 @@ module Bosh::Workspace::Schemas
|
|
32
38
|
let(:releases) { [release.delete_if { |k| k == "git" }] }
|
33
39
|
it { expect { subject }.to_not raise_error }
|
34
40
|
end
|
41
|
+
|
42
|
+
context "optional git" do
|
43
|
+
let(:releases) { [release.delete_if { |k| k == "path" }] }
|
44
|
+
it { expect { subject }.to_not raise_error }
|
45
|
+
end
|
35
46
|
end
|
36
47
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bosh-workspace
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.0.
|
4
|
+
version: 0.9.0.rc4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ruben Koster
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bosh_cli
|
@@ -188,6 +188,7 @@ files:
|
|
188
188
|
- lib/bosh/workspace.rb
|
189
189
|
- lib/bosh/workspace/credentials.rb
|
190
190
|
- lib/bosh/workspace/deployment_patch.rb
|
191
|
+
- lib/bosh/workspace/git_remote_url.rb
|
191
192
|
- lib/bosh/workspace/helpers/dns_helper.rb
|
192
193
|
- lib/bosh/workspace/helpers/git_credentials_helper.rb
|
193
194
|
- lib/bosh/workspace/helpers/project_deployment_helper.rb
|
@@ -218,6 +219,7 @@ files:
|
|
218
219
|
- spec/assets/dns/properties.yml
|
219
220
|
- spec/assets/empty-manifests-repo/.keep
|
220
221
|
- spec/assets/foo-boshrelease-repo-new-structure.zip
|
222
|
+
- spec/assets/foo-boshrelease-repo-subdir.zip
|
221
223
|
- spec/assets/foo-boshrelease-repo-updated.zip
|
222
224
|
- spec/assets/foo-boshrelease-repo.zip
|
223
225
|
- spec/assets/foo-boshworkspace.zip
|
@@ -230,6 +232,7 @@ files:
|
|
230
232
|
- spec/commands/project_deployment_spec.rb
|
231
233
|
- spec/credentials_spec.rb
|
232
234
|
- spec/deployment_patch_spec.rb
|
235
|
+
- spec/git_remote_url_spec.rb
|
233
236
|
- spec/helpers/dns_helper_spec.rb
|
234
237
|
- spec/helpers/git_credentials_helper_spec.rb
|
235
238
|
- spec/helpers/project_deployment_helper_spec.rb
|
@@ -285,6 +288,7 @@ test_files:
|
|
285
288
|
- spec/assets/dns/properties.yml
|
286
289
|
- spec/assets/empty-manifests-repo/.keep
|
287
290
|
- spec/assets/foo-boshrelease-repo-new-structure.zip
|
291
|
+
- spec/assets/foo-boshrelease-repo-subdir.zip
|
288
292
|
- spec/assets/foo-boshrelease-repo-updated.zip
|
289
293
|
- spec/assets/foo-boshrelease-repo.zip
|
290
294
|
- spec/assets/foo-boshworkspace.zip
|
@@ -297,6 +301,7 @@ test_files:
|
|
297
301
|
- spec/commands/project_deployment_spec.rb
|
298
302
|
- spec/credentials_spec.rb
|
299
303
|
- spec/deployment_patch_spec.rb
|
304
|
+
- spec/git_remote_url_spec.rb
|
300
305
|
- spec/helpers/dns_helper_spec.rb
|
301
306
|
- spec/helpers/git_credentials_helper_spec.rb
|
302
307
|
- spec/helpers/project_deployment_helper_spec.rb
|