mirror of
https://github.com/hashicorp/vagrant.git
synced 2026-02-03 20:39:27 -05:00
Introduce support for handling box architecture. Adds a new `box_architecture` setting that defaults to `:auto` which will perform automatic detection of the host system, but can be overridden with a custom value. Can also be set to `nil` which will result in it fetching the box flagged with the default architecture within the metadata. Box collection has been modified to allow existing boxes already downloaded and unpacked to still function as expected when architecture information is not available.
248 lines
8.2 KiB
Ruby
248 lines
8.2 KiB
Ruby
# Copyright (c) HashiCorp, Inc.
|
|
# SPDX-License-Identifier: BUSL-1.1
|
|
|
|
require "vagrant_cloud"
|
|
require "vagrant/util/downloader"
|
|
require "vagrant/util/presence"
|
|
require Vagrant.source_root.join("plugins/commands/cloud/errors")
|
|
|
|
module VagrantPlugins
|
|
module CloudCommand
|
|
class Client
|
|
# @private
|
|
# Reset the cached values for scrubber. This is not considered a public
|
|
# API and should only be used for testing.
|
|
def self.reset!
|
|
class_variables.each(&method(:remove_class_variable))
|
|
end
|
|
|
|
######################################################################
|
|
# Class that deals with managing users 'local' token for Vagrant Cloud
|
|
######################################################################
|
|
APP = "app".freeze
|
|
|
|
include Util
|
|
include Vagrant::Util::Presence
|
|
|
|
attr_accessor :client
|
|
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::cloud::client")
|
|
@env = env
|
|
@client = VagrantCloud::Client.new(
|
|
access_token: token,
|
|
url_base: api_server_url
|
|
)
|
|
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?
|
|
return false if !client.access_token
|
|
Vagrant::Util::CredentialScrubber.sensitive(client.access_token)
|
|
|
|
with_error_handling do
|
|
client.authentication_token_validate
|
|
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}'")
|
|
|
|
Vagrant::Util::CredentialScrubber.sensitive(password)
|
|
with_error_handling do
|
|
r = client.authentication_token_create(username: username_or_email,
|
|
password: password, description: description, code: code)
|
|
|
|
Vagrant::Util::CredentialScrubber.sensitive(r[:token])
|
|
@client = VagrantCloud::Client.new(
|
|
access_token: r[:token],
|
|
url_base: api_server_url
|
|
)
|
|
r[:token]
|
|
end
|
|
end
|
|
|
|
# Requests a 2FA code
|
|
# @param [String] delivery_method
|
|
def request_code(delivery_method)
|
|
@env.ui.warn("Requesting 2FA code via #{delivery_method.upcase}...")
|
|
|
|
Vagrant::Util::CredentialScrubber.sensitive(password)
|
|
with_error_handling do
|
|
r = client.authentication_request_2fa_code(
|
|
username: username_or_email, password: password, delivery_method: delivery_method)
|
|
|
|
two_factor = r[:two_factor]
|
|
obfuscated_destination = two_factor[:obfuscated_destination]
|
|
|
|
@env.ui.success("2FA code sent to #{obfuscated_destination}.")
|
|
end
|
|
end
|
|
|
|
# Stores the given token locally, removing any previous tokens.
|
|
#
|
|
# @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, url_base: api_server_url)
|
|
|
|
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?
|
|
# Only show warning if it has not been previously shown
|
|
if !defined?(@@double_token_warning)
|
|
@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
|
|
@@double_token_warning = true
|
|
end
|
|
end
|
|
|
|
if present?(ENV["VAGRANT_CLOUD_TOKEN"])
|
|
@logger.debug("Using authentication token from environment variable")
|
|
t = ENV["VAGRANT_CLOUD_TOKEN"]
|
|
elsif token_path.exist?
|
|
@logger.debug("Using authentication token from disk at #{token_path}")
|
|
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 !t.nil?
|
|
Vagrant::Util::CredentialScrubber.sensitive(t)
|
|
return t
|
|
end
|
|
|
|
@logger.debug("No authentication token in environment or #{token_path}")
|
|
|
|
nil
|
|
end
|
|
|
|
protected
|
|
|
|
def with_error_handling(&block)
|
|
yield
|
|
rescue VagrantCloud::Error::ClientError => e
|
|
@logger.debug("vagrantcloud request error:")
|
|
@logger.debug(e.message)
|
|
@logger.debug(e.backtrace.join("\n"))
|
|
raise Errors::Unexpected, error: e.message
|
|
rescue Excon::Error::Unauthorized
|
|
@logger.debug("Unauthorized!")
|
|
raise Errors::Unauthorized
|
|
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.body)
|
|
errors = parsed_response["errors"].join("\n")
|
|
raise Errors::ServerError, errors: errors
|
|
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.body)
|
|
|
|
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
|
|
|
|
@logger.debug("Got an unexpected error:")
|
|
@logger.debug(e.inspect)
|
|
raise Errors::Unexpected, error: e.inspect
|
|
rescue SocketError
|
|
@logger.info("Socket error")
|
|
raise Errors::ServerUnreachable, url: Vagrant.server_url.to_s
|
|
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
|