vagrant_cloud 3.1.1 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -0
- data/lib/vagrant_cloud/auth.rb +140 -0
- data/lib/vagrant_cloud/client.rb +19 -8
- 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
|
@@ -52,16 +50,12 @@ module VagrantCloud
|
|
52
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
|
|
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: 2024-
|
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
|