From 78d309a09b28d9e6d933ae820f97a8041079e05e Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 18 Sep 2020 14:35:38 -0700 Subject: [PATCH 1/4] Update cloud command to use refactored library implementation This PR is dependent on the 2.1.0 release of the vagrant_cloud library. It updates the `cloud` command to use the new interface for interacting with the Vagrant Cloud API. It also adds support for direct to backend storage uploads, and defaults to this method. Also included is a bit of cleanup refactoring, addition of method documentation, and fixing up some small issues around custom username usage within the internal client for authentication. --- lib/vagrant/util/uploader.rb | 9 +- plugins/commands/cloud/auth/login.rb | 43 +- plugins/commands/cloud/auth/logout.rb | 12 +- plugins/commands/cloud/auth/whoami.rb | 38 +- plugins/commands/cloud/box/create.rb | 62 +-- plugins/commands/cloud/box/delete.rb | 62 ++- plugins/commands/cloud/box/show.rb | 75 +-- plugins/commands/cloud/box/update.rb | 60 ++- plugins/commands/cloud/client/client.rb | 111 ++-- plugins/commands/cloud/list.rb | 7 +- plugins/commands/cloud/locales/en.yml | 18 +- plugins/commands/cloud/plugin.rb | 5 + plugins/commands/cloud/provider/create.rb | 66 ++- plugins/commands/cloud/provider/delete.rb | 68 +-- plugins/commands/cloud/provider/update.rb | 65 ++- plugins/commands/cloud/provider/upload.rb | 78 +-- plugins/commands/cloud/publish.rb | 289 +++++++---- plugins/commands/cloud/search.rb | 55 +- plugins/commands/cloud/util.rb | 480 +++++++++++------- plugins/commands/cloud/version/create.rb | 61 ++- plugins/commands/cloud/version/delete.rb | 63 ++- plugins/commands/cloud/version/release.rb | 66 +-- plugins/commands/cloud/version/revoke.rb | 65 +-- plugins/commands/cloud/version/update.rb | 54 +- .../plugins/commands/cloud/auth/login_test.rb | 202 +++++--- .../commands/cloud/auth/logout_test.rb | 6 +- .../commands/cloud/auth/whoami_test.rb | 110 +++- .../plugins/commands/cloud/box/create_test.rb | 173 +++++-- .../plugins/commands/cloud/box/delete_test.rb | 135 +++-- .../plugins/commands/cloud/box/show_test.rb | 175 +++++-- .../plugins/commands/cloud/box/update_test.rb | 174 +++++-- .../plugins/commands/cloud/client_test.rb | 380 +++++++------- .../commands/cloud/provider/create_test.rb | 211 +++++--- .../commands/cloud/provider/delete_test.rb | 170 +++++-- .../commands/cloud/provider/update_test.rb | 212 +++++--- .../commands/cloud/provider/upload_test.rb | 225 +++++--- .../plugins/commands/cloud/publish_test.rb | 393 ++++++++++---- .../plugins/commands/cloud/search_test.rb | 172 +++++-- .../commands/cloud/version/create_test.rb | 160 ++++-- .../commands/cloud/version/delete_test.rb | 148 ++++-- .../commands/cloud/version/release_test.rb | 164 +++--- .../commands/cloud/version/revoke_test.rb | 149 ++++-- .../commands/cloud/version/update_test.rb | 154 ++++-- test/unit/vagrant/util/uploader_test.rb | 2 +- vagrant.gemspec | 3 +- 45 files changed, 3503 insertions(+), 1927 deletions(-) diff --git a/lib/vagrant/util/uploader.rb b/lib/vagrant/util/uploader.rb index 17af39ef3..dc50e47c8 100644 --- a/lib/vagrant/util/uploader.rb +++ b/lib/vagrant/util/uploader.rb @@ -13,9 +13,11 @@ 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 + # @param [String] destination Valid URL to upload file to + # @param [String] file Location of file to upload on disk + # @param [Hash] options + # @option options [Vagrant::UI] :ui UI interface for output + # @option options [String, Symbol] :method Request method for upload def initialize(destination, file, options=nil) options ||= {} @logger = Log4r::Logger.new("vagrant::util::uploader") @@ -27,6 +29,7 @@ module Vagrant if !@request_method @request_method = "PUT" end + @request_method = @request_method.to_s.upcase end def upload! diff --git a/plugins/commands/cloud/auth/login.rb b/plugins/commands/cloud/auth/login.rb index a596e5a0c..511fb98dd 100644 --- a/plugins/commands/cloud/auth/login.rb +++ b/plugins/commands/cloud/auth/login.rb @@ -5,6 +5,8 @@ module VagrantPlugins module AuthCommand module Command class Login < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -16,15 +18,10 @@ module VagrantPlugins o.on("-c", "--check", "Checks if currently logged in") do |c| options[:check] = c end - o.on("-d", "--description DESCRIPTION", String, "Set description for the Vagrant Cloud token") do |d| options[:description] = d end - o.on("-k", "--logout", "Logout from Vagrant Cloud") do |k| - options[:logout] = k - end - o.on("-t", "--token TOKEN", String, "Set the Vagrant Cloud token") do |t| options[:token] = t end @@ -37,26 +34,32 @@ module VagrantPlugins # Parse the options argv = parse_options(opts) return if !argv + if !argv.empty? + raise Vagrant::Errors::CLIInvalidUsage, + help: opts.help.chomp + end - @client = Client.new(@env) - @client.username_or_email = options[:login] + client = Client.new(@env) + client.username_or_email = options[:login] # Determine what task we're actually taking based on flags if options[:check] - return execute_check - elsif options[:logout] - return execute_logout + return execute_check(client) elsif options[:token] - return execute_token(options[:token]) + return execute_token(client, options[:token]) else - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options) + if client.logged_in? + @env.ui.success(I18n.t("cloud_command.check_logged_in")) + else + client_login(@env, options.slice(:login, :description)) + end end 0 end - def execute_check - if @client.logged_in? + def execute_check(client) + if client.logged_in? @env.ui.success(I18n.t("cloud_command.check_logged_in")) return 0 else @@ -65,17 +68,11 @@ module VagrantPlugins end end - def execute_logout - @client.clear_token - @env.ui.success(I18n.t("cloud_command.logged_out")) - return 0 - end - - def execute_token(token) - @client.store_token(token) + def execute_token(client, token) + client.store_token(token) @env.ui.success(I18n.t("cloud_command.token_saved")) - if @client.logged_in? + if client.logged_in? @env.ui.success(I18n.t("cloud_command.check_logged_in")) return 0 else diff --git a/plugins/commands/cloud/auth/logout.rb b/plugins/commands/cloud/auth/logout.rb index 5f12d78cd..4dbd17834 100644 --- a/plugins/commands/cloud/auth/logout.rb +++ b/plugins/commands/cloud/auth/logout.rb @@ -9,15 +9,9 @@ module VagrantPlugins options = {} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant cloud auth logout [options]" + o.banner = "Usage: vagrant cloud auth logout" o.separator "" o.separator "Log out of Vagrant Cloud" - o.separator "" - o.separator "Options:" - o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |l| - options[:login] = l - end end # Parse the options @@ -28,9 +22,7 @@ module VagrantPlugins help: opts.help.chomp end - # Initializes client and deletes token on disk - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - + @client = Client.new(@env) @client.clear_token @env.ui.success(I18n.t("cloud_command.logged_out")) return 0 diff --git a/plugins/commands/cloud/auth/whoami.rb b/plugins/commands/cloud/auth/whoami.rb index 38deaee45..440f6bd98 100644 --- a/plugins/commands/cloud/auth/whoami.rb +++ b/plugins/commands/cloud/auth/whoami.rb @@ -5,19 +5,15 @@ module VagrantPlugins module AuthCommand module Command class Whoami < Vagrant.plugin("2", :command) + include Util + def execute options = {} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant cloud auth whoami [options] [token]" + o.banner = "Usage: vagrant cloud auth whoami [token]" o.separator "" o.separator "Display currently logged in user" - o.separator "" - o.separator "Options:" - o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |l| - options[:login] = l - end end # Parse the options @@ -28,28 +24,30 @@ module VagrantPlugins help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:login]) - if argv.first token = argv.first else - token = @client.token + client = Client.new(@env) + token = client.token end - whoami(token, options[:username]) + whoami(token) end - def whoami(access_token, username) - server_url = VagrantPlugins::CloudCommand::Util.api_server_url - account = VagrantPlugins::CloudCommand::Util.account(username, access_token, server_url) - + def whoami(access_token) + if access_token.to_s.empty? + @env.ui.error(I18n.t("cloud_command.check_not_logged_in")) + return 1 + end begin - success = account.validate_token - user = success["user"]["username"] - @env.ui.success("Currently logged in as #{user}") + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + @env.ui.success("Currently logged in as #{account.username}") return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.whoami.read_error", org: username)) + rescue VagrantCloud::Error::ClientError => e + @env.ui.error(I18n.t("cloud_command.errors.whoami.read_error")) @env.ui.error(e) return 1 end diff --git a/plugins/commands/cloud/box/create.rb b/plugins/commands/cloud/box/create.rb index 5027872e6..f8d89e7e7 100644 --- a/plugins/commands/cloud/box/create.rb +++ b/plugins/commands/cloud/box/create.rb @@ -5,6 +5,8 @@ module VagrantPlugins module BoxCommand module Command class Create < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -19,13 +21,10 @@ module VagrantPlugins o.on("-d", "--description DESCRIPTION", String, "Full description of the box") do |d| options[:description] = d end - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end o.on("-s", "--short-description DESCRIPTION", String, "Short description of the box") do |s| options[:short] = s end - o.on("-p", "--private", "Makes box private") do |p| + o.on("-p", "--[no-]private", "Makes box private") do |p| options[:private] = p end end @@ -38,35 +37,40 @@ module VagrantPlugins help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] - create_box(org, box_name, options, @client.token) + org, box_name = argv.first.split('/', 2) + create_box(org, box_name, @client.token, options.slice(:description, :short, :private)) end - # @param [String] - org - # @param [String] - box_name - # @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) - box = VagrantCloud::Box.new(account, box_name, nil, options[:short], options[:description], access_token) + # Create a new box + # + # @param [String] org Organization name of box + # @param [String] box_name Name of box + # @param [String] access_token User access token + # @param [Hash] options Options for box filtering + # @option options [String] :short Short description of box + # @option options [String] :description Full description of box + # @option options [Boolean] :private Set box visibility as private + # @return [Integer] + def create_box(org, box_name, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + box = account.organization(name: org).add_box(box_name) + box.short_description = options[:short] if options.key?(:short) + box.description = options[:description] if options.key?(:description) + box.private = options[:private] if options.key?(:private) + box.save - begin - success = box.create - @env.ui.success(I18n.t("cloud_command.box.create_success", org: org, box_name: box_name)) - success = success.delete_if { |_, v| v.nil? } - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.box.create_fail", org: org, box_name: box_name)) - @env.ui.error(e) - return 1 - end - - return 1 + @env.ui.success(I18n.t("cloud_command.box.create_success", org: org, box_name: box_name)) + format_box_results(box, @env) + 0 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.box.create_fail", org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/box/delete.rb b/plugins/commands/cloud/box/delete.rb index a0a01ad5f..a326d8576 100644 --- a/plugins/commands/cloud/box/delete.rb +++ b/plugins/commands/cloud/box/delete.rb @@ -5,6 +5,8 @@ module VagrantPlugins module BoxCommand module Command class Delete < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -16,8 +18,8 @@ module VagrantPlugins o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u + o.on("-f", "--[no-]force", "Do not prompt for deletion confirmation") do |f| + options[:force] = f end end @@ -29,34 +31,38 @@ module VagrantPlugins help: opts.help.chomp end - @env.ui.warn(I18n.t("cloud_command.box.delete_warn", box: argv.first)) - 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('/', 2) - org = box[0] - box_name = box[1] - delete_box(org, box_name, options[:username], @client.token) - end - - 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) - box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token) - - begin - success = box.delete(org, box_name) - @env.ui.success(I18n.t("cloud_command.box.delete_success", org: org, box_name: box_name)) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.box.delete_fail", org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + if !options[:force] + @env.ui.warn(I18n.t("cloud_command.box.delete_warn", box: argv.first)) + cont = @env.ui.ask(I18n.t("cloud_command.continue")) + return 1 if cont.strip.downcase != "y" end - return 1 + @client = client_login(@env) + + org, box_name = argv.first.split('/', 2) + delete_box(org, box_name, @client.token) + end + + # Delete the requested box + # + # @param [String] org Organization name of box + # @param [String] box_name Name of box + # @param [String] access_token User access token + # @return [Integer] + def delete_box(org, box_name, access_token) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_box(account: account, org: org, box: box_name) do |box| + box.delete + @env.ui.success(I18n.t("cloud_command.box.delete_success", org: org, box_name: box_name)) + 0 + end + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.box.delete_fail", org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/box/show.rb b/plugins/commands/cloud/box/show.rb index ac6cb724b..2a52fc357 100644 --- a/plugins/commands/cloud/box/show.rb +++ b/plugins/commands/cloud/box/show.rb @@ -5,8 +5,10 @@ module VagrantPlugins module BoxCommand module Command class Show < Vagrant.plugin("2", :command) + include Util + def execute - options = {} + options = {quiet: true, versions: []} opts = OptionParser.new do |o| o.banner = "Usage: vagrant cloud box show [options] organization/box-name" @@ -16,11 +18,11 @@ module VagrantPlugins o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u + o.on("--versions VERSION", String, "Display box information for a specific version (can be defined multiple times)") do |v| + options[:versions] << v end - o.on("--versions VERSION", String, "Display box information for a specific version") do |v| - options[:version] = v + o.on("--[no-]auth", "Authenticate with Vagrant Cloud if required before searching") do |l| + options[:quiet] = !l end end @@ -32,40 +34,47 @@ module VagrantPlugins help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - box = argv.first.split('/', 2) + @client = client_login(@env, options.slice(:quiet)) + org, box_name = argv.first.split('/', 2) - show_box(box[0], box[1], options, @client.token) + show_box(org, box_name, @client&.token, options.slice(:versions)) end - def show_box(org, box_name, options, access_token) - username = options[:username] - - server_url = VagrantPlugins::CloudCommand::Util.api_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 - success = box.read(org, box_name) - - if options[:version] - # 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)) - return 0 - end + # Display the requested box to the user + # + # @param [String] org Organization name of box + # @param [String] box_name Name of box + # @param [String] access_token User access token + # @param [Hash] options Options for box filtering + # @option options [String] :versions Specific verisons of box + # @return [Integer] + def show_box(org, box_name, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_box(account: account, org: org, box: box_name) do |box| + if box && options[:versions] + box = box.versions.find_all{ |v| options[:versions].include?(v.version) } else - results = success + box = [box] + end + + if !box.empty? + box.each do |b| + format_box_results(b, @env) + end + 0 + else + @env.ui.warn(I18n.t("cloud_command.box.show_filter_empty", + version: options[:version], org: org, box_name: box_name)) + 1 end - results = results.delete_if { |_, v| v.nil? } - VagrantPlugins::CloudCommand::Util.format_box_results(results, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.box.show_fail", org: org,box_name:box_name)) - @env.ui.error(e) - return 1 end + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.box.show_fail", org: org,box_name:box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/box/update.rb b/plugins/commands/cloud/box/update.rb index a71956f61..5df1a2c73 100644 --- a/plugins/commands/cloud/box/update.rb +++ b/plugins/commands/cloud/box/update.rb @@ -5,6 +5,8 @@ module VagrantPlugins module BoxCommand module Command class Update < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -19,13 +21,10 @@ module VagrantPlugins o.on("-d", "--description DESCRIPTION", "Full 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| - options[:username] = u - end o.on("-s", "--short-description DESCRIPTION", "Short description of the box") do |s| - options[:short_description] = s + options[:short] = s end - o.on("-p", "--private", "Makes box private") do |p| + o.on("-p", "--[no-]private", "Makes box private") do |p| options[:private] = p end end @@ -33,36 +32,45 @@ module VagrantPlugins # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 1 || options.length == 0 + if argv.empty? || argv.length > 1 || options.slice(:description, :short, :private).length == 0 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - box = argv.first.split('/', 2) + @client = client_login(@env) + org, box_name = argv.first.split('/', 2) - update_box(box[0], box[1], options, @client.token) + update_box(org, box_name, @client.token, options.slice(:short, :description, :private)) 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) - box = VagrantCloud::Box.new(account, box_name, nil, nil, nil, access_token) - - options[:organization] = org - options[:name] = box_name - begin - success = box.update(options) + # Update an existing box + # + # @param [String] org Organization name of box + # @param [String] box_name Name of box + # @param [String] access_token User access token + # @param [Hash] options Options for box filtering + # @option options [String] :short Short description of box + # @option options [String] :description Full description of box + # @option options [Boolean] :private Set box visibility as private + # @return [Integer] + def update_box(org, box_name, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_box(account: account, org: org, box: box_name) do |box| + box.short_description = options[:short] if options.key?(:short) + box.description = options[:description] if options.key?(:description) + box.private = options[:private] if options.key?(:private) + box.save @env.ui.success(I18n.t("cloud_command.box.update_success", org: org, box_name: box_name)) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.box.update_fail", org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + format_box_results(box, @env) + 0 end - return 1 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.box.update_fail", org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/client/client.rb b/plugins/commands/cloud/client/client.rb index f0df2782e..30de19043 100644 --- a/plugins/commands/cloud/client/client.rb +++ b/plugins/commands/cloud/client/client.rb @@ -14,6 +14,7 @@ module VagrantPlugins include Vagrant::Util::Presence + attr_accessor :client attr_accessor :username_or_email attr_accessor :password attr_reader :two_factor_default_delivery_method @@ -25,6 +26,7 @@ module VagrantPlugins def initialize(env) @logger = Log4r::Logger.new("vagrant::cloud::client") @env = env + @client = VagrantCloud::Client.new(access_token: token) end # Removes the token, effectively logging the user out. @@ -38,14 +40,11 @@ module VagrantPlugins # # @return [Boolean] def logged_in? - token = self.token - return false if !token - Vagrant::Util::CredentialScrubber.sensitive(token) + return false if !client.access_token + Vagrant::Util::CredentialScrubber.sensitive(client.access_token) with_error_handling do - url = "#{Vagrant.server_url}/api/v1/authenticate" + - "?access_token=#{token}" - RestClient.get(url, content_type: :json) + client.authentication_token_validate true end rescue Errors::Unauthorized @@ -62,23 +61,14 @@ module VagrantPlugins @logger.info("Logging in '#{username_or_email}'") Vagrant::Util::CredentialScrubber.sensitive(password) - response = post( - "/api/v1/authenticate", { - user: { - login: username_or_email, - password: password - }, - token: { - description: description - }, - two_factor: { - code: code - } - } - ) + with_error_handling do + r = client.authentication_token_create(username: username_or_email, + password: password, description: description, code: code) - Vagrant::Util::CredentialScrubber.sensitive(response["token"]) - response["token"] + Vagrant::Util::CredentialScrubber.sensitive(r[:token]) + @client = VagrantCloud::Client.new(access_token: r[:token]) + r[:token] + end end # Requests a 2FA code @@ -87,50 +77,14 @@ module VagrantPlugins @env.ui.warn("Requesting 2FA code via #{delivery_method.upcase}...") Vagrant::Util::CredentialScrubber.sensitive(password) - response = post( - "/api/v1/two-factor/request-code", { - user: { - login: username_or_email, - password: password - }, - two_factor: { - delivery_method: delivery_method.downcase - } - } - ) - - two_factor = response['two_factor'] - obfuscated_destination = two_factor['obfuscated_destination'] - - @env.ui.success("2FA code sent to #{obfuscated_destination}.") - end - - # Issues a post to a Vagrant Cloud path with the given payload. - # @param [String] path - # @param [Hash] payload - # @return [Hash] response data - def post(path, payload) with_error_handling do - url = File.join(Vagrant.server_url, path) + r = client.authentication_request_2fa_code( + username: username_or_email, password: password, delivery_method: delivery_method) - proxy = nil - proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"] - proxy ||= ENV["HTTP_PROXY"] || ENV["http_proxy"] - RestClient.proxy = proxy + two_factor = r[:two_factor] + obfuscated_destination = two_factor[:obfuscated_destination] - response = RestClient::Request.execute( - method: :post, - url: url, - payload: JSON.dump(payload), - proxy: proxy, - headers: { - accept: :json, - content_type: :json, - user_agent: Vagrant::Util::Downloader::USER_AGENT, - }, - ) - - JSON.load(response.to_s) + @env.ui.success("2FA code sent to #{obfuscated_destination}.") end end @@ -138,12 +92,16 @@ module VagrantPlugins # # @param [String] token def store_token(token) + Vagrant::Util::CredentialScrubber.sensitive(token) @logger.info("Storing token in #{token_path}") token_path.open("w") do |f| f.write(token) end + # Reset after we store the token since this is now _our_ token + @client = VagrantCloud::Client.new(access_token: token) + nil end @@ -167,17 +125,18 @@ EOH if present?(ENV["VAGRANT_CLOUD_TOKEN"]) @logger.debug("Using authentication token from environment variable") - return ENV["VAGRANT_CLOUD_TOKEN"] - end - - if token_path.exist? + t = ENV["VAGRANT_CLOUD_TOKEN"] + elsif token_path.exist? @logger.debug("Using authentication token from disk at #{token_path}") - return token_path.read.strip + t = token_path.read.strip + elsif present?(ENV["ATLAS_TOKEN"]) + @logger.warn("ATLAS_TOKEN detected within environment. Using ATLAS_TOKEN in place of VAGRANT_CLOUD_TOKEN.") + t = ENV["ATLAS_TOKEN"] end - if present?(ENV["ATLAS_TOKEN"]) - @logger.warn("ATLAS_TOKEN detected within environment. Using ATLAS_TOKEN in place of VAGRANT_CLOUD_TOKEN.") - return ENV["ATLAS_TOKEN"] + if !t.nil? + Vagrant::Util::CredentialScrubber.sensitive(t) + return t end @logger.debug("No authentication token in environment or #{token_path}") @@ -189,22 +148,22 @@ EOH def with_error_handling(&block) yield - rescue RestClient::Unauthorized + rescue Excon::Error::Unauthorized @logger.debug("Unauthorized!") raise Errors::Unauthorized - rescue RestClient::BadRequest => e + rescue Excon::Error::BadRequest => e @logger.debug("Bad request:") @logger.debug(e.message) @logger.debug(e.backtrace.join("\n")) - parsed_response = JSON.parse(e.response) + parsed_response = JSON.parse(e.response.body) errors = parsed_response["errors"].join("\n") raise Errors::ServerError, errors: errors - rescue RestClient::NotAcceptable => e + rescue Excon::Error::NotAcceptable => e @logger.debug("Got unacceptable response:") @logger.debug(e.message) @logger.debug(e.backtrace.join("\n")) - parsed_response = JSON.parse(e.response) + parsed_response = JSON.parse(e.response.body) if two_factor = parsed_response['two_factor'] store_two_factor_information two_factor diff --git a/plugins/commands/cloud/list.rb b/plugins/commands/cloud/list.rb index edb18b1d0..2df3d1bc8 100644 --- a/plugins/commands/cloud/list.rb +++ b/plugins/commands/cloud/list.rb @@ -4,6 +4,8 @@ module VagrantPlugins module CloudCommand module Command class List < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -27,9 +29,6 @@ module VagrantPlugins o.on("-s", "--sort-by", "Column to sort list (created, downloads, updated)") do |s| options[:check] = s end - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |t| - options[:username] = u - end end # Parse the options @@ -40,7 +39,7 @@ module VagrantPlugins help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) # TODO: This endpoint is not implemented yet diff --git a/plugins/commands/cloud/locales/en.yml b/plugins/commands/cloud/locales/en.yml index ddc0a40fa..451bd8dbe 100644 --- a/plugins/commands/cloud/locales/en.yml +++ b/plugins/commands/cloud/locales/en.yml @@ -13,14 +13,8 @@ en: Press ctrl-c to cancel... publish: - update_continue: |- - %{obj} already exists, updating instead... - box_create: - Creating a box entry... - version_create: - Creating a version entry... - provider_create: - Creating a provider entry... + box_save: + Saving box information... upload_provider: Uploading provider with file %{file} release: @@ -45,7 +39,7 @@ en: version_desc: |- Version Description: %{version_description} continue: |- - Do you wish to continue? [y/N] + Do you wish to continue? [y/N] box: show_filter_empty: |- No version matched %{version} for %{org}/%{box_name} @@ -57,6 +51,8 @@ en: This will completely remove %{box} from Vagrant Cloud. This cannot be undone. update_success: |- Updated box %{org}/%{box_name} + not_found: |- + Failed to locate requested box: %{org}/%{box_name} search: no_results: |- No results found for `%{query}` @@ -77,6 +73,8 @@ en: Deleted provider %{provider} on %{org}/%{box_name} for version %{version} update_success: |- Updated provider %{provider} on %{org}/%{box_name} for version %{version} + not_found: |- + Failed to locate %{provider_name} provider for %{org}/%{box_name} on version %{version} version: create_success: |- Created version %{version} on %{org}/%{box_name} for version %{version} @@ -94,6 +92,8 @@ en: This will release version %{version} from %{box} to Vagrant Cloud and be available to download. delete_warn: |- This will completely remove version %{version} from %{box} from Vagrant Cloud. This cannot be undone. + not_found: |- + Failed to locate version %{version} for %{org}/%{box_name} errors: search: fail: |- diff --git a/plugins/commands/cloud/plugin.rb b/plugins/commands/cloud/plugin.rb index cce50a01c..5967eacc9 100644 --- a/plugins/commands/cloud/plugin.rb +++ b/plugins/commands/cloud/plugin.rb @@ -12,6 +12,11 @@ module VagrantPlugins DESC command(:cloud) do + # Set this to match Vagant logging level so we get + # desired request/response information within the + # logger output + ENV["VAGRANT_CLOUD_LOG"] = Vagrant.log_level + require_relative "root" init! Command::Root diff --git a/plugins/commands/cloud/provider/create.rb b/plugins/commands/cloud/provider/create.rb index 5659d3836..ce1b66d6b 100644 --- a/plugins/commands/cloud/provider/create.rb +++ b/plugins/commands/cloud/provider/create.rb @@ -5,6 +5,8 @@ module VagrantPlugins module ProviderCommand module Command class Create < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -16,9 +18,6 @@ module VagrantPlugins o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c| options[:checksum] = c end @@ -30,48 +29,59 @@ module VagrantPlugins # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 4 + if argv.count < 3 || argv.count > 4 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + org, box_name = argv.first.split('/', 2) provider_name = argv[1] version = argv[2] url = argv[3] - upload_provider(org, box_name, provider_name, version, url, @client.token, options) + create_provider(org, box_name, version, provider_name, url, @client.token, options) end - def upload_provider(org, box_name, provider_name, version, url, access_token, options) + # Create a provider for the box version + # + # @param [String] org Organization name + # @param [String] box Box name + # @param [String] version Box version + # @param [String] provider Provider name + # @param [String] url Provider asset URL + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options + # @option options [String] :checksum Checksum of the box asset + # @option options [String] :checksum_type Type of the checksum + # @return [Integer] + def create_provider(org, box, version, provider, url, access_token, options={}) if !url @env.ui.warn(I18n.t("cloud_command.upload.no_url")) end + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_version(account: account, org: org, box: box, version: version) do |version| + provider = version.add_provider(provider) + provider.checksum = options[:checksum] if options.key?(:checksum) + provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type) + provider.url = url if url - org = options[:username] if options[:username] + provider.save - server_url = VagrantPlugins::CloudCommand::Util.api_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, nil, options[:checksum], options[:checksum_type]) - - begin - success = provider.create_provider - @env.ui.success(I18n.t("cloud_command.provider.create_success", provider: provider_name, org: org, box_name: box_name, version: version)) - success = success.compact - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.provider.create_fail", provider: provider_name, org: org, box_name: box_name, version: version)) - @env.ui.error(e) - return 1 + @env.ui.success(I18n.t("cloud_command.provider.create_success", + provider: provider.name, org: org, box_name: box, version: version.version)) + format_box_results(provider, @env) + 0 end + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.provider.create_fail", + provider: provider, org: org, box_name: box, version: version)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/provider/delete.rb b/plugins/commands/cloud/provider/delete.rb index cb77b501d..414e0e104 100644 --- a/plugins/commands/cloud/provider/delete.rb +++ b/plugins/commands/cloud/provider/delete.rb @@ -5,6 +5,8 @@ module VagrantPlugins module ProviderCommand module Command class Delete < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -15,53 +17,61 @@ module VagrantPlugins o.separator "" o.separator "Options:" o.separator "" - - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u + o.on("-f", "--[no-]force", "Force deletion of box version provider without confirmation") do |f| + options[:force] = f end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 3 + if argv.count != 3 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + org, box_name = argv.first.split('/', 2) 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)) - cont = @env.ui.ask(I18n.t("cloud_command.continue")) - return 1 if cont.strip.downcase != "y" + @env.ui.warn(I18n.t("cloud_command.provider.delete_warn", + provider: provider_name, version:version, box: argv.first)) - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + if !options[:force] + cont = @env.ui.ask(I18n.t("cloud_command.continue")) + return 1 if cont.strip.downcase != "y" + end - delete_provider(org, box_name, provider_name, version, @client.token, options) + @client = client_login(@env) + + delete_provider(org, box_name, version, provider_name, @client.token, options) end - def delete_provider(org, box_name, provider_name, version, access_token, options) - org = options[:username] if options[:username] - - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - begin - success = provider.delete - @env.ui.error(I18n.t("cloud_command.provider.delete_success", provider: provider_name, org: org, box_name: box_name, version: version)) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.provider.delete_fail", provider: provider_name, org: org, box_name: box_name, version: version)) - @env.ui.error(e) - return 1 + # Delete a provider for the box version + # + # @param [String] org Organization name + # @param [String] box Box name + # @param [String] version Box version + # @param [String] provider Provider name + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options Currently unused + # @return [Integer] + def delete_provider(org, box, version, provider, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_provider(account: account, org: org, box: box, version: version, provider: provider) do |p| + p.delete + @env.ui.error(I18n.t("cloud_command.provider.delete_success", + provider: provider, org: org, box_name: box, version: version)) + 0 end + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.provider.delete_fail", + provider: provider, org: org, box_name: box, version: version)) + @env.ui.error(e) + 1 end end end diff --git a/plugins/commands/cloud/provider/update.rb b/plugins/commands/cloud/provider/update.rb index 5febdd867..3c5760816 100644 --- a/plugins/commands/cloud/provider/update.rb +++ b/plugins/commands/cloud/provider/update.rb @@ -5,20 +5,19 @@ module VagrantPlugins module ProviderCommand module Command class Update < Vagrant.plugin("2", :command) + include Util + def execute options = {} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant cloud provider update [options] organization/box-name provider-name version url" + o.banner = "Usage: vagrant cloud provider update [options] organization/box-name provider-name version [url]" o.separator "" o.separator "Updates a provider entry on Vagrant Cloud" o.separator "" o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c| options[:checksum] = c end @@ -30,48 +29,58 @@ module VagrantPlugins # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 4 + if argv.count < 3 || argv.count > 4 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + org, box_name = argv.first.split('/', 2) provider_name = argv[1] version = argv[2] url = argv[3] - update_provider(org, box_name, provider_name, version, url, @client.token, options) + update_provider(org, box_name, version, provider_name, url, @client.token, options) end - def update_provider(org, box_name, provider_name, version, url, access_token, options) + # Update a provider for the box version + # + # @param [String] org Organization name + # @param [String] box Box name + # @param [String] version Box version + # @param [String] provider Provider name + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options + # @option options [String] :checksum Checksum of the box asset + # @option options [String] :checksum_type Type of the checksum + # @return [Integer] + def update_provider(org, box, version, provider, url, access_token, options) if !url @env.ui.warn(I18n.t("cloud_command.upload.no_url")) end + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) - org = options[:username] if options[:username] + with_provider(account: account, org: org, box: box, version: version, provider: provider) do |p| + p.checksum = options[:checksum] if options.key?(:checksum) + p.checksum_type = options[:checksum_type] if options.key?(:checksum_type) + p.url = url if !url.nil? + p.save - server_url = VagrantPlugins::CloudCommand::Util.api_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, nil, options[:checksum], options[:checksum_type]) + @env.ui.success(I18n.t("cloud_command.provider.update_success", + provider: provider, org: org, box_name: box, version: version)) - begin - success = provider.update - @env.ui.success(I18n.t("cloud_command.provider.update_success", provider:provider_name, org: org, box_name: box_name, version: version)) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.provider.update_fail", provider:provider_name, org: org, box_name: box_name, version: version)) - @env.ui.error(e) - return 1 + format_box_results(p, @env) + 0 end + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.provider.update_fail", + provider: provider, org: org, box_name: box, version: version)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/provider/upload.rb b/plugins/commands/cloud/provider/upload.rb index f599745ce..41b8e0613 100644 --- a/plugins/commands/cloud/provider/upload.rb +++ b/plugins/commands/cloud/provider/upload.rb @@ -6,8 +6,10 @@ module VagrantPlugins module ProviderCommand module Command class Upload < Vagrant.plugin("2", :command) + include Util + def execute - options = {} + options = {direct: true} opts = OptionParser.new do |o| o.banner = "Usage: vagrant cloud provider upload [options] organization/box-name provider-name version box-file" @@ -16,57 +18,65 @@ module VagrantPlugins o.separator "" o.separator "Options:" o.separator "" - - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u + o.on("-D", "--[no-]direct", "Upload asset directly to backend storage") do |d| + options[:direct] = d end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 4 + if argv.count != 4 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + org, box_name = argv.first.split('/', 2) provider_name = argv[1] version = argv[2] - file = argv[3] # path expand + file = File.expand_path(argv[3]) - upload_provider(org, box_name, provider_name, version, file, @client.token, options) + upload_provider(org, box_name, version, provider_name, file, @client.token, options) end - def upload_provider(org, box_name, provider_name, version, file, access_token, options) - org = options[:username] if options[:username] + # Upload an asset for a box version provider + # + # @param [String] org Organization name + # @param [String] box Box name + # @param [String] version Box version + # @param [String] provider Provider name + # @param [String] file Path to asset + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options + # @option options [Boolean] :direct Upload directly to backend storage + # @return [Integer] + def upload_provider(org, box, version, provider, file, access_token, options) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - ul = Vagrant::Util::Uploader.new(provider.upload_url, file, ui: @env.ui) - ui = Vagrant::UI::Prefixed.new(@env.ui, "cloud") - - begin - ui.output(I18n.t("cloud_command.provider.upload", org: org, box_name: box_name, version: version, provider: provider_name)) - ui.info("Upload File: #{file}") - - ul.upload! - - ui.success("Successfully uploaded box '#{org}/#{box_name}' (v#{version}) for '#{provider_name}'") - return 0 - rescue Vagrant::Errors::UploaderError, VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.provider.upload_fail", provider: provider_name, org: org, box_name: box_name, version: version)) - @env.ui.error(e) - return 1 + with_provider(account: account, org: org, box: box, version: version, provider: provider) do |p| + p.upload(direct: options[:direct]) do |upload_url| + m = options[:direct] ? :put : :put + uploader = Vagrant::Util::Uploader.new(upload_url, file, ui: @env.ui, method: m) + ui = Vagrant::UI::Prefixed.new(@env.ui, "cloud") + ui.output(I18n.t("cloud_command.provider.upload", + org: org, box_name: box, version: version, provider: provider)) + ui.info("Upload File: #{file}") + uploader.upload! + ui.success(I18n.t("cloud_command.provider.upload_success", + org: org, box_name: box, version: version, provider: provider)) + end + 0 end + rescue Vagrant::Errors::UploaderError, VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.provider.upload_fail", + provider: provider, org: org, box_name: box, version: version)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/publish.rb b/plugins/commands/cloud/publish.rb index d8b44a22b..a136eafb8 100644 --- a/plugins/commands/cloud/publish.rb +++ b/plugins/commands/cloud/publish.rb @@ -5,11 +5,13 @@ module VagrantPlugins module CloudCommand module Command class Publish < Vagrant.plugin("2", :command) + include Util + def execute - options = {} + options = {direct_upload: true} opts = OptionParser.new do |o| - o.banner = "Usage: vagrant cloud publish [options] organization/box-name version provider-name provider-file" + o.banner = "Usage: vagrant cloud publish [options] organization/box-name version provider-name [provider-file]" o.separator "" o.separator "Create and release a new Vagrant Box on Vagrant Cloud" o.separator "" @@ -19,7 +21,7 @@ module VagrantPlugins o.on("--box-version VERSION", String, "Version of box to create") do |v| options[:box_version] = v end - o.on("--url URL", String, "Remote URL to download this provider") do |u| + o.on("--url URL", String, "Remote URL to download this provider (cannot be used with provider-file)") do |u| options[:url] = u end o.on("-d", "--description DESCRIPTION", String, "Full description of box") do |d| @@ -28,154 +30,229 @@ module VagrantPlugins o.on("--version-description DESCRIPTION", String, "Description of the version to create") do |v| options[:version_description] = v end - o.on("-f", "--force", "Disables confirmation to create or update box") do |f| + o.on("-f", "--[no-]force", "Disables confirmation to create or update box") do |f| options[:force] = f end - o.on("-p", "--private", "Makes box private") do |p| + o.on("-p", "--[no-]private", "Makes box private") do |p| options[:private] = p end - o.on("-r", "--release", "Releases box") do |p| + o.on("-r", "--[no-]release", "Releases box") do |p| options[:release] = p end o.on("-s", "--short-description DESCRIPTION", String, "Short description of the box") do |s| options[:short_description] = s end - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c| options[:checksum] = c end o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c| options[:checksum_type] = c end + o.on("--[no-]direct-upload", "Upload asset directly to backend storage") do |d| + options[:direct_upload] = d + end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 4 || argv.length < 3 || (argv.length == 3 && !options[:url]) + if argv.length < 3 || # missing required arguments + argv.length > 4 || # too many arguments + (argv.length < 4 && !options.key?(:url)) || # file argument required if url is not provided + (argv.length > 3 && options.key?(:url)) # cannot provider url and file argument raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] - version = argv[1] - provider_name = argv[2] - box_file = argv[3] + org, box_name = argv.first.split('/', 2) + _, version, provider_name, box_file = argv - if !options[:url] && !File.file?(box_file) + if box_file && !File.file?(box_file) raise Vagrant::Errors::BoxFileNotExist, file: box_file end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) + params = options.slice(:private, :release, :url, :short_description, + :description, :version_description, :checksum, :checksum_type) - publish_box(org, box_name, version, provider_name, box_file, options, @client.token) - end - - 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(I18n.t("cloud_command.publish.confirm.warn")) - - @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] + # Display output to user describing action to be taken + display_preamble(org, box_name, version, provider_name, params) if !options[:force] 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) - 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, nil, options[:checksum], options[:checksum_type]) + # Load up all the models we'll need to publish the asset + box = load_box(org, box_name, @client.token) + box_v = load_box_version(box, version) + box_p = load_version_provider(box_v, provider_name) - ui = Vagrant::UI::Prefixed.new(@env.ui, "cloud") + # Update all the data + set_box_info(box, params.slice(:private, :short_description, :description)) + set_version_info(box_v, params.slice(:version_description)) + set_provider_info(box_p, params.slice(:checksum, :checksum_type, :url)) - begin - ui.info(I18n.t("cloud_command.publish.box_create")) - box.create - rescue VagrantCloud::ClientError => e - if e.error_code == 422 - 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)) - @env.ui.error(e) - return 1 - end + # Save any updated state + @env.ui.warn(I18n.t("cloud_command.publish.box_save")) + box.save + + # If we have a box file asset, upload it + if box_file + upload_box_file(box_p, box_file, options.slice(:direct_upload)) end - begin - ui.info(I18n.t("cloud_command.publish.version_create")) - cloud_version.create_version - rescue VagrantCloud::ClientError => e - if e.error_code == 422 - 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)) - @env.ui.error(e) - return 1 - end - rescue VagrantCloud::InvalidVersion => e - @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + # If configured to release the box, release it + if options[:release] && !box_v.released? + release_version(box_v) end - begin - ui.info(I18n.t("cloud_command.publish.provider_create")) - provider.create_provider - rescue VagrantCloud::ClientError => e - if e.error_code == 422 - 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)) - @env.ui.error(e) - return 1 - end - end + # And we're done! + @env.ui.success(I18n.t("cloud_command.publish.complete", org: org, box_name: box_name)) + format_box_results(box, @env) + 0 + rescue VagrantCloud::Error => err + @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name)) + @env.ui.error(err.message) + 1 + end - begin - if !options[:url] - box_file = File.absolute_path(box_file) - ui.info(I18n.t("cloud_command.publish.upload_provider", file: box_file)) - ul = Vagrant::Util::Uploader.new(provider.upload_url, box_file, ui: @env.ui) - ul.upload! - end - if options[:release] - ui.info(I18n.t("cloud_command.publish.release")) - cloud_version.release - end - @env.ui.success(I18n.t("cloud_command.publish.complete", org: org, box_name: box_name)) - success = box.read(org, box_name) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue Vagrant::Errors::UploaderError, VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + # Upload the file for the given box provider + # + # @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider + # @param [String] box_file Path to local asset for upload + # @param [Hash] options + # @option options [Boolean] :direct_upload Upload directly to backend storage + # @return [nil] + def upload_box_file(provider, box_file, options={}) + box_file = File.absolute_path(box_file) + @env.ui.info(I18n.t("cloud_command.publish.upload_provider", file: box_file)) + provider.upload(direct: options[:direct_upload]) do |upload_url| + Vagrant::Util::Uploader.new(upload_url, box_file, ui: @env.ui, method: :put).upload! end - return 1 + nil + end + + # Release the box version + # + # @param [VagrantCloud::Box::Version] version Vagrant Cloud box version + # @return [nil] + def release_version(version) + @env.ui.info(I18n.t("cloud_command.publish.release")) + version.release + nil + end + + # Set any box related attributes that were provided + # + # @param [VagrantCloud::Box] box Vagrant Cloud box + # @param [Hash] options + # @option options [Boolean] :private Box access is private + # @option options [String] :short_description Short description of box + # @option options [String] :description Full description of box + # @return [VagrantCloud::Box] + def set_box_info(box, options={}) + box.private = options[:private] if options.key?(:private) + box.short_description = options[:short_description] if options.key?(:short_description) + box.description = options[:description] if options.key?(:description) + box + end + + # Set any version related attributes that were provided + # + # @param [VagrantCloud::Box::Version] version Vagrant Cloud box version + # @param [Hash] options + # @option options [String] :version_description Description for this version + # @return [VagrantCloud::Box::Version] + def set_version_info(version, options={}) + version.description = options[:version_description] if options.key?(:version_description) + version + end + + # Set any provider related attributes that were provided + # + # @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider + # @param [Hash] options + # @option options [String] :url Remote URL for self hosted + # @option options [String] :checksum_type Type of checksum value provided + # @option options [String] :checksum Checksum of the box asset + # @return [VagrantCloud::Box::Provider] + def set_provider_info(provider, options={}) + provider.url = options[:url] if options.key?(:url) + provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type) + provider.checksum = options[:checksum] if options.key?(:checksum) + provider + end + + # Load the requested version provider + # + # @param [VagrantCloud::Box::Version] version The version of the Vagrant Cloud box + # @param [String] provider_name Name of the provider + # @return [VagrantCloud::Box::Provider] + def load_version_provider(version, provider_name) + provider = version.providers.detect { |pv| pv.name == provider_name } + return provider if provider + version.add_provider(provider_name) + end + + # Load the requested box version + # + # @param [VagrantCloud::Box] box The Vagrant Cloud box + # @param [String] version Version of the box + # @return [VagrantCloud::Box::Version] + def load_box_version(box, version) + v = box.versions.detect { |v| v.version == version } + return v if v + box.add_version(version) + end + + # Load the requested box + # + # @param [String] org Organization name for box + # @param [String] box_name Name of the box + # @param [String] access_token User access token + # @return [VagrantCloud::Box] + def load_box(org, box_name, access_token) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + box = account.organization(name: org).boxes.detect { |b| b.name == box_name } + return box if box + account.organization(name: org).add_box(box_name) + end + + # Display publishing information to user before starting process + # + # @param [String] org Organization name + # @param [String] box_name Name of the box to publish + # @param [String] version Version of the box to publish + # @param [String] provider_name Name of the provider being published + # @param [Hash] options + # @option options [Boolean] :private Box is private + # @option options [Boolean] :release Box should be released + # @option options [String] :url Remote URL for self-hosted boxes + # @option options [String] :description Description of the box + # @option options [String] :short_description Short description of the box + # @option options [String] :version_description Description of the box version + # @return [nil] + def display_preamble(org, box_name, version, provider_name, options={}) + @env.ui.warn(I18n.t("cloud_command.publish.confirm.warn")) + @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] + nil end end end diff --git a/plugins/commands/cloud/search.rb b/plugins/commands/cloud/search.rb index ab62b181d..5a2f72c96 100644 --- a/plugins/commands/cloud/search.rb +++ b/plugins/commands/cloud/search.rb @@ -4,8 +4,10 @@ module VagrantPlugins module CloudCommand module Command class Search < Vagrant.plugin("2", :command) + include Util + def execute - options = {} + options = {quiet: true} opts = OptionParser.new do |o| o.banner = "Usage: vagrant cloud search [options] query" @@ -37,45 +39,56 @@ module VagrantPlugins o.on("--sort-by SORT", "Field to sort results on (created, downloads, updated) Default: downloads") do |s| options[:sort] = s end - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address to login with") do |u| - options[:username] = u + o.on("--[no-]auth", "Authenticate with Vagrant Cloud if required before searching") do |l| + options[:quiet] = !l end end # Parse the options argv = parse_options(opts) return if !argv - if argv.length > 1 + if argv.length != 1 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env, options.slice(:quiet)) query = argv.first options[:limit] = 25 if !(options[:limit].to_i < 1) && !options[:limit] - search(query, options, @client.token) + search(query, @client&.token, options) end - def search(query, options, access_token) - server_url = VagrantPlugins::CloudCommand::Util.api_server_url - search = VagrantCloud::Search.new(access_token, server_url) + # Perform requested search and display results to user + # + # @param [String] query Search query string + # @param [Hash] options + # @option options [String] :provider Filter by provider + # @option options [String] :sort Field to sort results + # @option options [Integer] :limit Number of results to display + # @option options [Integer] :page Page of results to display + # @param [String] access_token User access token + # @return [Integer] + def search(query, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + params = {query: query}.merge(options.slice(:provider, :sort, :order, :limit, :page)) + result = account.searcher.search(**params) - begin - search_results = search.search(query, options[:provider], options[:sort], options[:order], options[:limit], options[:page]) - if !search_results["boxes"].empty? - VagrantPlugins::CloudCommand::Util.format_search_results(search_results["boxes"], options[:short], options[:json], @env) - else - @env.ui.warn(I18n.t("cloud_command.search.no_results", query: query)) - end + if result.boxes.empty? + @env.ui.warn(I18n.t("cloud_command.search.no_results", query: query)) return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.search.fail")) - @env.ui.error(e) - return 1 end - return 1 + + format_search_results(result.boxes, options[:short], options[:json], @env) + 0 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.search.fail")) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/util.rb b/plugins/commands/cloud/util.rb index ebcadbb9b..355af731d 100644 --- a/plugins/commands/cloud/util.rb +++ b/plugins/commands/cloud/util.rb @@ -1,205 +1,309 @@ module VagrantPlugins module CloudCommand - class Util - class << self - # @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 - @_account + module Util + # @return [String] Vagrant Cloud server URL + def api_server_url + if Vagrant.server_url == Vagrant::DEFAULT_SERVER_URL + return "#{Vagrant.server_url}/api/v1" + else + return Vagrant.server_url + end + end + + # @param [Vagrant::Environment] env + # @param [Hash] options + # @option options [String] :login Username or email + # @option options [String] :description Description of login usage for token + # @option options [String] :code 2FA code for login + # @option options [Boolean] :quiet Do not prompt user + # @returns [VagrantPlugins::CloudCommand::Client, nil] + def client_login(env, options={}) + return @_client if defined?(@_client) + @_client = Client.new(env) + return @_client if @_client.logged_in? + + # If directed to be quiet, do not continue and + # just return nil + return if options[:quiet] + + # Let the user know what is going on. + env.ui.output(I18n.t("cloud_command.command_header") + "\n") + + # If it is a private cloud installation, show that + if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL + env.ui.output("Vagrant Cloud URL: #{Vagrant.server_url}") end - def api_server_url - if Vagrant.server_url == Vagrant::DEFAULT_SERVER_URL - return "#{Vagrant.server_url}/api/v1" + options = {} if !options + # Ask for the username + if options[:login] + @_client.username_or_email = options[:login] + env.ui.output("Vagrant Cloud username or email: #{@_client.username_or_email}") + else + @_client.username_or_email = env.ui.ask("Vagrant Cloud username or email: ") + end + + @_client.password = env.ui.ask("Password (will be hidden): ", echo: false) + + description_default = "Vagrant login from #{Socket.gethostname}" + if !options[:description] + description = env.ui.ask("Token description (Defaults to #{description_default.inspect}): ") + else + description = options[:description] + env.ui.output("Token description: #{description}") + end + + description = description_default if description.empty? + + code = nil + + begin + token = @_client.login(description: description, code: code) + rescue Errors::TwoFactorRequired + until code + code = env.ui.ask("2FA code: ") + + if @_client.two_factor_delivery_methods.include?(code.downcase) + delivery_method, code = code, nil + @_client.request_code delivery_method + end + end + + retry + end + + @_client.store_token(token) + Vagrant::Util::CredentialScrubber.sensitive(token) + env.ui.success(I18n.t("cloud_command.logged_in")) + @_client + end + + # Print search results from Vagrant Cloud to the console + # + # @param [Array] search_results Box search results from Vagrant Cloud + # @param [Boolean] short Print short summary + # @param [Boolean] json Print output in JSON format + # @param [Vagrant::Environment] env Current Vagrant environment + # @return [nil] + def format_search_results(search_results, short, json, env) + result = search_results.map do |b| + { + name: b.tag, + version: b.current_version.version, + downloads: format_downloads(b.downloads.to_s), + providers: b.current_version.providers.map(&:name).join(", ") + } + end + + if short + result.map { |b| env.ui.info(b[:name]) } + elsif json + env.ui.info(result.to_json) + else + column_labels = {} + columns = result.first.keys + columns.each do |c| + column_labels[c] = c.to_s.upcase + end + print_search_table(env, column_labels, result, [:downloads]) + end + nil + end + + # Output box details result from Vagrant Cloud + # + # @param [VagrantCloud::Box, VagrantCloud::Box::Version] box Box or box version to display + # @param [Vagrant::Environment] env Current Vagrant environment + # @return [nil] + def format_box_results(box, env) + if box.is_a?(VagrantCloud::Box) + info = box_info(box) + elsif box.is_a?(VagrantCloud::Box::Provider) + info = version_info(box.version) + else + info = version_info(box) + end + + width = info.keys.map(&:size).max + info.each do |k, v| + whitespace = width - k.size + env.ui.info "#{k}: #{"".ljust(whitespace)} #{v}" + end + nil + end + + # Load box and yield + # + # @param [VagrantCloud::Account] account Vagrant Cloud account + # @param [String] org Organization name + # @param [String] box Box name + # @yieldparam [VagrantCloud::Box] box Requested Vagrant Cloud box + # @yieldreturn [Integer] + # @return [Integer] + def with_box(account:, org:, box:) + org = account.organization(name: org) + b = org.boxes.detect { |b| b.name == box } + if !b + @env.ui.error(I18n.t("cloud_command.box.not_found", + org: org.username, box_name: box)) + return 1 + end + yield b + end + + # Load box version and yield + # + # @param [VagrantCloud::Account] account Vagrant Cloud account + # @param [String] org Organization name + # @param [String] box Box name + # @param [String] version Box version + # @yieldparam [VagrantCloud::Box::Version] version Requested Vagrant Cloud box version + # @yieldreturn [Integer] + # @return [Integer] + def with_version(account:, org:, box:, version:) + with_box(account: account, org: org, box: box) do |b| + v = b.versions.detect { |v| v.version == version } + if !v + @env.ui.error(I18n.t("cloud_command.version.not_found", + box_name: box, org: org, version: version)) + return 1 + end + yield v + end + end + + # Load box version and yield + # + # @param [VagrantCloud::Account] account Vagrant Cloud account + # @param [String] org Organization name + # @param [String] box Box name + # @param [String] version Box version + # @param [String] provider Box version provider name + # @yieldparam [VagrantCloud::Box::Provider] provider Requested Vagrant Cloud box version provider + # @yieldreturn [Integer] + # @return [Integer] + def with_provider(account:, org:, box:, version:, provider:) + with_version(account: account, org: org, box: box, version: version) do |v| + p = v.providers.detect { |p| p.name == provider } + if !p + @env.ui.error(I18n.t("cloud_command.provider.not_found", + org: org, box_name: box, version: version, provider_name: provider)) + return 1 + end + yield p + end + end + + protected + + # Extract box information for display + # + # @param [VagrantCloud::Box] box Box for extracting information + # @return [Hash] + def box_info(box) + Hash.new.tap do |i| + i["Box"] = box.tag + i["Description"] = box.description + i["Private"] = box.private ? "yes" : "no" + i["Created"] = box.created_at + i["Updated"] = box.updated_at + if !box.current_version.nil? + i["Current Version"] = box.current_version.version else - return Vagrant.server_url + i["Current Version"] = "N/A" 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) - return @_client if @_client.logged_in? - - # Let the user know what is going on. - env.ui.output(I18n.t("cloud_command.command_header") + "\n") - - # If it is a private cloud installation, show that - if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL - env.ui.output("Vagrant Cloud URL: #{Vagrant.server_url}") - end - - options = {} if !options - # Ask for the username - if options[:login] - @_client.username_or_email = options[:login] - env.ui.output("Vagrant Cloud username or email: #{@_client.username_or_email}") - else - @_client.username_or_email = env.ui.ask("Vagrant Cloud username or email: ") - end - - @_client.password = env.ui.ask("Password (will be hidden): ", echo: false) - - description_default = "Vagrant login from #{Socket.gethostname}" - if !options[:description] - description = env.ui.ask("Token description (Defaults to #{description_default.inspect}): ") - else - description = options[:description] - env.ui.output("Token description: #{description}") - end - - description = description_default if description.empty? - - code = nil - - begin - token = @_client.login(description: description, code: code) - rescue Errors::TwoFactorRequired - until code - code = env.ui.ask("2FA code: ") - - if @_client.two_factor_delivery_methods.include?(code.downcase) - delivery_method, code = code, nil - @_client.request_code delivery_method - end - end - - retry - end - - @_client.store_token(token) - Vagrant::Util::CredentialScrubber.sensitive(token) - env.ui.success(I18n.t("cloud_command.logged_in")) - @_client + i["Versions"] = box.versions.slice(0, 5).map(&:version).join(", ") + if box.versions.size > 5 + i["Versions"] += " ..." end - @_client + i["Downloads"] = format_downloads(box.downloads) + end + end + + # Extract version information for display + # + # @param [VagrantCloud::Box::Version] version Box version for extracting information + # @return [Hash] + def version_info(version) + Hash.new.tap do |i| + i["Box"] = version.box.tag + i["Version"] = version.version + i["Description"] = version.description + i["Status"] = version.status + i["Providers"] = version.providers.map(&:name).sort.join(", ") + i["Created"] = version.created_at + i["Updated"] = version.updated_at + end + end + + # Print table results from search request + # + # @param [Vagrant::Environment] env Current Vagrant environment + # @param [Hash] column_labels A hash of key/value pairs for table labels (i.e. {col1: "COL1"}) + # @param [Array] results An array of hashes representing search resuls + # @param [Array] to_jrust_keys - List of columns keys to right justify (left justify is defualt) + # @return [nil] + # @note Modified from https://stackoverflow.com/a/28685559 + def print_search_table(env, column_labels, results, to_rjust_keys) + columns = column_labels.each_with_object({}) do |(col,label),h| + h[col] = { + label: label, + width: [results.map { |g| g[col].size }.max, label.size].max + } end - # =================================================== - # Modified from https://stackoverflow.com/a/28685559 - # for printing arrays of hashes in formatted tables - # =================================================== + write_header(env, columns) + write_divider(env, columns) + results.each { |h| write_line(env, columns, h, to_rjust_keys) } + write_divider(env, columns) + end - # @param [Vagrant::Environment] - env - # @param [Hash] - column_labels - A hash of key values for table labels (i.e. {:col1=>"COL1", :col2=>"COL2"}) - # @param [Array] - results - An array of hashes - # @param [Array] - to_jrust_keys - An array of column keys that should be right justified (default is left justified for all columns) - def print_search_table(env, column_labels, results, to_rjust_keys) - columns = column_labels.each_with_object({}) { |(col,label),h| - h[col] = { label: label, - width: [results.map { |g| g[col].size }.max, label.size].max - }} + # Write the header for a table + # + # @param [Vagrant::Environment] env Current Vagrant environment + # @param [Array] columns List of columns in Hash format with `:label` and `:width` keys + # @return [nil] + def write_header(env, columns) + env.ui.info "| #{ columns.map { |_,g| g[:label].ljust(g[:width]) }.join(' | ') } |" + nil + end - write_header(env, columns) - write_divider(env, columns) - results.each { |h| write_line(env, columns, h,to_rjust_keys) } - write_divider(env, columns) - end + # Write a row divider for a table + # + # @param [Vagrant::Environment] env Current Vagrant environment + # @param [Array] columns List of columns in Hash format with `:label` and `:width` keys + # @return [nil] + def write_divider(env, columns) + env.ui.info "+-#{ columns.map { |_,g| "-"*g[:width] }.join("-+-") }-+" + nil + end - def write_header(env, columns) - env.ui.info "| #{ columns.map { |_,g| g[:label].ljust(g[:width]) }.join(' | ') } |" - end - - def write_divider(env, columns) - env.ui.info "+-#{ columns.map { |_,g| "-"*g[:width] }.join("-+-") }-+" - end - - def write_line(env, columns,h,to_rjust_keys) - str = h.keys.map { |k| - if to_rjust_keys.include?(k) - h[k].rjust(columns[k][:width]) - else - h[k].ljust(columns[k][:width]) - end - }.join(" | ") - env.ui.info "| #{str} |" - end - - # =================================================== - # =================================================== - - # Takes a "mostly" flat key=>value hash from Vagrant Cloud - # and prints its results in a list - # - # @param [Hash] - results - A response hash from vagrant cloud - # @param [Vagrant::Environment] - env - def format_box_results(results, env) - # TODO: remove other description fields? Maybe leave "short"? - results.delete("description_html") - - if results["current_version"] - versions = results.delete("versions") - results["providers"] = results["current_version"]["providers"] - - results["old_versions"] = versions.map{ |v| v["version"] }[1..5].join(", ") + "..." - end - - - width = results.keys.map{|k| k.size}.max - results.each do |k,v| - if k == "versions" - v = v.map{ |ver| ver["version"] }.join(", ") - elsif k == "current_version" - v = v["version"] - elsif k == "providers" - v = v.map{ |p| p["name"] }.join(", ") - elsif k == "downloads" - v = format_downloads(v.to_s) - end - - whitespace = width-k.size - env.ui.info "#{k}:" + "".ljust(whitespace) + " #{v}" - end - end - - # Converts a string of numbers into a formatted number - # - # 1234 -> 1,234 - # - # @param [String] - download_string - def format_downloads(download_string) - return download_string.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse - end - - - # @param [Array] search_results - Box search results from Vagrant Cloud - # @param [String,nil] short - determines if short version will be printed - # @param [String,nil] json - determines if json version will be printed - # @param [Vagrant::Environment] - env - def format_search_results(search_results, short, json, env) - result = [] - search_results.each do |b| - box = {} - box = { - name: b["tag"], - version: b["current_version"]["version"], - downloads: format_downloads(b["downloads"].to_s), - providers: b["current_version"]["providers"].map{ |p| p["name"] }.join(",") - } - result << box - end - - if short - result.map {|b| env.ui.info(b[:name])} - elsif json - env.ui.info(result.to_json) + # Write a line of content for a table + # + # @param [Vagrant::Environment] env Current Vagrant environment + # @param [Array] columns List of columns in Hash format with `:label` and `:width` keys + # @param [Hash] h Values to print in row + # @param [Array] to_rjust_keys List of columns to right justify + # @return [nil] + def write_line(env, columns, h, to_rjust_keys) + str = h.keys.map { |k| + if to_rjust_keys.include?(k) + h[k].rjust(columns[k][:width]) else - column_labels = {} - columns = result.first.keys - columns.each do |c| - column_labels[c] = c.to_s.upcase - end - print_search_table(env, column_labels, result, [:downloads]) + h[k].ljust(columns[k][:width]) end - end + }.join(" | ") + env.ui.info "| #{str} |" + nil + end + + # Converts a string of numbers into a formatted number + # + # 1234 -> 1,234 + # + # @param [String] number Numer to format + def format_downloads(number) + number.to_s.chars.reverse.each_slice(3).map(&:join).join(",").reverse end end end diff --git a/plugins/commands/cloud/version/create.rb b/plugins/commands/cloud/version/create.rb index 0c92a3a0d..264075f63 100644 --- a/plugins/commands/cloud/version/create.rb +++ b/plugins/commands/cloud/version/create.rb @@ -5,6 +5,8 @@ module VagrantPlugins module VersionCommand module Command class Create < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -19,48 +21,51 @@ module VagrantPlugins o.on("-d", "--description DESCRIPTION", String, "A description for this version") do |d| options[:description] = d end - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 2 + if argv.empty? || argv.length != 2 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + @client = client_login(@env) + org, box_name = argv.first.split('/', 2) version = argv[1] - create_version(org, box_name, version, @client.token, options) + create_version(org, box_name, version, @client.token, options.slice(:description)) end - def create_version(org, box_name, box_version, access_token, options) - org = options[:username] if options[:username] - - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - begin - success = version.create_version - @env.ui.success(I18n.t("cloud_command.version.create_success", version: box_version, org: org, box_name: box_name)) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.version.create_fail", version: box_version, org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + # Create a new version of the box + # + # @param [String] org Organization box is within + # @param [String] box_name Name of box + # @param [String] box_version Version of box to create + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options + # @option options [String] :description Description of box version + # @return [Integer] + def create_version(org, box_name, box_version, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_box(account: account, org: org, box: box_name) do |box| + version = box.add_version(box_version) + version.description = options[:description] if options.key?(:description) + version.save + @env.ui.success(I18n.t("cloud_command.version.create_success", + version: box_version, org: org, box_name: box_name)) + format_box_results(version, @env) + 0 end - return 1 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.version.create_fail", + version: box_version, org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/version/delete.rb b/plugins/commands/cloud/version/delete.rb index 4dc77dcb6..bf1c94254 100644 --- a/plugins/commands/cloud/version/delete.rb +++ b/plugins/commands/cloud/version/delete.rb @@ -5,6 +5,8 @@ module VagrantPlugins module VersionCommand module Command class Delete < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -15,51 +17,56 @@ module VagrantPlugins o.separator "" o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u + o.on("-f", "--[no-]force", "Force deletion without confirmation") do |f| + options[:force] = f end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 2 + if argv.size != 2 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + org, box_name = argv.first.split('/', 2) version = argv[1] - @env.ui.warn(I18n.t("cloud_command.version.delete_warn", version: version, box: argv.first)) - cont = @env.ui.ask(I18n.t("cloud_command.continue")) - return 1 if cont.strip.downcase != "y" + if !options[:force] + @env.ui.warn(I18n.t("cloud_command.version.delete_warn", version: version, box: argv.first)) + cont = @env.ui.ask(I18n.t("cloud_command.continue")) + return 1 if cont.strip.downcase != "y" + end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) + @client = client_login(@env) - delete_version(org, box_name, version, options, @client.token) + delete_version(org, box_name, version, @client.token, options.slice) end - def delete_version(org, box_name, box_version, options, access_token) - org = options[:username] if options[:username] - - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - begin - success = version.delete - @env.ui.success(I18n.t("cloud_command.version.delete_success", version: box_version, org: org, box_name: box_name)) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.version.delete_fail", version: box_version, org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + # Delete the requested box version + # + # @param [String] org Box organization name + # @param [String] box_name Name of the box + # @param [String] box_version Version of the box + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options Current unsued + def delete_version(org, box_name, box_version, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_version(account: account, org: org, box: box_name, version: box_version) do |version| + version.delete + @env.ui.success(I18n.t("cloud_command.version.delete_success", + version: box_version, org: org, box_name: box_name)) + 0 end - return 1 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.version.delete_fail", + version: box_version, org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/version/release.rb b/plugins/commands/cloud/version/release.rb index eb7c16a73..777cfc242 100644 --- a/plugins/commands/cloud/version/release.rb +++ b/plugins/commands/cloud/version/release.rb @@ -5,6 +5,8 @@ module VagrantPlugins module VersionCommand module Command class Release < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -15,11 +17,7 @@ module VagrantPlugins o.separator "" o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end - options[:force] = false - o.on("-f", "--force", "Release without confirmation") do |f| + o.on("-f", "--[no-]force", "Release without confirmation") do |f| options[:force] = f end end @@ -27,46 +25,48 @@ module VagrantPlugins # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 2 + if argv.size != 2 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - if not options[:force] - @env.ui.warn(I18n.t("cloud_command.version.release_warn", version: argv[1], box: argv.first)) - cont = @env.ui.ask(I18n.t("cloud_command.continue")) - return 1 if cont.strip.downcase != "y" + if !options[:force] + @env.ui.warn(I18n.t("cloud_command.version.release_warn", version: argv[1], box: argv.first)) + cont = @env.ui.ask(I18n.t("cloud_command.continue")) + return 1 if cont.strip.downcase != "y" end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + @client = client_login(@env) + org, box_name = argv.first.split('/', 2) version = argv[1] release_version(org, box_name, version, @client.token, options) end - def release_version(org, box_name, version, access_token, options) - org = options[:username] if options[:username] - - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - begin - success = version.release - @env.ui.success(I18n.t("cloud_command.version.release_success", version: version, org: org, box_name: box_name)) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.version.release_fail", version: version, org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + # Release the box version + # + # @param [String] org Organization name + # @param [String] box_name Box name + # @param [String] version Version of the box + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options Currently unused + # @return [Integer] + def release_version(org, box_name, version, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_version(account: account, org: org, box: box_name, version: version) do |v| + v.release + @env.ui.success(I18n.t("cloud_command.version.release_success", + version: version, org: org, box_name: box_name)) + 0 end - return 1 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.version.release_fail", + version: version, org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/version/revoke.rb b/plugins/commands/cloud/version/revoke.rb index 5d6aeaf74..75d08fbb1 100644 --- a/plugins/commands/cloud/version/revoke.rb +++ b/plugins/commands/cloud/version/revoke.rb @@ -5,6 +5,8 @@ module VagrantPlugins module VersionCommand module Command class Revoke < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -15,52 +17,57 @@ module VagrantPlugins o.separator "" o.separator "Options:" o.separator "" - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u + o.on("-f", "--[no-]force", "Force revocation without confirmation") do |f| + options[:force] = f end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 2 + if argv.size != 2 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @env.ui.warn(I18n.t("cloud_command.version.revoke_warn", version: argv[1], box: argv.first)) - cont = @env.ui.ask(I18n.t("cloud_command.continue")) - return 1 if cont.strip.downcase != "y" + if !options[:force] + @env.ui.warn(I18n.t("cloud_command.version.revoke_warn", version: argv[1], box: argv.first)) + cont = @env.ui.ask(I18n.t("cloud_command.continue")) + return 1 if cont.strip.downcase != "y" + end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + @client = client_login(@env) + org, box_name = argv.first.split('/', 2) version = argv[1] revoke_version(org, box_name, version, @client.token, options) end - def revoke_version(org, box_name, box_version, access_token, options) - org = options[:username] if options[:username] - - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - begin - success = version.revoke - @env.ui.success(I18n.t("cloud_command.version.revoke_success", version: box_version, org: org, box_name: box_name)) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.version.revoke_fail", version: box_version, org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + # Revoke release of box version + # + # @param [String] org Organization name + # @param [String] box_name Box name + # @param [String] version Version of the box + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options Currently unused + # @return [Integer] + def revoke_version(org, box_name, box_version, access_token, options={}) + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_version(account: account, org: org, box: box_name, version: box_version) do |version| + version.revoke + @env.ui.success(I18n.t("cloud_command.version.revoke_success", + version: box_version, org: org, box_name: box_name)) + format_box_results(version, @env) + 0 end - return 1 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.version.revoke_fail", + version: box_version, org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/plugins/commands/cloud/version/update.rb b/plugins/commands/cloud/version/update.rb index 7691f6e72..5643908fc 100644 --- a/plugins/commands/cloud/version/update.rb +++ b/plugins/commands/cloud/version/update.rb @@ -5,6 +5,8 @@ module VagrantPlugins module VersionCommand module Command class Update < Vagrant.plugin("2", :command) + include Util + def execute options = {} @@ -19,48 +21,50 @@ module VagrantPlugins o.on("-d", "--description DESCRIPTION", "A description for this version") do |d| options[:description] = d end - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Vagrant Cloud username or email address") do |u| - options[:username] = u - end end # Parse the options argv = parse_options(opts) return if !argv - if argv.empty? || argv.length > 2 + if argv.size != 2 raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp end - @client = VagrantPlugins::CloudCommand::Util.client_login(@env, options[:username]) - box = argv.first.split('/', 2) - org = box[0] - box_name = box[1] + @client = client_login(@env) + org, box_name = argv.first.split('/', 2) version = argv[1] update_version(org, box_name, version, @client.token, options) end + # Update the version of the box + # @param [String] org Organization name + # @param [String] box_name Box name + # @param [String] version Version of the box + # @param [String] access_token User Vagrant Cloud access token + # @param [Hash] options + # @options options [String] :description Description of box version + # @return [Integer] def update_version(org, box_name, box_version, access_token, options) - org = options[:username] if options[:username] + account = VagrantCloud::Account.new( + custom_server: api_server_url, + access_token: access_token + ) + with_version(account: account, org: org, box: box_name, version: box_version) do |version| + version.description = options[:description] if options.key?(:description) + version.save - server_url = VagrantPlugins::CloudCommand::Util.api_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) - - begin - success = version.update - @env.ui.success(I18n.t("cloud_command.version.update_success", version: box_version, org: org, box_name: box_name)) - success = success.delete_if{|_, v|v.nil?} - VagrantPlugins::CloudCommand::Util.format_box_results(success, @env) - return 0 - rescue VagrantCloud::ClientError => e - @env.ui.error(I18n.t("cloud_command.errors.version.update_fail", version: box_version, org: org, box_name: box_name)) - @env.ui.error(e) - return 1 + @env.ui.success(I18n.t("cloud_command.version.update_success", + version: box_version, org: org, box_name: box_name)) + format_box_results(version, @env) + 0 end - return 1 + rescue VagrantCloud::Error => e + @env.ui.error(I18n.t("cloud_command.errors.version.update_fail", + version: box_version, org: org, box_name: box_name)) + @env.ui.error(e.message) + 1 end end end diff --git a/test/unit/plugins/commands/cloud/auth/login_test.rb b/test/unit/plugins/commands/cloud/auth/login_test.rb index c2868a5a3..76433a13b 100644 --- a/test/unit/plugins/commands/cloud/auth/login_test.rb +++ b/test/unit/plugins/commands/cloud/auth/login_test.rb @@ -7,95 +7,169 @@ describe VagrantPlugins::CloudCommand::AuthCommand::Command::Login do let(:argv) { [] } let(:env) { isolated_environment.create_vagrant_env } + let(:action_runner) { double("action_runner") } + let(:client) { double("client", logged_in?: logged_in) } + let(:logged_in) { true } - let(:token_path) { env.data_dir.join("vagrant_login_token") } - - let(:stdout) { StringIO.new } - let(:stderr) { StringIO.new } + before do + allow(env).to receive(:action_runner). + and_return(action_runner) + allow(VagrantPlugins::CloudCommand::Client). + to receive(:new).and_return(client) + end subject { described_class.new(argv, env) } - before do - stub_env("ATLAS_TOKEN" => "") + describe "#execute_check" do + context "when user is logged in" do + let(:logged_in) { true } + + it "should output a success message" do + expect(env.ui).to receive(:success) + subject.execute_check(client) + end + + it "should return zero value" do + expect(subject.execute_check(client)).to eq(0) + end + end + + context "when user is not logged in" do + let(:logged_in) { false } + + it "should output an error message" do + expect(env.ui).to receive(:error) + subject.execute_check(client) + end + + it "should return a non-zero value" do + r = subject.execute_check(client) + expect(r).not_to eq(0) + expect(r).to be_a(Integer) + end + end end - let(:action_runner) { double("action_runner") } + describe "#execute_token" do + let(:token) { double("token") } - before do - allow(env).to receive(:action_runner).and_return(action_runner) + before { allow(client).to receive(:store_token) } + + it "should store the token" do + expect(client).to receive(:store_token).with(token) + subject.execute_token(client, token) + end + + context "when token is valid" do + let(:logged_in) { true } + + it "should output a success message" do + expect(env.ui).to receive(:success).twice + subject.execute_token(client, token) + end + + it "should return a zero value" do + expect(subject.execute_token(client, token)).to eq(0) + end + end + + context "when token is invalid" do + let(:logged_in) { false } + + it "should output an error message" do + expect(env.ui).to receive(:error) + subject.execute_token(client, token) + end + + it "should return a non-zero value" do + r = subject.execute_token(client, token) + expect(r).not_to eq(0) + expect(r).to be_a(Integer) + end + end end describe "#execute" do - context "with no args" do - let(:argv) { [] } + before do + allow(client).to receive(:username_or_email=) + allow(client).to receive(:store_token) end - context "with --check" do - let(:argv) { ["--check"] } + context "when arguments are passed" do + before { argv << "argument" } - context "when there is a token" do - before do - stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) - .to_return(status: 200) - end - - before do - File.open(token_path, "w+") { |f| f.write("abcd1234") } - end - - it "returns 0" do - expect(subject.execute).to eq(0) - end - end - - context "when there is no token" do - it "returns 1" do - expect(subject.execute).to eq(1) - end + it "should print help" do + expect { subject.execute }.to raise_error(Vagrant::Errors::CLIInvalidUsage) end end - context "with --logout" do - let(:argv) { ["--logout"] } + context "when --check flag is used" do + before { argv << "--check" } - it "returns 0" do + it "should run login check" do + expect(subject).to receive(:execute_check).with(client) + subject.execute + end + + it "should return the value of the check execution" do + result = double("result") + expect(subject).to receive(:execute_check).with(client).and_return(result) + expect(subject.execute).to eq(result) + end + end + + context "when --token flag is used" do + let(:new_token) { "NEW-TOKEN" } + + before { argv.push("--token").push(new_token) } + + it "should execute the token action" do + expect(subject).to receive(:execute_token).with(client, new_token) + subject.execute + end + + it "should return value of token action" do + result = double("result") + expect(subject).to receive(:execute_token).with(client, new_token).and_return(result) + expect(subject.execute).to eq(result) + end + + it "should store the new token" do + expect(client).to receive(:store_token).with(new_token) + subject.execute + end + end + + context "when user is logged in" do + let(:logged_in) { true } + + it "should output success message" do + expect(env.ui).to receive(:success) + subject.execute + end + + it "should return a zero value" do expect(subject.execute).to eq(0) end - - it "clears the token" do - subject.execute - expect(File.exist?(token_path)).to be(false) - end end - context "with --token" do - let(:argv) { ["--token", "efgh5678"] } + context "when user is not logged in" do + let(:logged_in) { false } - context "when the token is valid" do - before do - stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) - .to_return(status: 200) - end - - it "sets the token" do - subject.execute - token = File.read(token_path).strip - expect(token).to eq("efgh5678") - end - - it "returns 0" do - expect(subject.execute).to eq(0) - end + it "should run the client login" do + expect(subject).to receive(:client_login) + subject.execute end - context "when the token is invalid" do - before do - stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) - .to_return(status: 401) - end + context "when username and description flags are supplied" do + let(:username) { "my-username" } + let(:description) { "my-description" } - it "returns 1" do - expect(subject.execute).to eq(1) + before { argv.push("--username").push(username).push("--description").push(description) } + + it "should include login and description to login" do + expect(subject).to receive(:client_login).with(env, hash_including(login: username, description: description)) + subject.execute end end end diff --git a/test/unit/plugins/commands/cloud/auth/logout_test.rb b/test/unit/plugins/commands/cloud/auth/logout_test.rb index 1356d1136..82c5affd0 100644 --- a/test/unit/plugins/commands/cloud/auth/logout_test.rb +++ b/test/unit/plugins/commands/cloud/auth/logout_test.rb @@ -12,17 +12,15 @@ describe VagrantPlugins::CloudCommand::AuthCommand::Command::Logout do env.vagrantfile("") env.create_vagrant_env end + let(:client) { double("client") } subject { described_class.new(argv, iso_env) } let(:action_runner) { double("action_runner") } - let(:client) { double("client", token: "1234token1234") } - before do + allow(VagrantPlugins::CloudCommand::Client).to receive(:new).and_return(client) allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) end context "with any arguments" do diff --git a/test/unit/plugins/commands/cloud/auth/whoami_test.rb b/test/unit/plugins/commands/cloud/auth/whoami_test.rb index daaec2c9b..32195e1e4 100644 --- a/test/unit/plugins/commands/cloud/auth/whoami_test.rb +++ b/test/unit/plugins/commands/cloud/auth/whoami_test.rb @@ -6,49 +6,105 @@ describe VagrantPlugins::CloudCommand::AuthCommand::Command::Whoami do include_context "unit" let(:argv) { [] } - let(:iso_env) do + let(:env) do # We have to create a Vagrantfile so there is a root path env = isolated_environment env.vagrantfile("") env.create_vagrant_env end - - subject { described_class.new(argv, iso_env) } - + let(:client) { double("client", token: token) } + let(:token) { double("token") } + let(:account_username) { "account-username" } + let(:account) { double("account", username: account_username) } let(:action_runner) { double("action_runner") } - let(:client) { double("client", token: "1234token1234") } - let(:account) { double("account") } + subject { described_class.new(argv, env) } before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:account). - and_return(account) + allow(env).to receive(:action_runner).and_return(action_runner) + allow(VagrantPlugins::CloudCommand::Client).to receive(:new).and_return(client) + allow(VagrantCloud::Account).to receive(:new).and_return(account) end - context "with too many arguments" do - let(:argv) { ["token", "token", "token"] } - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + describe "whoami" do + context "when token is unset" do + let(:token) { "" } + + it "should output an error" do + expect(env.ui).to receive(:error) + subject.whoami(token) + end + + it "should return non-zero" do + r = subject.whoami(token) + expect(r).not_to eq(0) + expect(r).to be_a(Integer) + end + end + + context "when token is set" do + let(:token) { "my-token" } + + it "should load an account to validate" do + expect(VagrantCloud::Account).to receive(:new). + with(hash_including(access_token: token)).and_return(account) + subject.whoami(token) + end + + it "should output the account username" do + expect(env.ui).to receive(:success).with(/#{account_username}/) + subject.whoami(token) + end + + it "should return zero value" do + expect(subject.whoami(token)).to eq(0) + end + + context "when error is encountered" do + before { allow(VagrantCloud::Account).to receive(:new).and_raise(VagrantCloud::Error::ClientError) } + + it "should output an error" do + expect(env.ui).to receive(:error).twice + subject.execute + end + + it "should return a non-zero value" do + r = subject.execute + expect(r).not_to eq(0) + expect(r).to be_a(Integer) + end + end end end - context "with username" do - let(:argv) { ["token"] } - let(:org_hash) { {"user"=>{"username"=>"mario"}, "boxes"=>[{"name"=>"box"}]} } - - it "gets information about a user" do - expect(account).to receive(:validate_token).and_return(org_hash) - expect(subject.execute).to eq(0) + describe "#execute" do + before do + allow(subject).to receive(:whoami) end - it "returns 1 if encountering an error making request" do - allow(account).to receive(:validate_token). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + context "with too many arguments" do + let(:argv) { ["token", "token", "token"] } + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with no argument" do + it "should use stored token via client" do + expect(subject).to receive(:whoami).with(token) + subject.execute + end + end + + context "with token argument" do + let(:token_arg) { "TOKEN_ARG" } + let(:argv) { [token_arg] } + + it "should use the passed token" do + expect(subject).to receive(:whoami).with(token_arg) + subject.execute + end end end end diff --git a/test/unit/plugins/commands/cloud/box/create_test.rb b/test/unit/plugins/commands/cloud/box/create_test.rb index e9981531f..ba130055a 100644 --- a/test/unit/plugins/commands/cloud/box/create_test.rb +++ b/test/unit/plugins/commands/cloud/box/create_test.rb @@ -5,57 +5,146 @@ require Vagrant.source_root.join("plugins/commands/cloud/box/create") describe VagrantPlugins::CloudCommand::BoxCommand::Command::Create do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end - - subject { described_class.new(argv, iso_env) } - - let(:action_runner) { double("action_runner") } - - let(:client) { double("client", token: "1234token1234") } + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:account) { double("account") } + let(:organization) { double("organization") } let(:box) { double("box") } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - end + describe "#create_box" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(subject).to receive(:format_box_results).with(box, env) + allow(organization).to receive(:add_box).and_return(box) + allow(box).to receive(:save) + end + + subject { described_class.new(argv, env) } + + it "should add a new box to the organization" do + expect(organization).to receive(:add_box).with(box_name). + and_return(box) + subject.create_box(org_name, box_name, access_token, options) + end + + it "should save the new box" do + expect(box).to receive(:save) + subject.create_box(org_name, box_name, access_token, options) + end + + it "should return a zero value on success" do + expect(subject.create_box(org_name, box_name, access_token, options)). + to eq(0) + end + + it "should return a non-zero value on error" do + expect(box).to receive(:save).and_raise(VagrantCloud::Error) + result = subject.create_box(org_name, box_name, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + context "with option set" do + let(:options) { {short: short, description: description, private: priv} } + let(:short) { double("short") } + let(:description) { double("description") } + let(:priv) { double("private") } + + it "should set info into box" do + expect(box).to receive(:short_description=).with(short) + expect(box).to receive(:description=).with(description) + expect(box).to receive(:private=).with(priv) + subject.create_box(org_name, box_name, access_token, options) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "-s", "short", "-d", "long"] } - - it "creates a box" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, "short", "long", client.token) - .and_return(box) - - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(box).to receive(:create).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, "short", "long", client.token) - .and_return(box) + subject { described_class.new(argv, iso_env) } - allow(box).to receive(:create). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 422)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + + let(:client) { double("client", token: access_token) } + let(:box) { double("box") } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login).and_return(client) + allow(subject).to receive(:format_box_results) + allow(subject).to receive(:create_box) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "should create the box" do + expect(subject).to receive(:create_box).with(org_name, box_name, any_args) + subject.execute + end + + context "when description flag is provided" do + let(:description) { "my-description" } + + before { argv.push("--description").push(description) } + + it "should create box with given description" do + expect(subject).to receive(:create_box). + with(org_name, box_name, access_token, hash_including(description: description)) + subject.execute + end + end + + context "when short flag is provided" do + let(:description) { "my-description" } + + before { argv.push("--short").push(description) } + + it "should create box with given short description" do + expect(subject).to receive(:create_box). + with(org_name, box_name, access_token, hash_including(short: description)) + subject.execute + end + end + + context "when private flag is provided" do + before { argv.push("--private") } + + it "should create box as private" do + expect(subject).to receive(:create_box). + with(org_name, box_name, access_token, hash_including(private: true)) + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/box/delete_test.rb b/test/unit/plugins/commands/cloud/box/delete_test.rb index 2ecdc95c4..116d159c4 100644 --- a/test/unit/plugins/commands/cloud/box/delete_test.rb +++ b/test/unit/plugins/commands/cloud/box/delete_test.rb @@ -5,58 +5,109 @@ require Vagrant.source_root.join("plugins/commands/cloud/box/delete") describe VagrantPlugins::CloudCommand::BoxCommand::Command::Delete do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end - - subject { described_class.new(argv, iso_env) } - - let(:action_runner) { double("action_runner") } - - let(:client) { double("client", token: "1234token1234") } + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:account) { double("account") } + let(:organization) { double("organization") } let(:box) { double("box") } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(iso_env.ui).to receive(:ask). - and_return("y") - end + describe "#delete_box" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name). + and_yield(box) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(box).to receive(:delete) + end + + subject { described_class.new(argv, env) } + + it "should return 0 on success" do + expect(subject.delete_box(org_name, box_name, access_token)).to eq(0) + end + + it "should delete the box" do + expect(box).to receive(:delete) + subject.delete_box(org_name, box_name, access_token) + end + + it "should return non-zero on error" do + expect(box).to receive(:delete).and_raise(VagrantCloud::Error) + result = subject.delete_box(org_name, box_name, access_token) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) end end - context "with arguments" do - let (:argv) { ["vagrant/box-name"] } + describe "#execute" do - it "creates a box" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - - expect(box).to receive(:delete).and_return({}) - expect(subject.execute).to eq(0) + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) + subject { described_class.new(argv, iso_env) } - allow(box).to receive(:delete). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + + let(:client) { double("client", token: access_token) } + let(:box) { double("box") } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(iso_env.ui).to receive(:ask). + and_return("y") + allow(subject).to receive(:delete_box) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let (:argv) { ["#{org_name}/#{box_name}"] } + + it "should delete the box" do + expect(subject).to receive(:delete_box). + with(org_name, box_name, access_token) + subject.execute + end + + it "should prompt for confirmation" do + expect(iso_env.ui).to receive(:ask).and_return("y") + subject.execute + end + + context "with force flag" do + before { argv.push("--force") } + + it "should not prompt for confirmation" do + expect(iso_env.ui).not_to receive(:ask) + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/box/show_test.rb b/test/unit/plugins/commands/cloud/box/show_test.rb index 20c3aa2c4..bd10cba22 100644 --- a/test/unit/plugins/commands/cloud/box/show_test.rb +++ b/test/unit/plugins/commands/cloud/box/show_test.rb @@ -5,59 +5,146 @@ require Vagrant.source_root.join("plugins/commands/cloud/box/show") describe VagrantPlugins::CloudCommand::BoxCommand::Command::Show do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end - - subject { described_class.new(argv, iso_env) } - - let(:action_runner) { double("action_runner") } - - let(:client) { double("client", token: "1234token1234") } + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:account) { double("account") } + let(:organization) { double("organization") } let(:box) { double("box") } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(iso_env.ui).to receive(:ask). - and_return("y") - end + describe "#show_box" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name). + and_yield(box) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(subject).to receive(:format_box_results) + end + + subject { described_class.new(argv, env) } + + it "should return 0 on success" do + expect(subject.show_box(org_name, box_name, access_token, options)).to eq(0) + end + + it "should display the box results" do + expect(subject).to receive(:format_box_results).with(box, env) + subject.show_box(org_name, box_name, access_token, options) + end + + it "should return non-zero on error" do + expect(subject).to receive(:with_box).and_raise(VagrantCloud::Error) + result = subject.show_box(org_name, box_name, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + context "with version defined" do + let(:options) { {versions: [version]} } + let(:box_version) { double("box_version", version: version) } + let(:box_versions) { [box_version] } + let(:version) { double("version") } + + before do + allow(box).to receive(:versions).and_return(box_versions) + end + + it "should print the version details" do + expect(subject).to receive(:format_box_results).with(box_version, env) + subject.show_box(org_name, box_name, access_token, options) + end + + context "when version is not found" do + let(:box_versions) { [] } + + it "should return non-zero" do + result = subject.show_box(org_name, box_name, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + it "should not print any box information" do + expect(subject).not_to receive(:format_box_results) + subject.show_box(org_name, box_name, access_token, options) + end + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name"] } - - it "creates a box" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - - expect(box).to receive(:read).and_return({}) - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) + subject { described_class.new(argv, iso_env) } - allow(box).to receive(:read). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:show_box) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let (:argv) { ["#{org_name}/#{box_name}"] } + + it "should show the box" do + expect(subject).to receive(:show_box).with(org_name, box_name, any_args) + subject.execute + end + + it "should create the client login quietly" do + expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: true)) + subject.execute + end + + context "with auth flag" do + before { argv.push("--auth") } + + it "should set quiet option to false when creating client" do + expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: false)) + subject.execute + end + end + + context "with versions flag set" do + let(:version_option) { "1.0.0" } + + before { argv.push("--versions").push(version_option) } + + it "should show box with version option set" do + expect(subject).to receive(:show_box). + with(org_name, box_name, access_token, hash_including(versions: [version_option])) + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/box/update_test.rb b/test/unit/plugins/commands/cloud/box/update_test.rb index 5fb42fc5f..6be231d51 100644 --- a/test/unit/plugins/commands/cloud/box/update_test.rb +++ b/test/unit/plugins/commands/cloud/box/update_test.rb @@ -1,64 +1,146 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/box/update") describe VagrantPlugins::CloudCommand::BoxCommand::Command::Update do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end - - subject { described_class.new(argv, iso_env) } - - let(:action_runner) { double("action_runner") } - - let(:client) { double("client", token: "1234token1234") } + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:account) { double("account") } + let(:organization) { double("organization") } let(:box) { double("box") } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - end + describe "#update_box" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name). + and_yield(box) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(subject).to receive(:format_box_results) + allow(box).to receive(:save) + end + + subject { described_class.new(argv, env) } + + it "should save the box" do + expect(box).to receive(:save) + subject.update_box(org_name, box_name, access_token, options) + end + + it "should return 0 on success" do + result = subject.update_box(org_name, box_name, access_token, options) + expect(result).to eq(0) + end + + it "should return non-zero on error" do + expect(box).to receive(:save).and_raise(VagrantCloud::Error) + result = subject.update_box(org_name, box_name, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + it "should display the box information" do + expect(subject).to receive(:format_box_results).with(box, env) + subject.update_box(org_name, box_name, access_token, options) + end + + context "with options set" do + let(:options) { {short: short, description: description, private: priv} } + let(:short) { double("short") } + let(:description) { double("description") } + let(:priv) { double("private") } + + it "should set box info" do + expect(box).to receive(:short_description=).with(short) + expect(box).to receive(:description=).with(description) + expect(box).to receive(:private=).with(priv) + subject.update_box(org_name, box_name, access_token, options) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "-d", "update", "-s", "short"] } - - it "creates a box" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - - expect(box).to receive(:update). - with(organization: "vagrant", name: "box-name", description: "update", short_description: "short"). - and_return({}) - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) + subject { described_class.new(argv, iso_env) } - allow(box).to receive(:update). - with(organization: "vagrant", name: "box-name", description: "update", short_description: "short"). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login).and_return(client) + allow(subject).to receive(:update_box) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "should show help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with description flag set" do + let(:description) { "my-description" } + + before { argv.push("--description").push(description) } + + it "should update box with description" do + expect(subject).to receive(:update_box). + with(org_name, box_name, access_token, hash_including(description: description)) + subject.execute + end + end + + context "with short flag set" do + let(:description) { "my-description" } + + before { argv.push("--short-description").push(description) } + + it "should update box with short description" do + expect(subject).to receive(:update_box). + with(org_name, box_name, access_token, hash_including(short: description)) + subject.execute + end + end + + context "with private flag set" do + before { argv.push("--private") } + + it "should update box with private" do + expect(subject).to receive(:update_box). + with(org_name, box_name, access_token, hash_including(private: true)) + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/client_test.rb b/test/unit/plugins/commands/cloud/client_test.rb index 649a3705c..654c9e89c 100644 --- a/test/unit/plugins/commands/cloud/client_test.rb +++ b/test/unit/plugins/commands/cloud/client_test.rb @@ -6,6 +6,8 @@ describe VagrantPlugins::CloudCommand::Client do include_context "unit" let(:env) { isolated_environment.create_vagrant_env } + let(:token) { nil } + let(:vc_client) { double("vagrantcloud-client", access_token: token) } subject(:client) { described_class.new(env) } @@ -16,246 +18,246 @@ describe VagrantPlugins::CloudCommand::Client do before do stub_env("ATLAS_TOKEN" => nil) - subject.clear_token + stub_env("VAGRANT_CLOUD_TOKEN" => nil) + allow(VagrantCloud::Client).to receive(:new).and_return(vc_client) + allow(Vagrant::Util::CredentialScrubber).to receive(:sensitive) + end + + after do + Vagrant::Util::CredentialScrubber.reset! end describe "#logged_in?" do - let(:url) { "#{Vagrant.server_url}/api/v1/authenticate?access_token=#{token}" } - let(:headers) { { "Content-Type" => "application/json" } } - before { allow(subject).to receive(:token).and_return(token) } - context "when there is no token" do - let(:token) { nil } - - it "returns false" do - expect(subject.logged_in?).to be(false) + context "when token is not set" do + it "should return false" do + expect(subject.logged_in?).to be_falsey end end - context "when there is a token" do - let(:token) { "ABCD1234" } + context "when token is set" do + let(:token) { double("token") } - it "returns true if the endpoint returns a 200" do - stub_request(:get, url) - .with(headers: headers) - .to_return(body: JSON.pretty_generate("token" => token)) - expect(subject.logged_in?).to be(true) + before do + allow(vc_client).to receive(:authentication_token_validate) end - it "raises an error if the endpoint returns a non-200" do - stub_request(:get, url) - .with(headers: headers) - .to_return(body: JSON.pretty_generate("bad" => true), status: 401) - expect(subject.logged_in?).to be(false) + it "should return true when token is valid" do + expect(subject.logged_in?).to be_truthy end - it "raises an exception if the server cannot be found" do - stub_request(:get, url) - .to_raise(SocketError) - expect { subject.logged_in? } - .to raise_error(VagrantPlugins::CloudCommand::Errors::ServerUnreachable) + it "should validate the set token" do + expect(vc_client).to receive(:authentication_token_validate) + subject.logged_in? + end + + it "should return false when token does not validate" do + expect(vc_client).to receive(:authentication_token_validate). + and_raise(Excon::Error::Unauthorized.new(StandardError.new)) + expect(subject.logged_in?).to be_falsey + end + + it "should add token to scrubber" do + expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(token) + subject.logged_in? end end end describe "#login" do - let(:request) { - { - user: { - login: login, - password: password, - }, - token: { - description: description, - }, - two_factor: { - code: nil - } - } - } + let(:new_token) { double("new-token") } + let(:result) { {token: new_token} } + let(:password) { double("password") } + let(:username) { double("username") } - let(:login) { "foo" } - let(:password) { "supersecretpassword" } - let(:description) { "Token description" } - - let(:headers) { - { - "Accept" => "application/json", - "Content-Type" => "application/json", - } - } - let(:response) { - { - token: "mysecrettoken" - } - } - - it "returns the access token after successful login" do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - with(body: JSON.dump(request), headers: headers). - to_return(status: 200, body: JSON.dump(response)) - - client.username_or_email = login - client.password = password - - expect(client.login(description: "Token description")).to eq("mysecrettoken") + before do + subject.username_or_email = username + subject.password = password + allow(vc_client).to receive(:authentication_token_create). + and_return(result) end - context "when 2fa is required" do - let(:response) { - { - two_factor: { - default_delivery_method: default_delivery_method, - delivery_methods: delivery_methods - } - } - } - let(:default_delivery_method) { "app" } - let(:delivery_methods) { ["app"] } - - before do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_return(status: 406, body: JSON.dump(response)) - end - - it "raises a two-factor required error" do - expect { - client.login - }.to raise_error(VagrantPlugins::CloudCommand::Errors::TwoFactorRequired) - end - - context "when the default delivery method is not app" do - let(:default_delivery_method) { "sms" } - let(:delivery_methods) { ["app", "sms"] } - - it "requests a code and then raises a two-factor required error" do - expect(client) - .to receive(:request_code) - .with(default_delivery_method) - - expect { - client.login - }.to raise_error(VagrantPlugins::CloudCommand::Errors::TwoFactorRequired) - end - end + it "should add password to scrubber" do + expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(password) + subject.login end - context "on bad login" do - before do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_return(status: 401, body: "") - end - - it "raises an error" do - expect { - client.login - }.to raise_error(VagrantPlugins::CloudCommand::Errors::Unauthorized) - end + it "should create an authentication token" do + expect(vc_client).to receive(:authentication_token_create). + and_return(result) + subject.login end - context "if it can't reach the server" do - before do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_raise(SocketError) - end + it "should wrap remote request to handle errors" do + expect(subject).to receive(:with_error_handling) + subject.login + end - it "raises an exception" do - expect { - subject.login - }.to raise_error(VagrantPlugins::CloudCommand::Errors::ServerUnreachable) + it "should add new token to scrubber" do + expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(new_token) + subject.login + end + + it "should create a new internal client" do + expect(VagrantCloud::Client).to receive(:new).with(access_token: new_token) + subject.login + end + + it "should create authentication token using username and password" do + expect(vc_client).to receive(:authentication_token_create). + with(hash_including(username: username, password: password)).and_return(result) + subject.login + end + + it "should return the new token" do + expect(subject.login).to eq(new_token) + end + + context "with description and code" do + let(:description) { double("description") } + let(:code) { double("code") } + + it "should create authentication token using description and code" do + expect(vc_client).to receive(:authentication_token_create).with( + hash_including(username: username, password: password, + description: description, code: code)) + subject.login(description: description, code: code) end end end describe "#request_code" do - let(:request) { - { - user: { - login: login, - password: password, - }, - two_factor: { - delivery_method: delivery_method - } - } - } + let(:password) { double("password") } + let(:username) { double("username") } + let(:delivery_method) { double("delivery-method", upcase: nil) } + let(:result) { {two_factor: two_factor} } + let(:two_factor) { {obfuscated_destination: obfuscated_destination} } + let(:obfuscated_destination) { double("obfuscated-destination", to_s: "2FA_DESTINATION") } - let(:login) { "foo" } - let(:password) { "supersecretpassword" } - let(:delivery_method) { "sms" } + before do + subject.password = password + subject.username_or_email = username + allow(vc_client).to receive(:authentication_request_2fa_code).and_return(result) + end - let(:headers) { - { - "Accept" => "application/json", - "Content-Type" => "application/json" - } - } + it "should add password to scrubber" do + expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(password) + subject.request_code(delivery_method) + end - let(:response) { - { - two_factor: { - obfuscated_destination: "SMS number ending in 1234" - } - } - } + it "should request the code" do + expect(vc_client).to receive(:authentication_request_2fa_code).with( + hash_including(username: username, password: password, delivery_method: delivery_method)) + subject.request_code(delivery_method) + end - it "displays that the code was sent" do - expect(env.ui) - .to receive(:success) - .with("2FA code sent to SMS number ending in 1234.") + it "should print the destination" do + expect(env.ui).to receive(:success).with(/2FA_DESTINATION/) + subject.request_code(delivery_method) + end + end - stub_request(:post, "#{Vagrant.server_url}/api/v1/two-factor/request-code"). - with(body: JSON.dump(request), headers: headers). - to_return(status: 201, body: JSON.dump(response)) + describe "#store_token" do + let(:token_path) { double("token-path") } + let(:new_token) { double("new-token") } - client.username_or_email = login - client.password = password + before do + allow(subject).to receive(:token_path).and_return(token_path) + allow(token_path).to receive(:open) + end - client.request_code delivery_method + it "should add token to scrubber" do + expect(Vagrant::Util::CredentialScrubber).to receive(:sensitive).with(new_token) + subject.store_token(new_token) + end + + it "should create a new internal client with token" do + expect(VagrantCloud::Client).to receive(:new).with(access_token: new_token) + subject.store_token(new_token) + end + + it "should open the token path and write the new token" do + f = double("file") + expect(token_path).to receive(:open).with("w").and_yield(f) + expect(f).to receive(:write).with(new_token) + subject.store_token(new_token) end end describe "#token" do - it "reads ATLAS_TOKEN" do - stub_env("ATLAS_TOKEN" => "ABCD1234") - expect(subject.token).to eq("ABCD1234") + let(:env_token) { "ENV_TOKEN" } + let(:file_token) { "FILE_TOKEN" } + let(:token_path) { double("token-path", read: file_token) } + let(:path_exists) { false } + + before do + expect(subject).to receive(:token).and_call_original + allow(subject).to receive(:token_path).and_return(token_path) + allow(token_path).to receive(:exist?).and_return(path_exists) end - it "reads the stored file" do - subject.store_token("EFGH5678") - expect(subject.token).to eq("EFGH5678") + context "when VAGRANT_CLOUD_TOKEN env var is set" do + before { stub_env("VAGRANT_CLOUD_TOKEN" => env_token) } + + it "should return the env token" do + expect(subject.token).to eq(env_token) + end + + context "when token path exists" do + let(:path_exists) { true } + + it "should return the env token" do + expect(subject.token).to eq(env_token) + end + + it "should print warning of two tokens" do + expect(env.ui).to receive(:warn) + subject.token + end + end end - it "prefers the environment variable" do - stub_env("VAGRANT_CLOUD_TOKEN" => "ABCD1234") - subject.store_token("EFGH5678") - expect(subject.token).to eq("ABCD1234") + context "when token path exists" do + let(:path_exists) { true } + + it "should return the stored token" do + expect(subject.token).to eq(file_token) + end + + context "when VAGRANT_CLOUD_TOKEN env var is set" do + before { stub_env("VAGRANT_CLOUD_TOKEN" => env_token) } + + it "should return the env token" do + expect(subject.token).to eq(env_token) + end + end end - it "prints a warning if the envvar and stored file are both present" do - stub_env("VAGRANT_CLOUD_TOKEN" => "ABCD1234") - subject.store_token("EFGH5678") - expect(env.ui).to receive(:warn).with(/detected both/) - subject.token - end + context "when ATLAS_TOKEN env var is set" do + before { stub_env("ATLAS_TOKEN" => env_token) } - it "returns nil if there's no token set" do - expect(subject.token).to be(nil) - end - end + it "should return the env token" do + expect(subject.token).to eq(env_token) + end - describe "#store_token, #clear_token" do - it "stores the token and can re-access it" do - subject.store_token("foo") - expect(subject.token).to eq("foo") - expect(described_class.new(env).token).to eq("foo") - end + context "when VAGRANT_CLOUD_TOKEN is set" do + let(:vc_token) { "VC_TOKEN" } - it "deletes the token" do - subject.store_token("foo") - subject.clear_token - expect(subject.token).to be_nil + before { stub_env("VAGRANT_CLOUD_TOKEN" => vc_token) } + + it "should return the VAGRANT_CLOUD_TOKEN value" do + expect(subject.token).to eq(vc_token) + end + end + + context "when file exists" do + let(:path_exists) { true } + + it "should return the file token" do + expect(subject.token).to eq(file_token) + end + end end end end diff --git a/test/unit/plugins/commands/cloud/provider/create_test.rb b/test/unit/plugins/commands/cloud/provider/create_test.rb index 7cc40b302..4999c8c26 100644 --- a/test/unit/plugins/commands/cloud/provider/create_test.rb +++ b/test/unit/plugins/commands/cloud/provider/create_test.rb @@ -1,85 +1,174 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/provider/create") describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Create do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { "0.1.0" } + let(:provider_name) { "my-provider" } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version, providers: [provider]) } + let(:provider) { double("provider", name: provider_name) } + let(:provider_url) { double("provider_url") } - subject { described_class.new(argv, iso_env) } + describe "#create_provider" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_version).with(account: account, org: org_name, box: box_name, version: box_version). + and_yield(version) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(version).to receive(:add_provider).and_return(provider) + allow(provider).to receive(:save) + allow(provider).to receive(:url=) + allow(subject).to receive(:format_box_results) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } - let(:provider) { double("provider") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - allow(VagrantCloud::Version).to receive(:new) - .with(box, "1.0.0", nil, nil, client.token) - .and_return(version) - end + it "should add a new provider to the box version" do + expect(version).to receive(:add_provider).with(provider_name) + subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should not set checksum or checksum_type when not provided" do + expect(provider).not_to receive(:checksum=) + expect(provider).not_to receive(:checksum_type=) + subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options) + end + + context "with checksum and checksum type options set" do + let(:checksum) { double("checksum") } + let(:checksum_type) { double("checksum_type") } + let(:options) { {checksum: checksum, checksum_type: checksum_type} } + + it "should set the checksum and checksum type" do + expect(provider).to receive(:checksum=).with(checksum) + expect(provider).to receive(:checksum_type=).with(checksum_type) + subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options) + end + end + + context "when URL is set" do + it "should set the URL" do + expect(provider).to receive(:url=).with(provider_url) + subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options) + end + end + + context "when URL is not set" do + let(:provider_url) { nil } + + it "should not set the URL" do + expect(provider).not_to receive(:url=).with(provider_url) + subject.create_provider(org_name, box_name, box_version, provider_name, provider_url, access_token, options) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "virtualbox", "1.0.0"] } - - it "creates a provider" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil). - and_return(provider) - - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(iso_env.ui).to receive(:warn) - expect(provider).to receive(:create_provider).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil). - and_return(provider) + subject { described_class.new(argv, iso_env) } - allow(provider).to receive(:create_provider). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 422)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:create_provider) end - end - context "with arguments and a remote url" do - let (:argv) { ["vagrant/box-name", "virtualbox", "1.0.0", "https://example.com/box"] } + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end - it "creates a provider" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, "https://example.com/box", "vagrant", "box-name", client.token, nil, nil, nil). - and_return(provider) + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(iso_env.ui).not_to receive(:warn) - expect(provider).to receive(:create_provider).and_return({}) - expect(subject.execute).to eq(0) + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with provider argument" do + let(:provider_arg) { "my-provider" } + + before { argv << provider_arg } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should create the provider" do + expect(subject).to receive(:create_provider).with(org_name, box_name, version_arg, provider_arg, any_args) + subject.execute + end + + it "should not provide URL value" do + expect(subject).to receive(:create_provider).with(org_name, box_name, version_arg, provider_arg, nil, any_args) + subject.execute + end + + context "with URL argument" do + let(:url_arg) { "provider-url" } + + before { argv << url_arg } + + it "should provide the URL value" do + expect(subject).to receive(:create_provider).with(org_name, box_name, version_arg, provider_arg, url_arg, any_args) + subject.execute + end + end + + context "with checksum and checksum type flags" do + let(:checksum_arg) { "checksum" } + let(:checksum_type_arg) { "checksum_type" } + + before { argv.push("--checksum").push(checksum_arg).push("--checksum-type").push(checksum_type_arg) } + + it "should include the checksum options" do + expect(subject).to receive(:create_provider). + with(org_name, box_name, version_arg, provider_arg, any_args, hash_including(checksum: checksum_arg, checksum_type: checksum_type_arg)) + subject.execute + end + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/provider/delete_test.rb b/test/unit/plugins/commands/cloud/provider/delete_test.rb index 7573b54f8..00630fae6 100644 --- a/test/unit/plugins/commands/cloud/provider/delete_test.rb +++ b/test/unit/plugins/commands/cloud/provider/delete_test.rb @@ -1,70 +1,140 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/provider/delete") describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Delete do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { "1.0.0" } + let(:box_version_provider) { "my-provider" } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version, providers: [provider]) } + let(:provider) { double("provider", name: box_version_provider) } - subject { described_class.new(argv, iso_env) } + describe "#delete_provider" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_provider). + with(account: account, org: org_name, box: box_name, version: box_version, provider: box_version_provider). + and_yield(provider) + allow(provider).to receive(:delete) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } - let(:provider) { double("provider") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - allow(VagrantCloud::Version).to receive(:new) - .with(box, "1.0.0", nil, nil, client.token) - .and_return(version) - allow(iso_env.ui).to receive(:ask). - and_return("y") - end + it "should delete the provider" do + expect(provider).to receive(:delete) + subject.delete_provider(org_name, box_name, box_version, box_version_provider, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should return zero on success" do + r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, access_token, options) + expect(r).to eq(0) + end + + context "when error is encountered" do + before do + expect(provider).to receive(:delete).and_raise(VagrantCloud::Error) + end + + it "should return non-zero" do + r = subject.delete_provider(org_name, box_name, box_version, box_version_provider, access_token, options) + expect(r).to be_a(Integer) + expect(r).not_to eq(0) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "virtualbox", "1.0.0"] } - - it "deletes a provider" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, nil, nil, client.token). - and_return(provider) - - expect(provider).to receive(:delete).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, nil, nil, client.token). - and_return(provider) + subject { described_class.new(argv, iso_env) } - allow(provider).to receive(:delete). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(iso_env.ui).to receive(:ask). + and_return("y") + allow(subject).to receive(:delete_provider) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with provider argument" do + let(:provider_arg) { "my-provider" } + + before { argv << provider_arg } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should delete the provider" do + expect(subject).to receive(:delete_provider). + with(org_name, box_name, version_arg, provider_arg, access_token, anything) + subject.execute + end + + it "should prompt for confirmation" do + expect(iso_env.ui).to receive(:ask).and_return("y") + subject.execute + end + + context "with force flag" do + before { argv << "--force" } + + it "should not prompt for confirmation" do + expect(iso_env.ui).not_to receive(:ask) + subject.execute + end + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/provider/update_test.rb b/test/unit/plugins/commands/cloud/provider/update_test.rb index e2fdb1d4f..dfa054269 100644 --- a/test/unit/plugins/commands/cloud/provider/update_test.rb +++ b/test/unit/plugins/commands/cloud/provider/update_test.rb @@ -1,85 +1,175 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/provider/update") describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Update do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { "1.0.0" } + let(:box_version_provider) { "my-provider" } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version, provdiers: [provider]) } + let(:provider) { double("provider", name: box_version_provider) } + let(:provider_url) { nil } - subject { described_class.new(argv, iso_env) } + describe "#update_provider" do + let(:argv) { [] } + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_provider). + with(account: account, org: org_name, box: box_name, version: box_version, provider: box_version_provider). + and_yield(provider) + allow(provider).to receive(:save) + allow(subject).to receive(:format_box_results) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box", create: true, read: {}) } - let(:version) { double("version", create_version: true, release: true) } - let(:provider) { double("provider", create_provider: true, upload_file: true) } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - allow(VagrantCloud::Version).to receive(:new) - .with(box, "1.0.0", nil, nil, client.token) - .and_return(version) - end + it "should update the provider" do + expect(provider).to receive(:save) + subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should return 0 on success" do + result = subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + expect(result).to eq(0) + end + + it "should return non-zero result on error" do + expect(provider).to receive(:save).and_raise(VagrantCloud::Error) + result = subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + it "should not update the URL when unset" do + expect(provider).not_to receive(:url=) + subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + end + + context "when URL is set" do + let(:provider_url) { double("provider-url") } + + it "should update the URL" do + expect(provider).to receive(:url=).with(provider_url) + subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + end + end + + context "with options set" do + let(:checksum) { double("checksum") } + let(:checksum_type) { double("checksum_type") } + let(:options) { {checksum: checksum, checksum_type: checksum_type} } + + it "should set checksum options before saving" do + expect(provider).to receive(:checksum=).with(checksum) + expect(provider).to receive(:checksum_type=).with(checksum_type) + subject.update_provider(org_name, box_name, box_version, box_version_provider, provider_url, access_token, options) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "virtualbox", "1.0.0"] } - - it "updates a provider" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil). - and_return(provider) - - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(iso_env.ui).to receive(:warn) - expect(provider).to receive(:update).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token, nil, nil, nil). - and_return(provider) + subject { described_class.new(argv, iso_env) } - allow(provider).to receive(:update). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:update_provider) end - end - context "with arguments and a remote url" do - let (:argv) { ["vagrant/box-name", "virtualbox", "1.0.0", "https://example.com/box"] } + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end - it "creates a provider" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, "https://example.com/box", "vagrant", "box-name", client.token, nil, nil, nil). - and_return(provider) + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(iso_env.ui).not_to receive(:warn) - expect(provider).to receive(:update).and_return({}) - expect(subject.execute).to eq(0) + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with provider argument" do + let(:provider_arg) { "my-provider" } + + before { argv << provider_arg } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should create the provider" do + expect(subject).to receive(:update_provider).with(org_name, box_name, version_arg, provider_arg, any_args) + subject.execute + end + + it "should not provide URL value" do + expect(subject).to receive(:update_provider).with(org_name, box_name, version_arg, provider_arg, nil, any_args) + subject.execute + end + + context "with URL argument" do + let(:url_arg) { "provider-url" } + + before { argv << url_arg } + + it "should provide the URL value" do + expect(subject).to receive(:update_provider).with(org_name, box_name, version_arg, provider_arg, url_arg, any_args) + subject.execute + end + end + + context "with checksum and checksum type flags" do + let(:checksum_arg) { "checksum" } + let(:checksum_type_arg) { "checksum_type" } + + before { argv.push("--checksum").push(checksum_arg).push("--checksum-type").push(checksum_type_arg) } + + it "should include the checksum options" do + expect(subject).to receive(:update_provider). + with(org_name, box_name, version_arg, provider_arg, any_args, hash_including(checksum: checksum_arg, checksum_type: checksum_type_arg)) + subject.execute + end + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/provider/upload_test.rb b/test/unit/plugins/commands/cloud/provider/upload_test.rb index b2b1576ba..66d18c89f 100644 --- a/test/unit/plugins/commands/cloud/provider/upload_test.rb +++ b/test/unit/plugins/commands/cloud/provider/upload_test.rb @@ -1,79 +1,184 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/provider/upload") describe VagrantPlugins::CloudCommand::ProviderCommand::Command::Upload do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { "1.0.0" } + let(:box_version_provider) { "my-provider" } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version, provdiers: [provider]) } + let(:provider) { double("provider", name: box_version_provider) } + let(:provider_file) { double("provider-file") } - subject { described_class.new(argv, iso_env) } + describe "#upload_provider" do + let(:argv) { [] } + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:upload_url) { double("upload-url") } + let(:uploader) { double("uploader") } - let(:action_runner) { double("action_runner") } - - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } - let(:provider) { double("provider") } - let(:uploader) { double("uploader") } - - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - allow(VagrantCloud::Version).to receive(:new) - .with(box, "1.0.0", nil, nil, client.token) - .and_return(version) - end - - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + before do + allow(ui).to receive(:info) + allow(ui).to receive(:output) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(I18n).to receive(:t) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_provider). + with(account: account, org: org_name, box: box_name, version: box_version, provider: box_version_provider). + and_yield(provider) + allow(provider).to receive(:upload).and_yield(upload_url) + allow(uploader).to receive(:upload!) + allow(Vagrant::UI::Prefixed).to receive(:new).with(ui, "cloud").and_return(ui) + allow(Vagrant::Util::Uploader).to receive(:new).and_return(uploader) end - end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "virtualbox", "1.0.0", "path/to/box.box"] } + subject { described_class.new(argv, env) } - it "uploads a provider" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token). - and_return(provider) - allow(provider).to receive(:upload_url). - and_return("http://example.com/there") - allow(Vagrant::Util::Uploader).to receive(:new). - with("http://example.com/there", "path/to/box.box", {ui: anything}). - and_return(uploader) + it "should upload the provider file" do + expect(provider).to receive(:upload) + subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) + end + it "should return zero on success" do + r = subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) + expect(r).to eq(0) + end + + it "should return non-zero on API error" do + expect(provider).to receive(:upload).and_raise(VagrantCloud::Error) + r = subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) + expect(r).not_to eq(0) + expect(r).to be_a(Integer) + end + + it "should return non-zero on upload error" do + expect(provider).to receive(:upload).and_raise(Vagrant::Errors::UploaderError) + r = subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) + expect(r).not_to eq(0) + expect(r).to be_a(Integer) + end + + it "should should upload via uploader" do expect(uploader).to receive(:upload!) - expect(subject.execute).to eq(0) + subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Provider).to receive(:new). - with(version, "virtualbox", nil, nil, "vagrant", "box-name", client.token). - and_return(provider) - allow(provider).to receive(:upload_url). - and_return("http://example.com/there") - allow(Vagrant::Util::Uploader).to receive(:new). - with("http://example.com/there", "path/to/box.box", {ui: anything}). - and_return(uploader) + it "should not use direct upload by default" do + expect(provider).to receive(:upload) do |**args| + expect(args[:direct]).to be_falsey + end + subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) + end - allow(uploader).to receive(:upload!). - and_raise(Vagrant::Errors::UploaderError.new(exit_code: 1, message: "Error")) - expect(subject.execute).to eq(1) + context "with direct option" do + let(:options) { {direct: true} } + + it "should use direct upload" do + expect(provider).to receive(:upload) do |**args| + expect(args[:direct]).to be_truthy + end + subject.upload_provider(org_name, box_name, box_version, box_version_provider, provider_file, access_token, options) + end + end + end + + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + + subject { described_class.new(argv, iso_env) } + + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:upload_provider) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with provider argument" do + let(:provider_arg) { "my-provider" } + + before { argv << provider_arg } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with file argument" do + let(:file_arg) { "/dev/null/file" } + + before { argv << file_arg } + + it "should upload the provider file" do + expect(subject).to receive(:upload_provider). + with(org_name, box_name, version_arg, provider_arg, file_arg, any_args) + subject.execute + end + + it "should do direct upload by default" do + expect(subject).to receive(:upload_provider). + with(any_args, hash_including(direct: true)) + subject.execute + end + + context "with --no-direct flag" do + before { argv << "--no-direct" } + + it "should not perform direct upload" do + expect(subject).to receive(:upload_provider). + with(any_args, hash_including(direct: false)) + subject.execute + end + end + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/publish_test.rb b/test/unit/plugins/commands/cloud/publish_test.rb index 3c2578e48..4c7b584ad 100644 --- a/test/unit/plugins/commands/cloud/publish_test.rb +++ b/test/unit/plugins/commands/cloud/publish_test.rb @@ -5,127 +5,316 @@ require Vagrant.source_root.join("plugins/commands/cloud/publish") describe VagrantPlugins::CloudCommand::Command::Publish do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end - - let(:client) { double("client", token: "1234token1234") } + let(:argv) { [] } + let(:iso_env) { double("iso_env") } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box") } + let(:version) { double("version") } + let(:provider) { double("provider") } + let(:uploader) { double("uploader") } + let(:ui) { double("ui") } + let(:upload_url) { double("upload_url") } + let(:access_token) { double("access_token") } subject { described_class.new(argv, iso_env) } - let(:action_runner) { double("action_runner") } - let(:box_path) { "path/to/the/virtualbox.box" } - - let(:box) { double("box", create: true, read: {}) } - let(:version) { double("version", create_version: true, release: true) } - let(:provider) { double("provider", create_provider: true, upload_file: true) } - let(:uploader) { double("uploader") } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(iso_env.ui).to receive(:ask). - and_return("y") - allow(VagrantCloud::Box).to receive(:new).and_return(box) - allow(VagrantCloud::Version).to receive(:new).and_return(version) - allow(VagrantCloud::Provider).to receive(:new).and_return(provider) - - allow(File).to receive(:absolute_path).and_return("/full/#{box_path}") - allow(File).to receive(:file?).and_return(true) + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(iso_env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: anything). + and_return(account) end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) - end - end - - context "missing required arguments" do - let(:argv) { ["vagrant/box", "1.0.0", "virtualbox"] } - - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) - end - end - - context "missing box file" do - let(:argv) { ["vagrant/box", "1.0.0", "virtualbox", "/notreal/file.box"] } - - it "raises an exception" do - allow(File).to receive(:file?).and_return(false) - expect { subject.execute }. - to raise_error(Vagrant::Errors::BoxFileNotExist) - end - end - - context "with arguments" do - let(:argv) { ["vagrant/box", "1.0.0", "virtualbox", box_path] } - - it "publishes a box given options" do - allow(provider).to receive(:upload_url).and_return("http://example.com/there") - allow(Vagrant::Util::Uploader).to receive(:new). - with("http://example.com/there", "/full/path/to/the/virtualbox.box", {ui: anything}). - and_return(uploader) + describe "#upload_box_file" do + before do + allow(provider).to receive(:upload).and_yield(upload_url) allow(uploader).to receive(:upload!) - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(subject.execute).to eq(0) + allow(File).to receive(:absolute_path).and_return(box) + allow(Vagrant::Util::Uploader).to receive(:new).and_return(uploader) end - it "catches a ClientError if something goes wrong" do - allow(provider).to receive(:upload_url).and_return("http://example.com/there") - allow(Vagrant::Util::Uploader).to receive(:new). - with("http://example.com/there", "/full/path/to/the/virtualbox.box", {ui: anything}). - and_return(uploader) - allow(uploader).to receive(:upload!) - allow(box).to receive(:create). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + it "should get absolute path for box file" do + expect(File).to receive(:absolute_path).and_return(box) + subject.upload_box_file(provider, box) end - it "calls update if entity already exists" do - allow(provider).to receive(:upload_url).and_return("http://example.com/there") - allow(Vagrant::Util::Uploader).to receive(:new). - with("http://example.com/there", "/full/path/to/the/virtualbox.box", {ui: anything}). - and_return(uploader) - allow(uploader).to receive(:upload!) - allow(box).to receive(:create). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 422)) - expect(box).to receive(:update) - expect(subject.execute).to eq(0) + it "should upload through provider" do + expect(provider).to receive(:upload).and_return(upload_url) + subject.upload_box_file(provider, box) + end + + it "should create uploader with given url" do + expect(Vagrant::Util::Uploader).to receive(:new). + with(upload_url, any_args).and_return(uploader) + subject.upload_box_file(provider, box) + end + + it "should upload with PUT method by default" do + expect(Vagrant::Util::Uploader).to receive(:new). + with(upload_url, anything, hash_including(method: :put)).and_return(uploader) + subject.upload_box_file(provider, box) + end + + it "should upload with PUT method when direct upload option set" do + expect(Vagrant::Util::Uploader).to receive(:new). + with(upload_url, anything, hash_including(method: :put)).and_return(uploader) + subject.upload_box_file(provider, box, direct_upload: true) end end - context "with arguments and releasing a box" do - let(:argv) { ["vagrant/box", "1.0.0", "virtualbox", box_path, "--release"] } - - it "releases the box" do - allow(provider).to receive(:upload_url).and_return("http://example.com/there") - allow(Vagrant::Util::Uploader).to receive(:new). - with("http://example.com/there", "/full/path/to/the/virtualbox.box", {ui: anything}). - and_return(uploader) - allow(uploader).to receive(:upload!) - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) + describe "#release_version" do + it "should release the version" do expect(version).to receive(:release) - expect(subject.execute).to eq(0) + subject.release_version(version) end end - context "with arguments and a remote url" do - let(:argv) { ["vagrant/box", "1.0.0", "virtualbox", "--url", "https://www.example.com/path/to/the/virtualbox.box"] } + describe "#set_box_info" do + context "with no options set" do + let(:options) { {} } - it "does not upload a file" do - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(subject.execute).to eq(0) - expect(provider).not_to receive(:upload_file) + it "should not modify the box" do + subject.set_box_info(box, options) + end + end + + context "with options set" do + let(:priv) { double("private") } + let(:short_description) { double("short_description") } + let(:description) { double("description") } + + let(:options) { + {private: priv, description: description, short_description: short_description} + } + + it "should set info on box" do + expect(box).to receive(:private=).with(priv) + expect(box).to receive(:short_description=).with(short_description) + expect(box).to receive(:description=).with(description) + subject.set_box_info(box, options) + end + end + end + + describe "#set_version_info" do + context "with no options set" do + let(:options) { {} } + + it "should not modify the verison" do + subject.set_version_info(version, options) + end + end + + context "with options set" do + let(:options) { {version_description: version_description} } + let(:version_description) { double("version_description") } + + it "should set info on version" do + expect(version).to receive(:description=).with(version_description) + subject.set_version_info(version, options) + end + end + end + + describe "#set_provider_info" do + context "with no options set" do + let(:options) { {} } + + it "should not modify the provider" do + subject.set_provider_info(provider, options) + end + end + + context "with options set" do + let(:options) { {url: url, checksum: checksum, checksum_type: checksum_type} } + let(:url) { double("url") } + let(:checksum) { double("checksum") } + let(:checksum_type) { double("checksum_type") } + + it "should set info on provider" do + expect(provider).to receive(:url=).with(url) + expect(provider).to receive(:checksum=).with(checksum) + expect(provider).to receive(:checksum_type=).with(checksum_type) + subject.set_provider_info(provider, options) + end + end + end + + describe "load_box_version" do + let(:box_version) { "1.0.0" } + + context "when vesion exists" do + before do + allow(box).to receive(:versions).and_return([version]) + allow(version).to receive(:version).and_return(box_version) + end + + it "should return the existing version" do + expect(subject.load_box_version(box, box_version)).to eq(version) + end + end + + context "when version does not exist" do + let(:new_version) { double("new_version") } + + before do + allow(box).to receive(:versions).and_return([version]) + allow(version).to receive(:version) + end + + it "should add a new version" do + expect(box).to receive(:add_version).with(box_version). + and_return(new_version) + expect(subject.load_box_version(box, box_version)).to eq(new_version) + end + end + end + + describe "#load_box" do + let(:org_name) { "org-name" } + let(:box_name) { "my-box" } + + before do + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + end + + context "when box exists" do + before do + allow(box).to receive(:name).and_return(box_name) + allow(organization).to receive(:boxes).and_return([box]) + end + + it "should return the existing box" do + expect(subject.load_box(org_name, box_name, access_token)).to eq(box) + end + end + + context "when box does not exist" do + let(:new_box) { double("new_box") } + + before do + allow(organization).to receive(:boxes).and_return([]) + end + + it "should add a new box to organization" do + expect(organization).to receive(:add_box).with(box_name). + and_return(new_box) + expect(subject.load_box(org_name, box_name, access_token)).to eq(new_box) + end + end + end + + context "#execute" do + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env + end + let(:client) { double("client", token: "1234token1234") } + let(:action_runner) { double("action_runner") } + let(:box_path) { "path/to/the/virtualbox.box" } + + before do + allow(iso_env).to receive(:action_runner). + and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:format_box_results) + + allow(iso_env.ui).to receive(:ask).and_return("y") + allow(File).to receive(:absolute_path).and_return("/full/#{box_path}") + allow(File).to receive(:file?).and_return(true) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "missing required arguments" do + let(:argv) { ["vagrant/box", "1.0.0", "virtualbox"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "missing box file" do + let(:argv) { ["vagrant/box", "1.0.0", "virtualbox", "/notreal/file.box"] } + + it "raises an exception" do + allow(File).to receive(:file?).and_return(false) + expect { subject.execute }. + to raise_error(Vagrant::Errors::BoxFileNotExist) + end + end + + context "with arguments" do + let(:org_name) { "vagrant" } + let(:box_name) { "box" } + let(:box_version) { "1.0.0" } + let(:box_version_provider) { "virtualbox" } + + let(:argv) { [ + "#{org_name}/#{box_name}", box_version, box_version_provider, box_path + ] } + + before do + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(subject).to receive(:load_box).and_return(box) + allow(subject).to receive(:load_box_version).and_return(version) + allow(subject).to receive(:load_version_provider).and_return(provider) + allow(provider).to receive(:upload) + + allow(box).to receive(:save) + end + + it "should prompt user for confirmation" do + expect(iso_env.ui).to receive(:ask).and_return("y") + expect(subject.execute).to eq(0) + end + + context "when --force option is provided" do + before { argv << "--force" } + + it "should not prompt user for confirmation" do + expect(iso_env.ui).not_to receive(:ask) + expect(subject.execute).to eq(0) + end + end + + context "when --release option is provided" do + before do + argv << "--release" + end + + it "should release box version when not released" do + expect(version).to receive(:released?).and_return(false) + expect(version).to receive(:release) + expect(subject.execute).to eq(0) + end + end + + context "when Vagrant Cloud error is encountered" do + before { expect(box).to receive(:save).and_raise(VagrantCloud::Error) } + + it "should return non-zero result" do + result = subject.execute + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + end end end end diff --git a/test/unit/plugins/commands/cloud/search_test.rb b/test/unit/plugins/commands/cloud/search_test.rb index 924f7b0e3..6a82533c6 100644 --- a/test/unit/plugins/commands/cloud/search_test.rb +++ b/test/unit/plugins/commands/cloud/search_test.rb @@ -5,6 +5,7 @@ require Vagrant.source_root.join("plugins/commands/cloud/search") describe VagrantPlugins::CloudCommand::Command::Search do include_context "unit" + let(:token) { double("token") } let(:argv) { [] } let(:iso_env) do # We have to create a Vagrantfile so there is a root path @@ -13,65 +14,146 @@ describe VagrantPlugins::CloudCommand::Command::Search do env.create_vagrant_env end - let(:client) { double("client", token: "1234token1234") } - subject { described_class.new(argv, iso_env) } - let(:action_runner) { double("action_runner") } + describe "#search" do + let(:query) { double("query") } + let(:options) { {} } + let(:account) { double("account", searcher: searcher) } + let(:searcher) { double("searcher") } + let(:results) { double("results", boxes: boxes) } + let(:boxes) { [] } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_search_results). - and_return(true) - end + before do + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: token).and_return(account) + allow(searcher).to receive(:search).and_return(results) + allow(subject).to receive(:format_search_results) + end - context "with no arguments" do - let (:search) { double("search", search: {"boxes"=>["all of them"]}) } + it "should perform search" do + expect(searcher).to receive(:search).with(hash_including(query: query)).and_return(results) + subject.search(query, token, options) + end - it "makes a request to search all boxes and formats them" do - allow(VagrantCloud::Search).to receive(:new). - and_return(search) - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_search_results) - expect(subject.execute).to eq(0) + it "should print a warning when no results are found" do + expect(iso_env.ui).to receive(:warn) + subject.search(query, token, options) + end + + context "with valid options" do + let(:provider) { double("provider") } + let(:sort) { double("sort") } + let(:order) { double("order") } + let(:limit) { double("limit") } + let(:page) { double("page") } + + let(:options) { { + provider: provider, + sort: sort, + order: order, + limit: limit, + page: page + } } + + it "should use options when performing search" do + expect(searcher).to receive(:search) do |**args| + options.each_pair do |k, v| + expect(args[k]).to eq(v) + end + results + end + subject.search(query, token, options) + end + + context "with invalid options" do + before { options[:invalid_option] = "testing" } + + it "should only pass supported options to search" do + expect(searcher).to receive(:search) do |**args| + options.each_pair do |k, v| + next if k == :invalid_option + expect(args[k]).to eq(v) + end + expect(args.key?(:invalid_option)).to be_falsey + results + end + subject.search(query, token, options) + end + end + end + + context "with search results" do + let(:results) { double("results", boxes: [double("result")]) } + + it "should format the results" do + expect(subject).to receive(:format_search_results).with(results.boxes, any_args) + subject.search(query, token, options) + end + + context "with format options" do + let(:options) { {short: true, json: false} } + + it "should pass options to format" do + expect(subject).to receive(:format_search_results).with(results.boxes, true, false, iso_env) + subject.search(query, token, options) + end + end end end - context "with no arguments and an error occurs making requests" do - let (:search) { double("search") } - - it "catches a ClientError if something goes wrong" do - allow(VagrantCloud::Search).to receive(:new). - and_return(search) - allow(search).to receive(:search). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - - expect(subject.execute).to eq(1) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - end - context "with no arguments and no results" do - let (:search) { double("search", search: {"boxes"=>[]}) } + subject { described_class.new(argv, iso_env) } - it "makes a request to search all boxes and formats them" do - allow(VagrantCloud::Search).to receive(:new). - and_return(search) - expect(VagrantPlugins::CloudCommand::Util).not_to receive(:format_search_results) - subject.execute + let(:action_runner) { double("action_runner") } + + let(:client) { double("client", token: token) } + let(:box) { double("box") } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login).and_return(client) + allow(subject).to receive(:search) end - end - context "with arguments" do - let (:search) { double("search", search: {"boxes"=>["all of them"]}) } - let (:argv) { ["ubuntu", "--page", "1", "--order", "desc", "--limit", "100", "--provider", "provider", "--sort", "downloads"] } + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end - it "sends the options to make a request with" do - allow(VagrantCloud::Search).to receive(:new). - and_return(search) - expect(search).to receive(:search). - with("ubuntu", "provider", "downloads", "desc", 100, 1) - subject.execute + context "with query argument" do + let(:query_arg) { "search-query" } + + before { argv << query_arg } + + it "should run the search" do + expect(subject).to receive(:search).with(query_arg, any_args) + subject.execute + end + + it "should setup client login quietly by default" do + expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: true)) + subject.execute + end + + context "with --auth flag" do + before { argv << "--auth" } + + it "should not setup login client quietly" do + expect(subject).to receive(:client_login).with(iso_env, hash_including(quiet: false)) + subject.execute + end + end end end end diff --git a/test/unit/plugins/commands/cloud/version/create_test.rb b/test/unit/plugins/commands/cloud/version/create_test.rb index f6451cce7..49867a890 100644 --- a/test/unit/plugins/commands/cloud/version/create_test.rb +++ b/test/unit/plugins/commands/cloud/version/create_test.rb @@ -1,65 +1,135 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/version/create") describe VagrantPlugins::CloudCommand::VersionCommand::Command::Create do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { double("box_version") } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version) } - subject { described_class.new(argv, iso_env) } + describe "#create_version" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_box).with(account: account, org: org_name, box: box_name). + and_yield(box) + allow(account).to receive(:organization).with(name: org_name). + and_return(organization) + allow(box).to receive(:add_version).and_return(version) + allow(version).to receive(:save) + allow(subject).to receive(:format_box_results) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - end + it "should add a new version to the box" do + expect(box).to receive(:add_version).with(box_version) + subject.create_version(org_name, box_name, box_version, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should save the new version" do + expect(version).to receive(:save) + subject.create_version(org_name, box_name, box_version, access_token, options) + end + + it "should return 0 on success" do + result = subject.create_version(org_name, box_name, box_version, access_token, options) + expect(result).to eq(0) + end + + it "should return non-zero on error" do + expect(version).to receive(:save).and_raise(VagrantCloud::Error) + result = subject.create_version(org_name, box_name, box_version, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + context "with description option set" do + let(:description) { double("description") } + let(:options) { {description: description} } + + it "should set description on version" do + expect(version).to receive(:description=).with(description) + subject.create_version(org_name, box_name, box_version, access_token, options) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "1.0.0", "-d", "description"] } - - it "creates a version" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, "description", client.token). - and_return(version) - - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(version).to receive(:create_version).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, "description", client.token). - and_return(version) + subject { described_class.new(argv, iso_env) } - allow(version).to receive(:create_version). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 422)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:create_version) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should create the version" do + expect(subject).to receive(:create_version).with(org_name, box_name, version_arg, any_args) + subject.execute + end + + context "with description flag" do + let(:description) { "my-description" } + + before { argv.push("--description").push(description) } + + it "should create version with description option set" do + expect(subject).to receive(:create_version). + with(org_name, box_name, version_arg, access_token, hash_including(description: description)) + subject.execute + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/version/delete_test.rb b/test/unit/plugins/commands/cloud/version/delete_test.rb index 33a77aff4..dc0c5a1b5 100644 --- a/test/unit/plugins/commands/cloud/version/delete_test.rb +++ b/test/unit/plugins/commands/cloud/version/delete_test.rb @@ -1,66 +1,122 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/version/delete") describe VagrantPlugins::CloudCommand::VersionCommand::Command::Delete do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { double("box_version") } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version) } - subject { described_class.new(argv, iso_env) } + describe "#delete_version" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_version). + with(account: account, org: org_name, box: box_name, version: box_version). + and_yield(version) + allow(version).to receive(:delete) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - allow(iso_env.ui).to receive(:ask). - and_return("y") - end + it "should delete the version" do + expect(version).to receive(:delete) + subject.delete_version(org_name, box_name, box_version, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should return 0 on success" do + result = subject.delete_version(org_name, box_name, box_version, access_token, options) + expect(result).to eq(0) + end + + it "should return non-zero on error" do + expect(version).to receive(:delete).and_raise(VagrantCloud::Error) + result = subject.delete_version(org_name, box_name, box_version, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "1.0.0"] } - - it "deletes a version" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) - - expect(version).to receive(:delete).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) + subject { described_class.new(argv, iso_env) } - allow(version).to receive(:delete). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(iso_env.ui).to receive(:ask). + and_return("y") + allow(subject).to receive(:delete_version) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should delete the version" do + expect(subject).to receive(:delete_version). + with(org_name, box_name, version_arg, access_token, anything) + subject.execute + end + + it "should prompt for confirmation" do + expect(iso_env.ui).to receive(:ask).and_return("y") + subject.execute + end + + context "with force flag" do + before { argv << "--force" } + + it "should not prompt for confirmation" do + expect(iso_env.ui).not_to receive(:ask) + subject.execute + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/version/release_test.rb b/test/unit/plugins/commands/cloud/version/release_test.rb index ac8f75c4e..5d35014a3 100644 --- a/test/unit/plugins/commands/cloud/version/release_test.rb +++ b/test/unit/plugins/commands/cloud/version/release_test.rb @@ -1,91 +1,121 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/version/release") describe VagrantPlugins::CloudCommand::VersionCommand::Command::Release do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { double("box_version") } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version) } - subject { described_class.new(argv, iso_env) } + describe "#release_version" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_version). + with(account: account, org: org_name, box: box_name, version: box_version). + and_yield(version) + allow(version).to receive(:release) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - end + it "should release the version" do + expect(version).to receive(:release) + subject.release_version(org_name, box_name, box_version, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should return 0 on success" do + result = subject.release_version(org_name, box_name, box_version, access_token, options) + expect(result).to eq(0) + end + + it "should return a non-zero on error" do + expect(version).to receive(:release).and_raise(VagrantCloud::Error) + result = subject.release_version(org_name, box_name, box_version, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) end end - context "interactive mode with arguments" do - let (:argv) { ["vagrant/box-name", "1.0.0"] } - - it "releases a version" do - allow(iso_env.ui).to receive(:ask). - and_return("y") - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) - - expect(version).to receive(:release).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(iso_env.ui).to receive(:ask). - and_return("y") - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) + subject { described_class.new(argv, iso_env) } - allow(version).to receive(:release). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) - end - end + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } - context "non-interactive mode with arguments" do - let (:argv) { ["--force", "vagrant/box-name", "1.0.0"] } - - it "releases a version" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) - - expect(version).to receive(:release).and_return({}) - expect(subject.execute).to eq(0) + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:release_version) + allow(iso_env.ui).to receive(:ask).and_return("y") end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end - allow(version).to receive(:release). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should release the version" do + expect(subject).to receive(:release_version). + with(org_name, box_name, version_arg, access_token, anything) + subject.execute + end + + it "should prompt for confirmation" do + expect(iso_env.ui).to receive(:ask).and_return("y") + subject.execute + end + + context "with force flag" do + before { argv << "--force" } + + it "should not prompt for confirmation" do + expect(iso_env.ui).not_to receive(:ask) + subject.execute + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/version/revoke_test.rb b/test/unit/plugins/commands/cloud/version/revoke_test.rb index f933de404..65b48fc47 100644 --- a/test/unit/plugins/commands/cloud/version/revoke_test.rb +++ b/test/unit/plugins/commands/cloud/version/revoke_test.rb @@ -1,66 +1,123 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/version/revoke") describe VagrantPlugins::CloudCommand::VersionCommand::Command::Revoke do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { double("box_version") } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version) } - subject { described_class.new(argv, iso_env) } + describe "#revoke_version" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_version). + with(account: account, org: org_name, box: box_name, version: box_version). + and_yield(version) + allow(version).to receive(:revoke) + allow(subject).to receive(:format_box_results) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - allow(iso_env.ui).to receive(:ask). - and_return("y") - end + it "should revoke the version" do + expect(version).to receive(:revoke) + subject.revoke_version(org_name, box_name, box_version, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should return 0 on success" do + result = subject.revoke_version(org_name, box_name, box_version, access_token, options) + expect(result).to eq(0) + end + + it "should return non-zero on error" do + expect(version).to receive(:revoke).and_raise(VagrantCloud::Error) + result = subject.revoke_version(org_name, box_name, box_version, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "1.0.0"] } - - it "revokes a version" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) - - expect(version).to receive(:revoke).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, nil, client.token). - and_return(version) + subject { described_class.new(argv, iso_env) } - expect(version).to receive(:revoke). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:revoke_version) + allow(iso_env.ui).to receive(:ask). + and_return("y") + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with box name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should revoke the version" do + expect(subject).to receive(:revoke_version). + with(org_name, box_name, version_arg, access_token, anything) + subject.execute + end + + it "should prompt for confirmation" do + expect(iso_env.ui).to receive(:ask).and_return("y") + subject.execute + end + + context "with force flag" do + before { argv << "--force" } + + it "should not prompt for confirmation" do + expect(iso_env.ui).not_to receive(:ask) + subject.execute + end + end + end end end end diff --git a/test/unit/plugins/commands/cloud/version/update_test.rb b/test/unit/plugins/commands/cloud/version/update_test.rb index d964fb364..833e9276d 100644 --- a/test/unit/plugins/commands/cloud/version/update_test.rb +++ b/test/unit/plugins/commands/cloud/version/update_test.rb @@ -1,65 +1,129 @@ require File.expand_path("../../../../../base", __FILE__) - require Vagrant.source_root.join("plugins/commands/cloud/version/update") describe VagrantPlugins::CloudCommand::VersionCommand::Command::Update do include_context "unit" - let(:argv) { [] } - let(:iso_env) do - # We have to create a Vagrantfile so there is a root path - env = isolated_environment - env.vagrantfile("") - env.create_vagrant_env - end + let(:access_token) { double("token") } + let(:org_name) { "my-org" } + let(:box_name) { "my-box" } + let(:box_version) { double("box_version") } + let(:account) { double("account") } + let(:organization) { double("organization") } + let(:box) { double("box", versions: [version]) } + let(:version) { double("version", version: box_version) } - subject { described_class.new(argv, iso_env) } + describe "#update_version" do + let(:options) { {} } + let(:env) { double("env", ui: ui) } + let(:ui) { double("ui") } + let(:argv) { [] } - let(:action_runner) { double("action_runner") } + before do + allow(ui).to receive(:info) + allow(ui).to receive(:warn) + allow(ui).to receive(:success) + allow(ui).to receive(:error) + allow(env).to receive(:ui).and_return(ui) + allow(VagrantCloud::Account).to receive(:new). + with(custom_server: anything, access_token: access_token). + and_return(account) + allow(subject).to receive(:with_version). + with(account: account, org: org_name, box: box_name, version: box_version). + and_yield(version) + allow(version).to receive(:save) + allow(subject).to receive(:format_box_results) + end - let(:client) { double("client", token: "1234token1234") } - let(:box) { double("box") } - let(:version) { double("version") } + subject { described_class.new(argv, env) } - before do - allow(iso_env).to receive(:action_runner).and_return(action_runner) - allow(VagrantPlugins::CloudCommand::Util).to receive(:client_login). - and_return(client) - allow(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results). - and_return(true) - allow(VagrantCloud::Box).to receive(:new) - .with(anything, "box-name", nil, nil, nil, client.token) - .and_return(box) - end + it "should update the version" do + expect(version).to receive(:save) + subject.update_version(org_name, box_name, box_version, access_token, options) + end - context "with no arguments" do - it "shows help" do - expect { subject.execute }. - to raise_error(Vagrant::Errors::CLIInvalidUsage) + it "should return 0 on success" do + result = subject.update_version(org_name, box_name, box_version, access_token, options) + expect(result).to eq(0) + end + + it "should return non-zero result on error" do + expect(version).to receive(:save).and_raise(VagrantCloud::Error) + result = subject.update_version(org_name, box_name, box_version, access_token, options) + expect(result).not_to eq(0) + expect(result).to be_a(Integer) + end + + context "with options set" do + let(:description) { double("description") } + let(:options) { {description: description} } + + it "should set version info before saving" do + expect(version).to receive(:description=).with(description) + subject.update_version(org_name, box_name, box_version, access_token, options) + end end end - context "with arguments" do - let (:argv) { ["vagrant/box-name", "1.0.0", "-d", "description"] } - - it "updates a version" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, "description", client.token). - and_return(version) - - expect(VagrantPlugins::CloudCommand::Util).to receive(:format_box_results) - expect(version).to receive(:update).and_return({}) - expect(subject.execute).to eq(0) + describe "#execute" do + let(:argv) { [] } + let(:iso_env) do + # We have to create a Vagrantfile so there is a root path + env = isolated_environment + env.vagrantfile("") + env.create_vagrant_env end - it "displays an error if encoutering a problem with the request" do - allow(VagrantCloud::Version).to receive(:new). - with(box, "1.0.0", nil, "description", client.token). - and_return(version) + subject { described_class.new(argv, iso_env) } - allow(version).to receive(:update). - and_raise(VagrantCloud::ClientError.new("Fail Message", "Message", 404)) - expect(subject.execute).to eq(1) + let(:action_runner) { double("action_runner") } + let(:client) { double("client", token: access_token) } + + before do + allow(iso_env).to receive(:action_runner).and_return(action_runner) + allow(subject).to receive(:client_login). + and_return(client) + allow(subject).to receive(:update_version) + end + + context "with no arguments" do + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + end + + context "with name argument" do + let(:argv) { ["#{org_name}/#{box_name}"] } + + it "shows help" do + expect { subject.execute }. + to raise_error(Vagrant::Errors::CLIInvalidUsage) + end + + context "with version argument" do + let(:version_arg) { "1.0.0" } + + before { argv << version_arg } + + it "should update the version" do + expect(subject).to receive(:update_version). + with(org_name, box_name, version_arg, access_token, anything) + subject.execute + end + + context "with description flag" do + let(:description) { "my-description" } + + before { argv.push("--description").push(description) } + + it "should update version with description" do + expect(subject).to receive(:update_version). + with(org_name, box_name, version_arg, access_token, hash_including(description: description)) + subject.execute + end + end + end end end end diff --git a/test/unit/vagrant/util/uploader_test.rb b/test/unit/vagrant/util/uploader_test.rb index 76cc5d7a8..b3789f63e 100644 --- a/test/unit/vagrant/util/uploader_test.rb +++ b/test/unit/vagrant/util/uploader_test.rb @@ -5,7 +5,7 @@ require "vagrant/util/uploader" describe Vagrant::Util::Uploader do let(:destination) { "fake" } let(:file) { "my/file.box" } - let(:curl_options) { [destination, "--request", "PUT", "--upload-file", file, {notify: :stderr}] } + let(:curl_options) { [destination, "--request", "PUT", "--upload-file", file, "--fail", {notify: :stderr}] } let(:subprocess_result) do double("subprocess_result").tap do |result| diff --git a/vagrant.gemspec b/vagrant.gemspec index 18a6b37a2..09ac3d83f 100644 --- a/vagrant.gemspec +++ b/vagrant.gemspec @@ -28,9 +28,8 @@ Gem::Specification.new do |s| s.add_dependency "net-sftp", "~> 3.0" s.add_dependency "net-scp", "~> 1.2.0" s.add_dependency "rb-kqueue", "~> 0.2.0" - s.add_dependency "rest-client", ">= 1.6.0", "< 3.0" s.add_dependency "rubyzip", "~> 2.0" - s.add_dependency "vagrant_cloud", "~> 2.0.3" + s.add_dependency "vagrant_cloud", "~> 3.0.2" s.add_dependency "wdm", "~> 0.1.0" s.add_dependency "winrm", ">= 2.3.4", "< 3.0" s.add_dependency "winrm-elevated", ">= 1.2.1", "< 2.0" From 70d36bc7cd6e5d54d760127c4e119ebb3e8fd80e Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 23 Oct 2020 15:36:49 -0700 Subject: [PATCH 2/4] Update uploader options to include `--fail` so failures are reported properly --- lib/vagrant/util/uploader.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vagrant/util/uploader.rb b/lib/vagrant/util/uploader.rb index dc50e47c8..b9bfbecf3 100644 --- a/lib/vagrant/util/uploader.rb +++ b/lib/vagrant/util/uploader.rb @@ -54,7 +54,7 @@ module Vagrant protected def build_options - options = [@destination, "--request", @request_method, "--upload-file", @file] + options = [@destination, "--request", @request_method, "--upload-file", @file, "--fail"] return options end From baa24af179675cc8bfd5856347088c00938bcb8f Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 27 Oct 2020 15:32:12 -0700 Subject: [PATCH 3/4] Remove all unused code related to the deprecated `login` command --- plugins/commands/cloud/client/client.rb | 1 - plugins/commands/login/client.rb | 253 ----------------- plugins/commands/login/command.rb | 137 --------- plugins/commands/login/errors.rb | 24 -- plugins/commands/login/locales/en.yml | 49 ---- plugins/commands/login/plugin.rb | 13 - .../plugins/commands/login/client_test.rb | 261 ------------------ .../plugins/commands/login/command_test.rb | 96 ------- 8 files changed, 834 deletions(-) delete mode 100644 plugins/commands/login/client.rb delete mode 100644 plugins/commands/login/command.rb delete mode 100644 plugins/commands/login/errors.rb delete mode 100644 plugins/commands/login/locales/en.yml delete mode 100644 test/unit/plugins/commands/login/client_test.rb delete mode 100644 test/unit/plugins/commands/login/command_test.rb diff --git a/plugins/commands/cloud/client/client.rb b/plugins/commands/cloud/client/client.rb index 30de19043..1c75c7901 100644 --- a/plugins/commands/cloud/client/client.rb +++ b/plugins/commands/cloud/client/client.rb @@ -1,4 +1,3 @@ -require "rest_client" require "vagrant_cloud" require "vagrant/util/downloader" require "vagrant/util/presence" diff --git a/plugins/commands/login/client.rb b/plugins/commands/login/client.rb deleted file mode 100644 index ebfe717b3..000000000 --- a/plugins/commands/login/client.rb +++ /dev/null @@ -1,253 +0,0 @@ -require "rest_client" -require "vagrant/util/downloader" -require "vagrant/util/presence" - -module VagrantPlugins - module LoginCommand - class Client - APP = "app".freeze - - include Vagrant::Util::Presence - - attr_accessor :username_or_email - attr_accessor :password - attr_reader :two_factor_default_delivery_method - attr_reader :two_factor_delivery_methods - - # Initializes a login client with the given Vagrant::Environment. - # - # @param [Vagrant::Environment] env - def initialize(env) - @logger = Log4r::Logger.new("vagrant::login::client") - @env = env - end - - # Removes the token, effectively logging the user out. - def clear_token - @logger.info("Clearing token") - token_path.delete if token_path.file? - end - - # Checks if the user is logged in by verifying their authentication - # token. - # - # @return [Boolean] - def logged_in? - token = self.token - return false if !token - - with_error_handling do - url = "#{Vagrant.server_url}/api/v1/authenticate" + - "?access_token=#{token}" - RestClient.get(url, content_type: :json) - true - end - rescue Errors::Unauthorized - false - end - - # Login logs a user in and returns the token for that user. The token - # is _not_ stored unless {#store_token} is called. - # - # @param [String] description - # @param [String] code - # @return [String] token The access token, or nil if auth failed. - def login(description: nil, code: nil) - @logger.info("Logging in '#{username_or_email}'") - - response = post( - "/api/v1/authenticate", { - user: { - login: username_or_email, - password: password - }, - token: { - description: description - }, - two_factor: { - code: code - } - } - ) - - response["token"] - end - - # Requests a 2FA code - # @param [String] delivery_method - def request_code(delivery_method) - @env.ui.warn("Requesting 2FA code via #{delivery_method.upcase}...") - - response = post( - "/api/v1/two-factor/request-code", { - user: { - login: username_or_email, - password: password - }, - two_factor: { - delivery_method: delivery_method.downcase - } - } - ) - - two_factor = response['two_factor'] - obfuscated_destination = two_factor['obfuscated_destination'] - - @env.ui.success("2FA code sent to #{obfuscated_destination}.") - end - - # Issues a post to a Vagrant Cloud path with the given payload. - # @param [String] path - # @param [Hash] payload - # @return [Hash] response data - def post(path, payload) - with_error_handling do - url = File.join(Vagrant.server_url, path) - - proxy = nil - proxy ||= ENV["HTTPS_PROXY"] || ENV["https_proxy"] - proxy ||= ENV["HTTP_PROXY"] || ENV["http_proxy"] - RestClient.proxy = proxy - - response = RestClient::Request.execute( - method: :post, - url: url, - payload: JSON.dump(payload), - proxy: proxy, - headers: { - accept: :json, - content_type: :json, - user_agent: Vagrant::Util::Downloader::USER_AGENT, - }, - ) - - JSON.load(response.to_s) - end - end - - # Stores the given token locally, removing any previous tokens. - # - # @param [String] token - def store_token(token) - @logger.info("Storing token in #{token_path}") - - token_path.open("w") do |f| - f.write(token) - end - - nil - end - - # Reads the access token if there is one. This will first read the - # `VAGRANT_CLOUD_TOKEN` environment variable and then fallback to the stored - # access token on disk. - # - # @return [String] - def token - if present?(ENV["VAGRANT_CLOUD_TOKEN"]) && token_path.exist? - @env.ui.warn <<-EOH.strip -Vagrant detected both the VAGRANT_CLOUD_TOKEN environment variable and a Vagrant login -token are present on this system. The VAGRANT_CLOUD_TOKEN environment variable takes -precedence over the locally stored token. To remove this error, either unset -the VAGRANT_CLOUD_TOKEN environment variable or remove the login token stored on disk: - - ~/.vagrant.d/data/vagrant_login_token - -EOH - end - - if present?(ENV["VAGRANT_CLOUD_TOKEN"]) - @logger.debug("Using authentication token from environment variable") - return ENV["VAGRANT_CLOUD_TOKEN"] - end - - if token_path.exist? - @logger.debug("Using authentication token from disk at #{token_path}") - return token_path.read.strip - end - - if present?(ENV["ATLAS_TOKEN"]) - @logger.warn("ATLAS_TOKEN detected within environment. Using ATLAS_TOKEN in place of VAGRANT_CLOUD_TOKEN.") - return ENV["ATLAS_TOKEN"] - end - - @logger.debug("No authentication token in environment or #{token_path}") - - nil - end - - protected - - def with_error_handling(&block) - yield - rescue RestClient::Unauthorized - @logger.debug("Unauthorized!") - raise Errors::Unauthorized - rescue RestClient::BadRequest => e - @logger.debug("Bad request:") - @logger.debug(e.message) - @logger.debug(e.backtrace.join("\n")) - parsed_response = JSON.parse(e.response) - errors = parsed_response["errors"].join("\n") - raise Errors::ServerError, errors: errors - rescue RestClient::NotAcceptable => e - @logger.debug("Got unacceptable response:") - @logger.debug(e.message) - @logger.debug(e.backtrace.join("\n")) - - parsed_response = JSON.parse(e.response) - - if two_factor = parsed_response['two_factor'] - store_two_factor_information two_factor - - if two_factor_default_delivery_method != APP - request_code two_factor_default_delivery_method - end - - raise Errors::TwoFactorRequired - end - - begin - errors = parsed_response["errors"].join("\n") - raise Errors::ServerError, errors: errors - rescue JSON::ParserError; end - - raise "An unexpected error occurred: #{e.inspect}" - rescue SocketError - @logger.info("Socket error") - raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s - end - - def token_path - @env.data_dir.join("vagrant_login_token") - end - - def store_two_factor_information(two_factor) - @two_factor_default_delivery_method = - two_factor['default_delivery_method'] - - @two_factor_delivery_methods = - two_factor['delivery_methods'] - - @env.ui.warn "2FA is enabled for your account." - if two_factor_default_delivery_method == APP - @env.ui.info "Enter the code from your authenticator." - else - @env.ui.info "Default method is " \ - "'#{two_factor_default_delivery_method}'." - end - - other_delivery_methods = - two_factor_delivery_methods - [APP] - - if other_delivery_methods.any? - other_delivery_methods_sentence = other_delivery_methods - .map { |word| "'#{word}'" } - .join(' or ') - @env.ui.info "You can also type #{other_delivery_methods_sentence} " \ - "to request a new code." - end - end - end - end -end diff --git a/plugins/commands/login/command.rb b/plugins/commands/login/command.rb deleted file mode 100644 index 10a8ef13f..000000000 --- a/plugins/commands/login/command.rb +++ /dev/null @@ -1,137 +0,0 @@ -require 'socket' - -module VagrantPlugins - module LoginCommand - class Command < Vagrant.plugin("2", "command") - def self.synopsis - "log in to HashiCorp's Vagrant Cloud" - end - - def execute - options = {} - - opts = OptionParser.new do |o| - o.banner = "Usage: vagrant login" - o.separator "" - o.on("-c", "--check", "Only checks if you're logged in") do |c| - options[:check] = c - end - - o.on("-d", "--description DESCRIPTION", String, "Description for the Vagrant Cloud token") do |t| - options[:description] = t - end - - o.on("-k", "--logout", "Logs you out if you're logged in") do |k| - options[:logout] = k - end - - o.on("-t", "--token TOKEN", String, "Set the Vagrant Cloud token") do |t| - options[:token] = t - end - - o.on("-u", "--username USERNAME_OR_EMAIL", String, "Specify your Vagrant Cloud username or email address") do |t| - options[:login] = t - end - end - - # Parse the options - argv = parse_options(opts) - return if !argv - - @client = Client.new(@env) - @client.username_or_email = options[:login] - - # Determine what task we're actually taking based on flags - if options[:check] - return execute_check - elsif options[:logout] - return execute_logout - elsif options[:token] - return execute_token(options[:token]) - end - - # Let the user know what is going on. - @env.ui.output(I18n.t("login_command.command_header") + "\n") - - # If it is a private cloud installation, show that - if Vagrant.server_url != Vagrant::DEFAULT_SERVER_URL - @env.ui.output("Vagrant Cloud URL: #{Vagrant.server_url}") - end - - # Ask for the username - if @client.username_or_email - @env.ui.output("Vagrant Cloud username or email: #{@client.username_or_email}") - end - until @client.username_or_email - @client.username_or_email = @env.ui.ask("Vagrant Cloud username or email: ") - end - - until @client.password - @client.password = @env.ui.ask("Password (will be hidden): ", echo: false) - end - - description = options[:description] - if description - @env.ui.output("Token description: #{description}") - else - description_default = "Vagrant login from #{Socket.gethostname}" - until description - description = - @env.ui.ask("Token description (Defaults to #{description_default.inspect}): ") - end - description = description_default if description.empty? - end - - code = nil - - begin - token = @client.login(description: description, code: code) - rescue Errors::TwoFactorRequired - until code - code = @env.ui.ask("2FA code: ") - - if @client.two_factor_delivery_methods.include?(code.downcase) - delivery_method, code = code, nil - @client.request_code delivery_method - end - end - - retry - end - - @client.store_token(token) - @env.ui.success(I18n.t("login_command.logged_in")) - 0 - end - - def execute_check - if @client.logged_in? - @env.ui.success(I18n.t("login_command.check_logged_in")) - return 0 - else - @env.ui.error(I18n.t("login_command.check_not_logged_in")) - return 1 - end - end - - def execute_logout - @client.clear_token - @env.ui.success(I18n.t("login_command.logged_out")) - return 0 - end - - def execute_token(token) - @client.store_token(token) - @env.ui.success(I18n.t("login_command.token_saved")) - - if @client.logged_in? - @env.ui.success(I18n.t("login_command.check_logged_in")) - return 0 - else - @env.ui.error(I18n.t("login_command.invalid_token")) - return 1 - end - end - end - end -end diff --git a/plugins/commands/login/errors.rb b/plugins/commands/login/errors.rb deleted file mode 100644 index 4d56612bd..000000000 --- a/plugins/commands/login/errors.rb +++ /dev/null @@ -1,24 +0,0 @@ -module VagrantPlugins - module LoginCommand - module Errors - class Error < Vagrant::Errors::VagrantError - error_namespace("login_command.errors") - end - - class ServerError < Error - error_key(:server_error) - end - - class ServerUnreachable < Error - error_key(:server_unreachable) - end - - class Unauthorized < Error - error_key(:unauthorized) - end - - class TwoFactorRequired < Error - end - end - end -end diff --git a/plugins/commands/login/locales/en.yml b/plugins/commands/login/locales/en.yml deleted file mode 100644 index 3f41a1067..000000000 --- a/plugins/commands/login/locales/en.yml +++ /dev/null @@ -1,49 +0,0 @@ -en: - login_command: - middleware: - authentication: - different_target: |- - Vagrant has detected a custom Vagrant server in use for downloading - box files. An authentication token is currently set which will be - added to the box request. If the custom Vagrant server should not - be receiving the authentication token, please unset it. - - Known Vagrant server: %{known_host} - Custom Vagrant server: %{custom_host} - - Press ctrl-c to cancel... - errors: - server_error: |- - The Vagrant Cloud server responded with a not-OK response: - - %{errors} - server_unreachable: |- - The Vagrant Cloud server is not currently accepting connections. Please check - your network connection and try again later. - - unauthorized: |- - Invalid username or password. Please try again. - - check_logged_in: |- - You are already logged in. - check_not_logged_in: |- - You are not currently logged in. Please run `vagrant login` and provide - your login information to authenticate. - command_header: |- - In a moment we will ask for your username and password to HashiCorp's - Vagrant Cloud. After authenticating, we will store an access token locally on - disk. Your login details will be transmitted over a secure connection, and - are never stored on disk locally. - - If you do not have an Vagrant Cloud account, sign up at - https://www.vagrantcloud.com - invalid_login: |- - Invalid username or password. Please try again. - invalid_token: |- - Invalid token. Please try again. - logged_in: |- - You are now logged in. - logged_out: |- - You are logged out. - token_saved: |- - The token was successfully saved. diff --git a/plugins/commands/login/plugin.rb b/plugins/commands/login/plugin.rb index 9151c55a1..7cfbdb013 100644 --- a/plugins/commands/login/plugin.rb +++ b/plugins/commands/login/plugin.rb @@ -2,9 +2,6 @@ require "vagrant" module VagrantPlugins module LoginCommand - autoload :Client, File.expand_path("../client", __FILE__) - autoload :Errors, File.expand_path("../errors", __FILE__) - class Plugin < Vagrant.plugin("2") name "vagrant-login" description <<-DESC @@ -13,18 +10,8 @@ module VagrantPlugins command(:login) do require File.expand_path("../../cloud/auth/login", __FILE__) - init! VagrantPlugins::CloudCommand::AuthCommand::Command::Login end - - protected - - def self.init! - return if defined?(@_init) - I18n.load_path << File.expand_path("../../cloud/locales/en.yml", __FILE__) - I18n.reload! - @_init = true - end end end end diff --git a/test/unit/plugins/commands/login/client_test.rb b/test/unit/plugins/commands/login/client_test.rb deleted file mode 100644 index 06a110706..000000000 --- a/test/unit/plugins/commands/login/client_test.rb +++ /dev/null @@ -1,261 +0,0 @@ -require File.expand_path("../../../../base", __FILE__) - -require Vagrant.source_root.join("plugins/commands/login/command") - -describe VagrantPlugins::LoginCommand::Client do - include_context "unit" - - let(:env) { isolated_environment.create_vagrant_env } - - subject(:client) { described_class.new(env) } - - before(:all) do - I18n.load_path << Vagrant.source_root.join("plugins/commands/login/locales/en.yml") - I18n.reload! - end - - before do - stub_env("ATLAS_TOKEN" => nil) - subject.clear_token - end - - describe "#logged_in?" do - let(:url) { "#{Vagrant.server_url}/api/v1/authenticate?access_token=#{token}" } - let(:headers) { { "Content-Type" => "application/json" } } - - before { allow(subject).to receive(:token).and_return(token) } - - context "when there is no token" do - let(:token) { nil } - - it "returns false" do - expect(subject.logged_in?).to be(false) - end - end - - context "when there is a token" do - let(:token) { "ABCD1234" } - - it "returns true if the endpoint returns a 200" do - stub_request(:get, url) - .with(headers: headers) - .to_return(body: JSON.pretty_generate("token" => token)) - expect(subject.logged_in?).to be(true) - end - - it "raises an error if the endpoint returns a non-200" do - stub_request(:get, url) - .with(headers: headers) - .to_return(body: JSON.pretty_generate("bad" => true), status: 401) - expect(subject.logged_in?).to be(false) - end - - it "raises an exception if the server cannot be found" do - stub_request(:get, url) - .to_raise(SocketError) - expect { subject.logged_in? } - .to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) - end - end - end - - describe "#login" do - let(:request) { - { - user: { - login: login, - password: password, - }, - token: { - description: description, - }, - two_factor: { - code: nil - } - } - } - - let(:login) { "foo" } - let(:password) { "bar" } - let(:description) { "Token description" } - - let(:headers) { - { - "Accept" => "application/json", - "Content-Type" => "application/json", - } - } - let(:response) { - { - token: "baz" - } - } - - it "returns the access token after successful login" do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - with(body: JSON.dump(request), headers: headers). - to_return(status: 200, body: JSON.dump(response)) - - client.username_or_email = login - client.password = password - - expect(client.login(description: "Token description")).to eq("baz") - end - - context "when 2fa is required" do - let(:response) { - { - two_factor: { - default_delivery_method: default_delivery_method, - delivery_methods: delivery_methods - } - } - } - let(:default_delivery_method) { "app" } - let(:delivery_methods) { ["app"] } - - before do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_return(status: 406, body: JSON.dump(response)) - end - - it "raises a two-factor required error" do - expect { - client.login - }.to raise_error(VagrantPlugins::LoginCommand::Errors::TwoFactorRequired) - end - - context "when the default delivery method is not app" do - let(:default_delivery_method) { "sms" } - let(:delivery_methods) { ["app", "sms"] } - - it "requests a code and then raises a two-factor required error" do - expect(client) - .to receive(:request_code) - .with(default_delivery_method) - - expect { - client.login - }.to raise_error(VagrantPlugins::LoginCommand::Errors::TwoFactorRequired) - end - end - end - - context "on bad login" do - before do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_return(status: 401, body: "") - end - - it "raises an error" do - expect { - client.login - }.to raise_error(VagrantPlugins::LoginCommand::Errors::Unauthorized) - end - end - - context "if it can't reach the server" do - before do - stub_request(:post, "#{Vagrant.server_url}/api/v1/authenticate"). - to_raise(SocketError) - end - - it "raises an exception" do - expect { - subject.login - }.to raise_error(VagrantPlugins::LoginCommand::Errors::ServerUnreachable) - end - end - end - - describe "#request_code" do - let(:request) { - { - user: { - login: login, - password: password, - }, - two_factor: { - delivery_method: delivery_method - } - } - } - - let(:login) { "foo" } - let(:password) { "bar" } - let(:delivery_method) { "sms" } - - let(:headers) { - { - "Accept" => "application/json", - "Content-Type" => "application/json" - } - } - - let(:response) { - { - two_factor: { - obfuscated_destination: "SMS number ending in 1234" - } - } - } - - it "displays that the code was sent" do - expect(env.ui) - .to receive(:success) - .with("2FA code sent to SMS number ending in 1234.") - - stub_request(:post, "#{Vagrant.server_url}/api/v1/two-factor/request-code"). - with(body: JSON.dump(request), headers: headers). - to_return(status: 201, body: JSON.dump(response)) - - client.username_or_email = login - client.password = password - - client.request_code delivery_method - end - end - - describe "#token" do - it "reads ATLAS_TOKEN" do - stub_env("ATLAS_TOKEN" => "ABCD1234") - expect(subject.token).to eq("ABCD1234") - end - - it "reads the stored file" do - subject.store_token("EFGH5678") - expect(subject.token).to eq("EFGH5678") - end - - it "prefers the environment variable" do - stub_env("VAGRANT_CLOUD_TOKEN" => "ABCD1234") - subject.store_token("EFGH5678") - expect(subject.token).to eq("ABCD1234") - end - - it "prints a warning if the envvar and stored file are both present" do - stub_env("VAGRANT_CLOUD_TOKEN" => "ABCD1234") - subject.store_token("EFGH5678") - expect(env.ui).to receive(:warn).with(/detected both/) - subject.token - end - - it "returns nil if there's no token set" do - expect(subject.token).to be(nil) - end - end - - describe "#store_token, #clear_token" do - it "stores the token and can re-access it" do - subject.store_token("foo") - expect(subject.token).to eq("foo") - expect(described_class.new(env).token).to eq("foo") - end - - it "deletes the token" do - subject.store_token("foo") - subject.clear_token - expect(subject.token).to be_nil - end - end -end diff --git a/test/unit/plugins/commands/login/command_test.rb b/test/unit/plugins/commands/login/command_test.rb deleted file mode 100644 index c92c51174..000000000 --- a/test/unit/plugins/commands/login/command_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require File.expand_path("../../../../base", __FILE__) - -require Vagrant.source_root.join("plugins/commands/login/command") - -describe VagrantPlugins::LoginCommand::Command do - include_context "unit" - - let(:env) { isolated_environment.create_vagrant_env } - - let(:token_path) { env.data_dir.join("vagrant_login_token") } - - let(:stdout) { StringIO.new } - let(:stderr) { StringIO.new } - - subject { described_class.new(argv, env) } - - before do - stub_env("ATLAS_TOKEN" => "") - end - - describe "#execute" do - context "with no args" do - let(:argv) { [] } - end - - context "with --check" do - let(:argv) { ["--check"] } - - context "when there is a token" do - before do - stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) - .to_return(status: 200) - end - - before do - File.open(token_path, "w+") { |f| f.write("abcd1234") } - end - - it "returns 0" do - expect(subject.execute).to eq(0) - end - end - - context "when there is no token" do - it "returns 1" do - expect(subject.execute).to eq(1) - end - end - end - - context "with --logout" do - let(:argv) { ["--logout"] } - - it "returns 0" do - expect(subject.execute).to eq(0) - end - - it "clears the token" do - subject.execute - expect(File.exist?(token_path)).to be(false) - end - end - - context "with --token" do - let(:argv) { ["--token", "efgh5678"] } - - context "when the token is valid" do - before do - stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) - .to_return(status: 200) - end - - it "sets the token" do - subject.execute - token = File.read(token_path).strip - expect(token).to eq("efgh5678") - end - - it "returns 0" do - expect(subject.execute).to eq(0) - end - end - - context "when the token is invalid" do - before do - stub_request(:get, %r{^#{Vagrant.server_url}/api/v1/authenticate}) - .to_return(status: 401) - end - - it "returns 1" do - expect(subject.execute).to eq(1) - end - end - end - end -end From 8b0790168b7f5e3da5aaa82e45eb4d03dfc57550 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 28 Oct 2020 15:17:31 -0700 Subject: [PATCH 4/4] Check for filter versions and break up box output --- plugins/commands/cloud/box/show.rb | 3 ++- test/unit/plugins/commands/cloud/box/show_test.rb | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/commands/cloud/box/show.rb b/plugins/commands/cloud/box/show.rb index 2a52fc357..bdafc2537 100644 --- a/plugins/commands/cloud/box/show.rb +++ b/plugins/commands/cloud/box/show.rb @@ -54,7 +54,7 @@ module VagrantPlugins access_token: access_token ) with_box(account: account, org: org, box: box_name) do |box| - if box && options[:versions] + if box && !Array(options[:versions]).empty? box = box.versions.find_all{ |v| options[:versions].include?(v.version) } else box = [box] @@ -63,6 +63,7 @@ module VagrantPlugins if !box.empty? box.each do |b| format_box_results(b, @env) + @env.ui.output("") end 0 else diff --git a/test/unit/plugins/commands/cloud/box/show_test.rb b/test/unit/plugins/commands/cloud/box/show_test.rb index bd10cba22..623b7a048 100644 --- a/test/unit/plugins/commands/cloud/box/show_test.rb +++ b/test/unit/plugins/commands/cloud/box/show_test.rb @@ -23,6 +23,7 @@ describe VagrantPlugins::CloudCommand::BoxCommand::Command::Show do allow(ui).to receive(:warn) allow(ui).to receive(:success) allow(ui).to receive(:error) + allow(ui).to receive(:output) allow(env).to receive(:ui).and_return(ui) allow(VagrantCloud::Account).to receive(:new). with(custom_server: anything, access_token: access_token).