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.
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