bosh_cli 0.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. data/README +4 -0
  2. data/Rakefile +55 -0
  3. data/bin/bosh +17 -0
  4. data/lib/cli.rb +76 -0
  5. data/lib/cli/cache.rb +44 -0
  6. data/lib/cli/changeset_helper.rb +142 -0
  7. data/lib/cli/command_definition.rb +52 -0
  8. data/lib/cli/commands/base.rb +245 -0
  9. data/lib/cli/commands/biff.rb +300 -0
  10. data/lib/cli/commands/blob.rb +125 -0
  11. data/lib/cli/commands/cloudcheck.rb +169 -0
  12. data/lib/cli/commands/deployment.rb +147 -0
  13. data/lib/cli/commands/job.rb +42 -0
  14. data/lib/cli/commands/job_management.rb +117 -0
  15. data/lib/cli/commands/log_management.rb +81 -0
  16. data/lib/cli/commands/maintenance.rb +131 -0
  17. data/lib/cli/commands/misc.rb +240 -0
  18. data/lib/cli/commands/package.rb +112 -0
  19. data/lib/cli/commands/property_management.rb +125 -0
  20. data/lib/cli/commands/release.rb +469 -0
  21. data/lib/cli/commands/ssh.rb +271 -0
  22. data/lib/cli/commands/stemcell.rb +184 -0
  23. data/lib/cli/commands/task.rb +213 -0
  24. data/lib/cli/commands/user.rb +28 -0
  25. data/lib/cli/commands/vms.rb +53 -0
  26. data/lib/cli/config.rb +154 -0
  27. data/lib/cli/core_ext.rb +145 -0
  28. data/lib/cli/dependency_helper.rb +62 -0
  29. data/lib/cli/deployment_helper.rb +263 -0
  30. data/lib/cli/deployment_manifest_compiler.rb +28 -0
  31. data/lib/cli/director.rb +633 -0
  32. data/lib/cli/director_task.rb +64 -0
  33. data/lib/cli/errors.rb +48 -0
  34. data/lib/cli/event_log_renderer.rb +351 -0
  35. data/lib/cli/job_builder.rb +226 -0
  36. data/lib/cli/package_builder.rb +254 -0
  37. data/lib/cli/packaging_helper.rb +248 -0
  38. data/lib/cli/release.rb +176 -0
  39. data/lib/cli/release_builder.rb +215 -0
  40. data/lib/cli/release_compiler.rb +178 -0
  41. data/lib/cli/release_tarball.rb +272 -0
  42. data/lib/cli/runner.rb +771 -0
  43. data/lib/cli/stemcell.rb +83 -0
  44. data/lib/cli/task_log_renderer.rb +40 -0
  45. data/lib/cli/templates/help_message.erb +75 -0
  46. data/lib/cli/validation.rb +42 -0
  47. data/lib/cli/version.rb +7 -0
  48. data/lib/cli/version_calc.rb +48 -0
  49. data/lib/cli/versions_index.rb +126 -0
  50. data/lib/cli/yaml_helper.rb +62 -0
  51. data/spec/assets/biff/bad_gateway_config.yml +28 -0
  52. data/spec/assets/biff/good_simple_config.yml +63 -0
  53. data/spec/assets/biff/good_simple_golden_config.yml +63 -0
  54. data/spec/assets/biff/good_simple_template.erb +69 -0
  55. data/spec/assets/biff/multiple_subnets_config.yml +40 -0
  56. data/spec/assets/biff/network_only_template.erb +34 -0
  57. data/spec/assets/biff/no_cc_config.yml +27 -0
  58. data/spec/assets/biff/no_range_config.yml +27 -0
  59. data/spec/assets/biff/no_subnet_config.yml +16 -0
  60. data/spec/assets/biff/ok_network_config.yml +30 -0
  61. data/spec/assets/biff/properties_template.erb +6 -0
  62. data/spec/assets/deployment.MF +0 -0
  63. data/spec/assets/plugins/bosh/cli/commands/echo.rb +43 -0
  64. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +24 -0
  65. data/spec/assets/release/jobs/cacher.tgz +0 -0
  66. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  67. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  68. data/spec/assets/release/jobs/cacher/job.MF +6 -0
  69. data/spec/assets/release/jobs/cacher/monit +1 -0
  70. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  71. data/spec/assets/release/jobs/cleaner/job.MF +4 -0
  72. data/spec/assets/release/jobs/cleaner/monit +1 -0
  73. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  74. data/spec/assets/release/jobs/sweeper/config/test.conf +1 -0
  75. data/spec/assets/release/jobs/sweeper/job.MF +5 -0
  76. data/spec/assets/release/jobs/sweeper/monit +1 -0
  77. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  78. data/spec/assets/release/packages/stuff.tgz +0 -0
  79. data/spec/assets/release/release.MF +17 -0
  80. data/spec/assets/release_invalid_checksum.tgz +0 -0
  81. data/spec/assets/release_invalid_jobs.tgz +0 -0
  82. data/spec/assets/release_no_name.tgz +0 -0
  83. data/spec/assets/release_no_version.tgz +0 -0
  84. data/spec/assets/stemcell/image +1 -0
  85. data/spec/assets/stemcell/stemcell.MF +6 -0
  86. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  87. data/spec/assets/stemcell_no_image.tgz +0 -0
  88. data/spec/assets/valid_release.tgz +0 -0
  89. data/spec/assets/valid_stemcell.tgz +0 -0
  90. data/spec/spec_helper.rb +25 -0
  91. data/spec/unit/base_command_spec.rb +66 -0
  92. data/spec/unit/biff_spec.rb +135 -0
  93. data/spec/unit/cache_spec.rb +36 -0
  94. data/spec/unit/cli_commands_spec.rb +481 -0
  95. data/spec/unit/config_spec.rb +139 -0
  96. data/spec/unit/core_ext_spec.rb +77 -0
  97. data/spec/unit/dependency_helper_spec.rb +52 -0
  98. data/spec/unit/deployment_manifest_compiler_spec.rb +63 -0
  99. data/spec/unit/director_spec.rb +511 -0
  100. data/spec/unit/director_task_spec.rb +48 -0
  101. data/spec/unit/event_log_renderer_spec.rb +171 -0
  102. data/spec/unit/hash_changeset_spec.rb +73 -0
  103. data/spec/unit/job_builder_spec.rb +454 -0
  104. data/spec/unit/package_builder_spec.rb +567 -0
  105. data/spec/unit/release_builder_spec.rb +65 -0
  106. data/spec/unit/release_spec.rb +66 -0
  107. data/spec/unit/release_tarball_spec.rb +33 -0
  108. data/spec/unit/runner_spec.rb +140 -0
  109. data/spec/unit/ssh_spec.rb +78 -0
  110. data/spec/unit/stemcell_spec.rb +17 -0
  111. data/spec/unit/version_calc_spec.rb +27 -0
  112. data/spec/unit/versions_index_spec.rb +132 -0
  113. metadata +338 -0
data/README ADDED
@@ -0,0 +1,4 @@
1
+ Copyright (c) 2009-2012 VMware, Inc.
2
+
3
+ BOSH CLI is a command-line interface to VMware AppCloud Outer Shell.
4
+
@@ -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
@@ -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)
@@ -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
@@ -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