bosh-workspace 0.8.5 → 0.9.0.rc1

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +1 -2
  4. data/.ruby-gemset +1 -1
  5. data/.travis.yml +2 -0
  6. data/Guardfile +2 -2
  7. data/README.md +4 -4
  8. data/bosh-workspace.gemspec +2 -1
  9. data/lib/bosh/cli/commands/deployment_patch.rb +96 -0
  10. data/lib/bosh/cli/commands/prepare.rb +6 -4
  11. data/lib/bosh/workspace/credentials.rb +30 -0
  12. data/lib/bosh/workspace/deployment_patch.rb +90 -0
  13. data/lib/bosh/workspace/helpers/git_credentials_helper.rb +95 -0
  14. data/lib/bosh/workspace/helpers/spiff_helper.rb +1 -0
  15. data/lib/bosh/workspace/project_deployment.rb +2 -44
  16. data/lib/bosh/workspace/release.rb +20 -23
  17. data/lib/bosh/workspace/schemas/credentials.rb +27 -0
  18. data/lib/bosh/workspace/schemas/deployment_patch.rb +15 -0
  19. data/lib/bosh/workspace/schemas/project_deployment.rb +21 -0
  20. data/lib/bosh/workspace/schemas/releases.rb +16 -0
  21. data/lib/bosh/workspace/schemas/stemcells.rb +25 -0
  22. data/lib/bosh/workspace/shell.rb +67 -0
  23. data/lib/bosh/workspace/tasks/bosh_command_runner.rb +29 -0
  24. data/lib/bosh/workspace/tasks/deployment.rb +63 -0
  25. data/lib/bosh/workspace/tasks/workspace.rake +69 -0
  26. data/lib/bosh/workspace/tasks.rb +15 -0
  27. data/lib/bosh/workspace/version.rb +1 -1
  28. data/lib/bosh/workspace.rb +14 -0
  29. data/spec/assets/foo-boshrelease-repo-new-structure.zip +0 -0
  30. data/spec/assets/foo-boshrelease-repo-updated.zip +0 -0
  31. data/spec/assets/foo-boshrelease-repo.zip +0 -0
  32. data/spec/assets/foo-boshworkspace.zip +0 -0
  33. data/spec/commands/deployment_patch_spec.rb +152 -0
  34. data/spec/commands/prepare_spec.rb +5 -3
  35. data/spec/credentials_spec.rb +46 -0
  36. data/spec/deployment_patch_spec.rb +171 -0
  37. data/spec/helpers/git_credentials_helper_spec.rb +160 -0
  38. data/spec/helpers/spiff_helper_spec.rb +16 -3
  39. data/spec/project_deployment_spec.rb +52 -163
  40. data/spec/release_spec.rb +208 -80
  41. data/spec/schemas/credentials_spec.rb +28 -0
  42. data/spec/schemas/deployment_patch_spec.rb +30 -0
  43. data/spec/schemas/project_deployment_spec.rb +45 -0
  44. data/spec/schemas/releases_spec.rb +31 -0
  45. data/spec/schemas/stemcells_spec.rb +37 -0
  46. data/spec/shell_spec.rb +70 -0
  47. data/spec/spec_helper.rb +11 -5
  48. data/spec/support/shared_contexts/rake.rb +37 -0
  49. data/spec/tasks/bosh_command_runner_spec.rb +39 -0
  50. data/spec/tasks/deployment_spec.rb +80 -0
  51. data/spec/tasks/workspace_task_spec.rb +99 -0
  52. metadata +69 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3dbff47b8f17bd6be608e2994e74bd9f22c509f3
4
- data.tar.gz: 73dd2a4c5c3e90094a9b3967bda00c8460491c65
3
+ metadata.gz: a0e593085c05c3aa75cd00f1dd9d94782a3b8688
4
+ data.tar.gz: 55aaae5f63c4035d13650769a99605d90e678959
5
5
  SHA512:
6
- metadata.gz: 81a9e37ecfe5b7c7983b9ecd3934a94af6984759868c2fba97eff004e9817e9decc1f509db9337491021b6ad06e0d78b65090bf57e7b9f91deb9ec99ec4420af
7
- data.tar.gz: 3d1911a2617d98c87506b11a762d786b6564057adc1aa6f1f1149ef2277d682516242e458afb8adc5250dd2db33aadb0eed0cc237206658c6345d82475fb3893
6
+ metadata.gz: 4880764b4fa20f940a5ed3242f8317e58dfc5faeec1f35205b1b1e2dfb2fa729413831eabf71406bf116365d7861e68ca4da3cf3370d6d010454a33777f3d54c
7
+ data.tar.gz: 08daea7f845dcc9f5f9c8531b1504d88c6832cc1847dcbf49204830fd9e232bc087cd3efaca31b5afac293978de9d1f94f752f4fdd77c3ccf7e050a1be8d4398
data/.gitignore CHANGED
@@ -17,3 +17,4 @@ test/version_tmp
17
17
  tmp
18
18
  *.generated_manifests
19
19
  *.DS_Store
20
+ /spec/assets/manifests-repo/.releases/*
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --format progress
2
1
  --color
3
- --require spec_helper
2
+ --require spec_helper
data/.ruby-gemset CHANGED
@@ -1 +1 @@
1
- bosh-manifests
1
+ bosh-workspace
data/.travis.yml CHANGED
@@ -1,4 +1,6 @@
1
1
  language: ruby
2
+ cache: bundler
3
+ sudo: false
2
4
  rvm:
3
5
  - 2.0
4
6
  addons:
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/swisscom/bosh-workspace/master.svg?style=flat-square)](https://travis-ci.org/swisscom/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/swisscom/bosh-workspace.svg?style=flat-square)](https://gemnasium.com/swisscom/bosh-workspace)
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.0.0" > .ruby-version
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.
@@ -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 "git", "~> 1.2.6"
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 "Cloning release '#{release.name.make_green}' to satisfy template references"
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
@@ -11,6 +11,7 @@ module Bosh::Workspace
11
11
  private
12
12
 
13
13
  def spiff(verb, params)
14
+ params.map!(&:shellescape)
14
15
  cmd = ["spiff", verb.to_s] + params + ["2>&1"]
15
16
  sh(cmd.join(" "), :yield => :on_false) do |result|
16
17
  spiff_not_found if result.not_found?
@@ -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 = Psych.load(File.read(@file))
10
+ @manifest = YAML.load_file @file
53
11
  end
54
12
 
55
13
  def perform_validation(options = {})
56
- PROJECT_DEPLOYMENT_SCHEMA.validate @manifest
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, :git_uri, :repo_dir, :ref
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
- @git_uri = release["git"]
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
- @repo.fetch "origin", tags: true
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
- Hash[Dir[File.join(repo_dir, "releases", "*.yml")]
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("releases", File.basename(dir)) }
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
- @repo.log.object(manifest).first
60
- end
61
-
62
- def init_repo
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