bosh-workspace 0.8.5 → 0.9.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rspec +1 -2
- data/.ruby-gemset +1 -1
- data/.travis.yml +2 -0
- data/Guardfile +2 -2
- data/README.md +4 -4
- data/bosh-workspace.gemspec +2 -1
- data/lib/bosh/cli/commands/deployment_patch.rb +96 -0
- data/lib/bosh/cli/commands/prepare.rb +6 -4
- data/lib/bosh/workspace/credentials.rb +30 -0
- data/lib/bosh/workspace/deployment_patch.rb +90 -0
- data/lib/bosh/workspace/helpers/git_credentials_helper.rb +95 -0
- data/lib/bosh/workspace/helpers/spiff_helper.rb +1 -0
- data/lib/bosh/workspace/project_deployment.rb +2 -44
- data/lib/bosh/workspace/release.rb +20 -23
- data/lib/bosh/workspace/schemas/credentials.rb +27 -0
- data/lib/bosh/workspace/schemas/deployment_patch.rb +15 -0
- data/lib/bosh/workspace/schemas/project_deployment.rb +21 -0
- data/lib/bosh/workspace/schemas/releases.rb +16 -0
- data/lib/bosh/workspace/schemas/stemcells.rb +25 -0
- data/lib/bosh/workspace/shell.rb +67 -0
- data/lib/bosh/workspace/tasks/bosh_command_runner.rb +29 -0
- data/lib/bosh/workspace/tasks/deployment.rb +63 -0
- data/lib/bosh/workspace/tasks/workspace.rake +69 -0
- data/lib/bosh/workspace/tasks.rb +15 -0
- data/lib/bosh/workspace/version.rb +1 -1
- data/lib/bosh/workspace.rb +14 -0
- data/spec/assets/foo-boshrelease-repo-new-structure.zip +0 -0
- data/spec/assets/foo-boshrelease-repo-updated.zip +0 -0
- data/spec/assets/foo-boshrelease-repo.zip +0 -0
- data/spec/assets/foo-boshworkspace.zip +0 -0
- data/spec/commands/deployment_patch_spec.rb +152 -0
- data/spec/commands/prepare_spec.rb +5 -3
- data/spec/credentials_spec.rb +46 -0
- data/spec/deployment_patch_spec.rb +171 -0
- data/spec/helpers/git_credentials_helper_spec.rb +160 -0
- data/spec/helpers/spiff_helper_spec.rb +16 -3
- data/spec/project_deployment_spec.rb +52 -163
- data/spec/release_spec.rb +208 -80
- data/spec/schemas/credentials_spec.rb +28 -0
- data/spec/schemas/deployment_patch_spec.rb +30 -0
- data/spec/schemas/project_deployment_spec.rb +45 -0
- data/spec/schemas/releases_spec.rb +31 -0
- data/spec/schemas/stemcells_spec.rb +37 -0
- data/spec/shell_spec.rb +70 -0
- data/spec/spec_helper.rb +11 -5
- data/spec/support/shared_contexts/rake.rb +37 -0
- data/spec/tasks/bosh_command_runner_spec.rb +39 -0
- data/spec/tasks/deployment_spec.rb +80 -0
- data/spec/tasks/workspace_task_spec.rb +99 -0
- metadata +69 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0e593085c05c3aa75cd00f1dd9d94782a3b8688
|
4
|
+
data.tar.gz: 55aaae5f63c4035d13650769a99605d90e678959
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4880764b4fa20f940a5ed3242f8317e58dfc5faeec1f35205b1b1e2dfb2fa729413831eabf71406bf116365d7861e68ca4da3cf3370d6d010454a33777f3d54c
|
7
|
+
data.tar.gz: 08daea7f845dcc9f5f9c8531b1504d88c6832cc1847dcbf49204830fd9e232bc087cd3efaca31b5afac293978de9d1f94f752f4fdd77c3ccf7e050a1be8d4398
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.ruby-gemset
CHANGED
@@ -1 +1 @@
|
|
1
|
-
bosh-
|
1
|
+
bosh-workspace
|
data/.travis.yml
CHANGED
data/Guardfile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
guard :rspec, cmd: 'rspec' do
|
2
|
-
notification :off
|
1
|
+
guard :rspec, cmd: 'rspec', notification: false do
|
3
2
|
watch(%r{^spec/(.+_spec)\.rb$})
|
4
3
|
watch(%r{^lib/bosh/cli/commands/(.+)\.rb$}) { |m| "spec/commands/#{m[1]}_spec.rb" }
|
5
4
|
watch(%r{^lib/bosh/workspace/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
5
|
+
watch(%r{^lib/bosh/workspace/(.+)\.rake$}) { |m| "spec/#{m[1]}_task_spec.rb" }
|
6
6
|
watch('spec/spec_helper.rb') { "spec" }
|
7
7
|
end
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Bosh workspace
|
2
|
-
[![Build Status](https://img.shields.io/travis/
|
2
|
+
[![Build Status](https://img.shields.io/travis/cloudfoundry-incubator/bosh-workspace/master.svg?style=flat-square)](https://travis-ci.org/cloudfoundry-incubator/bosh-workspace) [![Test Coverage](https://img.shields.io/codeclimate/coverage/github/rkoster/bosh-workspace.svg?style=flat-square)](https://codeclimate.com/github/rkoster/bosh-workspace) [![Code Climate](https://img.shields.io/codeclimate/github/rkoster/bosh-workspace.svg?style=flat-square)](https://codeclimate.com/github/rkoster/bosh-workspace) [![Dependency Status](https://img.shields.io/gemnasium/cloudfoundry-incubator/bosh-workspace.svg?style=flat-square)](https://gemnasium.com/cloudfoundry-incubator/bosh-workspace)
|
3
3
|
|
4
4
|
|
5
5
|
This is a `bosh` cli plugin for creating reproducible and upgradable deployments.
|
@@ -18,9 +18,9 @@ cd fg-boshworkspace
|
|
18
18
|
Lets create the initial files & directories.
|
19
19
|
```
|
20
20
|
mkdir deployments templates
|
21
|
-
echo 'source "https://rubygems.org"\n\ngem "bosh-workspace"' > Gemfile
|
22
|
-
echo "2.
|
23
|
-
echo '.stemcells*\n.deployments*\n.releases*\n.stubs*\n' > .gitignore
|
21
|
+
echo -e 'source "https://rubygems.org"\n\ngem "bosh-workspace"' > Gemfile
|
22
|
+
echo "2.1.0" > .ruby-version
|
23
|
+
echo -e '.stemcells*\n.deployments*\n.releases*\n.stubs*\n' > .gitignore
|
24
24
|
```
|
25
25
|
|
26
26
|
Now install the gems by running bundler.
|
data/bosh-workspace.gemspec
CHANGED
@@ -24,7 +24,8 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_runtime_dependency "bosh_common", ">= 1.2682.0"
|
25
25
|
spec.add_runtime_dependency "semi_semantic", "~> 1.1.0"
|
26
26
|
spec.add_runtime_dependency "membrane", "~> 1.1.0"
|
27
|
-
spec.add_runtime_dependency "
|
27
|
+
spec.add_runtime_dependency "hashdiff", "~> 0.2.1"
|
28
|
+
spec.add_runtime_dependency "rugged", "~> 0.22.0b5"
|
28
29
|
|
29
30
|
spec.add_development_dependency "bundler", "~> 1.6"
|
30
31
|
spec.add_development_dependency "rspec", "~> 3.1.0"
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "bosh/workspace"
|
2
|
+
|
3
|
+
module Bosh::Cli::Command
|
4
|
+
class DeploymentPatch < Base
|
5
|
+
include Bosh::Workspace::ProjectDeploymentHelper
|
6
|
+
include Bosh::Workspace::GitCredenialsHelper
|
7
|
+
|
8
|
+
usage "create deployment patch"
|
9
|
+
desc "Extract patch from the current directory and optionally writes to file"
|
10
|
+
def create(deployment_patch)
|
11
|
+
require_project_deployment
|
12
|
+
current_deployment_patch.to_file(deployment_patch)
|
13
|
+
say "Wrote patch to #{deployment_patch}"
|
14
|
+
end
|
15
|
+
|
16
|
+
usage "apply deployment patch"
|
17
|
+
desc "Apply a build patch to the current working directory"
|
18
|
+
option "--dry-run", "only show the changes without applying them"
|
19
|
+
option "--no-commit", "do not commit applied changes"
|
20
|
+
def apply(deployment_patch)
|
21
|
+
require_project_deployment
|
22
|
+
@patch = Bosh::Workspace::DeploymentPatch.from_file(deployment_patch)
|
23
|
+
validate_deployment_patch(@patch, deployment_patch)
|
24
|
+
|
25
|
+
if current_deployment_patch.changes?(@patch)
|
26
|
+
if !options[:dry_run]
|
27
|
+
fetch_repo(templates_dir) if @patch.templates_ref
|
28
|
+
@patch.apply(current_deployment_file, templates_dir)
|
29
|
+
commit_all unless options[:no_commit]
|
30
|
+
say "Successfully applied deployment patch:"
|
31
|
+
else
|
32
|
+
say "Deployment patch:"
|
33
|
+
end
|
34
|
+
|
35
|
+
say patch_changes_table
|
36
|
+
else
|
37
|
+
say "No changes, nothing to do"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def templates_dir
|
44
|
+
File.join(Dir.getwd, 'templates')
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_deployment_file
|
48
|
+
@current_deployment_file ||= project_deployment.file
|
49
|
+
end
|
50
|
+
|
51
|
+
def current_deployment_patch
|
52
|
+
@current_deployment_patch ||= begin
|
53
|
+
Bosh::Workspace::DeploymentPatch
|
54
|
+
.create(current_deployment_file, templates_dir)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_deployment_patch(patch, file)
|
59
|
+
unless patch.valid?
|
60
|
+
say("Validation errors:".make_red)
|
61
|
+
patch.errors.each { |error| say("- #{error}") }
|
62
|
+
err("'#{file}' is not valid".make_red)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def commit_all
|
67
|
+
index = repo.index
|
68
|
+
index.read_tree(repo.head.target.tree)
|
69
|
+
index.add_all
|
70
|
+
options = {
|
71
|
+
tree: index.write_tree(repo),
|
72
|
+
message: changes_message,
|
73
|
+
update_ref: 'HEAD'
|
74
|
+
}
|
75
|
+
Rugged::Commit.create(repo, options)
|
76
|
+
end
|
77
|
+
|
78
|
+
def repo
|
79
|
+
@repo ||= Rugged::Repository.new(Dir.getwd)
|
80
|
+
end
|
81
|
+
|
82
|
+
def patch_changes
|
83
|
+
@patch_changes ||= current_deployment_patch.changes(@patch)
|
84
|
+
end
|
85
|
+
|
86
|
+
def changes_message
|
87
|
+
"Applied " + patch_changes.map { |k, v| "#{k} #{v}" }.join(', ').to_s
|
88
|
+
end
|
89
|
+
|
90
|
+
def patch_changes_table
|
91
|
+
table do |t|
|
92
|
+
patch_changes.each { |k, v| t << [k.to_s, v] }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -5,6 +5,7 @@ module Bosh::Cli::Command
|
|
5
5
|
include Bosh::Cli::Validation
|
6
6
|
include Bosh::Workspace
|
7
7
|
include ProjectDeploymentHelper
|
8
|
+
include GitCredenialsHelper
|
8
9
|
include ReleaseHelper
|
9
10
|
include StemcellHelper
|
10
11
|
|
@@ -25,7 +26,8 @@ module Bosh::Cli::Command
|
|
25
26
|
|
26
27
|
def prepare_release_repos
|
27
28
|
project_deployment_releases.each do |release|
|
28
|
-
say "
|
29
|
+
say "Fetching release '#{release.name.make_green}' to satisfy template references"
|
30
|
+
fetch_or_clone_repo(release.repo_dir, release.git_url)
|
29
31
|
release.update_repo
|
30
32
|
msg = "Version '#{release.version.to_s.make_green}'"
|
31
33
|
msg = "Ref '#{release.ref.make_green}'" if release.ref
|
@@ -64,11 +66,11 @@ module Bosh::Cli::Command
|
|
64
66
|
cached_stemcell_upload(stemcell)
|
65
67
|
end
|
66
68
|
end
|
67
|
-
|
68
|
-
def cached_stemcell_upload(stemcell)
|
69
|
+
|
70
|
+
def cached_stemcell_upload(stemcell)
|
69
71
|
unless stemcell.downloaded?
|
70
72
|
say "Downloading '#{stemcell.name_version.make_green}'"
|
71
|
-
stemcell_download(stemcell.file_name)
|
73
|
+
stemcell_download(stemcell.file_name)
|
72
74
|
end
|
73
75
|
say "Uploading '#{stemcell.name_version.make_green}'"
|
74
76
|
stemcell_upload(stemcell.file)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
class Credentials
|
3
|
+
include Bosh::Cli::Validation
|
4
|
+
|
5
|
+
def initialize(file)
|
6
|
+
@raw_credentials = YAML.load_file file
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform_validation(options = {})
|
10
|
+
Schemas::Credentials.new.validate @raw_credentials
|
11
|
+
rescue Membrane::SchemaValidationError => e
|
12
|
+
errors << e.message
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_by_url(url)
|
16
|
+
credentials[url]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def credentials
|
22
|
+
@credentials ||= begin
|
23
|
+
Hash[@raw_credentials.map do |c|
|
24
|
+
[c.delete('url'),
|
25
|
+
c.each_with_object({}) { |(k, v), h| h[k.to_sym] = v }]
|
26
|
+
end]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
class DeploymentPatch
|
3
|
+
include Bosh::Cli::Validation
|
4
|
+
attr_reader :stemcells, :releases, :templates_ref
|
5
|
+
|
6
|
+
def self.create(deployment_file, templates_dir)
|
7
|
+
if File.exist? File.join(templates_dir, '.git')
|
8
|
+
ref = Rugged::Repository.new(templates_dir).head.target.oid
|
9
|
+
end
|
10
|
+
deployment = YAML.load_file deployment_file
|
11
|
+
new(deployment["stemcells"], deployment["releases"], ref)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.from_file(patch_file)
|
15
|
+
a = YAML.load_file patch_file
|
16
|
+
new(a["stemcells"], a["releases"], a["templates_ref"])
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(stemcells, releases, templates_ref)
|
20
|
+
@stemcells = stemcells
|
21
|
+
@releases = releases
|
22
|
+
@templates_ref = templates_ref
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform_validation(options = {})
|
26
|
+
Schemas::DeploymentPatch.new.validate to_hash
|
27
|
+
rescue Membrane::SchemaValidationError => e
|
28
|
+
errors << e.message
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_hash
|
32
|
+
{ "stemcells" => stemcells, "releases" => releases }
|
33
|
+
.tap { |h| h["templates_ref"] = templates_ref if templates_ref }
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_yaml
|
37
|
+
to_hash.to_yaml
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_file(patch_file)
|
41
|
+
IO.write(patch_file, to_yaml)
|
42
|
+
end
|
43
|
+
|
44
|
+
def apply(deployment_file, templates_dir)
|
45
|
+
checkout_submodule(templates_dir, templates_ref) if templates_ref
|
46
|
+
deployment = YAML.load_file deployment_file
|
47
|
+
deployment.merge! 'stemcells' => stemcells, 'releases' => releases
|
48
|
+
IO.write(deployment_file, deployment.to_yaml)
|
49
|
+
end
|
50
|
+
|
51
|
+
def changes(patch)
|
52
|
+
{
|
53
|
+
stemcells: item_changes(stemcells, patch.stemcells),
|
54
|
+
releases: item_changes(releases, patch.releases),
|
55
|
+
templates_ref: item_changes(templates_ref, patch.templates_ref)
|
56
|
+
}.reject { |_, v| v.empty? }
|
57
|
+
end
|
58
|
+
|
59
|
+
def changes?(patch)
|
60
|
+
!changes(patch).empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def item_changes(old, new)
|
66
|
+
return [] unless old
|
67
|
+
old, new = presentify_item(old), presentify_item(new)
|
68
|
+
changes = HashDiff.diff(old, new).map { |a| a.join(' ').squeeze(' ') }
|
69
|
+
presentify_changes(changes).join(', ')
|
70
|
+
end
|
71
|
+
|
72
|
+
def presentify_changes(changes)
|
73
|
+
translations = { '~' => "changed", '-' => 'removed', '+' => 'added' }
|
74
|
+
changes.map { |l| l.gsub(/^./) { |m| translations.fetch(m, m) } }
|
75
|
+
end
|
76
|
+
|
77
|
+
def presentify_item(item)
|
78
|
+
item.is_a?(Array) ? versions_hash(item) : item[0..6]
|
79
|
+
end
|
80
|
+
|
81
|
+
def versions_hash(array)
|
82
|
+
Hash[array.map { |v| [v["name"], v["version"]]}]
|
83
|
+
end
|
84
|
+
|
85
|
+
def checkout_submodule(dir, ref)
|
86
|
+
repo = Rugged::Repository.new(dir)
|
87
|
+
repo.checkout ref, strategy: :force
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
module GitCredenialsHelper
|
3
|
+
REFSPEC = ['HEAD:refs/remotes/origin/HEAD']
|
4
|
+
|
5
|
+
def fetch_or_clone_repo(dir, url)
|
6
|
+
repo = File.exist?(dir) ? open_repo(dir) : init_repo(dir, url)
|
7
|
+
fetch_and_checkout(repo)
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch_repo(dir)
|
11
|
+
fetch_and_checkout(open_repo(dir))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def fetch_and_checkout(repo)
|
17
|
+
url = repo.remotes['origin'].url
|
18
|
+
repo.fetch('origin', REFSPEC, connection_options_for(repo, url))
|
19
|
+
repo.checkout 'refs/remotes/origin/HEAD', strategy: :force
|
20
|
+
end
|
21
|
+
|
22
|
+
def connection_options_for(repo, url)
|
23
|
+
return {} if check_connection(repo, url)
|
24
|
+
|
25
|
+
options = { credentials: require_credetials_for(url) }
|
26
|
+
unless check_connection(repo, url, options)
|
27
|
+
say "Using credentials from: #{credentials_file}"
|
28
|
+
err "Invalid credentials for: #{url}"
|
29
|
+
end
|
30
|
+
options
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_connection(repo, url, options = {})
|
34
|
+
repo.remotes.create_anonymous(url).check_connection(:fetch, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def init_repo(dir, url)
|
38
|
+
FileUtils.mkdir_p File.dirname(dir)
|
39
|
+
Rugged::Repository.init_at(dir).tap do |repo|
|
40
|
+
repo.remotes.create('origin', url)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def open_repo(dir)
|
45
|
+
Rugged::Repository.new(dir)
|
46
|
+
end
|
47
|
+
|
48
|
+
def credentials
|
49
|
+
@credentials ||= Credentials.new(credentials_file)
|
50
|
+
end
|
51
|
+
|
52
|
+
def require_credetials_for(url)
|
53
|
+
unless File.exist? credentials_file
|
54
|
+
say("Authentication is required for: #{url}".make_red)
|
55
|
+
err("Credentials file does not exist: #{credentials_file}".make_red)
|
56
|
+
end
|
57
|
+
unless credentials.valid?
|
58
|
+
say("Validation errors:".make_red)
|
59
|
+
credentials.errors.each { |error| say("- #{error}") }
|
60
|
+
err("'#{credentials_file}' is not valid".make_red)
|
61
|
+
end
|
62
|
+
if creds = credentials.find_by_url(url)
|
63
|
+
load_git_credentials(creds)
|
64
|
+
else
|
65
|
+
say("Credential look up failed in: #{credentials_file}")
|
66
|
+
err("No credentials found for: #{url}".make_red)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def credentials_file
|
71
|
+
File.join work_dir, '.credentials.yml'
|
72
|
+
end
|
73
|
+
|
74
|
+
def load_git_credentials(credentials)
|
75
|
+
case credentials.keys
|
76
|
+
when %i(private_key)
|
77
|
+
options = {
|
78
|
+
username: 'git',
|
79
|
+
privatekey: temp_key_file(credentials[:private_key])
|
80
|
+
}
|
81
|
+
Rugged::Credentials::SshKey.new(options)
|
82
|
+
when %i(username password)
|
83
|
+
Rugged::Credentials::UserPassword.new(credentials)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def temp_key_file(key)
|
88
|
+
file = Tempfile.new('sshkey')
|
89
|
+
file.write key
|
90
|
+
file.close
|
91
|
+
File.chmod(0600, file.path)
|
92
|
+
file.path
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -1,59 +1,17 @@
|
|
1
|
-
require "membrane"
|
2
1
|
module Bosh::Workspace
|
3
2
|
class ProjectDeployment
|
4
3
|
include Bosh::Cli::Validation
|
5
4
|
attr_writer :director_uuid
|
6
5
|
attr_reader :file
|
7
6
|
|
8
|
-
RELEASE_SCHEMA = Membrane::SchemaParser.parse do
|
9
|
-
{
|
10
|
-
"name" => String,
|
11
|
-
"version" => enum(Integer, "latest"),
|
12
|
-
optional("ref") => enum(String),
|
13
|
-
"git" => String,
|
14
|
-
}
|
15
|
-
end
|
16
|
-
|
17
|
-
class StemcellVersionValidator < Membrane::Schemas::Base
|
18
|
-
def validate(object)
|
19
|
-
return if object.is_a? Integer
|
20
|
-
return if object.is_a? Float
|
21
|
-
return if object == "latest"
|
22
|
-
return if object.to_s =~ /^\d+\.\d+$/
|
23
|
-
raise Membrane::SchemaValidationError.new(
|
24
|
-
"Should match: latest, version.patch or version. Given: #{object}")
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
STEMCELL_SCHEMA = Membrane::SchemaParser.parse do
|
29
|
-
{
|
30
|
-
"name" => String,
|
31
|
-
"version" => StemcellVersionValidator.new
|
32
|
-
}
|
33
|
-
end
|
34
|
-
|
35
|
-
UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
|
36
|
-
|
37
|
-
PROJECT_DEPLOYMENT_SCHEMA = Membrane::SchemaParser.parse do
|
38
|
-
{
|
39
|
-
"name" => String,
|
40
|
-
"director_uuid" => enum(UUID_REGEX, "current"),
|
41
|
-
optional("domain_name") => String,
|
42
|
-
"releases" => [RELEASE_SCHEMA],
|
43
|
-
"stemcells" => [STEMCELL_SCHEMA],
|
44
|
-
"templates" => [String],
|
45
|
-
"meta" => Hash
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
7
|
def initialize(file)
|
50
8
|
@file = file
|
51
9
|
err("Deployment file does not exist: #{file}") unless File.exist? @file
|
52
|
-
@manifest =
|
10
|
+
@manifest = YAML.load_file @file
|
53
11
|
end
|
54
12
|
|
55
13
|
def perform_validation(options = {})
|
56
|
-
|
14
|
+
Schemas::ProjectDeployment.new.validate @manifest
|
57
15
|
rescue Membrane::SchemaValidationError => e
|
58
16
|
errors << e.message
|
59
17
|
end
|
@@ -1,21 +1,17 @@
|
|
1
|
-
require "git"
|
2
1
|
module Bosh::Workspace
|
3
2
|
class Release
|
4
|
-
attr_reader :name, :
|
3
|
+
attr_reader :name, :git_url, :repo_dir
|
5
4
|
|
6
5
|
def initialize(release, releases_dir)
|
7
6
|
@name = release["name"]
|
8
7
|
@ref = release["ref"]
|
9
8
|
@spec_version = release["version"]
|
10
|
-
@
|
9
|
+
@git_url = release["git"]
|
11
10
|
@repo_dir = File.join(releases_dir, @name)
|
12
|
-
init_repo
|
13
11
|
end
|
14
12
|
|
15
13
|
def update_repo
|
16
|
-
|
17
|
-
@repo.checkout last_commit
|
18
|
-
@repo.checkout ref || version_ref
|
14
|
+
repo.checkout ref || version_ref, strategy: :force
|
19
15
|
end
|
20
16
|
|
21
17
|
def manifest_file
|
@@ -38,34 +34,35 @@ module Bosh::Workspace
|
|
38
34
|
@spec_version.to_i
|
39
35
|
end
|
40
36
|
|
37
|
+
def ref
|
38
|
+
@ref && repo.lookup(@ref).oid
|
39
|
+
end
|
40
|
+
|
41
41
|
private
|
42
42
|
|
43
|
+
def repo
|
44
|
+
@repo ||= Rugged::Repository.new(repo_dir)
|
45
|
+
end
|
46
|
+
|
43
47
|
# transforms releases/foo-1.yml, releases/bar-2.yml to:
|
44
48
|
# { "1" => foo-1.yml, "2" => bar-2.yml }
|
45
49
|
def final_releases
|
46
50
|
@final_releases ||= begin
|
47
|
-
|
51
|
+
new_style_repo = File.directory?(File.join(repo_dir, "releases", @name))
|
52
|
+
releases_dir = new_style_repo ? "releases/#{@name}" : "releases"
|
53
|
+
|
54
|
+
Hash[Dir[File.join(repo_dir, releases_dir, "*.yml")]
|
48
55
|
.reject { |f| f[/index.yml/] }
|
49
|
-
.map { |dir| File.join(
|
56
|
+
.map { |dir| File.join(releases_dir, File.basename(dir)) }
|
50
57
|
.map { |version| [version[/(\d+)/].to_i, version] }]
|
51
58
|
end
|
52
59
|
end
|
53
60
|
|
54
|
-
def last_commit
|
55
|
-
@repo.log.object("origin").first
|
56
|
-
end
|
57
|
-
|
58
61
|
def version_ref
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
if File.directory?(repo_dir)
|
64
|
-
@repo ||= Git.open(repo_dir)
|
65
|
-
else
|
66
|
-
releases_dir = File.dirname(repo_dir)
|
67
|
-
FileUtils.mkdir_p(releases_dir)
|
68
|
-
@repo = Git.clone(@git_uri, @name, path: releases_dir)
|
62
|
+
# figure out the last commit which changed the release manifest file
|
63
|
+
# in other words the commit sha of the final release
|
64
|
+
repo.walk(repo.head.target) do |commit|
|
65
|
+
return commit.oid if commit.tree.path(manifest)
|
69
66
|
end
|
70
67
|
end
|
71
68
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
module Schemas
|
3
|
+
class Credentials < Membrane::Schemas::Base
|
4
|
+
def validate(object)
|
5
|
+
Membrane::SchemaParser.parse do
|
6
|
+
[enum(UsernamePassword.new, SshKey.new)]
|
7
|
+
end.validate object
|
8
|
+
end
|
9
|
+
|
10
|
+
class UsernamePassword < Membrane::Schemas::Base
|
11
|
+
def validate(object)
|
12
|
+
Membrane::SchemaParser.parse do
|
13
|
+
{ "url" => String, "username" => String, "password" => String }
|
14
|
+
end.validate object
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SshKey < Membrane::Schemas::Base
|
19
|
+
def validate(object)
|
20
|
+
Membrane::SchemaParser.parse do
|
21
|
+
{ "url" => String, "private_key" => String }
|
22
|
+
end.validate object
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
module Schemas
|
3
|
+
class DeploymentPatch < Membrane::Schemas::Base
|
4
|
+
def validate(object)
|
5
|
+
Membrane::SchemaParser.parse do
|
6
|
+
{
|
7
|
+
"stemcells" => Stemcells.new,
|
8
|
+
"releases" => Releases.new,
|
9
|
+
optional("templates_ref") => String
|
10
|
+
}
|
11
|
+
end.validate object
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
module Schemas
|
3
|
+
class ProjectDeployment < Membrane::Schemas::Base
|
4
|
+
UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
|
5
|
+
|
6
|
+
def validate(object)
|
7
|
+
Membrane::SchemaParser.parse do
|
8
|
+
{
|
9
|
+
"name" => String,
|
10
|
+
"director_uuid" => enum(UUID_REGEX, "current"),
|
11
|
+
optional("domain_name") => String,
|
12
|
+
"releases" => Releases.new,
|
13
|
+
"stemcells" => Stemcells.new,
|
14
|
+
"templates" => [String],
|
15
|
+
"meta" => Hash
|
16
|
+
}
|
17
|
+
end.validate object
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Bosh::Workspace
|
2
|
+
module Schemas
|
3
|
+
class Releases < Membrane::Schemas::Base
|
4
|
+
def validate(object)
|
5
|
+
Membrane::SchemaParser.parse do
|
6
|
+
[{
|
7
|
+
"name" => String,
|
8
|
+
"version" => enum(Integer, "latest"),
|
9
|
+
optional("ref") => enum(String),
|
10
|
+
"git" => String,
|
11
|
+
}]
|
12
|
+
end.validate object
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|