bosh-workspace 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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