Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge "CLI 1.0.rc1"

  • Loading branch information...
commit 9f0cb28674cf297b8ec94a12bb746582dfdc41d9 2 parents 6bb0315 + ddfbc7f
@olegshaldybin olegshaldybin authored Gerrit Code Review committed
Showing with 1,614 additions and 2,412 deletions.
  1. +1 −1  cli/Gemfile.lock
  2. +8 −6 cli/lib/cli.rb
  3. +38 −44 cli/lib/cli/{commands/base.rb → base_command.rb}
  4. +0 −52 cli/lib/cli/command_definition.rb
  5. +40 −0 cli/lib/cli/command_discovery.rb
  6. +135 −0 cli/lib/cli/command_handler.rb
  7. +15 −18 cli/lib/cli/commands/biff.rb
  8. +10 −19 cli/lib/cli/commands/blob_management.rb
  9. +13 −18 cli/lib/cli/commands/cloudcheck.rb
  10. +29 −0 cli/lib/cli/commands/complete.rb
  11. +69 −94 cli/lib/cli/commands/deployment.rb
  12. +96 −0 cli/lib/cli/commands/help.rb
  13. +3 −3 cli/lib/cli/commands/job.rb
  14. +36 −41 cli/lib/cli/commands/job_management.rb
  15. +11 −17 cli/lib/cli/commands/job_rename.rb
  16. +28 −39 cli/lib/cli/commands/log_management.rb
  17. +6 −6 cli/lib/cli/commands/maintenance.rb
  18. +129 −132 cli/lib/cli/commands/misc.rb
  19. +6 −71 cli/lib/cli/commands/package.rb
  20. +20 −22 cli/lib/cli/commands/property_management.rb
  21. +155 −189 cli/lib/cli/commands/release.rb
  22. +176 −214 cli/lib/cli/commands/ssh.rb
  23. +90 −94 cli/lib/cli/commands/stemcell.rb
  24. +70 −86 cli/lib/cli/commands/task.rb
  25. +5 −5 cli/lib/cli/commands/user.rb
  26. +17 −18 cli/lib/cli/commands/vms.rb
  27. +27 −1 cli/lib/cli/config.rb
  28. +27 −1 cli/lib/cli/core_ext.rb
  29. +7 −5 cli/lib/cli/director.rb
  30. +118 −699 cli/lib/cli/runner.rb
  31. +0 −82 cli/lib/cli/templates/help_message.erb
  32. +1 −1  cli/lib/cli/version.rb
  33. +32 −37 cli/spec/unit/base_command_spec.rb
  34. +9 −6 cli/spec/unit/biff_spec.rb
  35. +71 −63 cli/spec/unit/cli_commands_spec.rb
  36. +1 −1  cli/spec/unit/core_ext_spec.rb
  37. +2 −2 cli/spec/unit/job_builder_spec.rb
  38. +7 −6 cli/spec/unit/job_rename_spec.rb
  39. +2 −2 cli/spec/unit/package_builder_spec.rb
  40. +1 −151 cli/spec/unit/runner_spec.rb
  41. +15 −9 cli/spec/unit/ssh_spec.rb
  42. +85 −155 spec/cli_spec.rb
  43. +3 −2 spec/spec_helper.rb
View
2  cli/Gemfile.lock
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
- bosh_cli (0.20)
+ bosh_cli (1.0.rc1)
blobstore_client (~> 0.4.0)
bosh_common (>= 0.5.1)
highline (~> 1.6.2)
View
14 cli/lib/cli.rb
@@ -3,7 +3,7 @@
module Bosh
module Cli
DEFAULT_CONFIG_PATH = File.expand_path("~/.bosh_config")
- DEFAULT_CACHE_DIR = File.expand_path("~/.bosh_cache")
+ DEFAULT_CACHE_DIR = File.expand_path("~/.bosh_cache")
end
end
@@ -74,17 +74,19 @@ module Cli
require "cli/job_property_collection"
require "cli/job_property_validator"
-require "cli/command_definition"
+require "cli/command_discovery"
+require "cli/command_handler"
require "cli/runner"
+require "cli/base_command"
-YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE.yamler)
+if defined?(YAML::ENGINE.yamler)
+ YAML::ENGINE.yamler = "syck"
+end
tmpdir = Dir.mktmpdir
at_exit { FileUtils.rm_rf(tmpdir) }
ENV["TMPDIR"] = tmpdir
-require File.expand_path(File.dirname(__FILE__) + "/cli/commands/base")
-
Dir[File.dirname(__FILE__) + "/cli/commands/*.rb"].each do |file|
- require File.expand_path(file)
+ require file
end
View
82 cli/lib/cli/commands/base.rb → cli/lib/cli/base_command.rb
@@ -3,31 +3,50 @@
module Bosh::Cli
module Command
class Base
- attr_reader :cache, :config, :options, :work_dir
- attr_accessor :out, :usage
+ extend Bosh::Cli::CommandDiscovery
+
+ attr_reader :options
+ attr_reader :work_dir
+ attr_reader :runner
+
+ attr_accessor :out
+
+ # @return [Array] Arguments passed to command handler
+ attr_accessor :args
DEFAULT_DIRECTOR_PORT = 25555
- def initialize(options = {})
- @options = options.dup
+ # @param [Bosh::Cli::Runner] runner
+ def initialize(runner = nil)
+ @runner = runner
+ @options = {}
@work_dir = Dir.pwd
- config_file = @options[:config] || Bosh::Cli::DEFAULT_CONFIG_PATH
- @config = Config.new(config_file)
- @cache = Config.cache
@exit_code = 0
@out = nil
- @usage = nil
+ @args = []
end
- class << self
- attr_reader :commands
+ # @return [Bosh::Cli::Cache] Current CLI cache
+ def cache
+ Config.cache
+ end
- def command(name, &block)
- @commands ||= {}
- @commands[name] = block
+ # @return [Bosh::Cli::Config] Current configuration
+ def config
+ @config ||= begin
+ config_file = options[:config] || Bosh::Cli::DEFAULT_CONFIG_PATH
+ Bosh::Cli::Config.new(config_file)
end
end
+ def add_option(name, value)
+ @options[name] = value
+ end
+
+ def remove_option(name)
+ @options.delete(name)
+ end
+
def director
@director ||= Bosh::Cli::Director.new(target, username, password)
end
@@ -51,38 +70,24 @@ def logged_in?
end
def non_interactive?
- !interactive?
+ options[:non_interactive]
end
def interactive?
- !options[:non_interactive]
+ !non_interactive?
end
def verbose?
- options[:verbose]
- end
-
- # TODO: implement it
- def dry_run?
- options[:dry_run]
- end
-
- def show_usage
- say("Usage: #{@usage}") if @usage
- end
-
- def run(namespace, action, *args)
- eval(namespace.to_s.capitalize).new(options).send(action.to_sym, *args)
+ @options[:verbose]
end
def redirect(*args)
- run(*args)
- raise Bosh::Cli::GracefulExit, "redirected to %s" % [args.join(" ")]
+ Bosh::Cli::Runner.new(args, @options).run
end
def confirmed?(question = "Are you sure?")
- non_interactive? ||
- ask("#{question} (type 'yes' to continue): ") == "yes"
+ return true if non_interactive?
+ ask("#{question} (type 'yes' to continue): ") == "yes"
end
# @return [String] Target director URL
@@ -112,17 +117,6 @@ def target_name
config.target_name || target_url
end
- def target_version
- config.target_version ? "Ver: " + config.target_version : ""
- end
-
- def full_target_name
- # TODO refactor this method
- ret = (target_name.blank? || target_name == target_url ?
- target_name : "%s (%s)" % [target_name, target_url])
- ret + " %s" % target_version if ret
- end
-
# Sets or returns command exit code
# @param [optional,Integer] code If param is given, sets exit code. If
# it's nil, returns previously set exit_code
View
52 cli/lib/cli/command_definition.rb
@@ -1,52 +0,0 @@
-# Copyright (c) 2009-2012 VMware, Inc.
-
-module Bosh::Cli
- class CommandDefinition
- attr_reader :options
- attr_reader :power_options
- attr_reader :keywords
-
- def initialize
- @options = []
- @power_options = []
- @hints = []
- @keywords = []
- end
-
- def usage(str = nil)
- if str
- @usage = str.strip
- @keywords = str.split(/\s+/).select do |word|
- word.match(/^[a-z]/i)
- end
- else
- @usage
- end
- end
-
- def description(str = nil)
- str ? (@description = str.to_s.strip) : @description
- end
-
- alias :desc :description
-
- def option(name, value = "")
- @options << [name, value]
- end
-
- def power_option(name, value = "")
- @power_options << [name, value]
- end
-
- def route(*args, &block)
- if args.size > 0
- @route = args
- elsif block_given?
- @route = block
- else
- @route
- end
- end
- end
-end
-
View
40 cli/lib/cli/command_discovery.rb
@@ -0,0 +1,40 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Cli
+ module CommandDiscovery
+
+ def usage(string = nil)
+ @usage = string
+ end
+
+ def desc(string)
+ @desc = string
+ end
+
+ def option(name, *args)
+ (@options ||= []) << [name, args]
+ end
+
+ # @param [Symbol] method_name Method name
+ def method_added(method_name)
+ if @usage && @desc
+ @options ||= []
+ method = instance_method(method_name)
+ register_command(method, @usage, @desc, @options)
+ end
+ @usage = nil
+ @desc = nil
+ @options = []
+ end
+
+ # @param [UnboundMethod] method Method implementing the command
+ # @param [String] usage Command usage (used to parse command)
+ # @param [String] desc Command description
+ # @param [Array] options Command options
+ def register_command(method, usage, desc, options = [])
+ command = CommandHandler.new(self, method, usage, desc, options)
+ Bosh::Cli::Config.register_command(command)
+ end
+
+ end
+end
View
135 cli/lib/cli/command_handler.rb
@@ -0,0 +1,135 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Cli
+ class CommandHandler
+
+ # @return [Array]
+ attr_reader :keywords
+
+ # @return [String]
+ attr_reader :usage
+
+ # @return [String]
+ attr_reader :desc
+
+ # @return [Bosh::Cli::Runner]
+ attr_accessor :runner
+
+ # @param [Class] klass
+ # @param [UnboundMethod] method
+ # @param [String] usage
+ # @param [String] desc
+ def initialize(klass, method, usage, desc, options = [])
+ @klass = klass
+ @method = method
+ @usage = usage
+ @desc = desc
+
+ @options = options
+
+ @hints = []
+ @keywords = []
+
+ @parser = OptionParser.new
+ @runner = nil
+ extract_keywords
+ end
+
+ # Run handler with provided args
+ # @param [Array] args
+ # @return [Integer] Command exit code
+ def run(args, extra_options = {})
+ handler = @klass.new(@runner)
+
+ @options.each do |(name, arguments)|
+ @parser.on(name, *arguments) do |value|
+ handler.add_option(format_option_name(name), value)
+ end
+ end
+
+ extra_options.each_pair do |name, value|
+ handler.add_option(format_option_name(name), value)
+ end
+
+ args = parse_options(args)
+
+ begin
+ handler.send(@method.name, *args)
+ handler.exit_code
+ rescue ArgumentError => e
+ say(e.message)
+ err("Usage: #{usage_with_params}")
+ end
+ end
+
+ def usage_with_params
+ result = [@usage]
+ @method.parameters.each do |parameter|
+ next if parameter.size < 2
+ kind, name = parameter
+ if kind == :opt
+ result << "[<#{name}>]"
+ elsif kind == :req
+ result << "<#{name}>"
+ end
+ end
+
+ @options.each do |(name, _)|
+ result << "[#{name}]"
+ end
+
+ result.join(" ")
+ end
+
+ def has_options?
+ @options.size > 0
+ end
+
+ def options_summary
+ result = []
+ padding = 5
+
+ margin = @options.inject(0) do |max, (name, _)|
+ [max, name.size].max
+ end
+
+ @options.each do |(name, desc)|
+ desc = desc.select { |word| word.is_a?(String) }
+ column_width = terminal_width - padding - margin
+
+ result << name.ljust(margin).yellow + " " +
+ desc.join(" ").columnize(
+ column_width, [margin + 1, name.size + 1].max)
+ end
+
+ result.join("\n")
+ end
+
+ # @param [Array] args Arguments to parse
+ def parse_options(args)
+ @parser.parse!(args)
+ end
+
+ private
+
+ def format_option_name(name)
+ case name
+ when Symbol
+ name
+ when String
+ name.split(/\s+/)[0].gsub(/^-*/, "").gsub("-", "_").to_sym
+ else
+ name
+ end
+ end
+
+ def extract_keywords
+ words = @usage.split(/\s+/)
+ words.each do |word|
+ break unless word.match(/^[a-z]/i)
+ @keywords << word
+ end
+ end
+
+ end
+end
View
33 cli/lib/cli/commands/biff.rb
@@ -9,28 +9,25 @@ class Biff < Base
# @param [String] template The string path to the template that should be
# used.
#
- # usage "diff [<template_file>]"
- # desc "Diffs your current BOSH deployment configuration against " +
- # "the specified BOSH deployment configuration template so that " +
- # "you can keep your deployment configuration file up to date. " +
- # "A dev template can be found in deployments repos."
- # route :biff, :biff
+ usage "diff"
+ desc "Diffs your current BOSH deployment configuration against " +
+ "the specified BOSH deployment configuration template so that " +
+ "you can keep your deployment configuration file up to date. " +
+ "A dev template can be found in deployments repos."
def biff(template)
- begin
- setup(template)
+ setup(template)
- template_to_fill = ERB.new(File.read(@template_file), 0, "%<>-")
- @template_output = template_to_fill.result(binding)
+ template_to_fill = ERB.new(File.read(@template_file), 0, "%<>-")
+ @template_output = template_to_fill.result(binding)
- if @errors == 0
- print_string_diff(File.read(@deployment_file), @template_output)
- keep_new_file unless @no_differences
- else
- err("There were #{@errors} errors.")
- end
- ensure
- delete_temp_diff_files
+ if @errors == 0
+ print_string_diff(File.read(@deployment_file), @template_output)
+ keep_new_file unless @no_differences
+ else
+ err("There were #{@errors} errors.")
end
+ ensure
+ delete_temp_diff_files
end
private
View
29 cli/lib/cli/commands/blob_management.rb
@@ -4,10 +4,8 @@ module Bosh::Cli::Command
class BlobManagement < Base
# Prints out blobs status
- #
- # usage "blobs"
- # desc "Print current blobs status"
- # route :blob_management, :status
+ usage "blobs"
+ desc "Print current blobs status"
def status
blob_manager.print_status
end
@@ -15,15 +13,13 @@ def status
# Adds blob to managed blobs
# @param [String] local_path Local file path
# @param [optional, String] blob_dir Directory to store blob in, relative
- # to blobs dir
- #
- # usage "add blob <local_path> [<blob_dir>]"
- # desc "Add a local file as BOSH blob"
- # route :blob_management, :add
+ # to blobs dir
+ usage "add blob"
+ desc "Add a local file as BOSH blob"
def add(local_path, blob_dir = nil)
blob_path = File.basename(local_path)
if blob_dir
- # We don't need about blobs prefix,
+ # We don't need 'blobs/' prefix,
# but it might be handy for people who rely on auto-completion
if blob_dir[0..5] == "blobs/"
blob_dir = blob_dir[6..-1]
@@ -34,10 +30,8 @@ def add(local_path, blob_dir = nil)
end
# Uploads all blobs that need to be uploaded
- #
- # usage "upload blobs"
- # desc "Upload new and updated blobs to the blobstore"
- # route :blob_management, :upload
+ usage "upload blobs"
+ desc "Upload new and updated blobs to the blobstore"
def upload
blob_manager.print_status
@@ -50,14 +44,11 @@ def upload
end
# Syncs blobs with blobstore
- #
- # usage "sync blobs"
- # desc "Sync blob with the blobstore"
- # route :blob_management, :sync
+ usage "sync blobs"
+ desc "Sync blob with the blobstore"
def sync
blob_manager.sync
blob_manager.print_status
end
-
end
end
View
31 cli/lib/cli/commands/cloudcheck.rb
@@ -4,30 +4,25 @@ module Bosh::Cli::Command
class CloudCheck < Base
include Bosh::Cli::DeploymentHelper
- # usage "cloudcheck [<deployment>]"
- # desc "Cloud consistency check and interactive repair"
- # option "--auto", "resolve problems automatically " +
- # "(not recommended for production)"
- # option "--report", "generate report only, " +
- # "don't attempt to resolve problems"
- # route :cloud_check, :perform
- def perform(*options)
+ # bosh cloudcheck
+ usage "cloudcheck"
+ desc "Cloud consistency check and interactive repair"
+ option "--auto",
+ "resolve problems automatically ",
+ "(not recommended for production)"
+ option "--report",
+ "generate report only, ",
+ "don't attempt to resolve problems"
+ def perform(deployment_name = nil)
auth_required
- # TODO: introduce option helpers
- deployment_name = options.shift unless options[0] =~ /^--/
-
- @auto_mode = options.delete("--auto")
- @report_mode = options.delete("--report")
+ @auto_mode = options[:auto]
+ @report_mode = options[:report]
if non_interactive? && !@report_mode
err ("Cloudcheck cannot be run in non-interactive mode\n" +
"Please use `--auto' flag if you want automated resolutions")
end
- if options.size > 0
- err("Unknown options: #{options.join(", ")}")
- end
-
if @auto_mode && @report_mode
err("Can't use --auto and --report mode together")
end
@@ -97,7 +92,7 @@ def verify_problems
if @problems.empty?
say("No problems found".green)
- quit
+ exit(0)
end
@problems.each do |problem|
View
29 cli/lib/cli/commands/complete.rb
@@ -0,0 +1,29 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Cli::Command
+ class Complete < Base
+
+ # bosh complete: Bash-compatible command line completion
+ usage "complete"
+ desc "Command completion options"
+ def complete(*args)
+ unless ENV.has_key?("COMP_LINE")
+ err("COMP_LINE must be set when calling bosh complete")
+ end
+ line = ENV["COMP_LINE"].gsub(/^\S*bosh\s*/, "")
+ say(completions(line).join("\n"))
+ end
+
+ private
+
+ # @param [String] line
+ def completions(line)
+ if runner.nil?
+ err("Command runner is not instantiated")
+ end
+
+ runner.find_completions(line.split(/\s+/))
+ end
+
+ end
+end
View
163 cli/lib/cli/commands/deployment.rb
@@ -4,43 +4,19 @@ module Bosh::Cli::Command
class Deployment < Base
include Bosh::Cli::DeploymentHelper
- # usage "deployment [<name>]"
- # desc "Choose deployment to work with " +
- # "(it also updates current target)"
- # route do |args|
- # if args.size > 0
- # [:deployment, :set_current]
- # else
- # [:deployment, :show_current]
- # end
- # end
- def show_current
- if deployment
- if interactive?
- say("Current deployment is `#{deployment.green}'")
- else
- say(deployment)
- end
- else
- err("Deployment not set")
+ # bosh deployment
+ usage "deployment"
+ desc "Get/set current deployment"
+ def set_current(filename = nil)
+ if filename.nil?
+ show_current
+ return
end
- end
- # usage "deployment [<name>]"
- # desc "Choose deployment to work with " +
- # "(it also updates current target)"
- # route do |args|
- # if args.size > 0
- # [:deployment, :set_current]
- # else
- # [:deployment, :show_current]
- # end
- # end
- def set_current(name)
- manifest_filename = find_deployment(name)
+ manifest_filename = find_deployment(filename)
unless File.exists?(manifest_filename)
- err("Missing manifest for #{name} (tried `#{manifest_filename}')")
+ err("Missing manifest for `#{filename}'")
end
manifest = load_yaml_file(manifest_filename)
@@ -71,13 +47,14 @@ def set_current(name)
if new_target_url.blank?
err("This manifest references director with UUID " +
- "#{new_director_uuid}.\n" +
- "You've never targeted it before.\n" +
- "Please find your director IP or hostname and target it first.")
+ "#{new_director_uuid}.\n" +
+ "You've never targeted it before.\n" +
+ "Please find your director IP or hostname and target it first.")
end
- new_director = Bosh::Cli::Director.new(new_target_url,
- username, password)
+ new_director = Bosh::Cli::Director.new(
+ new_target_url, username, password)
+
status = new_director.get_status
config.target = new_target_url
@@ -93,29 +70,25 @@ def set_current(name)
config.save
end
- # usage "edit deployment"
- # desc "Edit current deployment manifest"
- # route :deployment, :edit
+ # bosh edit deployment
+ usage "edit deployment"
+ desc "Edit current deployment manifest"
def edit
- unless deployment
- quit("Deployment not set".red)
- end
-
+ deployment_required
editor = ENV['EDITOR'] || "vi"
system("#{editor} #{deployment}")
end
- # usage "deploy"
- # desc "Deploy according to the currently selected " +
- # "deployment manifest"
- # option "--recreate", "recreate all VMs in deployment"
- # route :deployment, :perform
- def perform(*options)
+ # bosh deploy
+ usage "deploy"
+ desc "Deploy according to the currently selected deployment manifest"
+ option "--recreate", "recreate all VMs in deployment"
+ def perform
auth_required
- recreate = options.include?("--recreate")
+ recreate = !!options[:recreate]
- manifest_yaml =
- prepare_deployment_manifest(:yaml => true, :resolve_properties => true)
+ manifest_yaml = prepare_deployment_manifest(
+ :yaml => true, :resolve_properties => true)
if interactive?
inspect_deployment_changes(YAML.load(manifest_yaml))
@@ -133,16 +106,16 @@ def perform(*options)
task_report(status, "Deployed #{desc}")
end
- # usage "delete deployment <name>"
- # desc "Delete deployment"
- # option "--force", "ignore all errors while deleting parts " +
- # "of the deployment"
- # route :deployment, :delete
- def delete(name, *options)
+ # bosh delete deployment
+ usage "delete deployment"
+ desc "Delete deployment"
+ option "--force", "ignore errors while deleting"
+ def delete(name)
auth_required
- force = options.include?("--force")
+ force = !!options[:force]
- say("\nYou are going to delete deployment `#{name}'.\n\n")
+ say("\nYou are going to delete deployment `#{name}'.".red)
+ nl
say("THIS IS A VERY DESTRUCTIVE OPERATION AND IT CANNOT BE UNDONE!\n".red)
unless confirmed?
@@ -155,10 +128,10 @@ def delete(name, *options)
task_report(status, "Deleted deployment `#{name}'")
end
- # usage "validate jobs"
- # desc "Validates all jobs in the current release using current " +
- # "deployment manifest as the source of properties"
- # route :deployment, :validate_jobs
+ # bosh validate jobs
+ usage "validate jobs"
+ desc "Validates all jobs in the current release using current " +
+ "deployment manifest as the source of properties"
def validate_jobs
check_if_release_dir
manifest = prepare_deployment_manifest(:resolve_properties => true)
@@ -212,15 +185,14 @@ def validate_jobs
end
end
- # usage "deployments"
- # desc "Show the list of available deployments"
- # route :deployment, :list
+ # bosh deployments
+ usage "deployments"
+ desc "Show the list of available deployments"
def list
auth_required
-
deployments = director.list_deployments
- err("No deployments") if deployments.size == 0
+ err("No deployments") if deployments.empty?
deployments_table = table do |t|
t.headings = %w(Name)
@@ -229,45 +201,48 @@ def list
end
end
- say("\n")
+ nl
say(deployments_table)
- say("\n")
+ nl
say("Deployments total: %d" % deployments.size)
end
- def download_manifest(deployment_name, given_path = "")
+ # bosh download manifest
+ usage "download manifest"
+ desc "Download deployment manifest locally"
+ def download_manifest(deployment_name, save_as = nil)
auth_required
- existing = director.list_deployments.select do |dp|
- dp["name"] == deployment_name
+ if save_as && File.exists?(save_as) &&
+ !confirmed?("Overwrite `#{save_as}'?")
+ err("Please choose another file to save the manifest to")
end
- if existing.empty?
- err("Deployment `#{deployment_name}' not found on director")
- end
+ deployment = director.get_deployment(deployment_name)
- if File.directory?(given_path)
- download_path = File.join(given_path, deployment_name + ".yml")
+ if save_as
+ File.open(save_as, "w") do |f|
+ f.write(deployment["manifest"])
+ end
+ say("Deployment manifest saved to `#{save_as}'".green)
else
- download_path = given_path
- end
-
- if File.exists?(download_path) &&
- !agree("Local file `#{download_path}' already exists. Overwrite it? [yn]")
- return
+ say(deployment["manifest"])
end
+ end
- deployment = director.get_deployment(deployment_name)
- downloaded_deployment = YAML.load(deployment["manifest"])
+ private
- unless download_path.blank?
- File.open(download_path, "w") do |f|
- dump_yaml_to_file(downloaded_deployment, f)
+ def show_current
+ if deployment
+ if interactive?
+ say("Current deployment is `#{deployment.green}'")
+ else
+ say(deployment)
end
- say("Downloaded deployment manifest from director to `#{download_path}'")
else
- say(YAML.dump(downloaded_deployment))
+ err("Deployment not set")
end
end
+
end
end
View
96 cli/lib/cli/commands/help.rb
@@ -0,0 +1,96 @@
+# Copyright (c) 2009-2012 VMware, Inc.
+
+module Bosh::Cli::Command
+ class Help < Base
+
+ # bosh help: shows either a high level help message or drills down to a
+ # specific area (release, deployment etc)
+ usage "help"
+ desc "Show help message"
+ option "--all", "Show help for all BOSH commands"
+ # @param [Array] keywords What specific kind of help is requested
+ def help(*keywords)
+ if runner.nil?
+ err("Cannot show help message, command runner is not instantiated")
+ end
+
+ keywords = "all" if keywords.empty? && options[:all]
+
+ if keywords.empty?
+ generic_help
+ else
+ keyword_help(keywords)
+ end
+ end
+
+ private
+
+ def generic_help
+ message = <<-HELP.gsub(/^\s*\|/, "")
+ |BOSH CLI helps you manage your BOSH deployments and releases.
+ |
+ |#{runner.usage}
+ |
+ |The most commonly used BOSH commands are:
+ | target Point CLI to BOSH Director
+ | deployment Set deployment
+ | status Current status
+ | create release Create new release
+ | upload release Upload release
+ | upload stemcell Upload stemcell image
+ | deploy Perform deployment
+ | task <task_id> Track task / show task log
+ | tasks List running tasks
+ | tasks recent List recent tasks
+ | cloudcheck Find and resolve deployment problems
+ | deployments List deployments
+ | releases List releases
+ | start,restart,recreate,stop Job management
+ | add blob Add large binary file to release
+ |
+ |You can run 'bosh help <keywords...>' to see different commands,
+ |i.e. 'bosh help release', 'bosh help cloudcheck'.
+ |Or run 'bosh help --all' to see all available BOSH commands
+ HELP
+
+ say message
+ end
+
+ # @param [Array] keywords
+ def keyword_help(keywords)
+ matches = Bosh::Cli::Config.commands.values
+
+ if keywords == "all"
+ good_matches = matches.sort { |a, b| a.usage <=> b.usage }
+ else
+ good_matches = []
+ matches.each do |command|
+ common_keywords = command.keywords & keywords
+ if common_keywords.size > 0
+ good_matches << command
+ end
+
+ good_matches.sort! do |a, b|
+ cmp = (b.keywords & keywords).size <=> (a.keywords & keywords).size
+ cmp = (a.usage <=> b.usage) if cmp == 0
+ cmp
+ end
+ end
+ end
+
+ help_column_width = terminal_width - 5
+ help_indent = 4
+
+ good_matches.each_with_index do |command, i|
+ nl if i > 0
+ margin = command.usage.size + 1
+ say("#{command.usage_with_params.columnize(70, margin).green}")
+ say(command.desc.columnize(help_column_width).indent(help_indent))
+ if command.has_options?
+ say(command.options_summary.indent(help_indent))
+ end
+ end
+ end
+
+ end
+end
View
6 cli/lib/cli/commands/job.rb
@@ -3,9 +3,9 @@
module Bosh::Cli::Command
class Job < Base
- # usage "generate job <name>"
- # desc "Generate job template"
- # route :job, :generate
+ # bosh generate job
+ usage "generate job"
+ desc "Generate job template"
def generate(name)
check_if_release_dir
View
77 cli/lib/cli/commands/job_management.rb
@@ -4,41 +4,43 @@ module Bosh::Cli::Command
class JobManagement < Base
include Bosh::Cli::DeploymentHelper
- # usage "start <job> [<index>]"
- # desc "Start job/instance"
- # power_option "--force"
- # route :job_management, :start_job
- def start_job(*args)
- change_job_state(:start, *args)
+ FORCE = "Proceed even when there are other manifest changes"
+
+ # bosh start
+ usage "start"
+ desc "Start job/instance"
+ option "--force", FORCE
+ def start_job(job, index = nil)
+ change_job_state(:start, job, index)
end
- # usage "stop <job> [<index>]"
- # desc "Stop job/instance"
- # option "--soft", "stop process only"
- # option "--hard", "power off VM"
- # power_option "--force"
- # route :job_management, :stop_job
- def stop_job(*args)
- change_job_state(:stop, *args)
+ # bosh stop
+ usage "stop"
+ desc "Stop job/instance"
+ option "--soft", "Stop process only"
+ option "--hard", "Power off VM"
+ option "--force", FORCE
+ def stop_job(job, index = nil)
+ change_job_state(:stop, job, index)
end
- # usage "restart <job> [<index>]"
- # desc "Restart job/instance (soft stop + start)"
- # power_option "--force"
- # route :job_management, :restart_job
- def restart_job(*args)
- change_job_state(:restart, *args)
+ # bosh restart
+ usage "restart"
+ desc "Restart job/instance (soft stop + start)"
+ option "--force", FORCE
+ def restart_job(job, index = nil)
+ change_job_state(:restart, job, index)
end
- # usage "recreate <job> [<index>]"
- # desc "Recreate job/instance (hard stop + start)"
- # power_option "--force"
- # route :job_management, :recreate_job
- def recreate_job(*args)
- change_job_state(:recreate, *args)
+ # bosh recreate
+ usage "recreate"
+ desc "Recreate job/instance (hard stop + start)"
+ option "--force", FORCE
+ def recreate_job(job, index = nil)
+ change_job_state(:recreate, job, index)
end
- def change_job_state(operation, *args)
+ def change_job_state(operation, job, index)
auth_required
manifest_yaml = prepare_deployment_manifest(:yaml => true)
manifest = YAML.load(manifest_yaml)
@@ -48,10 +50,9 @@ def change_job_state(operation, *args)
"`start', `stop', `restart', `recreate'")
end
- args = args.dup
- hard = args.delete("--hard")
- soft = args.delete("--soft")
- force = args.delete("--force")
+ hard = options[:hard]
+ soft = options[:soft]
+ force = options[:force]
if hard && soft
err("Cannot handle both --hard and --soft options, please choose one")
@@ -61,9 +62,7 @@ def change_job_state(operation, *args)
err("--hard and --soft options only make sense for `stop' operation")
end
- job = args.shift
- index = args.shift
- job_desc = index ? "#{job}(#{index})" : "#{job}"
+ job_desc = index ? "#{job}/#{index}" : "#{job}"
op_desc = nil
new_state = nil
@@ -101,8 +100,6 @@ def change_job_state(operation, *args)
say("You are about to #{op_desc.green}")
if interactive?
- # TODO: refactor inspect_deployment_changes
- # to decouple changeset structure and rendering
other_changes_present = inspect_deployment_changes(
manifest, :show_empty_changeset => false)
@@ -114,14 +111,12 @@ def change_job_state(operation, *args)
cancel_deployment
end
end
- nl
+ nl
say("Performing `#{op_desc}'...")
- status, _ =
- director.change_job_state(manifest["name"],
- manifest_yaml,
- job, index, new_state)
+ status, _ = director.change_job_state(
+ manifest["name"], manifest_yaml, job, index, new_state)
task_report(status, completion_desc)
end
View
28 cli/lib/cli/commands/job_rename.rb
@@ -4,39 +4,34 @@ module Bosh::Cli::Command
class JobRename < Base
include Bosh::Cli::DeploymentHelper
- # usage "rename <old_job_name> <new_job_name>"
- # desc "Renames a job. NOTE, your deployment manifest must also be " +
- # "updated to reflect the new job name."
- # power_option "--force"
- # route :job_rename, :rename
- def rename(*args)
+ # bosh rename
+ usage "rename job"
+ desc "Renames a job. NOTE, your deployment manifest must also be " +
+ "updated to reflect the new job name."
+ option "--force", "Ignore errors"
+ def rename(old_name, new_name)
auth_required
manifest_yaml = prepare_deployment_manifest(:yaml => true)
manifest = YAML.load(manifest_yaml)
- args = args.dup
- force = args.delete("--force")
- old_name = args.shift
- new_name = args.shift
-
- say("You are about to rename #{old_name.green} to #{new_name.green}")
+ force = options[:force]
+ say("You are about to rename `#{old_name.green}' to `#{new_name.green}'")
unless confirmed?
nl
say("Job rename canceled".green)
- return
+ exit(0)
end
sanity_check_job_rename(manifest_yaml, old_name, new_name)
- status, _ = director.rename_job(manifest["name"], manifest_yaml,
- old_name, new_name, force)
+ status, _ = director.rename_job(
+ manifest["name"], manifest_yaml, old_name, new_name, force)
task_report(status, "Rename successful")
end
def sanity_check_job_rename(manifest_yaml, old_name, new_name)
-
# Makes sure the new deployment manifest contains the renamed job
manifest = YAML.load(manifest_yaml)
new_jobs = manifest["jobs"].map { |job| job["name"] }
@@ -92,7 +87,6 @@ def sanity_check_job_rename(manifest_yaml, old_name, new_name)
err("Manifest does not rename old job `#{old_name}'")
end
-
# Final sanity check, make sure that no
# other properties or anything other than the names
# have changed. So update current manifest with new name
View
67 cli/lib/cli/commands/log_management.rb
@@ -4,62 +4,51 @@ module Bosh::Cli::Command
class LogManagement < Base
include Bosh::Cli::DeploymentHelper
- # usage "logs <job> <index>"
- # desc "Fetch job (default) or agent (if option provided) logs"
- # option "--agent", "fetch agent logs"
- # option "--only <filter1>[...]", "only fetch logs that satisfy " +
- # "given filters (defined in job spec)"
- # option "--all", "fetch all files in the job or agent log directory"
- # route :log_management, :fetch_logs
- def fetch_logs(*args)
+ # bosh logs
+ usage "logs"
+ desc "Fetch job or agent logs from a BOSH-managed VM"
+ option "--agent", "fetch agent logs"
+ option "--job", "fetch job logs"
+ option "--only filter1,filter2,...", Array,
+ "only fetch logs that satisfy",
+ "given filters (defined in job spec)"
+ option "--all", "fetch all files in the job or agent log directory"
+ def fetch_logs(job, index)
auth_required
target_required
- job = args.shift
- index = args.shift
- filters = nil
- log_type = nil
-
- for_job = args.delete("--job")
- for_agent = args.delete("--agent")
+ if index !~ /^\d+$/
+ err("Job index is expected to be a positive integer")
+ end
- if for_job && for_agent
- err("Please specify which logs you want, job or agent")
- elsif for_agent
+ if options[:agent]
+ if options[:job]
+ err("You can't use --job and --agent together")
+ end
log_type = "agent"
- else # default log type is 'job'
+ else
log_type = "job"
end
- if args.include?("--only")
- pos = args.index("--only")
- filters = args[pos+1]
- if filters.nil?
- err("Please provide a list of filters separated by comma")
+ if options[:only]
+ if options[:all]
+ err("You can't use --only and --all together")
end
- args.delete("--only")
- args.delete(filters)
- elsif args.include?("--all")
- args.delete("--all")
+ filters = options[:only].join(",")
+ elsif options[:all]
filters = "all"
+ else
+ filters = nil
end
- if for_agent && !filters.nil? && filters != "all"
+ if options[:agent] && filters && filters != "all"
err("Custom filtering is not supported for agent logs")
end
- if index !~ /^\d+$/
- err("Job index is expected to be a positive integer")
- end
-
- if args.size > 0
- err("Unknown arguments: #{args.join(", ")}")
- end
-
manifest = prepare_deployment_manifest
- resource_id = director.fetch_logs(manifest["name"], job, index,
- log_type, filters)
+ resource_id = director.fetch_logs(
+ manifest["name"], job, index, log_type, filters)
if resource_id.nil?
err("Error retrieving logs")
View
12 cli/lib/cli/commands/maintenance.rb
@@ -7,11 +7,9 @@ class Maintenance < Base
RELEASES_TO_KEEP = 2
STEMCELLS_TO_KEEP = 2
- # usage "cleanup"
- # desc "Remove all but several recent stemcells and releases " +
- # "from current director " +
- # "(stemcells and releases currently in use are NOT deleted)"
- # route :maintenance, :cleanup
+ # bosh cleanup
+ usage "cleanup"
+ desc "Cleanup releases and stemcells"
def cleanup
target_required
auth_required
@@ -33,7 +31,9 @@ def cleanup
Releases and stemcells that are in use will not be affected.
EOS
- say("\n#{desc}\n")
+ nl
+ say(desc)
+ nl
err("Cleanup canceled") unless confirmed?
View
261 cli/lib/cli/commands/misc.rb
@@ -4,19 +4,18 @@ module Bosh::Cli::Command
class Misc < Base
DEFAULT_STATUS_TIMEOUT = 3 # seconds
- # usage "version"
- # desc "Show version"
- # route :misc, :version
+ # bosh version
+ usage "version"
+ desc "Show version"
def version
say("BOSH %s" % [Bosh::Cli::VERSION])
end
- # usage "status"
- # desc "Show current status (current target, " +
- # "user, deployment info etc.)"
- # route :misc, :status
+ # bosh status
+ usage "status"
+ desc "Show current status (current target, user, deployment info etc)"
def status
- if config.target && options[:director_checks]
+ if config.target
say("Updating director data...", " ")
begin
@@ -31,64 +30,61 @@ def status
say("done".green)
end
rescue TimeoutError
- err("timed out")
+ say("timed out".red)
rescue => e
- err("error: #{e.message}")
+ say("error: #{e.message}")
end
nl
end
- target_name = full_target_name ? full_target_name.green : "not set".red
- target_uuid = config.target_uuid ? config.target_uuid.green : "n/a".red
- user = logged_in? ? username.green : "not set".red
- deployment = config.deployment ? config.deployment.green : "not set".red
+ say("Director".green)
+ if target_url.nil?
+ say(" not set".yellow)
+ else
+ print_value("Name", config.target_name)
+ print_value("URL", target_url)
+ print_value("Version", config.target_version)
+ print_value("User", username, "not logged in")
+ print_value("UUID", config.target_uuid)
+ end
+
+ nl
+ say("Deployment".green)
- say("Target".ljust(15) + target_name)
- say("UUID".ljust(15) + target_uuid)
- say("User".ljust(15) + user)
- say("Deployment".ljust(15) + deployment)
+ if deployment
+ print_value("Manifest", deployment)
+ else
+ say(" not set".yellow)
+ end
if in_release_dir?
- header("You are in release directory")
+ nl
+ say("Release".green)
- dev_name = release.dev_name
dev_version = Bosh::Cli::VersionsIndex.new(
- File.join(work_dir, "dev_releases")).latest_version
+ File.join(work_dir, "dev_releases")).latest_version
- final_name = release.final_name
final_version = Bosh::Cli::VersionsIndex.new(
- File.join(work_dir, "releases")).latest_version
-
- say("Dev name: %s" % [dev_name ? dev_name.green : "not set".red])
- say("Dev version: %s" % [dev_version ?
- dev_version.to_s.green :
- "no versions yet".red])
- say("\n")
- say("Final name: %s" % [final_name ?
- final_name.green :
- "not set".red])
- say("Final version: %s" % [final_version ?
- final_version.to_s.green :
- "no versions yet".red])
-
- say("\n")
- say("Packages")
- print_specs("package", "packages")
-
- say("\n")
- say("Jobs")
- print_specs("job", "jobs")
+ File.join(work_dir, "releases")).latest_version
+
+ dev = release.dev_name
+ dev += "/#{dev_version}" if dev && dev_version
+
+ final = release.final_name
+ final += "/#{final_version}" if final && final_version
+
+ print_value("dev", dev)
+ print_value("final", final)
end
end
- # usage "login [<name>] [<password>]"
- # desc "Provide credentials for the subsequent interactions " +
- # "with targeted director"
- # route :misc, :login
+ # bosh login
+ usage "login"
+ desc "Log in to currently targeted director"
def login(username = nil, password = nil)
target_required
- unless options[:non_interactive]
+ if interactive?
username = ask("Your username: ").to_s if username.blank?
password_retries = 0
@@ -103,75 +99,57 @@ def login(username = nil, password = nil)
end
logged_in = false
- if options[:director_checks]
- director = Bosh::Cli::Director.new(target, username, password)
+ director.user = username
+ director.password = password
- if director.authenticated?
- say("Logged in as `#{username}'")
- logged_in = true
- elsif options[:non_interactive]
- err("Cannot log in as `#{username}'")
- else
- say("Cannot log in as `#{username}', please try again")
- redirect(:misc, :login, username, nil)
- end
+ if director.authenticated?
+ say("Logged in as `#{username}'".green)
+ logged_in = true
+ elsif non_interactive?
+ err("Cannot log in as `#{username}'".red)
+ else
+ say("Cannot log in as `#{username}', please try again".red)
+ login(username)
end
- if logged_in || !options[:director_checks]
+ if logged_in
config.set_credentials(target, username, password)
config.save
end
end
- # usage "logout"
- # desc "Forget saved credentials for targeted director"
- # route :misc, :logout
+ # bosh logout
+ usage "logout"
+ desc "Forget saved credentials for targeted director"
def logout
target_required
config.set_credentials(target, nil, nil)
config.save
- say("You are no longer logged in to '#{target}'")
+ say("You are no longer logged in to `#{target}'".yellow)
end
- # usage "purge"
- # desc "Purge local manifest cache"
- # route :misc, :purge_cache
+ # bosh purge
+ usage "purge"
+ desc "Purge local manifest cache"
def purge_cache
if cache.cache_dir != Bosh::Cli::DEFAULT_CACHE_DIR
- # TODO use different exit code?
- say("Cache directory `#{@cache.cache_dir}' differs from default, " +
- "please remove manually")
+ err("Cache directory overriden, please remove manually")
else
FileUtils.rm_rf(cache.cache_dir)
- say("Purged cache")
+ say("Purged cache".green)
end
end
- # usage "target [<name>] [<alias>]"
- # desc "Choose director to talk to (optionally creating an alias). " +
- # "If no arguments given, show currently targeted director"
- # route do |args|
- # (args.size > 0) ? [:misc, :set_target] : [:misc, :show_target]
- # end
- def show_target
- if target
- if interactive?
- say("Current target is `#{full_target_name.green}'")
- else
- say(full_target_name)
- end
- else
- err("Target not set")
+ # bosh target
+ usage "target"
+ desc "Choose director to talk to (optionally creating an alias). " +
+ "If no arguments given, show currently targeted director"
+ def set_target(director_url = nil, name = nil)
+ if director_url.nil?
+ show_target
+ return
end
- end
- # usage "target [<name>] [<alias>]"
- # desc "Choose director to talk to (optionally creating an alias). " +
- # "If no arguments given, show currently targeted director"
- # route do |args|
- # (args.size > 0) ? [:misc, :set_target] : [:misc, :show_target]
- # end
- def set_target(director_url, name = nil)
if name.nil?
director_url =
config.resolve_alias(:target, director_url) || director_url
@@ -183,23 +161,19 @@ def set_target(director_url, name = nil)
director_url = normalize_url(director_url)
if target && director_url == normalize_url(target)
- say("Target already set to `#{full_target_name.green}'")
+ say("Target already set to `#{target_name.green}'")
return
end
director = Bosh::Cli::Director.new(director_url)
- if options[:director_checks]
- begin
- status = director.get_status
- rescue Bosh::Cli::AuthError
- status = {}
- rescue Bosh::Cli::DirectorError
- err("Cannot talk to director at `#{director_url}', " +
- "please set correct target")
- end
- else
- status = {"name" => "Unknown Director", "version" => "n/a"}
+ begin
+ status = director.get_status
+ rescue Bosh::Cli::AuthError
+ status = {}
+ rescue Bosh::Cli::DirectorError
+ err("Cannot talk to director at `#{director_url}', " +
+ "please set correct target")
end
config.target = director_url
@@ -216,65 +190,88 @@ def set_target(director_url, name = nil)
end
config.save
- say("Target set to `#{full_target_name.green}'")
+ say("Target set to `#{target_name.green}'")
if interactive? && !logged_in?
- redirect(:misc, :login)
+ redirect("login")
end
end
- # usage "targets"
- # desc "Show the list of available targets"
- # route :misc, :list_targets
+ # bosh targets
+ usage "targets"
+ desc "Show the list of available targets"
def list_targets
- # TODO: Bonus point will be checking each director status
- # (maybe an --status option?)
targets = config.aliases(:target) || {}
- err("No targets found") if targets.size == 0
+ err("No targets found") if targets.empty?
targets_table = table do |t|
t.headings = [ "Name", "Director URL" ]
targets.each { |row| t << [row[0], row[1]] }
end
- say("\n")
+ nl
say(targets_table)
- say("\n")
+ nl
say("Targets total: %d" % targets.size)
end
- # usage "alias <name> <command>"
- # desc "Create an alias <name> for command <command>"
- # route :misc, :set_alias
- def set_alias(name, value)
- config.set_alias(:cli, name, value.to_s.strip)
+ # bosh alias
+ usage "alias"
+ desc "Create an alias <name> for command <command>"
+ def set_alias(name, command)
+ config.set_alias(:cli, name, command.to_s.strip)
config.save
- say("Alias `#{name.green}' created for command `#{value.green}'")
+ say("Alias `#{name.green}' created for command `#{command.green}'")
end
- # usage "aliases"
- # desc "Show the list of available command aliases"
- # route :misc, :list_aliases
+ # bosh aliases
+ usage "aliases"
+ desc "Show the list of available command aliases"
def list_aliases
aliases = config.aliases(:cli) || {}
+ err("No aliases found") if aliases.empty?
- err("No aliases found") if aliases.size == 0
-
- sorted = aliases.sort_by { |name, value| name }
+ sorted = aliases.sort_by { |name, _| name }
aliases_table = table do |t|
- t.headings = [ "Alias", "Command" ]
+ t.headings = %w(Alias Command)
sorted.each { |row| t << [row[0], row[1]] }
end
- say("\n")
+ nl
say(aliases_table)
- say("\n")
+ nl
say("Aliases total: %d" % aliases.size)
end
private
+ def print_value(label, value, if_none = nil)
+ if value
+ message = label.ljust(10) + value.yellow
+ else
+ message = label.ljust(10) + (if_none || "n/a").yellow
+ end
+ say(message.indent(2))
+ end
+
+ def show_target
+ if config.target
+ if interactive?
+ if config.target_name
+ name = "#{config.target} (#{config.target_name})"
+ else
+ name = config.target
+ end
+ say("Current target is #{name.green}")
+ else
+ say(config.target)
+ end
+ else
+ err("Target not set")
+ end
+ end
+
def print_specs(entity, dir)
specs = Dir[File.join(work_dir, dir, "*", "spec")]
@@ -282,7 +279,7 @@ def print_specs(entity, dir)
say("No #{entity} specs found")
end
- t = table ["Name", "Dev", "Final"]
+ t = table %w(Name Dev Final)
specs.each do |spec_file|
if spec_file.is_a?(String) && File.file?(spec_file)
View
77 cli/lib/cli/commands/package.rb
@@ -3,42 +3,8 @@
module Bosh::Cli::Command
class Package < Base
- def create_all
- specs_glob = File.join(work_dir, "packages", "*", "spec")
-
- Dir[specs_glob].each do |spec|
- create(spec)
- end
- end
-
- # usage "create package <name>|<path>"
- # desc "Build a single package"
- # route :package, :create
- def create(name_or_path)
- if name_or_path == "--all"
- redirect(:package, :create_all)
- end
-
- spec = read_spec(name_or_path)
-
- unless spec.is_a?(Hash) && spec.has_key?("name") && spec.has_key?("files")
- err("Sorry, '#{name_or_path}' doesn't look like a valid package spec")
- end
-
- package_name = spec["name"]
- header("Found '#{package_name}' spec")
- print_spec(spec)
- header("Building package...")
-
- builder = Bosh::Cli::PackageBuilder.new(spec, work_dir,
- false, release.blobstore)
- builder.build
- builder
- end
-
- # usage "generate package <name>"
- # desc "Generate package template"
- # route :package, :generate
+ usage "generate package"
+ desc "Generate package template"
def generate(name)
check_if_release_dir
@@ -56,11 +22,13 @@ def generate(name)
FileUtils.mkdir_p(package_dir)
generate_file(package_dir, "packaging") do
- "# abort script on any command that exit with a non zero value\nset -e\n"
+ "# abort script on any command that exit " +
+ "with a non zero value\nset -e\n"
end
generate_file(package_dir, "pre_packaging") do
- "# abort script on any command that exit with a non zero value\nset -e\n"
+ "# abort script on any command that exit " +
+ "with a non zero value\nset -e\n"
end
generate_file(package_dir, "spec") do
@@ -81,38 +49,5 @@ def generate_file(dir, file)
end
end
- def print_spec(spec)
- say("Package name: #{spec["name"]}")
- say("Files:")
- for file in spec["files"]
- say(" - #{file}")
- end
- end
-
- def read_spec(name)
- load_yaml_file(find_spec(name))
- end
-
- def find_spec(name)
- if File.directory?(name)
- spec_path = File.join(name, "spec")
- if File.exists?(spec_path)
- spec_path
- else
- err("Cannot find spec file in '#{name}' directory")
- end
- elsif File.file?(name)
- name
- else
- package_dir = File.join(work_dir, "packages", name)
- if File.directory?(package_dir)
- find_spec(package_dir)
- else
- err("Cannot find package '#{name}' (tried '#{package_dir}')")
- end
-
- end
- end
-
end
end
View
42 cli/lib/cli/commands/property_management.rb
@@ -4,9 +4,9 @@ module Bosh::Cli::Command
class PropertyManagement < Base
include Bosh::Cli::DeploymentHelper
- # usage "set property <name> <value>"
- # desc "Set deployment property"
- # route :property_management, :set
+ # bosh set property
+ usage "set property"
+ desc "Set deployment property"
def set(name, value)
prepare
show_header
@@ -26,10 +26,9 @@ def set(name, value)
end
prompt = "Are you sure you want to set property" +
- " `#{name.green}' to `#{format_property(value).green}'? " +
- "(type yes to proceed): "
+ " `#{name.green}' to `#{format_property(value).green}'?"
- if interactive? && ask(prompt) != "yes"
+ unless confirmed?(prompt)
err("Canceled")
end
@@ -46,17 +45,17 @@ def set(name, value)
end
end
- # usage "unset property <name>"
- # desc "Unset deployment property"
- # route :property_management, :unset
+ # bosh unset property
+ usage "unset property"
+ desc "Unset deployment property"
def unset(name)
prepare
show_header
prompt = "Are you sure you want to unset property " +
- "`#{name.green}'? (type yes to proceed): "
+ "`#{name.green}'?"
- if interactive? && ask(prompt) != "yes"
+ unless confirmed?(prompt)
err("Canceled")
end
@@ -69,9 +68,9 @@ def unset(name)
end
end
- # usage "get property <name>"
- # desc "Get deployment property"
- # route :property_management, :get
+ # bosh get property
+ usage "get property"
+ desc "Get deployment property"
def get(name)
prepare
show_header
@@ -85,13 +84,13 @@ def get(name)
end
end
- # usage "properties"
- # desc "List current deployment properties"
- # option "--terse", "easy to parse output"
- # route :property_management, :list
- def list(*args)
+ # bosh properties
+ usage "properties"
+ desc "List deployment properties"
+ option "--terse", "easy to parse output"
+ def list
prepare
- terse = args.include?("--terse")
+ terse = options[:terse]
show_header unless terse
properties = director.list_properties(@deployment_name)
@@ -110,12 +109,11 @@ def list(*args)
else
if output.size > 0
properties_table = table do |t|
- t.headings = ["Name", "Value"]
+ t.headings = %w(Name Value)
output.each { |row| t << [row[0], row[1].truncate(40)] }
end
say(properties_table)
else
- # TODO normalize? we use err() in other places
say("No properties found")
end
end
View
344 cli/lib/cli/commands/release.rb
@@ -7,25 +7,18 @@ class Release < Base
include Bosh::Cli::DependencyHelper
include Bosh::Cli::VersionCalc
- # usage "init release [<path>]"
- # desc "Initialize release directory"
- # option "--git", "initialize git repository"
- # route :release, :init
- def init(base=nil, *options)
- if base[0..0] == "-"
- # TODO: need to add some option parsing helpers to avoid that
- options.unshift(base)
- base = nil
- end
- git = options.include?("--git")
-
+ # bosh init release
+ usage "init release"
+ desc "Initialize release directory"
+ option "--git", "initialize git repository"
+ def init(base = nil)
if base
- FileUtils.mkdir_p(base) unless Dir.exist?(base)
+ FileUtils.mkdir_p(base)
Dir.chdir(base)
end
err("Release already initialized") if in_release_dir?
- git_init if git
+ git_init if options[:git]
%w[config jobs packages src blobs].each do |dir|
FileUtils.mkdir(dir)
@@ -39,78 +32,65 @@ def init(base=nil, *options)
say("Release directory initialized".green)
end
- def git_init
- out = %x{git init 2>&1}
- if $? != 0
- say("error running 'git init':\n#{out}")
+ # bosh create release
+ usage "create release"
+ desc "Create release (assumes current directory " +
+ "to be a release repository)"
+ option "--force", "bypass git dirty state check"
+ option "--final", "create final release"
+ option "--with-tarball", "create release tarball"
+ option "--dry-run", "stop before writing release manifest"
+ def create(manifest_file = nil)
+ check_if_release_dir
+
+ if manifest_file && File.file?(manifest_file)
+ release_filename = create_from_manifest(manifest_file)
else
- File.open(".gitignore", "w") do |f|
- f << <<-EOS.gsub(/^\s{10}/, '')
- config/dev.yml
- config/private.yml
- releases/*.tgz
- dev_releases
- .blobs
- blobs
- .dev_builds
- .idea
- .DS_Store
- .final_builds/jobs/**/*.tgz
- .final_builds/packages/**/*.tgz
- *.swp
- *~
- *#
- #*
- EOS
- end
+ release_filename = create_from_spec
+ end
+
+ if release_filename
+ release.latest_release_filename = release_filename
+ release.save_config
end
- rescue Errno::ENOENT
- say("Unable to run 'git init'".red)
end
- # usage "verify release <path>"
- # desc "Verify release"
- # route :release, :verify
+ # bosh verify release
+ usage "verify release"
+ desc "Verify release"
def verify(tarball_path)
tarball = Bosh::Cli::ReleaseTarball.new(tarball_path)
- say("\nVerifying release...")
+ nl
+ say("Verifying release...")
tarball.validate
nl
if tarball.valid?
- say("'%s' is a valid release" % [tarball_path] )
+ say("`#{tarball_path}' is a valid release".green)
else
say("Validation errors:".red)
- for error in tarball.errors
- say("- %s" % [error])
+ tarball.errors.each do |error|
+ say("- #{error}")
end
- err("'%s' is not a valid release" % [tarball_path] )
+ err("`#{tarball_path}' is not a valid release".red)
end
end
- # usage "upload release [<path>]"
- # desc "Upload release (<path> can point to tarball or manifest, " +
- # "defaults to the most recently created release)"
- # route :release, :upload
- def upload(*options)
+ usage "upload release"
+ desc "Upload release"
+ option "--rebase",
+ "Rebases this release onto the latest version",
+ "known by director (discards local job/package",
+ "versions in favor of versions assigned by director)"
+ def upload(release_file = nil)
auth_required
- # TODO: need option helpers badly!
- release_file = nil
- if options.size > 0 && options.first[0..0] != "-"
- release_file = options.shift
- end
-
upload_options = {
- :rebase => !!options.delete("--rebase"),
+ :rebase => options[:rebase],
:repack => true
}
- if options.size > 0
- err("Unknown options: #{options.join(", ")}")
- end
-
if release_file.nil?
check_if_release_dir
release_file = release.latest_release_filename
@@ -133,11 +113,86 @@ def upload(*options)
if file_type =~ /text\/(plain|yaml)/
upload_manifest(release_file, upload_options)
- else # Just assume tarball
+ else
upload_tarball(release_file, upload_options)
end
end
+ usage "reset release"
+ desc "Reset dev release"
+ def reset
+ check_if_release_dir
+
+ say("Your dev release environment will be completely reset".red)
+ if confirmed?
+ say("Removing dev_builds index...")
+ FileUtils.rm_rf(".dev_builds")
+ say("Clearing dev name...")
+ release.dev_name = nil
+ release.save_config
+ say("Removing dev tarballs...")
+ FileUtils.rm_rf("dev_releases")
+
+ say("Release has been reset".green)
+ else
+ say("Canceled")
+ end
+ end
+
+ usage "releases"
+ desc "Show the list of available releases"
+ def list
+ auth_required
+ releases = director.list_releases.sort do |r1, r2|
+ r1["name"] <=> r2["name"]
+ end
+
+ err("No releases") if releases.empty?
+
+ releases_table = table do |t|
+ t.headings = "Name", "Versions"
+ releases.each do |r|
+ versions = r["versions"].sort do |v1, v2|
+ version_cmp(v1, v2)
+ end
+
+ t << [r["name"], versions.join(", ")]
+ end
+ end
+
+ nl
+ say(releases_table)
+ nl
+ say("Releases total: %d" % releases.size)
+ end
+
+ usage "delete release"
+ desc "Delete release (or a particular release version)"
+ option "--force", "ignore errors during deletion"
+ def delete(name, version = nil)
+ auth_required
+ force = !!options[:force]
+
+ desc = "#{name}"
+ desc << "/#{version}" if version
+
+ if force
+ say("Deleting `#{desc}' (FORCED DELETE, WILL IGNORE ERRORS)".red)
+ else
+ say("Deleting `#{desc}'".red)
+ end
+
+ if confirmed?
+ status, _ = director.delete_release(
+ name, :force => force, :version => version)
+ task_report(status, "Deleted `#{desc}'")
+ else
+ say("Canceled deleting release".green)
+ end
+ end
+
+ protected
+
def upload_manifest(manifest_path, upload_options = {})
package_matches = match_remote_packages(File.read(manifest_path))
@@ -211,55 +266,22 @@ def upload_tarball(tarball_path, upload_options = {})
end
end
- # usage "create release"
- # desc "Create release (assumes current directory " +
- # "to be a release repository)"
- # option "--force", "bypass git dirty state check"
- # option "--final", "create production-ready release " +
- # "(stores artefacts in blobstore, bumps final version)"
- # option "--with-tarball", "create full release tarball" +
- # "(by default only manifest is created)"
- # option "--dry-run", "stop before writing release " +
- # "manifest (for diagnostics)"
- # route :release, :create
- def create(*options)
- check_if_release_dir
- if options.size == 1 && File.file?(options[0])
- create_from_manifest(options[0])
- release_filename = options[0]
- else
- release_filename = create_from_spec(*options)
- end
-
- if release_filename
- release.latest_release_filename = release_filename
- release.save_config
- end
- end
-
def create_from_manifest(manifest_file)
say("Recreating release from the manifest")
Bosh::Cli::ReleaseCompiler.compile(manifest_file, release.blobstore)
+ manifest_file
end
- def create_from_spec(*options)
- flags = options.inject({}) { |h, option| h[option] = true; h }
-
- final = flags.delete("--final")
- force = flags.delete("--force")
- manifest_only = !flags.delete("--with-tarball")
- dry_run = flags.delete("--dry-run")
+ def create_from_spec
+ final = options[:final]
+ force = options[:force]
+ manifest_only = !options[:with_tarball]
+ dry_run = options[:dry_run]
if final && !release.has_blobstore_secret?
err("Can't create final release without blobstore secret")
end
- if flags.size > 0
- say("Unknown flags: #{flags.keys.join(", ")}".red)
- show_usage
- exit(1)
- end
-
blob_manager.sync
if blob_manager.dirty?
blob_manager.print_status
@@ -273,16 +295,13 @@ def create_from_spec(*options)
check_if_dirty_state unless force
confirmation = "Are you sure you want to " +
- "generate #{'final'.red} version? "
+ "generate #{'final'.red} version? "
if final && !dry_run && !confirmed?(confirmation)
say("Canceled release generation".green)
exit(1)
end
- packages = []
- jobs = []
-
if final
header("Building FINAL release".green)
release_name = release.final_name
@@ -293,12 +312,12 @@ def create_from_spec(*options)
if version_greater(release.min_cli_version, Bosh::Cli::VERSION)
err("You should use CLI >= #{release.min_cli_version} " +
- "with this release, you have #{Bosh::Cli::VERSION}")
+ "with this release, you have #{Bosh::Cli::VERSION}")
end
if release_name.blank?
confirmation = "Please enter %s release name: " % [
- final ? "final" : "development"]
+ final ? "final" : "development"]
name = interactive? ? ask(confirmation).to_s : DEFAULT_RELEASE_NAME
err("Canceled release creation, no name given") if name.blank?
if final
@@ -377,7 +396,7 @@ def create_from_spec(*options)
unless manifest_only
say("Release tarball (#{pretty_size(builder.tarball_path)}): " +
- builder.tarball_path.green)
+ builder.tarball_path.green)
end
release.min_cli_version = Bosh::Cli::VERSION
@@ -386,85 +405,33 @@ def create_from_spec(*options)
builder.manifest_path
end
- # usage "reset release"
- # desc "Reset release development environment " +
- # "(deletes all dev artifacts)"
- # route :release, :reset
- def reset
- check_if_release_dir
-
- say("Your dev release environment will be completely reset".red)
- if confirmed?
- say("Removing dev_builds index...")
- FileUtils.rm_rf(".dev_builds")
- say("Clearing dev name...")
- release.dev_name = nil
- release.save_config
- say("Removing dev tarballs...")
- FileUtils.rm_rf("dev_releases")
-
- say("Release has been reset".green)
+ def git_init
+ out = %x{git init 2>&1}
+ if $? != 0
+ say("error running 'git init':\n#{out}")
else
- say("Canceled")
- end
- end
-
- # usage "releases"
- # desc "Show the list of available releases"
- # route :release, :list
- def list
- auth_required
- releases = director.list_releases.sort do |r1, r2|
- r1["name"] <=> r2["name"]
- end
-
- err("No releases") if releases.empty?
-
- releases_table = table do |t|
- t.headings = "Name", "Versions"
- releases.each do |r|
- versions = r["versions"].sort do |v1, v2|
- version_cmp(v1, v2)
- end
-
- t << [r["name"], versions.join(", ")]
+ File.open(".gitignore", "w") do |f|
+ f << <<-EOS.gsub(/^\s{10}/, '')
+ config/dev.yml
+ config/private.yml
+ releases/*.tgz
+ dev_releases
+ .blobs
+ blobs
+ .dev_builds
+ .idea
+ .DS_Store
+ .final_builds/jobs/**/*.tgz
+