vagrant_cloud 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/lib/vagrant_cloud/auth.rb +140 -0
- data/lib/vagrant_cloud/client.rb +40 -20
- data/lib/vagrant_cloud/error.rb +7 -0
- data/lib/vagrant_cloud.rb +1 -0
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 013f62b5aeeb2125349790b206a2fc42ee6e60288bb28fab28aefa129af8c55c
|
4
|
+
data.tar.gz: a928446b785eb485285ba3e030589391cc3fd3e49fdeb15053677e58ff864e2e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58ae036121f99b0e1054e49deb30644510f1310ab55ea7f07d2be89b6b27287f4c5a933ff20199e1eb64556506d548677beb0e543d6a7bf5f8e3217698c12c99
|
7
|
+
data.tar.gz: 3de0d70c11834c2228c78201600f07354a788b563d378e222001c9fbc1651f40e4ae1c0d3346fd342ccad16cb48d8a5efd6471987ab5321d1dd71ed0644d9312
|
data/README.md
CHANGED
@@ -13,6 +13,14 @@ The Vagrant Cloud library provides two methods for interacting with the Vagrant
|
|
13
13
|
first is direct interaction using a `VagrantCloud::Client` instance. The second is a basic
|
14
14
|
model based approach using a `VagrantCloud::Account` instance.
|
15
15
|
|
16
|
+
### Authentication
|
17
|
+
|
18
|
+
The access token that is used for authenticated requests can be set in one of three ways:
|
19
|
+
|
20
|
+
* Static access token set directly in the client
|
21
|
+
* Static access token extracted from the `VAGRANT_CLOUD_TOKEN` environment variable
|
22
|
+
* Generated [HCP service principal](https://developer.hashicorp.com/hcp/docs/hcp/iam/service-principal) access token when `HCP_CLIENT_ID` and `HCP_CLIENT_SECRET` environment variables are set
|
23
|
+
|
16
24
|
### Direct Client
|
17
25
|
|
18
26
|
The `VagrantCloud::Client` class contains all the underlying functionality which with
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require "oauth2"
|
2
|
+
|
3
|
+
module VagrantCloud
|
4
|
+
class Auth
|
5
|
+
|
6
|
+
# Default authentication URL
|
7
|
+
DEFAULT_AUTH_URL = "https://auth.idp.hashicorp.com".freeze
|
8
|
+
# Default authorize path
|
9
|
+
DEFAULT_AUTH_PATH = "/oauth2/auth".freeze
|
10
|
+
# Default token path
|
11
|
+
DEFAULT_TOKEN_PATH = "/oauth2/token".freeze
|
12
|
+
# Number of seconds to pad token expiry
|
13
|
+
TOKEN_EXPIRY_PADDING = 5
|
14
|
+
|
15
|
+
# HCP configuration for generating authentication tokens
|
16
|
+
#
|
17
|
+
# @param [String] client_id Service principal client ID
|
18
|
+
# @param [String] client_secret Service principal client secret
|
19
|
+
# @param [String] auth_url Authentication URL end point
|
20
|
+
# @param [String] auth_path Authorization path (relative to end point)
|
21
|
+
# @param [String] token_path Token path (relative to end point)
|
22
|
+
HCPConfig = Struct.new(:client_id, :client_secret, :auth_url, :auth_path, :token_path, keyword_init: true) do
|
23
|
+
# Raise exception if any values are missing
|
24
|
+
def validate!
|
25
|
+
[:client_id, :client_secret, :auth_url, :auth_path, :token_path].each do |name|
|
26
|
+
raise ArgumentError,
|
27
|
+
"Missing required HCP authentication configuration value: HCP_#{name.to_s.upcase}" if self.send(name).to_s.empty?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# HCP token
|
33
|
+
#
|
34
|
+
# @param [String] token HCP token value
|
35
|
+
# @param [Integer] expires_at Epoch seconds
|
36
|
+
HCPToken = Struct.new(:token, :expires_at, keyword_init: true) do
|
37
|
+
# Raise exception if any values are missing
|
38
|
+
def validate!
|
39
|
+
[:token, :expires_at].each do |name|
|
40
|
+
raise ArgumentError,
|
41
|
+
"Missing required token value - #{name.inspect}" if self.send(name).nil?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Boolean] token is expired
|
46
|
+
# @note Will show token as expired TOKEN_EXPIRY_PADDING
|
47
|
+
# seconds prior to actual expiry
|
48
|
+
def expired?
|
49
|
+
validate!
|
50
|
+
|
51
|
+
Time.now.to_i > (expires_at - TOKEN_EXPIRY_PADDING)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Boolean] token is not expired
|
55
|
+
def valid?
|
56
|
+
!expired?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Create a new auth instance
|
61
|
+
#
|
62
|
+
# @param [String] access_token Static access token
|
63
|
+
# @note If no access token is provided, the token will be extracted
|
64
|
+
# from the VAGRANT_CLOUD_TOKEN environment variable. If that value
|
65
|
+
# is not set, the HCP_CLIENT_ID and HCP_CLIENT_SECRET environment
|
66
|
+
# variables will be checked. If found, tokens will be generated as
|
67
|
+
# needed using the client id and secret. Otherwise, no token will
|
68
|
+
# will be available.
|
69
|
+
def initialize(access_token: nil)
|
70
|
+
@token = access_token
|
71
|
+
|
72
|
+
# The Vagrant Cloud token has precedence over
|
73
|
+
# anything else, so if it is set then it is
|
74
|
+
# the only value used.
|
75
|
+
@token = ENV["VAGRANT_CLOUD_TOKEN"] if @token.nil?
|
76
|
+
|
77
|
+
# If there is no token set, attempt to load HCP configuration
|
78
|
+
if @token.to_s.empty? && (ENV["HCP_CLIENT_ID"] || ENV["HCP_CLIENT_SECRET"])
|
79
|
+
@config = HCPConfig.new(
|
80
|
+
client_id: ENV["HCP_CLIENT_ID"],
|
81
|
+
client_secret: ENV["HCP_CLIENT_SECRET"],
|
82
|
+
auth_url: ENV.fetch("HCP_AUTH_URL", DEFAULT_AUTH_URL),
|
83
|
+
auth_path: ENV.fetch("HCP_AUTH_PATH", DEFAULT_AUTH_PATH),
|
84
|
+
token_path: ENV.fetch("HCP_TOKEN_PATH", DEFAULT_TOKEN_PATH)
|
85
|
+
)
|
86
|
+
|
87
|
+
# Validate configuration is populated
|
88
|
+
@config.validate!
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [String] authentication token
|
93
|
+
def token
|
94
|
+
# If a static token is defined, use that value
|
95
|
+
return @token if @token
|
96
|
+
|
97
|
+
# If no configuration is set, there is no auth to provide
|
98
|
+
return if @config.nil?
|
99
|
+
|
100
|
+
# If an HCP token exists and is not expired
|
101
|
+
return @hcp_token.token if @hcp_token&.valid?
|
102
|
+
|
103
|
+
# Generate a new HCP token
|
104
|
+
refresh_token!
|
105
|
+
|
106
|
+
@hcp_token.token
|
107
|
+
end
|
108
|
+
|
109
|
+
# @return [Boolean] Authentication token is available
|
110
|
+
def available?
|
111
|
+
!!(@token || @config)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Refresh the HCP oauth2 token.
|
117
|
+
# @todo rescue exceptions and make them nicer
|
118
|
+
def refresh_token!
|
119
|
+
client = OAuth2::Client.new(
|
120
|
+
@config.client_id,
|
121
|
+
@config.client_secret,
|
122
|
+
site: @config.auth_url,
|
123
|
+
authorize_url: @config.auth_path,
|
124
|
+
token_url: @config.token_path,
|
125
|
+
)
|
126
|
+
|
127
|
+
begin
|
128
|
+
response = client.client_credentials.get_token
|
129
|
+
@hcp_token = HCPToken.new(
|
130
|
+
token: response.token,
|
131
|
+
expires_at: response.expires_at,
|
132
|
+
)
|
133
|
+
rescue OAuth2::Error => err
|
134
|
+
raise Error::AuthenticationError,
|
135
|
+
err.response.body.chomp,
|
136
|
+
err.response.status
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/vagrant_cloud/client.rb
CHANGED
@@ -23,8 +23,6 @@ module VagrantCloud
|
|
23
23
|
DEFAULT_INSTRUMENTOR
|
24
24
|
end
|
25
25
|
|
26
|
-
# @return [String] Access token for Vagrant Cloud
|
27
|
-
attr_reader :access_token
|
28
26
|
# @return [String] Base request path
|
29
27
|
attr_reader :path_base
|
30
28
|
# @return [String] URL for initializing connection
|
@@ -49,19 +47,15 @@ module VagrantCloud
|
|
49
47
|
remote_url = URI.parse(url_base)
|
50
48
|
@url_base = "#{remote_url.scheme}://#{remote_url.host}"
|
51
49
|
@path_base = remote_url.path
|
52
|
-
if @path_base == API_V1_PATH || @path_base == API_V2_PATH
|
50
|
+
if @path_base.empty? || @path_base == API_V1_PATH || @path_base == API_V2_PATH
|
53
51
|
@path_base = nil
|
54
52
|
end
|
55
|
-
@
|
56
|
-
if !@access_token && ENV["VAGRANT_CLOUD_TOKEN"]
|
57
|
-
@access_token = ENV["VAGRANT_CLOUD_TOKEN"].dup.freeze
|
58
|
-
end
|
53
|
+
@auth = Auth.new(access_token: access_token)
|
59
54
|
@retry_count = retry_count.nil? ? IDEMPOTENT_RETRIES : retry_count.to_i
|
60
55
|
@retry_interval = retry_interval.nil? ? IDEMPOTENT_RETRY_INTERVAL : retry_interval.to_i
|
61
56
|
@instrumentor = instrumentor.nil? ? Instrumentor::Collection.new : instrumentor
|
62
57
|
headers = {}.tap do |h|
|
63
58
|
h["Accept"] = "application/json"
|
64
|
-
h["Authorization"] = "Bearer #{@access_token}" if @access_token
|
65
59
|
h["Content-Type"] = "application/json"
|
66
60
|
end
|
67
61
|
@connection_lock = Mutex.new
|
@@ -71,6 +65,11 @@ module VagrantCloud
|
|
71
65
|
)
|
72
66
|
end
|
73
67
|
|
68
|
+
# @return [String] Access token for Vagrant Cloud
|
69
|
+
def access_token
|
70
|
+
@auth.token
|
71
|
+
end
|
72
|
+
|
74
73
|
# Use the remote connection
|
75
74
|
#
|
76
75
|
# @param [Boolean] wait Wait for the connection to be available
|
@@ -79,16 +78,28 @@ module VagrantCloud
|
|
79
78
|
def with_connection(wait: true)
|
80
79
|
raise ArgumentError,
|
81
80
|
"Block expected but no block given" if !block_given?
|
81
|
+
|
82
|
+
# Adds authentication header to connection if available
|
83
|
+
set_authentication = ->(conn) {
|
84
|
+
if @auth.available?
|
85
|
+
conn.connection[:headers]["Authorization"] = "Bearer #{@auth.token}"
|
86
|
+
end
|
87
|
+
}
|
88
|
+
|
82
89
|
if !wait
|
83
90
|
raise Error::ClientError::ConnectionLockedError,
|
84
91
|
"Connection is currently locked" if !@connection_lock.try_lock
|
92
|
+
set_authentication.call(@connection)
|
85
93
|
begin
|
86
94
|
yield @connection
|
87
95
|
ensure
|
88
96
|
@connection_lock.unlock
|
89
97
|
end
|
90
98
|
else
|
91
|
-
@connection_lock.synchronize
|
99
|
+
@connection_lock.synchronize do
|
100
|
+
set_authentication.call(@connection)
|
101
|
+
yield @connection
|
102
|
+
end
|
92
103
|
end
|
93
104
|
end
|
94
105
|
|
@@ -98,19 +109,28 @@ module VagrantCloud
|
|
98
109
|
# @param [Hash] params Parameters to send with request
|
99
110
|
# @return [Hash]
|
100
111
|
def request(path:, method: :get, params: {}, api_version: 2)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
112
|
+
# Apply any path modifications that are required
|
113
|
+
catch(:done) do
|
114
|
+
# If a base path is defined, and the provided path
|
115
|
+
# is already properly prefixed with it, do nothing.
|
116
|
+
throw :done if !path_base.nil? && path.start_with?(path_base)
|
117
|
+
|
118
|
+
# If the path does not include an API version
|
119
|
+
# prefix, add it now.
|
120
|
+
if !path.start_with?(API_V1_PATH) && !path.start_with?(API_V2_PATH)
|
121
|
+
case api_version
|
122
|
+
when 1
|
123
|
+
start_path = API_V1_PATH
|
124
|
+
when 2
|
125
|
+
start_path = API_V2_PATH
|
126
|
+
else
|
127
|
+
raise ArgumentError, "Unsupported API version provided"
|
128
|
+
end
|
110
129
|
end
|
111
|
-
|
112
|
-
path = [start_path, path].compact.join("/").gsub(/\/{2,}/, "/")
|
130
|
+
|
131
|
+
path = [path_base, start_path, path].compact.join("/").gsub(/\/{2,}/, "/")
|
113
132
|
end
|
133
|
+
|
114
134
|
method = method.to_s.downcase.to_sym
|
115
135
|
|
116
136
|
# Build base request parameters
|
data/lib/vagrant_cloud/error.rb
CHANGED
@@ -29,6 +29,13 @@ module VagrantCloud
|
|
29
29
|
end
|
30
30
|
|
31
31
|
class ConnectionLockedError < ClientError; end
|
32
|
+
class AuthenticationError < ClientError
|
33
|
+
def initialize(msg, http_code)
|
34
|
+
@error_arr = [msg]
|
35
|
+
@error_code = http_code.to_i
|
36
|
+
super(msg)
|
37
|
+
end
|
38
|
+
end
|
32
39
|
end
|
33
40
|
|
34
41
|
class BoxError < Error
|
data/lib/vagrant_cloud.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vagrant_cloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.1.
|
4
|
+
version: 3.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HashiCorp
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-11-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: excon
|
@@ -17,42 +17,56 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - "~>"
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '0
|
20
|
+
version: '1.0'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - "~>"
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '0
|
27
|
+
version: '1.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: log4r
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: 1.1
|
34
|
+
version: '1.1'
|
35
35
|
type: :runtime
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: 1.1
|
41
|
+
version: '1.1'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: rexml
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: 3.
|
48
|
+
version: '3.3'
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
53
|
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 3.
|
55
|
+
version: '3.3'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: oauth2
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '2.0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '2.0'
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: rake
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -105,6 +119,7 @@ files:
|
|
105
119
|
- README.md
|
106
120
|
- lib/vagrant_cloud.rb
|
107
121
|
- lib/vagrant_cloud/account.rb
|
122
|
+
- lib/vagrant_cloud/auth.rb
|
108
123
|
- lib/vagrant_cloud/box.rb
|
109
124
|
- lib/vagrant_cloud/box/provider.rb
|
110
125
|
- lib/vagrant_cloud/box/version.rb
|