diff --git a/lib/vagrant/util/uploader.rb b/lib/vagrant/util/uploader.rb index 17af39ef3..b9bfbecf3 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! @@ -51,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 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..bdafc2537 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,48 @@ 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 && !Array(options[:versions]).empty? + 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) + @env.ui.output("") + 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..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" @@ -14,6 +13,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 +25,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 +39,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 +60,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 +76,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 +91,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 +124,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 +147,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/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/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..623b7a048 100644 --- a/test/unit/plugins/commands/cloud/box/show_test.rb +++ b/test/unit/plugins/commands/cloud/box/show_test.rb @@ -5,59 +5,147 @@ 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(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). + 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/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 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"