vagrant_cloud 2.0.0 → 3.0.1
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 +5 -5
- data/README.md +145 -39
- data/lib/vagrant_cloud.rb +20 -10
- data/lib/vagrant_cloud/account.rb +86 -164
- data/lib/vagrant_cloud/box.rb +115 -154
- data/lib/vagrant_cloud/box/provider.rb +175 -0
- data/lib/vagrant_cloud/box/version.rb +163 -0
- data/lib/vagrant_cloud/client.rb +449 -39
- data/lib/vagrant_cloud/data.rb +293 -0
- data/lib/vagrant_cloud/error.rb +48 -0
- data/lib/vagrant_cloud/instrumentor.rb +7 -0
- data/lib/vagrant_cloud/instrumentor/collection.rb +123 -0
- data/lib/vagrant_cloud/instrumentor/core.rb +9 -0
- data/lib/vagrant_cloud/instrumentor/logger.rb +97 -0
- data/lib/vagrant_cloud/logger.rb +64 -0
- data/lib/vagrant_cloud/organization.rb +62 -0
- data/lib/vagrant_cloud/response.rb +7 -0
- data/lib/vagrant_cloud/response/create_token.rb +7 -0
- data/lib/vagrant_cloud/response/request_2fa.rb +7 -0
- data/lib/vagrant_cloud/response/search.rb +65 -0
- data/lib/vagrant_cloud/search.rb +113 -15
- data/lib/vagrant_cloud/version.rb +1 -186
- metadata +26 -34
- data/bin/vagrant_cloud +0 -5
- data/lib/vagrant_cloud/errors.rb +0 -25
- data/lib/vagrant_cloud/provider.rb +0 -149
@@ -0,0 +1,97 @@
|
|
1
|
+
module VagrantCloud
|
2
|
+
module Instrumentor
|
3
|
+
class Logger < Core
|
4
|
+
REDACTED = "REDACTED".freeze
|
5
|
+
|
6
|
+
include VagrantCloud::Logger
|
7
|
+
|
8
|
+
# Perform event logging
|
9
|
+
#
|
10
|
+
# @param [String] name Name of event "namespace.event"
|
11
|
+
# @param [Hash] params Data available with event
|
12
|
+
def instrument(name, params = {})
|
13
|
+
namespace, event = name.split(".", 2)
|
14
|
+
|
15
|
+
if event == "error"
|
16
|
+
logger.error { "#{namespace} #{event.upcase} #{params[:error]}" }
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
logger.info do
|
21
|
+
case namespace
|
22
|
+
when "excon"
|
23
|
+
# Make a copy so we can modify
|
24
|
+
params = params.dup
|
25
|
+
info = excon(event, params)
|
26
|
+
else
|
27
|
+
info = params.dup
|
28
|
+
end
|
29
|
+
"#{namespace} #{event.upcase} #{format_output(info)}"
|
30
|
+
end
|
31
|
+
|
32
|
+
logger.debug do
|
33
|
+
"#{namespace} #{event.upcase} #{format_output(params)}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Format output to make it look nicer
|
38
|
+
#
|
39
|
+
# @param [Hash] info Output information
|
40
|
+
# @return [String]
|
41
|
+
def format_output(info)
|
42
|
+
info.map do |key, value|
|
43
|
+
if value.is_a?(Enumerable)
|
44
|
+
value = value.map{ |k,v| [k, v].compact.join(": ") }.join(", ")
|
45
|
+
end
|
46
|
+
"#{key}=#{value.inspect}"
|
47
|
+
end.join(" ")
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generate information based on excon event
|
51
|
+
#
|
52
|
+
# @param [String] event Event name
|
53
|
+
# @param [Hash] params Event data
|
54
|
+
# @return [Hash] data to be printed
|
55
|
+
def excon(event, params)
|
56
|
+
# Remove noisy stuff that may be present from excon
|
57
|
+
params.delete(:connection)
|
58
|
+
params.delete(:stack)
|
59
|
+
|
60
|
+
# Remove any credential information
|
61
|
+
params[:password] = REDACTED if params.key?(:password)
|
62
|
+
params[:access_token] = REDACTED if params[:access_token]
|
63
|
+
if params.dig(:headers, "Authorization") || params.dig(:headers, "Proxy-Authorization")
|
64
|
+
params[:headers] = params[:headers].dup.tap do |h|
|
65
|
+
h["Authorization"] = REDACTED if h["Authorization"]
|
66
|
+
h["Proxy-Authorization"] = REDACTED if h["Proxy-Authorization"]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
if params.dig(:proxy, :password)
|
70
|
+
params[:proxy] = params[:proxy].dup.tap do |proxy|
|
71
|
+
proxy[:password] = REDACTED
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
info = {}
|
76
|
+
|
77
|
+
case event
|
78
|
+
when "request", "retry"
|
79
|
+
info[:method] = params[:method]
|
80
|
+
info[:identifier] = params.dig(:headers, 'X-Request-Id')
|
81
|
+
info[:url] = "#{params[:scheme]}://#{File.join(params[:host], params[:path])}"
|
82
|
+
info[:query] = params[:query] if params[:query]
|
83
|
+
info[:headers] = params[:headers] if params[:headers]
|
84
|
+
when "response"
|
85
|
+
info[:status] = params[:status]
|
86
|
+
info[:identifier] = params.dig(:headers, 'X-Request-Id')
|
87
|
+
info[:body] = params[:body]
|
88
|
+
else
|
89
|
+
info = params.dup
|
90
|
+
end
|
91
|
+
duration = (params.dig(:timing, :duration).to_f * 1000).to_i
|
92
|
+
info[:duration] = "#{duration}ms"
|
93
|
+
info
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module VagrantCloud
|
2
|
+
module Logger
|
3
|
+
|
4
|
+
@@lock = Mutex.new
|
5
|
+
|
6
|
+
# @return [Log4r::Logger] default logger
|
7
|
+
def self.default
|
8
|
+
@@lock.synchronize do
|
9
|
+
if !@logger
|
10
|
+
# Require Log4r and define the levels we'll be using
|
11
|
+
require 'log4r/config'
|
12
|
+
Log4r.define_levels(*Log4r::Log4rConfig::LogLevels)
|
13
|
+
|
14
|
+
level = nil
|
15
|
+
begin
|
16
|
+
level = Log4r.const_get(ENV.fetch("VAGRANT_CLOUD_LOG", "FATAL").upcase)
|
17
|
+
rescue NameError
|
18
|
+
# This means that the logging constant wasn't found,
|
19
|
+
# which is fine. We just keep `level` as `nil`. But
|
20
|
+
# we tell the user.
|
21
|
+
level = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
# Some constants, such as "true" resolve to booleans, so the
|
25
|
+
# above error checking doesn't catch it. This will check to make
|
26
|
+
# sure that the log level is an integer, as Log4r requires.
|
27
|
+
level = nil if !level.is_a?(Integer)
|
28
|
+
|
29
|
+
# Only override the log output format if the default is set
|
30
|
+
if Log4r::Outputter.stderr.formatter.is_a?(Log4r::DefaultFormatter)
|
31
|
+
base_formatter = Log4r::PatternFormatter.new(
|
32
|
+
pattern: "%d [%5l] %m",
|
33
|
+
date_pattern: "%F %T"
|
34
|
+
)
|
35
|
+
Log4r::Outputter.stderr.formatter = base_formatter
|
36
|
+
end
|
37
|
+
|
38
|
+
logger = Log4r::Logger.new("vagrantcloud")
|
39
|
+
logger.outputters = Log4r::Outputter.stderr
|
40
|
+
logger.level = level
|
41
|
+
@logger = logger
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.included(klass)
|
48
|
+
klass.class_variable_set(:@@logger, Log4r::Logger.new(klass.name.downcase))
|
49
|
+
klass.class_eval { define_method(:logger) { self.class.class_variable_get(:@@logger) } }
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Log4r::Logger] logger instance for current context
|
53
|
+
def logger
|
54
|
+
@@lock.synchronize do
|
55
|
+
if !@logger
|
56
|
+
@logger = Log4r::Logger.new(self.class.name.downcase)
|
57
|
+
end
|
58
|
+
@logger
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Logger.default
|
64
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module VagrantCloud
|
2
|
+
class Organization < Data::Mutable
|
3
|
+
attr_reader :account
|
4
|
+
|
5
|
+
attr_required :username
|
6
|
+
attr_optional :boxes, :avatar_url, :profile_html, :profile_markdown
|
7
|
+
|
8
|
+
attr_mutable :boxes
|
9
|
+
|
10
|
+
def initialize(account:, **opts)
|
11
|
+
@account = account
|
12
|
+
opts[:boxes] ||= []
|
13
|
+
super(**opts)
|
14
|
+
bxs = boxes.map do |b|
|
15
|
+
if !b.is_a?(Box)
|
16
|
+
b = Box.load(organization: self, **b)
|
17
|
+
end
|
18
|
+
b
|
19
|
+
end
|
20
|
+
clean(data: {boxes: bxs})
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add a new box to the organization
|
24
|
+
#
|
25
|
+
# @param [String] name Name of the box
|
26
|
+
# @return [Box]
|
27
|
+
def add_box(name)
|
28
|
+
if boxes.any? { |b| b.name == name }
|
29
|
+
raise Error::BoxError::BoxExistsError,
|
30
|
+
"Box with name #{name} already exists"
|
31
|
+
end
|
32
|
+
b = Box.new(organization: self, name: name)
|
33
|
+
clean(data: {boxes: boxes + [b]})
|
34
|
+
b
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check if this instance is dirty
|
38
|
+
#
|
39
|
+
# @param [Boolean] deep Check nested instances
|
40
|
+
# @return [Boolean] instance is dirty
|
41
|
+
def dirty?(key=nil, deep: false)
|
42
|
+
if key
|
43
|
+
super(key)
|
44
|
+
else
|
45
|
+
d = super()
|
46
|
+
if deep && !d
|
47
|
+
d = boxes.any? { |b| b.dirty?(deep: true) }
|
48
|
+
end
|
49
|
+
d
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Save the organization
|
54
|
+
#
|
55
|
+
# @return [self]
|
56
|
+
# @note This only saves boxes within organization
|
57
|
+
def save
|
58
|
+
boxes.map(&:save)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module VagrantCloud
|
2
|
+
class Response
|
3
|
+
class Search < Response
|
4
|
+
# @return [Account]
|
5
|
+
attr_reader :account
|
6
|
+
# @return [Hash] search parameters
|
7
|
+
attr_reader :search_parameters
|
8
|
+
|
9
|
+
attr_optional :boxes
|
10
|
+
|
11
|
+
def initialize(account:, params:, **opts)
|
12
|
+
if !account.is_a?(Account)
|
13
|
+
raise TypeError,
|
14
|
+
"Expected type `#{Account.name}` but received `#{account.class.name}`"
|
15
|
+
end
|
16
|
+
@account = account
|
17
|
+
@search_parameters = params
|
18
|
+
opts[:boxes] = reload_boxes(opts[:boxes])
|
19
|
+
super(**opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Integer]
|
23
|
+
def page
|
24
|
+
pg = @search_parameters.fetch(:page, 0).to_i
|
25
|
+
pg > 0 ? pg : 1
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Search] previous page of search results
|
29
|
+
def previous
|
30
|
+
if page <= 1
|
31
|
+
raise ArgumentError,
|
32
|
+
"Cannot request page results less than one"
|
33
|
+
end
|
34
|
+
account.searcher.from_response(self) do |s|
|
35
|
+
s.prev_page
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Search] next page of search results
|
40
|
+
def next
|
41
|
+
account.searcher.from_response(self) do |s|
|
42
|
+
s.next_page
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# Load all the box data into proper instances
|
49
|
+
def reload_boxes(boxes)
|
50
|
+
org_cache = {}
|
51
|
+
boxes.map do |b|
|
52
|
+
org_name = b[:username]
|
53
|
+
if !org_cache[org_name]
|
54
|
+
org_cache[org_name] = account.organization(name: org_name)
|
55
|
+
end
|
56
|
+
org = org_cache[org_name]
|
57
|
+
box = Box.new(organization: org, **b)
|
58
|
+
org.boxes = org.boxes + [box]
|
59
|
+
org.clean!
|
60
|
+
box
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/vagrant_cloud/search.rb
CHANGED
@@ -1,9 +1,37 @@
|
|
1
1
|
module VagrantCloud
|
2
2
|
class Search
|
3
|
-
|
3
|
+
# @return [Account]
|
4
|
+
attr_reader :account
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
# Create a new search instance
|
7
|
+
#
|
8
|
+
# @param [String] access_token Authentication token
|
9
|
+
# @param [Account] account Account instance
|
10
|
+
# @param [Client] client Client instance
|
11
|
+
# @return [Search]
|
12
|
+
def initialize(access_token: nil, account: nil, client: nil)
|
13
|
+
args = {access_token: access_token, account: account, client: client}.compact
|
14
|
+
if args.size > 1
|
15
|
+
raise ArgumentError,
|
16
|
+
"Search accepts `access_token`, `account`, or `client` but received multiple (#{args.keys.join(", ")})"
|
17
|
+
end
|
18
|
+
if client
|
19
|
+
if !client.is_a?(Client)
|
20
|
+
raise TypeError,
|
21
|
+
"Expecting type `#{Client.name}` but received `#{client.class.name}`"
|
22
|
+
end
|
23
|
+
@account = Account.new(client: client)
|
24
|
+
elsif account
|
25
|
+
if !account.is_a?(Account)
|
26
|
+
raise TypeError,
|
27
|
+
"Expecting type `#{Account.name}` but received `#{account.class.name}`"
|
28
|
+
end
|
29
|
+
@account = account
|
30
|
+
else
|
31
|
+
@account = Account.new(access_token: access_token)
|
32
|
+
end
|
33
|
+
@params = {}
|
34
|
+
@lock = Mutex.new
|
7
35
|
end
|
8
36
|
|
9
37
|
# Requests a search based on the given parameters
|
@@ -14,18 +42,88 @@ module VagrantCloud
|
|
14
42
|
# @param [String] order
|
15
43
|
# @param [String] limit
|
16
44
|
# @param [String] page
|
17
|
-
# @return [
|
18
|
-
def search(query
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
45
|
+
# @return [Response::Search]
|
46
|
+
def search(query: Data::Nil, provider: Data::Nil, sort: Data::Nil, order: Data::Nil, limit: Data::Nil, page: Data::Nil)
|
47
|
+
@lock.synchronize do
|
48
|
+
@params = {
|
49
|
+
query: query,
|
50
|
+
provider: provider,
|
51
|
+
sort: sort,
|
52
|
+
order: order,
|
53
|
+
limit: limit,
|
54
|
+
page: page
|
55
|
+
}
|
56
|
+
execute
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Request the next page of the search results
|
61
|
+
#
|
62
|
+
# @param [Response::Search]
|
63
|
+
def next_page
|
64
|
+
@lock.synchronize do
|
65
|
+
if @params.empty?
|
66
|
+
raise ArgumentError, "No active search currently cached"
|
67
|
+
end
|
68
|
+
page = @params[:page].to_i
|
69
|
+
page = 1 if page < 1
|
70
|
+
@params[:page] = page + 1
|
71
|
+
execute
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Request the previous page of the search results
|
76
|
+
#
|
77
|
+
# @param [Response::Search]
|
78
|
+
def prev_page
|
79
|
+
@lock.synchronize do
|
80
|
+
if @params.empty?
|
81
|
+
raise ArgumentError, "No active search currently cached"
|
82
|
+
end
|
83
|
+
page = @params[:page].to_i - 1
|
84
|
+
@params[:page] = page < 1 ? 1 : page
|
85
|
+
execute
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Boolean] Search terms are stored
|
90
|
+
def active?
|
91
|
+
!@params.empty?
|
92
|
+
end
|
93
|
+
|
94
|
+
# Clear the currently cached search parameters
|
95
|
+
#
|
96
|
+
# @return [self]
|
97
|
+
def clear!
|
98
|
+
@lock.synchronize { @params.clear }
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
# Seed the parameters
|
103
|
+
#
|
104
|
+
# @return [self]
|
105
|
+
def seed(**params)
|
106
|
+
@lock.synchronize { @params = params }
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# Generate a new instance seeded with search
|
111
|
+
# parameters from given response
|
112
|
+
#
|
113
|
+
# @param [Response::Search] response Search response
|
114
|
+
# @yieldparam [Search] Seeded search instance
|
115
|
+
# @return [Object] result of given block
|
116
|
+
def from_response(response)
|
117
|
+
s = self.class.new(account: account)
|
118
|
+
yield s.seed(**response.search_parameters)
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
# @return [Response::Search]
|
124
|
+
def execute
|
125
|
+
r = account.client.search(**@params)
|
126
|
+
Response::Search.new(account: account, params: @params, **r)
|
29
127
|
end
|
30
128
|
end
|
31
129
|
end
|