bosh_cli 0.16
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.
- data/README +4 -0
- data/Rakefile +55 -0
- data/bin/bosh +17 -0
- data/lib/cli.rb +76 -0
- data/lib/cli/cache.rb +44 -0
- data/lib/cli/changeset_helper.rb +142 -0
- data/lib/cli/command_definition.rb +52 -0
- data/lib/cli/commands/base.rb +245 -0
- data/lib/cli/commands/biff.rb +300 -0
- data/lib/cli/commands/blob.rb +125 -0
- data/lib/cli/commands/cloudcheck.rb +169 -0
- data/lib/cli/commands/deployment.rb +147 -0
- data/lib/cli/commands/job.rb +42 -0
- data/lib/cli/commands/job_management.rb +117 -0
- data/lib/cli/commands/log_management.rb +81 -0
- data/lib/cli/commands/maintenance.rb +131 -0
- data/lib/cli/commands/misc.rb +240 -0
- data/lib/cli/commands/package.rb +112 -0
- data/lib/cli/commands/property_management.rb +125 -0
- data/lib/cli/commands/release.rb +469 -0
- data/lib/cli/commands/ssh.rb +271 -0
- data/lib/cli/commands/stemcell.rb +184 -0
- data/lib/cli/commands/task.rb +213 -0
- data/lib/cli/commands/user.rb +28 -0
- data/lib/cli/commands/vms.rb +53 -0
- data/lib/cli/config.rb +154 -0
- data/lib/cli/core_ext.rb +145 -0
- data/lib/cli/dependency_helper.rb +62 -0
- data/lib/cli/deployment_helper.rb +263 -0
- data/lib/cli/deployment_manifest_compiler.rb +28 -0
- data/lib/cli/director.rb +633 -0
- data/lib/cli/director_task.rb +64 -0
- data/lib/cli/errors.rb +48 -0
- data/lib/cli/event_log_renderer.rb +351 -0
- data/lib/cli/job_builder.rb +226 -0
- data/lib/cli/package_builder.rb +254 -0
- data/lib/cli/packaging_helper.rb +248 -0
- data/lib/cli/release.rb +176 -0
- data/lib/cli/release_builder.rb +215 -0
- data/lib/cli/release_compiler.rb +178 -0
- data/lib/cli/release_tarball.rb +272 -0
- data/lib/cli/runner.rb +771 -0
- data/lib/cli/stemcell.rb +83 -0
- data/lib/cli/task_log_renderer.rb +40 -0
- data/lib/cli/templates/help_message.erb +75 -0
- data/lib/cli/validation.rb +42 -0
- data/lib/cli/version.rb +7 -0
- data/lib/cli/version_calc.rb +48 -0
- data/lib/cli/versions_index.rb +126 -0
- data/lib/cli/yaml_helper.rb +62 -0
- data/spec/assets/biff/bad_gateway_config.yml +28 -0
- data/spec/assets/biff/good_simple_config.yml +63 -0
- data/spec/assets/biff/good_simple_golden_config.yml +63 -0
- data/spec/assets/biff/good_simple_template.erb +69 -0
- data/spec/assets/biff/multiple_subnets_config.yml +40 -0
- data/spec/assets/biff/network_only_template.erb +34 -0
- data/spec/assets/biff/no_cc_config.yml +27 -0
- data/spec/assets/biff/no_range_config.yml +27 -0
- data/spec/assets/biff/no_subnet_config.yml +16 -0
- data/spec/assets/biff/ok_network_config.yml +30 -0
- data/spec/assets/biff/properties_template.erb +6 -0
- data/spec/assets/deployment.MF +0 -0
- data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
- data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
- data/spec/assets/release/jobs/cacher.tgz +0 -0
- data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
- data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
- data/spec/assets/release/jobs/cacher/job.MF +6 -0
- data/spec/assets/release/jobs/cacher/monit +1 -0
- data/spec/assets/release/jobs/cleaner.tgz +0 -0
- data/spec/assets/release/jobs/cleaner/job.MF +4 -0
- data/spec/assets/release/jobs/cleaner/monit +1 -0
- data/spec/assets/release/jobs/sweeper.tgz +0 -0
- data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
- data/spec/assets/release/jobs/sweeper/job.MF +5 -0
- data/spec/assets/release/jobs/sweeper/monit +1 -0
- data/spec/assets/release/packages/mutator.tar.gz +0 -0
- data/spec/assets/release/packages/stuff.tgz +0 -0
- data/spec/assets/release/release.MF +17 -0
- data/spec/assets/release_invalid_checksum.tgz +0 -0
- data/spec/assets/release_invalid_jobs.tgz +0 -0
- data/spec/assets/release_no_name.tgz +0 -0
- data/spec/assets/release_no_version.tgz +0 -0
- data/spec/assets/stemcell/image +1 -0
- data/spec/assets/stemcell/stemcell.MF +6 -0
- data/spec/assets/stemcell_invalid_mf.tgz +0 -0
- data/spec/assets/stemcell_no_image.tgz +0 -0
- data/spec/assets/valid_release.tgz +0 -0
- data/spec/assets/valid_stemcell.tgz +0 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/unit/base_command_spec.rb +66 -0
- data/spec/unit/biff_spec.rb +135 -0
- data/spec/unit/cache_spec.rb +36 -0
- data/spec/unit/cli_commands_spec.rb +481 -0
- data/spec/unit/config_spec.rb +139 -0
- data/spec/unit/core_ext_spec.rb +77 -0
- data/spec/unit/dependency_helper_spec.rb +52 -0
- data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
- data/spec/unit/director_spec.rb +511 -0
- data/spec/unit/director_task_spec.rb +48 -0
- data/spec/unit/event_log_renderer_spec.rb +171 -0
- data/spec/unit/hash_changeset_spec.rb +73 -0
- data/spec/unit/job_builder_spec.rb +454 -0
- data/spec/unit/package_builder_spec.rb +567 -0
- data/spec/unit/release_builder_spec.rb +65 -0
- data/spec/unit/release_spec.rb +66 -0
- data/spec/unit/release_tarball_spec.rb +33 -0
- data/spec/unit/runner_spec.rb +140 -0
- data/spec/unit/ssh_spec.rb +78 -0
- data/spec/unit/stemcell_spec.rb +17 -0
- data/spec/unit/version_calc_spec.rb +27 -0
- data/spec/unit/versions_index_spec.rb +132 -0
- metadata +338 -0
data/README
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
$:.unshift(File.expand_path("../../rake", __FILE__))
|
|
4
|
+
|
|
5
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __FILE__)
|
|
6
|
+
|
|
7
|
+
require "rubygems"
|
|
8
|
+
require "bundler"
|
|
9
|
+
|
|
10
|
+
Bundler.setup(:default, :test)
|
|
11
|
+
|
|
12
|
+
require "rake"
|
|
13
|
+
begin
|
|
14
|
+
require "rspec/core/rake_task"
|
|
15
|
+
rescue LoadError
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require "bundler_task"
|
|
19
|
+
require "ci_task"
|
|
20
|
+
|
|
21
|
+
if defined?(RSpec)
|
|
22
|
+
namespace :spec do
|
|
23
|
+
SPEC_OPTS = %w(--format progress --colour)
|
|
24
|
+
|
|
25
|
+
desc "Run unit tests"
|
|
26
|
+
unit_rspec_task = RSpec::Core::RakeTask.new(:unit) do |t|
|
|
27
|
+
t.pattern = "spec/unit/**/*_spec.rb"
|
|
28
|
+
t.rspec_opts = SPEC_OPTS
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
CiTask.new do |task|
|
|
32
|
+
task.rspec_task = unit_rspec_task
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Run tests"
|
|
37
|
+
task :spec => %w(spec:unit)
|
|
38
|
+
|
|
39
|
+
desc "Run tests for CIs"
|
|
40
|
+
task "spec:ci" => %w(spec:unit:ci)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
BundlerTask.new
|
|
44
|
+
|
|
45
|
+
gem_helper = Bundler::GemHelper.new(Dir.pwd)
|
|
46
|
+
|
|
47
|
+
desc "Build BOSH CLI gem into the pkg directory"
|
|
48
|
+
task "build" do
|
|
49
|
+
gem_helper.build_gem
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "Build and install BOSH CLI into system gems"
|
|
53
|
+
task "install_head" do
|
|
54
|
+
gem_helper.install_gem
|
|
55
|
+
end
|
data/bin/bosh
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
gemfile = File.expand_path("../../Gemfile", __FILE__)
|
|
4
|
+
|
|
5
|
+
if File.exists?(gemfile)
|
|
6
|
+
ENV["BUNDLE_GEMFILE"] = gemfile
|
|
7
|
+
require "rubygems"
|
|
8
|
+
require "bundler/setup"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
|
12
|
+
require "cli"
|
|
13
|
+
|
|
14
|
+
trap("INT") { puts "\nExiting..."; exit(1) }
|
|
15
|
+
|
|
16
|
+
Thread.abort_on_exception = true
|
|
17
|
+
Bosh::Cli::Runner.run(ARGV.dup)
|
data/lib/cli.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh
|
|
4
|
+
module Cli
|
|
5
|
+
DEFAULT_CONFIG_PATH = File.expand_path("~/.bosh_config")
|
|
6
|
+
DEFAULT_CACHE_DIR = File.expand_path("~/.bosh_cache")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
autoload :HTTPClient, "httpclient"
|
|
11
|
+
Bosh.autoload :Blobstore, "blobstore_client"
|
|
12
|
+
|
|
13
|
+
require "base64"
|
|
14
|
+
require "digest/sha1"
|
|
15
|
+
require "fileutils"
|
|
16
|
+
require "highline/import"
|
|
17
|
+
require "json"
|
|
18
|
+
require "monitor"
|
|
19
|
+
require "optparse"
|
|
20
|
+
require "ostruct"
|
|
21
|
+
require "pathname"
|
|
22
|
+
require "progressbar"
|
|
23
|
+
require "set"
|
|
24
|
+
require "tempfile"
|
|
25
|
+
require "terminal-table/import"
|
|
26
|
+
require "time"
|
|
27
|
+
require "timeout"
|
|
28
|
+
require "tmpdir"
|
|
29
|
+
require "uri"
|
|
30
|
+
require "yaml"
|
|
31
|
+
require "netaddr"
|
|
32
|
+
require "net/ssh"
|
|
33
|
+
require "net/scp"
|
|
34
|
+
require "net/ssh/gateway"
|
|
35
|
+
|
|
36
|
+
unless defined?(Bosh::Cli::VERSION)
|
|
37
|
+
require "cli/version"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
require "cli/config"
|
|
41
|
+
require "cli/core_ext"
|
|
42
|
+
require "cli/errors"
|
|
43
|
+
require "cli/yaml_helper"
|
|
44
|
+
require "cli/version_calc"
|
|
45
|
+
require "cli/dependency_helper"
|
|
46
|
+
require "cli/deployment_helper"
|
|
47
|
+
require "cli/validation"
|
|
48
|
+
require "cli/cache"
|
|
49
|
+
require "cli/stemcell"
|
|
50
|
+
require "cli/director"
|
|
51
|
+
require "cli/director_task"
|
|
52
|
+
|
|
53
|
+
require "cli/versions_index"
|
|
54
|
+
require "cli/packaging_helper"
|
|
55
|
+
require "cli/package_builder"
|
|
56
|
+
require "cli/job_builder"
|
|
57
|
+
require "cli/changeset_helper"
|
|
58
|
+
require "cli/task_log_renderer"
|
|
59
|
+
require "cli/event_log_renderer"
|
|
60
|
+
require "cli/deployment_manifest_compiler"
|
|
61
|
+
|
|
62
|
+
require "cli/release"
|
|
63
|
+
require "cli/release_builder"
|
|
64
|
+
require "cli/release_compiler"
|
|
65
|
+
require "cli/release_tarball"
|
|
66
|
+
|
|
67
|
+
require "cli/command_definition"
|
|
68
|
+
require "cli/runner"
|
|
69
|
+
|
|
70
|
+
YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE.yamler)
|
|
71
|
+
|
|
72
|
+
require File.expand_path(File.dirname(__FILE__) + "/cli/commands/base")
|
|
73
|
+
|
|
74
|
+
Dir[File.dirname(__FILE__) + "/cli/commands/*.rb"].each do |file|
|
|
75
|
+
require File.expand_path(file)
|
|
76
|
+
end
|
data/lib/cli/cache.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
class Cache
|
|
5
|
+
|
|
6
|
+
attr_reader :cache_dir
|
|
7
|
+
|
|
8
|
+
def initialize(cache_dir = nil)
|
|
9
|
+
@cache_dir = cache_dir || Bosh::Cli::DEFAULT_CACHE_DIR
|
|
10
|
+
|
|
11
|
+
if File.exists?(@cache_dir) && !File.directory?(@cache_dir)
|
|
12
|
+
raise CacheDirectoryError, "BOSH cache directory error: " +
|
|
13
|
+
"`#{@cache_dir}' is a file, not directory"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
unless File.exists?(@cache_dir)
|
|
17
|
+
FileUtils.mkdir_p(@cache_dir)
|
|
18
|
+
File.chmod(0700, @cache_dir)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def read(key)
|
|
23
|
+
cached_file = path(key)
|
|
24
|
+
return nil unless File.exists?(cached_file)
|
|
25
|
+
File.read(cached_file)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write(key, value)
|
|
29
|
+
File.open(path(key), "w") do |f|
|
|
30
|
+
f.write(value)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def path(key)
|
|
37
|
+
File.expand_path(hash_for(key), @cache_dir)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def hash_for(key)
|
|
41
|
+
Digest::SHA1.hexdigest(key)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
class HashChangeset
|
|
5
|
+
class FormatError < StandardError; end
|
|
6
|
+
|
|
7
|
+
attr_accessor :values
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@children = {}
|
|
11
|
+
@values = { :old => nil, :new => nil }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def [](key)
|
|
15
|
+
@children[key.to_s]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def keys
|
|
19
|
+
@children.keys
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def []=(key, value)
|
|
23
|
+
@children[key.to_s] = value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_hash(hash, as)
|
|
27
|
+
unless hash.is_a?(Hash)
|
|
28
|
+
raise FormatError, "Trying to add #{hash.class} to a changeset, " +
|
|
29
|
+
"Hash expected"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
self.values[as] = hash
|
|
33
|
+
|
|
34
|
+
hash.each_pair do |k, v|
|
|
35
|
+
self[k] ||= HashChangeset.new
|
|
36
|
+
self[k].values[as] = v
|
|
37
|
+
|
|
38
|
+
if v.is_a?(Hash)
|
|
39
|
+
self[k].add_hash(v, as)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def leaf?
|
|
45
|
+
@children.empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def each(&block)
|
|
49
|
+
@children.each_value { |v| yield v }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def summary(level = 0)
|
|
53
|
+
indent = " " * level
|
|
54
|
+
out = []
|
|
55
|
+
|
|
56
|
+
@children.each_pair do |k, v|
|
|
57
|
+
if v.state == :mismatch
|
|
58
|
+
out << indent + "#{k} type changed: ".yellow +
|
|
59
|
+
"#{v.old.class.to_s} -> #{v.new.class.to_s}"
|
|
60
|
+
out << diff(v.old, v.new, indent + " ")
|
|
61
|
+
elsif v.leaf?
|
|
62
|
+
case v.state
|
|
63
|
+
when :added
|
|
64
|
+
out << indent + "added #{k}: ".yellow + v.new.to_s
|
|
65
|
+
when :removed
|
|
66
|
+
out << indent + "removed #{k}: ".red + v.old.to_s
|
|
67
|
+
when :changed
|
|
68
|
+
out << indent + "changed #{k}: ".yellow
|
|
69
|
+
out << diff(v.old, v.new, indent + " ")
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
# TODO: track renames?
|
|
73
|
+
child_summary = v.summary(level + 1)
|
|
74
|
+
|
|
75
|
+
unless child_summary.empty?
|
|
76
|
+
out << indent + k
|
|
77
|
+
out << child_summary
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
out
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def diff(old_value, new_value, indent)
|
|
85
|
+
old_value = [old_value] unless old_value.kind_of?(Array)
|
|
86
|
+
new_value = [new_value] unless new_value.kind_of?(Array)
|
|
87
|
+
|
|
88
|
+
added = new_value - old_value
|
|
89
|
+
removed = old_value - new_value
|
|
90
|
+
|
|
91
|
+
lines = []
|
|
92
|
+
|
|
93
|
+
# Ruby 1.8 has ugly Hash#to_s, hence the normalization
|
|
94
|
+
|
|
95
|
+
removed.each do |line|
|
|
96
|
+
line = line.inspect if line.is_a?(Hash)
|
|
97
|
+
lines << "#{indent}- #{line}".red
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
added.each do |line|
|
|
101
|
+
line = line.inspect if line.is_a?(Hash)
|
|
102
|
+
lines << "#{indent}+ #{line}".green
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
lines.join("\n")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def old
|
|
109
|
+
@values[:old]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def new
|
|
113
|
+
@values[:new]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def state
|
|
117
|
+
if old.nil? && new.nil?
|
|
118
|
+
:none
|
|
119
|
+
elsif old.nil? && !new.nil?
|
|
120
|
+
:added
|
|
121
|
+
elsif !old.nil? && new.nil?
|
|
122
|
+
:removed
|
|
123
|
+
elsif old.class != new.class && !(boolean?(old) && boolean?(new))
|
|
124
|
+
:mismatch
|
|
125
|
+
elsif old == new
|
|
126
|
+
:same
|
|
127
|
+
else
|
|
128
|
+
:changed
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
[:added, :removed, :mismatch, :changed, :same].each do |s|
|
|
133
|
+
define_method("#{s}?".to_sym) do
|
|
134
|
+
state == s
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def boolean?(value)
|
|
139
|
+
value.kind_of?(TrueClass) || value.kind_of?(FalseClass)
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
class CommandDefinition
|
|
5
|
+
attr_reader :options
|
|
6
|
+
attr_reader :power_options
|
|
7
|
+
attr_reader :keywords
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@options = []
|
|
11
|
+
@power_options = []
|
|
12
|
+
@hints = []
|
|
13
|
+
@keywords = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def usage(str = nil)
|
|
17
|
+
if str
|
|
18
|
+
@usage = str.strip
|
|
19
|
+
@keywords = str.split(/\s+/).select do |word|
|
|
20
|
+
word.match(/^[a-z]/i)
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
@usage
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def description(str = nil)
|
|
28
|
+
str ? (@description = str.to_s.strip) : @description
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
alias :desc :description
|
|
32
|
+
|
|
33
|
+
def option(name, value = "")
|
|
34
|
+
@options << [name, value]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def power_option(name, value = "")
|
|
38
|
+
@power_options << [name, value]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def route(*args, &block)
|
|
42
|
+
if args.size > 0
|
|
43
|
+
@route = args
|
|
44
|
+
elsif block_given?
|
|
45
|
+
@route = block
|
|
46
|
+
else
|
|
47
|
+
@route
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Copyright (c) 2009-2012 VMware, Inc.
|
|
2
|
+
|
|
3
|
+
module Bosh::Cli
|
|
4
|
+
module Command
|
|
5
|
+
class Base
|
|
6
|
+
BLOBS_DIR = "blobs"
|
|
7
|
+
BLOBS_INDEX_FILE = "blob_index.yml"
|
|
8
|
+
|
|
9
|
+
attr_reader :cache, :config, :options, :work_dir
|
|
10
|
+
attr_accessor :out, :usage
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@options = options.dup
|
|
14
|
+
@work_dir = Dir.pwd
|
|
15
|
+
config_file = @options[:config] || Bosh::Cli::DEFAULT_CONFIG_PATH
|
|
16
|
+
cache_dir = @options[:cache_dir] || Bosh::Cli::DEFAULT_CACHE_DIR
|
|
17
|
+
@config = Config.new(config_file)
|
|
18
|
+
@cache = Cache.new(cache_dir)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
attr_reader :commands
|
|
23
|
+
|
|
24
|
+
def command(name, &block)
|
|
25
|
+
@commands ||= {}
|
|
26
|
+
@commands[name] = block
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def director
|
|
31
|
+
@director ||= Bosh::Cli::Director.new(target, username, password)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def release
|
|
35
|
+
return @release if @release
|
|
36
|
+
check_if_release_dir
|
|
37
|
+
@release = Bosh::Cli::Release.new(@work_dir)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def blobstore
|
|
41
|
+
release.blobstore
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def logged_in?
|
|
45
|
+
username && password
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def non_interactive?
|
|
49
|
+
!interactive?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def interactive?
|
|
53
|
+
!options[:non_interactive]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def verbose?
|
|
57
|
+
options[:verbose]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# TODO: implement it
|
|
61
|
+
def dry_run?
|
|
62
|
+
options[:dry_run]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def show_usage
|
|
66
|
+
say("Usage: #{@usage}") if @usage
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def run(namespace, action, *args)
|
|
70
|
+
eval(namespace.to_s.capitalize).new(options).send(action.to_sym, *args)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def redirect(*args)
|
|
74
|
+
run(*args)
|
|
75
|
+
raise Bosh::Cli::GracefulExit, "redirected to %s" % [args.join(" ")]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def confirmed?(question = "Are you sure?")
|
|
79
|
+
non_interactive? ||
|
|
80
|
+
ask("#{question} (type 'yes' to continue): ") == "yes"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
[:username, :password, :target, :deployment].each do |attr_name|
|
|
84
|
+
define_method attr_name do
|
|
85
|
+
config.send(attr_name)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
alias_method :target_url, :target
|
|
90
|
+
|
|
91
|
+
def target_name
|
|
92
|
+
config.target_name || target_url
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def target_version
|
|
96
|
+
config.target_version ? "Ver: " + config.target_version : ""
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def full_target_name
|
|
100
|
+
# TODO refactor this method
|
|
101
|
+
ret = (target_name.blank? || target_name == target_url ?
|
|
102
|
+
target_name : "%s (%s)" % [target_name, target_url])
|
|
103
|
+
ret + " %s" % target_version if ret
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Returns whether there is currently a task running. A wrapper for the
|
|
108
|
+
# director.rb method.
|
|
109
|
+
#
|
|
110
|
+
# @return [Boolean] Whether there is a task currently running.
|
|
111
|
+
def task_running?
|
|
112
|
+
director.has_current?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Cancels the task currently running. A wrapper for the director.rb
|
|
117
|
+
# method.
|
|
118
|
+
def cancel_current_task
|
|
119
|
+
director.cancel_current
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
protected
|
|
123
|
+
|
|
124
|
+
def auth_required
|
|
125
|
+
target_required
|
|
126
|
+
err("Please log in first") unless logged_in?
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def target_required
|
|
130
|
+
err("Please choose target first") if target.nil?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def deployment_required
|
|
134
|
+
err("Please choose deployment first") if deployment.nil?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def check_if_release_dir
|
|
138
|
+
unless in_release_dir?
|
|
139
|
+
err("Sorry, your current directory doesn't look " +
|
|
140
|
+
"like release directory")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def check_if_dirty_state
|
|
145
|
+
if dirty_state?
|
|
146
|
+
say("\n%s\n" % [`git status`])
|
|
147
|
+
err("Your current directory has some local modifications, " +
|
|
148
|
+
"please discard or commit them first")
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def in_release_dir?
|
|
153
|
+
File.directory?("packages") &&
|
|
154
|
+
File.directory?("jobs") &&
|
|
155
|
+
File.directory?("src")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def dirty_state?
|
|
159
|
+
`which git`
|
|
160
|
+
return false unless $? == 0
|
|
161
|
+
File.directory?(".git") && `git status --porcelain | wc -l`.to_i > 0
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def normalize_url(url)
|
|
165
|
+
url = "http://#{url}" unless url.match(/^https?/)
|
|
166
|
+
URI.parse(url).to_s
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def check_if_blobs_supported
|
|
170
|
+
check_if_release_dir
|
|
171
|
+
unless File.directory?(BLOBS_DIR)
|
|
172
|
+
err("Can't find blob directory '#{BLOBS_DIR}'.")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
unless File.file?(BLOBS_INDEX_FILE)
|
|
176
|
+
err("Can't find '#{BLOBS_INDEX_FILE}'")
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def check_dirty_blobs
|
|
181
|
+
if File.file?(File.join(work_dir, BLOBS_INDEX_FILE)) && blob_status != 0
|
|
182
|
+
err("Your 'blobs' directory is not in sync. " +
|
|
183
|
+
"Resolve using 'bosh sync blobs' command")
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def get_blobs_index
|
|
188
|
+
load_yaml_file(File.join(work_dir, BLOBS_INDEX_FILE))
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def blob_status(verbose = false)
|
|
192
|
+
check_if_blobs_supported
|
|
193
|
+
untracked = []
|
|
194
|
+
modified = []
|
|
195
|
+
tracked= []
|
|
196
|
+
unsynced = []
|
|
197
|
+
|
|
198
|
+
local_blobs = {}
|
|
199
|
+
Dir.chdir(BLOBS_DIR) do
|
|
200
|
+
Dir.glob("**/*").select { |entry| File.file?(entry) }.each do |file|
|
|
201
|
+
local_blobs[file] = Digest::SHA1.file(file).hexdigest
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
remote_blobs = get_blobs_index
|
|
205
|
+
|
|
206
|
+
local_blobs.each do |blob_name, blob_sha|
|
|
207
|
+
if remote_blobs[blob_name].nil?
|
|
208
|
+
untracked << blob_name
|
|
209
|
+
elsif blob_sha != remote_blobs[blob_name]["sha"]
|
|
210
|
+
modified << blob_name
|
|
211
|
+
else
|
|
212
|
+
tracked << blob_name
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
remote_blobs.each_key do |blob_name|
|
|
217
|
+
unsynced << blob_name if local_blobs[blob_name].nil?
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
changes = modified.size + untracked.size + unsynced.size
|
|
221
|
+
return changes unless verbose
|
|
222
|
+
|
|
223
|
+
if modified.size > 0
|
|
224
|
+
say("\nModified blobs ('bosh upload blob' to update): ".green)
|
|
225
|
+
modified.each { |blob| say(blob) }
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
if untracked.size > 0
|
|
229
|
+
say("\nNew blobs ('bosh upload blob' to add): ".green)
|
|
230
|
+
untracked.each { |blob| say(blob) }
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if unsynced.size > 0
|
|
234
|
+
say("\nMissing blobs ('bosh sync blobs' to fetch) : ".green)
|
|
235
|
+
unsynced.each { |blob| say(blob) }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
if changes == 0
|
|
239
|
+
say("\nRelease blobs are up to date".green)
|
|
240
|
+
end
|
|
241
|
+
changes
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|