bosh-workspace 0.9.2 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bosh-workspace.gemspec +5 -4
- data/lib/bosh/cli/commands/deployment_patch.rb +0 -1
- data/lib/bosh/cli/commands/prepare.rb +12 -11
- data/lib/bosh/workspace.rb +4 -3
- data/lib/bosh/workspace/credentials.rb +12 -5
- data/lib/bosh/workspace/git_credentials_provider.rb +80 -0
- data/lib/bosh/workspace/{git_remote_url.rb → helpers/git_protocol_helper.rb} +4 -8
- data/lib/bosh/workspace/helpers/project_deployment_helper.rb +10 -2
- data/lib/bosh/workspace/helpers/release_helper.rb +33 -7
- data/lib/bosh/workspace/manifest_builder.rb +6 -8
- data/lib/bosh/workspace/merge_tool.rb +73 -0
- data/lib/bosh/workspace/project_deployment.rb +20 -5
- data/lib/bosh/workspace/release.rb +103 -67
- data/lib/bosh/workspace/schemas/credentials.rb +22 -0
- data/lib/bosh/workspace/schemas/project_deployment.rb +14 -1
- data/lib/bosh/workspace/version.rb +1 -1
- data/spec/assets/bin/spruce +0 -0
- data/spec/assets/manifests-repo/deployments/foo.yml +1 -0
- data/spec/assets/manifests-repo/stubs/foo.yml +4 -0
- data/spec/commands/prepare_spec.rb +39 -12
- data/spec/credentials_spec.rb +8 -0
- data/spec/git_credentials_provider_spec.rb +82 -0
- data/spec/{git_remote_url_spec.rb → helpers/git_protocol_helper_spec.rb} +10 -11
- data/spec/helpers/project_deployment_helper_spec.rb +12 -1
- data/spec/helpers/release_helper_spec.rb +157 -73
- data/spec/manifest_builder_spec.rb +6 -5
- data/spec/merge_tool_spec.rb +98 -0
- data/spec/project_deployment_spec.rb +43 -1
- data/spec/release_spec.rb +369 -354
- data/spec/rugged_spec.rb +64 -0
- data/spec/schemas/credentials_spec.rb +22 -5
- data/spec/spec_helper.rb +1 -0
- metadata +35 -15
- data/lib/bosh/workspace/helpers/git_credentials_helper.rb +0 -111
- data/lib/bosh/workspace/helpers/spiff_helper.rb +0 -34
- data/spec/helpers/git_credentials_helper_spec.rb +0 -190
- data/spec/helpers/spiff_helper_spec.rb +0 -68
@@ -6,18 +6,17 @@ module Bosh::Workspace
|
|
6
6
|
|
7
7
|
def initialize(file)
|
8
8
|
@file = file
|
9
|
-
err("Deployment file does not exist: #{file}") unless File.exist?
|
10
|
-
@manifest = Psych.load(ERB.new(File.read(@file)).result)
|
9
|
+
err("Deployment file does not exist: #{file}") unless File.exist?(@file)
|
11
10
|
end
|
12
11
|
|
13
12
|
def perform_validation(options = {})
|
14
|
-
Schemas::ProjectDeployment.new.validate
|
13
|
+
Schemas::ProjectDeployment.new.validate manifest
|
15
14
|
rescue Membrane::SchemaValidationError => e
|
16
15
|
errors << e.message
|
17
16
|
end
|
18
17
|
|
19
18
|
def director_uuid
|
20
|
-
@director_uuid ||
|
19
|
+
@director_uuid || manifest["director_uuid"]
|
21
20
|
end
|
22
21
|
|
23
22
|
def merged_file
|
@@ -28,9 +27,25 @@ module Bosh::Workspace
|
|
28
27
|
end
|
29
28
|
end
|
30
29
|
|
30
|
+
def merge_tool
|
31
|
+
Bosh::Workspace::MergeTool.new(manifest['merge_tool'])
|
32
|
+
end
|
33
|
+
|
34
|
+
def manifest
|
35
|
+
return @manifest unless @manifest.nil?
|
36
|
+
renderer = Bosh::Template::Renderer.new(context: stub.to_json)
|
37
|
+
@manifest = Psych.load(renderer.render(file))
|
38
|
+
end
|
39
|
+
|
40
|
+
def stub
|
41
|
+
return @stub unless @stub.nil?
|
42
|
+
stub_file = File.expand_path(File.join(file_dirname, "../stubs", file_basename))
|
43
|
+
@stub = File.exist?(stub_file) ? Psych.load(File.read(stub_file)) : {}
|
44
|
+
end
|
45
|
+
|
31
46
|
%w[name templates releases stemcells meta domain_name].each do |var|
|
32
47
|
define_method var do
|
33
|
-
|
48
|
+
manifest[var]
|
34
49
|
end
|
35
50
|
end
|
36
51
|
|
@@ -1,35 +1,25 @@
|
|
1
1
|
module Bosh::Workspace
|
2
2
|
class Release
|
3
|
+
REFSPEC = ['HEAD:refs/remotes/origin/HEAD']
|
3
4
|
attr_reader :name, :git_url, :repo_dir
|
4
5
|
|
5
|
-
def initialize(release, releases_dir)
|
6
|
-
@name
|
7
|
-
@ref
|
8
|
-
@path
|
9
|
-
@spec_version
|
10
|
-
@git_url
|
11
|
-
@repo_dir
|
12
|
-
@url
|
6
|
+
def initialize(release, releases_dir, credentials_callback, options = {})
|
7
|
+
@name = release["name"]
|
8
|
+
@ref = release["ref"]
|
9
|
+
@path = release["path"]
|
10
|
+
@spec_version = release["version"].to_s
|
11
|
+
@git_url = release["git"]
|
12
|
+
@repo_dir = File.join(releases_dir, @name)
|
13
|
+
@url = release["url"]
|
14
|
+
@credentials_callback = credentials_callback
|
15
|
+
@offline = options[:offline]
|
13
16
|
end
|
14
17
|
|
15
|
-
def update_repo
|
18
|
+
def update_repo(options = {})
|
19
|
+
fetch_repo
|
16
20
|
hash = ref || release[:commit]
|
17
21
|
update_repo_with_ref(repo, hash)
|
18
|
-
|
19
|
-
|
20
|
-
def update_submodule(submodule)
|
21
|
-
update_repo_with_ref(submodule.repository, submodule.head_oid)
|
22
|
-
end
|
23
|
-
|
24
|
-
def required_submodules
|
25
|
-
required = []
|
26
|
-
symlink_templates.each do |template|
|
27
|
-
submodule = submodule_for(template)
|
28
|
-
if submodule
|
29
|
-
required.push(submodule)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
required
|
22
|
+
update_submodules
|
33
23
|
end
|
34
24
|
|
35
25
|
def manifest_file
|
@@ -62,8 +52,55 @@ module Bosh::Workspace
|
|
62
52
|
|
63
53
|
private
|
64
54
|
|
55
|
+
def update_submodules
|
56
|
+
required_submodules.each do |submodule|
|
57
|
+
fetch_repo(submodule_repo(submodule.path, submodule.url))
|
58
|
+
update_repo_with_ref(submodule.repository, submodule.head_oid)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def required_submodules
|
63
|
+
symlink_templates.map { |t| submodule_for(t) }.compact
|
64
|
+
end
|
65
|
+
|
66
|
+
def repo_path(path)
|
67
|
+
File.join(@repo_dir, path)
|
68
|
+
end
|
69
|
+
|
70
|
+
def submodule_repo(path, url)
|
71
|
+
dir = repo_path(path)
|
72
|
+
repo_exists?(dir) ? open_repo(dir) : init_repo(dir, url)
|
73
|
+
end
|
74
|
+
|
65
75
|
def repo
|
66
|
-
|
76
|
+
repo_exists? ? open_repo : init_repo
|
77
|
+
end
|
78
|
+
|
79
|
+
def fetch_repo(repo = repo)
|
80
|
+
return if offline?
|
81
|
+
repo.fetch('origin', REFSPEC, credentials: @credentials_callback)
|
82
|
+
commit = repo.references['refs/remotes/origin/HEAD'].resolve.target_id
|
83
|
+
update_repo_with_ref(repo, commit)
|
84
|
+
end
|
85
|
+
|
86
|
+
def repo_exists?(dir = @repo_dir)
|
87
|
+
File.exist?(File.join(dir, '.git'))
|
88
|
+
end
|
89
|
+
|
90
|
+
def open_repo(dir = @repo_dir)
|
91
|
+
Rugged::Repository.new(dir)
|
92
|
+
end
|
93
|
+
|
94
|
+
def init_repo(dir = @repo_dir, url = @git_url)
|
95
|
+
offline_err(url) if offline?
|
96
|
+
FileUtils.mkdir_p File.dirname(dir)
|
97
|
+
Rugged::Repository.init_at(dir).tap do |repo|
|
98
|
+
repo.remotes.create('origin', url)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def offline_err(url)
|
103
|
+
err "Cloning repo: '#{url}' not allowed in offline mode"
|
67
104
|
end
|
68
105
|
|
69
106
|
def update_repo_with_ref(repository, ref)
|
@@ -93,68 +130,67 @@ module Bosh::Workspace
|
|
93
130
|
final_releases = []
|
94
131
|
releases_tree.walk_blobs(:preorder) do |_, entry|
|
95
132
|
next if entry[:filemode] == 40960 # Skip symlinks
|
96
|
-
|
97
|
-
blame =
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
133
|
+
next unless version = entry[:name][/#{@name}-(.+)\.yml/, 1]
|
134
|
+
blame = repo_blame(File.join(releases_dir, entry[:name]))
|
135
|
+
final_releases << {
|
136
|
+
version: version,
|
137
|
+
manifest: blame[:orig_path],
|
138
|
+
commit: blame[:final_commit_id]
|
103
139
|
}
|
104
|
-
commit_id = blame[:final_commit_id]
|
105
|
-
manifest = blame[:orig_path]
|
106
|
-
version = entry[:name][/#{@name}-(.+)\.yml/, 1]
|
107
|
-
if ! version.nil?
|
108
|
-
final_releases.push({
|
109
|
-
version: version, manifest: manifest, commit: commit_id
|
110
|
-
})
|
111
|
-
end
|
112
140
|
end
|
113
|
-
|
114
141
|
final_releases.sort! { |a, b| a[:version].to_i <=> b[:version].to_i }
|
115
142
|
end
|
116
143
|
end
|
117
144
|
|
145
|
+
def repo_blame(path)
|
146
|
+
Rugged::Blame.new(repo, path).reduce do |m, h|
|
147
|
+
return h unless m
|
148
|
+
return h if h[:final_signature][:time] > m[:final_signature][:time]
|
149
|
+
return m
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
118
153
|
def release
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
154
|
+
latest_offline_warning if latest? && offline?
|
155
|
+
return final_releases.last if latest?
|
156
|
+
final_releases.find { |v| v[:version] == @spec_version }.tap do |release|
|
157
|
+
missing_release_err(@spec_version, @name) unless release
|
123
158
|
end
|
124
|
-
|
159
|
+
end
|
160
|
+
|
161
|
+
def missing_release_err(version, name)
|
162
|
+
err "Could not find version: #{version} for release: #{name}"
|
163
|
+
end
|
164
|
+
|
165
|
+
def latest_offline_warning
|
166
|
+
warning "Using 'latest' local version since in offline mode"
|
125
167
|
end
|
126
168
|
|
127
169
|
def templates_dir
|
128
|
-
|
170
|
+
repo_path 'templates'
|
129
171
|
end
|
130
172
|
|
131
173
|
def symlink_target(file)
|
132
|
-
if File.readlink(file).start_with?("/")
|
133
|
-
|
134
|
-
else
|
135
|
-
return File.expand_path(File.join(File.dirname(file), File.readlink(file)))
|
136
|
-
end
|
174
|
+
return File.readlink(file) if File.readlink(file).start_with?("/")
|
175
|
+
File.expand_path(File.join(File.dirname(file), File.readlink(file)))
|
137
176
|
end
|
138
177
|
|
139
178
|
def submodule_for(file)
|
140
|
-
repo.submodules.
|
141
|
-
if file.start_with?(File.join(repo.workdir, submodule.path))
|
142
|
-
return submodule
|
143
|
-
end
|
144
|
-
end
|
145
|
-
false
|
179
|
+
repo.submodules.find { |s| file.start_with? repo_path(s.path) }
|
146
180
|
end
|
147
181
|
|
148
182
|
def symlink_templates
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
183
|
+
return [] unless File.exist?(templates_dir)
|
184
|
+
Find.find(templates_dir)
|
185
|
+
.select { |f| File.symlink?(f) }.map { |f| symlink_target(f) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def offline?
|
189
|
+
@offline
|
190
|
+
end
|
191
|
+
|
192
|
+
def latest?
|
193
|
+
@spec_version == "latest"
|
158
194
|
end
|
159
195
|
end
|
160
196
|
end
|
@@ -1,10 +1,32 @@
|
|
1
1
|
module Bosh::Workspace
|
2
2
|
module Schemas
|
3
3
|
class Credentials < Membrane::Schemas::Base
|
4
|
+
include Bosh::Workspace::GitProtocolHelper
|
5
|
+
|
4
6
|
def validate(object)
|
5
7
|
Membrane::SchemaParser.parse do
|
6
8
|
[enum(UsernamePassword.new, SshKey.new)]
|
7
9
|
end.validate object
|
10
|
+
validate_protocol_credentials_combination(object)
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate_protocol_credentials_combination(object)
|
14
|
+
object.each do |creds|
|
15
|
+
case git_protocol_from_url(creds['url'])
|
16
|
+
when :https, :http
|
17
|
+
next if creds.keys.include? 'username'
|
18
|
+
validation_err "Provide username/password for: #{creds['url']}"
|
19
|
+
when :ssh
|
20
|
+
next if creds.keys.include? 'private_key'
|
21
|
+
validation_err "Provide private_key for: #{creds['url']}"
|
22
|
+
else
|
23
|
+
validation_err "Credentials not supported for: #{creds['url']}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def validation_err(message)
|
29
|
+
raise Membrane::SchemaValidationError.new(message)
|
8
30
|
end
|
9
31
|
|
10
32
|
class UsernamePassword < Membrane::Schemas::Base
|
@@ -12,10 +12,23 @@ module Bosh::Workspace
|
|
12
12
|
"releases" => Releases.new,
|
13
13
|
"stemcells" => Stemcells.new,
|
14
14
|
"templates" => [String],
|
15
|
-
"meta" => Hash
|
15
|
+
"meta" => Hash,
|
16
|
+
optional("merge_tool") => MergeTool.new
|
16
17
|
}
|
17
18
|
end.validate object
|
18
19
|
end
|
20
|
+
|
21
|
+
class MergeTool < Membrane::Schemas::Base
|
22
|
+
def validate(object)
|
23
|
+
return if object.is_a? String
|
24
|
+
return if object.is_a? Hash &&
|
25
|
+
(%w(name version) & object.keys).size == 2 &&
|
26
|
+
object['version'] =~ /^\d+(\.\d+){1,2}|current$/
|
27
|
+
raise Membrane::SchemaValidationError.new(
|
28
|
+
"Should match: String, object.name and object.version. Given: #{object}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
19
32
|
end
|
20
33
|
end
|
21
34
|
end
|
File without changes
|
@@ -3,10 +3,11 @@ require "bosh/cli/commands/prepare"
|
|
3
3
|
describe Bosh::Cli::Command::Prepare do
|
4
4
|
describe "#prepare" do
|
5
5
|
let(:command) { Bosh::Cli::Command::Prepare.new }
|
6
|
+
let(:url) { nil }
|
6
7
|
let(:release) do
|
7
8
|
instance_double("Bosh::Workspace::Release",
|
8
9
|
name: "foo", version: "1", repo_dir: ".releases/foo", git_url: "/.git",
|
9
|
-
release_dir: '.releases/foo/sub', name_version: "foo/1", url:
|
10
|
+
release_dir: '.releases/foo/sub', name_version: "foo/1", url: url,
|
10
11
|
manifest_file: "releases/foo-1.yml")
|
11
12
|
end
|
12
13
|
let(:stemcell) do
|
@@ -30,18 +31,24 @@ describe Bosh::Cli::Command::Prepare do
|
|
30
31
|
let(:stemcells) { [] }
|
31
32
|
let(:ref) { nil }
|
32
33
|
|
34
|
+
context "when only performing local operations" do
|
35
|
+
before { command.add_option(:local, true) }
|
36
|
+
let(:releases) { [] }
|
37
|
+
|
38
|
+
it "enables offline mode" do
|
39
|
+
expect(command).to receive(:offline!)
|
40
|
+
command.prepare
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
33
44
|
context "release with git " do
|
34
45
|
before do
|
35
|
-
allow(release).to receive(:required_submodules).and_return(subrepos)
|
36
|
-
|
37
46
|
expect(release).to receive(:update_repo)
|
38
|
-
expect(release).to_not receive(:update_submodule)
|
39
47
|
expect(release).to receive(:ref).and_return(ref)
|
40
48
|
expect(command).to receive(:release_uploaded?)
|
41
|
-
|
42
|
-
expect(command).to receive(:fetch_or_clone_repo)
|
43
|
-
.with(release.repo_dir, release.git_url)
|
49
|
+
.with(release.name, release.version).and_return(release_uploaded)
|
44
50
|
end
|
51
|
+
|
45
52
|
context "release uploaded" do
|
46
53
|
let(:release_uploaded) { true }
|
47
54
|
|
@@ -64,14 +71,25 @@ describe Bosh::Cli::Command::Prepare do
|
|
64
71
|
end
|
65
72
|
end
|
66
73
|
end
|
74
|
+
|
75
|
+
context "release not uploaded with url" do
|
76
|
+
let(:release_uploaded) { false }
|
77
|
+
let(:url) { "bosh.io/foo/bar.tgz" }
|
78
|
+
|
79
|
+
it "does uploads a remote release" do
|
80
|
+
expect(command).to receive(:release_upload_from_url)
|
81
|
+
.with(release.url)
|
82
|
+
command.prepare
|
83
|
+
end
|
84
|
+
end
|
67
85
|
end
|
68
86
|
|
69
87
|
context "if the release git_url is not given" do
|
70
88
|
let(:release_uploaded) { false }
|
71
89
|
let(:release) do
|
72
90
|
instance_double("Bosh::Workspace::Release",
|
73
|
-
|
74
|
-
|
91
|
+
name: "foo", version: "1", repo_dir: ".releases/foo", git_url: nil,
|
92
|
+
name_version: "foo/1", manifest_file: "releases/foo-1.yml")
|
75
93
|
end
|
76
94
|
|
77
95
|
it "notifies the user that the git property must be specified" do
|
@@ -110,21 +128,30 @@ describe Bosh::Cli::Command::Prepare do
|
|
110
128
|
context "stemcell downloaded" do
|
111
129
|
let(:stemcell_downloaded) { true }
|
112
130
|
|
113
|
-
it "
|
131
|
+
it "uploads the already downloaded stemcell" do
|
114
132
|
expect(command).to_not receive(:stemcell_download)
|
115
133
|
expect(command).to receive(:stemcell_upload).with(stemcell.file)
|
116
134
|
command.prepare
|
117
135
|
end
|
118
136
|
end
|
119
137
|
|
120
|
-
context "stemcell downloaded" do
|
138
|
+
context "when stemcell not downloaded" do
|
121
139
|
let(:stemcell_downloaded) { false }
|
122
140
|
|
123
|
-
it "
|
141
|
+
it "downloads and uploads the stemcell" do
|
124
142
|
expect(command).to receive(:stemcell_download).with(stemcell.file_name)
|
125
143
|
expect(command).to receive(:stemcell_upload).with(stemcell.file)
|
126
144
|
command.prepare
|
127
145
|
end
|
146
|
+
|
147
|
+
context "while being offline" do
|
148
|
+
before { command.offline! }
|
149
|
+
|
150
|
+
it "raises an error" do
|
151
|
+
expect(command).to_not receive(:stemcell_download)
|
152
|
+
expect{command.prepare}.to raise_error /not available offline/
|
153
|
+
end
|
154
|
+
end
|
128
155
|
end
|
129
156
|
end
|
130
157
|
end
|