vagrant_cloud 2.0.3 → 3.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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