bosh-workspace 0.7.0

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjQwYmNmNWQzMDgxYjhmNTFkOGYxNGJmMTNjOWE1ODZiM2U5ZmJlYg==
5
+ data.tar.gz: !binary |-
6
+ Y2Y1M2Y2MGZjNmY5MDFhY2NiN2RhYTk3MzliZDA3ZDg5NzFlZWIxMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NGE4ZWQzNjVmY2ZkZDgwOGE3Y2QyNzVlMGUxMGZkMjBkYmE2ZGJlNzFhY2M0
10
+ YjE2MmY5OThlOWI1MDcxNmJiNTRkNWM0OWM1MWUwMTNlNzYxODcxNjI5OGJh
11
+ N2M0OGI5OTEwOTM4M2U5ZTkyNTdmMjE3MTAwOTQwNThkYWZmMzk=
12
+ data.tar.gz: !binary |-
13
+ NGFhZmE1YmMxNzdiZDZhZGFiMjYzNGZiN2UwNmFkZmYyZTZjMzdjMTNlODAz
14
+ ZjI5NjIyMzQ3NDQzZTRmZTQzNGNmYmM0YWI4MjI2ODBhMzdiNGE1NTc5Yzgy
15
+ OTk1YWFlODU5ZGYwNTBiNDNjMTJlMDkzODg5Y2I4YTYxMzQyNDY=
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.generated_manifests
19
+ *.DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format progress
2
+ --color
3
+ --require spec_helper
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ bosh-manifests
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p484
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bosh-manifests.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "guard-rspec"
8
+ end
data/Guardfile ADDED
@@ -0,0 +1,7 @@
1
+ guard 'rspec', spec_paths: ["spec"] do
2
+ notification :off
3
+ watch(%r{^spec/(.+_spec)\.rb$})
4
+ watch(%r{^lib/bosh/cli/commands/(.+)\.rb$}) { |m| "spec/plugin_spec.rb" }
5
+ watch(%r{^lib/bosh/cloudfoundry/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
6
+ watch('spec/spec_helper.rb') { "spec" }
7
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Ruben Koster
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # Bosh workspace
2
+
3
+ ## Design Goals
4
+ Enabling BOSH operators to share common configuration between different deployments.
5
+ For example having a `QA` and `production` deployment for which only networking differs.
6
+
7
+ ## Installation
8
+ Before you start make sure ruby, bundler and spiff are available on your system.
9
+ Instructions for installing spiff can found [here](https://github.com/cloudfoundry-incubator/spiff#installation).
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'bosh-workspace'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install bosh-workspace
22
+
23
+
24
+ ## Usage
25
+ This BOSH plugin improves the deployments work-flow,
26
+ by extending some of the build in commands bosh commands.
27
+
28
+ **Set deployment**
29
+ Sets the current deployment. Will search in `./deployments`.
30
+ ```
31
+ bosh deployment cf-warden
32
+ ```
33
+
34
+ **Prepare deployment**
35
+ Resolves upstream templates (via releases).
36
+ Resolves and uploads releases/stemcells.
37
+ ```
38
+ bosh prepare deployment
39
+ ```
40
+
41
+ **Deploy**
42
+ Merges the specified templates into one deployment manifests using spiff.
43
+ And uses this file to initiate a `deploy`.
44
+ ```
45
+ bosh deploy
46
+ ```
47
+
48
+ ### Deployment file structure
49
+ A deployment file has the following structure:
50
+
51
+ **name:**
52
+ The name of your deployment
53
+
54
+ **director_uuid:**
55
+ The director_uuid of the targeted BOSH director
56
+
57
+ **releases:**
58
+ Array of releases used for resolving upstream templates
59
+
60
+ **meta:**
61
+ The meta hash is the last file merged into the final deployment file.
62
+ It can be used to define properties deployment specific properties.
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bosh/workspace/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bosh-workspace"
8
+ spec.version = Bosh::Manifests::VERSION
9
+ spec.authors = ["Ruben Koster"]
10
+ spec.email = ["rkoster@starkandwayne.com"]
11
+ spec.description = %q{Manage your bosh workspace}
12
+ spec.summary = %q{Manage your bosh workspace}
13
+ spec.homepage = "http://starkandwayne.com"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '>= 2.0.0'
22
+
23
+ spec.add_runtime_dependency "bosh_cli", ">= 1.1722.0"
24
+ spec.add_runtime_dependency "git", "~> 1.2.6"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.3"
27
+ spec.add_development_dependency "rspec", "~> 2.13.0"
28
+ spec.add_development_dependency "rspec-fire"
29
+ spec.add_development_dependency "rake"
30
+ spec.add_development_dependency "archive-zip", "~> 0.6.0"
31
+ end
@@ -0,0 +1,74 @@
1
+ require "bosh/workspace"
2
+
3
+ module Bosh::Cli::Command
4
+ class Manifests < Base
5
+ include Bosh::Cli::Validation
6
+ include Bosh::Manifests
7
+ include ProjectDeploymentHelper
8
+
9
+ # Hack to unregister original deployment command
10
+ Bosh::Cli::Config.instance_eval("@commands.delete('deployment')")
11
+
12
+ usage "deployment"
13
+ desc "Get/set current deployment"
14
+ def set_current(filename = nil)
15
+ unless filename.nil?
16
+ deployment = find_deployment(filename)
17
+
18
+ if project_deployment_file?(deployment)
19
+ self.project_deployment = deployment
20
+ validate_project_deployment
21
+ filename = project_deployment.merged_file
22
+ create_placeholder_deployment unless File.exists?(filename)
23
+ end
24
+ end
25
+
26
+ deployment_cmd(options).set_current(filename)
27
+ end
28
+
29
+ usage "prepare deployment"
30
+ desc "Resolve deployment requirements"
31
+ def prepare
32
+ require_project_deployment
33
+ auth_required
34
+
35
+ release_manager.update_release_repos
36
+
37
+ # TODO: Implement
38
+ # current_deployment.stemcells.each do |stemcell|
39
+ # stemcell.upload unless stemcell.exists?
40
+ # end
41
+ end
42
+
43
+ # Hack to unregister original deploy command
44
+ Bosh::Cli::Config.instance_eval("@commands.delete('deploy')")
45
+
46
+ usage "deploy"
47
+ desc "Deploy according to the currently selected deployment manifest"
48
+ option "--recreate", "recreate all VMs in deployment"
49
+ def deploy
50
+ if project_deployment?
51
+ require_project_deployment
52
+ build_project_deployment
53
+ end
54
+
55
+ deployment_cmd(options).perform
56
+ end
57
+
58
+ private
59
+
60
+ def deployment_cmd(options = {})
61
+ cmd ||= Bosh::Cli::Command::Deployment.new
62
+ options.each do |key, value|
63
+ cmd.add_option key.to_sym, value
64
+ end
65
+ cmd
66
+ end
67
+
68
+ def release_manager
69
+ @release_manager ||= begin
70
+ ReleaseManager.new(project_deployment.releases, work_dir)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,67 @@
1
+ module Bosh::Manifests
2
+ class DeploymentManifest
3
+ include Bosh::Cli::Validation
4
+ attr_writer :director_uuid
5
+ attr_reader :file
6
+
7
+ def initialize(file)
8
+ @file = file
9
+ err("Deployment file does not exist: #{file}") unless File.exist? @file
10
+ @manifest = Psych.load(File.read(@file))
11
+ end
12
+
13
+ def perform_validation(options = {})
14
+ if @manifest.is_a?(Hash)
15
+ unless @manifest.has_key?("name") && @manifest["name"].is_a?(String)
16
+ errors << "Manifest should contain a name"
17
+ end
18
+
19
+ unless @manifest.has_key?("director_uuid") && @manifest["director_uuid"].is_a?(String)
20
+ errors << "Manifest should contain a director_uuid"
21
+ end
22
+
23
+ unless @manifest.has_key?("templates") && @manifest["templates"].is_a?(Array)
24
+ errors << "Manifest should contain templates"
25
+ end
26
+
27
+ unless @manifest.has_key?("releases") && @manifest["releases"].is_a?(Array)
28
+ errors << "Manifest should contain releases"
29
+ end
30
+
31
+ unless @manifest.has_key?("meta") && @manifest["meta"].is_a?(Hash)
32
+ errors << "Manifest should contain meta hash"
33
+ end
34
+ else
35
+ errors << "Manifest should be a hash"
36
+ end
37
+ end
38
+
39
+ def director_uuid
40
+ @director_uuid || @manifest["director_uuid"]
41
+ end
42
+
43
+ def merged_file
44
+ @merged_file ||= begin
45
+ path = File.join(file_dirname, "../.deployments", file_basename)
46
+ FileUtils.mkpath File.dirname(path)
47
+ File.expand_path path
48
+ end
49
+ end
50
+
51
+ %w[name templates releases meta].each do |var|
52
+ define_method var do
53
+ @manifest[var]
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def file_basename
60
+ File.basename(@file)
61
+ end
62
+
63
+ def file_dirname
64
+ File.dirname(@file)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,103 @@
1
+ module Bosh::Manifests
2
+ module ProjectDeploymentHelper
3
+
4
+ def project_deployment?
5
+ File.exists?(project_deployment_file) &&
6
+ project_deployment_file?(project_deployment_file)
7
+ end
8
+
9
+ def project_deployment
10
+ @project_deployment ||= DeploymentManifest.new(project_deployment_file)
11
+ end
12
+
13
+ def project_deployment=(deployment)
14
+ @project_deployment = DeploymentManifest.new(deployment)
15
+ end
16
+
17
+ def project_deployment_file?(deployment)
18
+ Psych.load(File.read(deployment)).has_key?("templates")
19
+ end
20
+
21
+ def require_project_deployment
22
+ unless project_deployment?
23
+ err("Deployment is not a project deployment: #{deployment}")
24
+ end
25
+ validate_project_deployment
26
+ end
27
+
28
+ def create_placeholder_deployment
29
+ resolve_director_uuid
30
+
31
+ File.open(project_deployment.merged_file, "w") do |file|
32
+ file.write(placeholder_deployment_content)
33
+ end
34
+ end
35
+
36
+ def validate_project_deployment
37
+ unless project_deployment.valid?
38
+ say("Validation errors:".make_red)
39
+ project_deployment.errors.each { |error| say("- #{error}") }
40
+ err("'#{project_deployment.file}' is not valid".make_red)
41
+ end
42
+ end
43
+
44
+ def build_project_deployment
45
+ resolve_director_uuid
46
+
47
+ say("Generating deployment manifest")
48
+ ManifestBuilder.build(project_deployment, work_dir)
49
+ end
50
+
51
+ def resolve_director_uuid
52
+ use_targeted_director_uuid if director_uuid_current?
53
+ end
54
+
55
+ private
56
+
57
+ def use_targeted_director_uuid
58
+ no_warden_error unless warden_cpi?
59
+ project_deployment.director_uuid = bosh_uuid
60
+ end
61
+
62
+ def no_warden_error
63
+ say("Please put 'director_uuid: #{bosh_uuid}' in '#{deployment}'")
64
+ err("'director_uuid: current' may not be used in production")
65
+ end
66
+
67
+ def director_uuid_current?
68
+ project_deployment.director_uuid == "current"
69
+ end
70
+
71
+ def warden_cpi?
72
+ bosh_status["cpi"] == "warden"
73
+ end
74
+
75
+ def project_deployment_file
76
+ @project_deployment_file ||= begin
77
+ path = File.join(deployment_dir, "../deployments", deployment_basename)
78
+ File.expand_path path
79
+ end
80
+ end
81
+
82
+ def deployment_dir
83
+ File.dirname(deployment)
84
+ end
85
+
86
+ def deployment_basename
87
+ File.basename(deployment)
88
+ end
89
+
90
+ def placeholder_deployment_content
91
+ { "director_uuid" => project_deployment.director_uuid }.to_yaml +
92
+ "# Don't edit; placeholder deployment for: #{project_deployment.file}"
93
+ end
94
+
95
+ def bosh_status
96
+ @bosh_status ||= director.get_status
97
+ end
98
+
99
+ def bosh_uuid
100
+ bosh_status["uuid"]
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,33 @@
1
+ module Bosh::Manifests
2
+ module SpiffHelper
3
+ include Bosh::Exec
4
+
5
+ def spiff_merge(templates, target_file)
6
+ spiff(:merge, templates) do |output|
7
+ File.open(target_file, 'w') { |file| file.write(output) }
8
+ end
9
+ end
10
+
11
+ private
12
+
13
+ def spiff(verb, params)
14
+ cmd = ["spiff", verb.to_s] + params + ["2>&1"]
15
+ sh(cmd.join(" "), :yield => :on_false) do |result|
16
+ spiff_not_found if result.not_found?
17
+ spiff_failed(result.command, result.output) if result.failed?
18
+ yield result.output
19
+ end
20
+ end
21
+
22
+ def spiff_not_found
23
+ say("Can't find spiff in $PATH".make_red)
24
+ say("Go to spiff.cfapps.io for installation instructions")
25
+ err("Please make sure spiff is installed")
26
+ end
27
+
28
+ def spiff_failed(command, output)
29
+ say("Command failed: '#{command}'")
30
+ err(output)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,68 @@
1
+ module Bosh::Manifests
2
+ class ManifestBuilder
3
+ include Bosh::Manifests::SpiffHelper
4
+
5
+ def self.build(manifest, work_dir)
6
+ manifest_builder = ManifestBuilder.new(manifest, work_dir)
7
+ manifest_builder.merge_templates
8
+ end
9
+
10
+ def initialize(manifest, work_dir)
11
+ @manifest = manifest
12
+ @work_dir = work_dir
13
+ end
14
+
15
+ def merge_templates
16
+ spiff_merge spiff_template_paths, @manifest.merged_file
17
+ end
18
+
19
+ private
20
+
21
+ def spiff_template_paths
22
+ spiff_templates = template_paths
23
+ spiff_templates << stub_file_path
24
+ end
25
+
26
+ def template_paths
27
+ @manifest.templates.map { |t| template_path(t) }
28
+ end
29
+
30
+ def stub_file_path
31
+ path = hidden_file_path(:stubs)
32
+ File.open(path, 'w') { |file| file.write(stub_file_content) }
33
+ path
34
+ end
35
+
36
+ def stub_file_content
37
+ {
38
+ "director_uuid" => @manifest.director_uuid,
39
+ "releases" => filterd_releases,
40
+ "meta" => @manifest.meta
41
+ }.to_yaml
42
+ end
43
+
44
+ def filterd_releases
45
+ allowed_keys = %w[name version]
46
+ @manifest.releases.map do |release|
47
+ release.select { |key| allowed_keys.include?(key) }
48
+ end
49
+ end
50
+
51
+ def hidden_file_path(type)
52
+ File.join(hidden_dir_path(type), "#{@manifest.name}.yml")
53
+ end
54
+
55
+ def hidden_dir_path(name)
56
+ dir = File.join(@work_dir, ".#{name.to_s}")
57
+ Dir.mkdir(dir) unless File.exists? dir
58
+ dir
59
+ end
60
+
61
+ # TODO move to template validator which should have access to @work_dir
62
+ def template_path(template)
63
+ path = File.join(@work_dir, "templates", template)
64
+ err("Template does not exist: #{template}") unless File.exists? path
65
+ path
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,58 @@
1
+ require "git"
2
+ module Bosh::Manifests
3
+ class Release
4
+ def initialize(release, releases_dir)
5
+ @name = release["name"]
6
+ @version = release["version"]
7
+ @git_uri = release["git"]
8
+ @work_dir = File.join(releases_dir)
9
+ end
10
+
11
+ def checkout_current_version
12
+ repo.pull
13
+ repo.checkout(current_version_ref)
14
+ end
15
+
16
+ private
17
+
18
+ def repo
19
+ if File.directory?(repo_dir)
20
+ @repo ||= Git.open(repo_dir)
21
+ else
22
+ FileUtils.mkdir_p(@work_dir)
23
+ @repo = Git.clone(@git_uri, @name, path: @work_dir)
24
+ end
25
+ end
26
+
27
+ def current_version_ref
28
+ repo.log().object("releases/#{available_versions[current_version]}")
29
+ end
30
+
31
+ def current_version
32
+ if @version == "latest"
33
+ available_versions.keys.sort.last
34
+ else
35
+ version = @version.to_i
36
+ unless available_versions[version]
37
+ err("Could not find version: #{@version} for release: #{@name}")
38
+ end
39
+ version
40
+ end
41
+ end
42
+
43
+ # transforms releases/foo-1.yml, releases/bar-2.yml to:
44
+ # { "1" => foo-1.yml, "2" => bar-2.yml }
45
+ def available_versions
46
+ @available_versions ||= begin
47
+ Hash[Dir[File.join(repo_dir, "releases", "*.yml")].
48
+ reject { |f| f[/index.yml/] }.
49
+ map { |dir| File.basename(dir) }.
50
+ map { |version| [ version[/(\d+)/].to_i, version ] }]
51
+ end
52
+ end
53
+
54
+ def repo_dir
55
+ File.join(@work_dir, @name)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,14 @@
1
+ module Bosh::Manifests
2
+ class ReleaseManager
3
+ def initialize(releases, work_dir)
4
+ releases_dir = File.join(work_dir, ".releases")
5
+ @releases = releases.map { |r| Release.new(r, releases_dir) }
6
+ end
7
+
8
+ def update_release_repos
9
+ @releases.each do |release|
10
+ release.checkout_current_version
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Bosh
2
+ module Manifests
3
+ VERSION = "0.7.0"
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ module Bosh; module Manifests; end; end
2
+
3
+ require "cli/core_ext"
4
+ require "cli/validation"
5
+
6
+ require "bosh/workspace/helpers/spiff_helper"
7
+ require "bosh/workspace/helpers/project_deployment_helper"
8
+
9
+ require "bosh/workspace/manifest_builder"
10
+ require "bosh/workspace/release_manager"
11
+ require "bosh/workspace/release"
12
+ require "bosh/workspace/deployment_manifest"
13
+ require "bosh/workspace/version"
File without changes
File without changes
@@ -0,0 +1 @@
1
+ name: foo
@@ -0,0 +1 @@
1
+ name: bar
@@ -0,0 +1,4 @@
1
+ name: foo
2
+ templates:
3
+ - foo/bar.yml
4
+ meta: {}
File without changes