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.
@@ -0,0 +1,9 @@
1
+ module VagrantCloud
2
+ module Instrumentor
3
+ class Core
4
+ def instrument(*_)
5
+ raise NotImplementedError
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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,7 @@
1
+ module VagrantCloud
2
+ class Response < Data::Immutable
3
+ autoload :CreateToken, "vagrant_cloud/response/create_token"
4
+ autoload :Request2FA, "vagrant_cloud/response/request_2fa"
5
+ autoload :Search, "vagrant_cloud/response/search"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module VagrantCloud
2
+ class Response
3
+ class CreateToken < Response
4
+ attr_required :token, :token_hash, :created_at, :description
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module VagrantCloud
2
+ class Response
3
+ class Request2FA < Response
4
+ attr_required :destination
5
+ end
6
+ end
7
+ 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
@@ -1,9 +1,37 @@
1
1
  module VagrantCloud
2
2
  class Search
3
- attr_accessor :account
3
+ # @return [Account]
4
+ attr_reader :account
4
5
 
5
- def initialize(access_token = nil, custom_server = nil)
6
- @client = Client.new(access_token, custom_server)
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 [Hash]
18
- def search(query = nil, provider = nil, sort = nil, order = nil, limit = nil, page = nil)
19
- params = {
20
- q: query,
21
- provider: provider,
22
- sort: sort,
23
- order: order,
24
- limit: limit,
25
- page: page
26
- }.delete_if { |_, v| v.nil? }
27
-
28
- @client.request('get', '/search', params)
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