Fixup: Update vagrant cloud command PR with feedback

This commit is contained in:
Brian Cain 2018-09-04 12:25:19 -07:00
parent e67dac0dbb
commit d8ec19faa8
No known key found for this signature in database
GPG Key ID: 9FC4639B2E4510A0
26 changed files with 121 additions and 88 deletions

View File

@ -13,12 +13,15 @@ module Vagrant
# a hand-rolled Ruby library, so we defer to its expertise.
class Uploader
# @param [String] destination - valid URL to upload file to
# @param [String] file - location of file to upload on disk
# @param [Hash] options
def initialize(destination, file, options=nil)
options ||= {}
@logger = Log4r::Logger.new("vagrant::util::uploader")
@logger = Log4r::Logger.new("vagrant::util::uploader")
@destination = destination.to_s
@file = file.to_s
@ui = options[:ui]
@file = file.to_s
@ui = options[:ui]
@request_method = options[:method]
if !@request_method
@ -68,7 +71,7 @@ module Vagrant
Subprocess.execute("curl", *options, &data_proc)
end
# If the download was interrupted, then raise a specific error
# If the upload was interrupted, then raise a specific error
raise Errors::UploaderInterrupted if interrupted
# If it didn't exit successfully, we need to parse the data and
@ -76,20 +79,15 @@ module Vagrant
if result.exit_code != 0
@logger.warn("Uploader exit code: #{result.exit_code}")
check = result.stderr.match(/\n*curl:\s+\((?<code>\d+)\)\s*(?<error>.*)$/)
if check && check[:code] == "416"
# All good actually. 416 means there is no more bytes to download
@logger.warn("Uploader got a 416, but is likely fine. Continuing on...")
if !check
err_msg = result.stderr
else
if !check
err_msg = result.stderr
else
err_msg = check[:error]
end
raise Errors::UploaderError,
exit_code: result.exit_code,
message: err_msg
err_msg = check[:error]
end
raise Errors::UploaderError,
exit_code: result.exit_code,
message: err_msg
end
if @ui

View File

@ -33,7 +33,6 @@ module VagrantPlugins
options[:login] = l
end
end
# TODO: Should be an alias for the existing login command
# Parse the options
argv = parse_options(opts)

View File

@ -23,7 +23,7 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.length > 1
if !argv.empty?
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end

View File

@ -33,14 +33,14 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2
if argv.empty? || argv.length > 1
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
create_box(org, box_name, options, @client.token)
@ -51,7 +51,7 @@ module VagrantPlugins
# @param [Hash] - options
def create_box(org, box_name, options, access_token)
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, options[:short], options[:description], access_token)
begin

View File

@ -24,18 +24,18 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2
if argv.empty? || argv.length > 1
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@env.ui.warn(I18n.t("cloud_command.box.delete_warn", box: argv.first))
continue = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if continue.downcase != "y"
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
delete_box(org, box_name, options[:username], @client.token)
@ -43,7 +43,7 @@ module VagrantPlugins
def delete_box(org, box_name, username, access_token)
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(username, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(username, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
begin

View File

@ -27,13 +27,13 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2
if argv.empty? || argv.length > 1
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
show_box(box[0], box[1], options, @client.token)
end
@ -42,7 +42,7 @@ module VagrantPlugins
username = options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(username, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(username, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
begin
@ -52,7 +52,7 @@ module VagrantPlugins
# show *this* version only
results = success["versions"].select{ |v| v if v["version"] == options[:version] }.first
if !results
@env.ui.warn(I18n.t("cloud_command.box.show_filter_empty", version: options[:version], org: org,box_name:box_name))
@env.ui.warn(I18n.t("cloud_command.box.show_filter_empty", version: options[:version], org: org, box_name: box_name))
return 0
end
else

View File

@ -16,8 +16,7 @@ module VagrantPlugins
o.separator "Options:"
o.separator ""
o.on("-d", "--description DESCRIPTION", "Longer desscription of the box") do |d|
o.on("-d", "--description DESCRIPTION", "Longer description of the box") do |d|
options[:description] = d
end
o.on("-u", "--username", "The username of the organization that will own the box") do |u|
@ -34,20 +33,20 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 2 || options.length == 0
if argv.empty? || argv.length > 1 || options.length == 0
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
update_box(box[0], box[1], options, @client.token)
end
def update_box(org, box_name, options, access_token)
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(options[:username], access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(options[:username], access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
options[:organization] = org

View File

@ -75,6 +75,7 @@ module VagrantPlugins
}
)
Vagrant::Util::CredentialScrubber.sensitive(response["token"])
response["token"]
end
@ -217,7 +218,9 @@ EOH
raise Errors::ServerError, errors: errors
rescue JSON::ParserError; end
raise "An unexpected error occurred: #{e.inspect}"
@logger.debug("Got an unexpected error:")
@logger.debug(e.inspect)
raise Errors::Unexpected, error: e.inspect
rescue SocketError
@logger.info("Socket error")
raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s

View File

@ -17,6 +17,10 @@ module VagrantPlugins
error_key(:unauthorized)
end
class Unexpected < Error
error_key(:unexpected_error)
end
class TwoFactorRequired < Error
end
end

View File

@ -35,7 +35,7 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.length > 2
if argv.length > 1
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end

View File

@ -1,6 +1,8 @@
en:
cloud_command:
publish:
update_continue: |-
%{obj} already exists, updating instead...
box_create:
Creating a box entry...
version_create:
@ -13,6 +15,23 @@ en:
Releasing box...
complete:
Complete! Published %{org}/%{box_name}
confirm:
warn: |-
You are about to publish a box on Vagrant Cloud with the following options:
box: |-
%{org}/%{box_name}: (v%{version}) for provider '%{provider_name}'
private: |-
Private: true
release: |-
Automatic Release: true
box_url: |-
Remote Box file: %{url}
box_description: |-
Box Description: %{description}
box_short_desc: |-
Box Short Description: %{short_description}
version_desc: |-
Version Description: %{version_description}
continue: |-
Do you wish to continue? [y/N]
box:
@ -112,6 +131,8 @@ en:
unauthorized: |-
Invalid username or password. Please try again.
unexpected_error: |-
An unexpected error occured: %{error}
check_logged_in: |-
You are already logged in.

View File

@ -31,7 +31,7 @@ module VagrantPlugins
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
provider_name = argv[1]
@ -49,7 +49,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, nil, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, url, org, box_name, access_token)

View File

@ -29,15 +29,15 @@ module VagrantPlugins
help: opts.help.chomp
end
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
provider_name = argv[1]
version = argv[2]
@env.ui.warn(I18n.t("cloud_command.provider.delete_warn", provider: provider_name, version:version, box: argv.first))
continue = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if continue.downcase != "y"
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
@ -48,7 +48,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, nil, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, nil, nil, nil, access_token)

View File

@ -31,7 +31,7 @@ module VagrantPlugins
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
provider_name = argv[1]
@ -49,7 +49,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, nil, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, url, org, box_name, access_token)

View File

@ -32,7 +32,7 @@ module VagrantPlugins
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
provider_name = argv[1]
@ -46,7 +46,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, nil, access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, nil, org, box_name, access_token)

View File

@ -48,14 +48,14 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.empty? || argv.length > 5 || argv.length < 3
if argv.empty? || argv.length > 4 || argv.length < 3
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
version = argv[1]
@ -67,23 +67,27 @@ module VagrantPlugins
def publish_box(org, box_name, version, provider_name, box_file, options, access_token)
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
@env.ui.warn("You are about to publish a box on Vagrant Cloud with the following options:\n")
box_opts = " #{org}/#{box_name}: (v#{version}) for provider '#{provider_name}'\n"
box_opts << " Private: true\n" if options[:private]
box_opts << " Automatic Release: true\n" if options[:release]
box_opts << " Remote Box file: true\n" if options[:url]
box_opts << " Box Description: #{options[:description]}\n" if options[:description]
box_opts << " Box Short Description: #{options[:short_description]}\n" if options[:short_description]
box_opts << " Version Description: #{options[:version_description]}\n" if options[:version_description]
@env.ui.warn(I18n.t("cloud_command.publish.confirm.warn"))
@env.ui.info(box_opts)
@env.ui.info(I18n.t("cloud_command.publish.confirm.box", org: org,
box_name: box_name, version: version, provider_name: provider_name))
@env.ui.info(I18n.t("cloud_command.publish.confirm.private")) if options[:private]
@env.ui.info(I18n.t("cloud_command.publish.confirm.release")) if options[:release]
@env.ui.info(I18n.t("cloud_command.publish.confirm.box_url",
url: options[:url])) if options[:url]
@env.ui.info(I18n.t("cloud_command.publish.confirm.box_description",
description: options[:description])) if options[:description]
@env.ui.info(I18n.t("cloud_command.publish.confirm.box_short_desc",
short_description: options[:short_description])) if options[:short_description]
@env.ui.info(I18n.t("cloud_command.publish.confirm.version_desc",
version_description: options[:version_description])) if options[:version_description]
if !options[:force]
continue = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if continue.downcase != "y"
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
end
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, options[:short_description], options[:description], access_token)
cloud_version = VagrantCloud::Version.new(box, version, nil, options[:version_description], access_token)
provider = VagrantCloud::Provider.new(cloud_version, provider_name, nil, options[:url], org, box_name, access_token)
@ -95,7 +99,7 @@ module VagrantPlugins
box.create
rescue VagrantCloud::ClientError => e
if e.error_code == 422
ui.warn("Box already exists, updating instead...")
ui.warn(I18n.t("cloud_command.publish.update_continue", obj: "Box"))
box.update(options)
else
@env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
@ -109,7 +113,7 @@ module VagrantPlugins
cloud_version.create_version
rescue VagrantCloud::ClientError => e
if e.error_code == 422
ui.warn("Version already exists, updating instead...")
ui.warn(I18n.t("cloud_command.publish.update_continue", obj: "Version"))
cloud_version.update
else
@env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
@ -123,7 +127,7 @@ module VagrantPlugins
provider.create_provider
rescue VagrantCloud::ClientError => e
if e.error_code == 422
ui.warn("Provider already exists, updating instead...")
ui.warn(I18n.t("cloud_command.publish.update_continue", obj: "Provider"))
provider.update
else
@env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))

View File

@ -44,7 +44,7 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.length > 2
if argv.length > 1
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@ -52,7 +52,7 @@ module VagrantPlugins
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
query = argv.first
options[:limit] = 25 if !options[:limit]
options[:limit] = 25 if !(options[:limit].to_i < 1) && !options[:limit]
search(query, options, @client.token)
end

View File

@ -2,7 +2,11 @@ module VagrantPlugins
module CloudCommand
class Util
class << self
def account?(username, access_token, vagrant_cloud_server)
# @param [String] username - Vagrant Cloud username
# @param [String] access_token - Vagrant Cloud Token used to authenticate
# @param [String] vagrant_cloud_server - Vagrant Cloud server to make API request
# @return [VagrantCloud::Account]
def account(username, access_token, vagrant_cloud_server)
if !defined?(@_account)
@_account = VagrantCloud::Account.new(username, access_token, vagrant_cloud_server)
end
@ -17,6 +21,9 @@ module VagrantPlugins
end
end
# @param [Vagrant::Environment] env
# @param [Hash] options
# @returns [VagrantPlugins::CloudCommand::Client]
def client_login(env, options)
if !defined?(@_client)
@_client = Client.new(env)

View File

@ -33,7 +33,7 @@ module VagrantPlugins
end
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
version = argv[1]
@ -45,7 +45,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
version = VagrantCloud::Version.new(box, box_version, nil, options[:description], access_token)

View File

@ -28,14 +28,14 @@ module VagrantPlugins
help: opts.help.chomp
end
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
version = argv[1]
@env.ui.warn(I18n.t("cloud_command.version.delete_warn", version: version, box: argv.first))
continue = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if continue.downcase != "y"
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
@ -46,7 +46,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
version = VagrantCloud::Version.new(box, box_version, nil, nil, access_token)

View File

@ -29,11 +29,11 @@ module VagrantPlugins
end
@env.ui.warn(I18n.t("cloud_command.version.release_warn", version: argv[1], box: argv.first))
continue = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if continue.downcase != "y"
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
version = argv[1]
@ -45,7 +45,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
version = VagrantCloud::Version.new(box, version, nil, nil, access_token)

View File

@ -29,11 +29,11 @@ module VagrantPlugins
end
@env.ui.warn(I18n.t("cloud_command.version.revoke_warn", version: argv[1], box: argv.first))
continue = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if continue.downcase != "y"
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
version = argv[1]
@ -45,7 +45,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
version = VagrantCloud::Version.new(box, box_version, nil, nil, access_token)

View File

@ -33,7 +33,7 @@ module VagrantPlugins
end
@client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username])
box = argv.first.split('/')
box = argv.first.split('/', 2)
org = box[0]
box_name = box[1]
version = argv[1]
@ -45,7 +45,7 @@ module VagrantPlugins
org = options[:username] if options[:username]
server_url = VagrantPlugins::CloudCommand::Util.api_server_url
account = VagrantPlugins::CloudCommand::Util.account?(org, access_token, server_url)
account = VagrantPlugins::CloudCommand::Util.account(org, access_token, server_url)
box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token)
version = VagrantCloud::Version.new(box, box_version, nil, options[:description], access_token)

View File

@ -14,7 +14,7 @@ module VagrantPlugins
command(:login) do
require File.expand_path("../../cloud/auth/login", __FILE__)
init!
# TODO: Print dep warning here?
$stderr.puts "WARNING: This command has been deprecated in favor of `vagrant cloud auth login`"
VagrantPlugins::CloudCommand::AuthCommand::Command::Login
end

View File

@ -87,7 +87,7 @@ describe VagrantPlugins::CloudCommand::Client do
}
let(:response) {
{
token: "baz"
token: "mysecrettoken"
}
}
@ -99,7 +99,7 @@ describe VagrantPlugins::CloudCommand::Client do
client.username_or_email = login
client.password = password
expect(client.login(description: "Token description")).to eq("baz")
expect(client.login(description: "Token description")).to eq("mysecrettoken")
end
context "when 2fa is required" do

View File

@ -26,8 +26,6 @@ The main functionality of this command is exposed via even more subcommands:
**Command: `vagrant cloud auth`**
Information about this subcommand goes here
* [`login`](#cloud-auth-login)
* [`logout`](#cloud-auth-logout)
* [`who`](#cloud-auth-who)