bosh-workspace 0.9.2 → 0.9.3
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/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
|