latitude-api 0.2.0

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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +10 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +283 -0
  5. data/Rakefile +13 -0
  6. data/lib/latitude/api/api_object.rb +105 -0
  7. data/lib/latitude/api/api_resource.rb +260 -0
  8. data/lib/latitude/api/attributes.rb +64 -0
  9. data/lib/latitude/api/client.rb +80 -0
  10. data/lib/latitude/api/configuration.rb +88 -0
  11. data/lib/latitude/api/errors.rb +134 -0
  12. data/lib/latitude/api/json_api.rb +40 -0
  13. data/lib/latitude/api/list_object.rb +129 -0
  14. data/lib/latitude/api/objects.rb +216 -0
  15. data/lib/latitude/api/operations/action.rb +41 -0
  16. data/lib/latitude/api/operations/create.rb +22 -0
  17. data/lib/latitude/api/operations/delete.rb +22 -0
  18. data/lib/latitude/api/operations/list.rb +54 -0
  19. data/lib/latitude/api/operations/nested.rb +53 -0
  20. data/lib/latitude/api/operations/retrieve.rb +22 -0
  21. data/lib/latitude/api/operations/update.rb +22 -0
  22. data/lib/latitude/api/request_executor.rb +217 -0
  23. data/lib/latitude/api/request_options.rb +86 -0
  24. data/lib/latitude/api/request_params.rb +70 -0
  25. data/lib/latitude/api/resources/api_key.rb +61 -0
  26. data/lib/latitude/api/resources/billing_usage.rb +51 -0
  27. data/lib/latitude/api/resources/elastic_ip.rb +96 -0
  28. data/lib/latitude/api/resources/event.rb +55 -0
  29. data/lib/latitude/api/resources/firewall.rb +129 -0
  30. data/lib/latitude/api/resources/ip.rb +110 -0
  31. data/lib/latitude/api/resources/kubernetes_cluster.rb +184 -0
  32. data/lib/latitude/api/resources/plan.rb +658 -0
  33. data/lib/latitude/api/resources/project.rb +179 -0
  34. data/lib/latitude/api/resources/region.rb +41 -0
  35. data/lib/latitude/api/resources/role.rb +20 -0
  36. data/lib/latitude/api/resources/server.rb +251 -0
  37. data/lib/latitude/api/resources/ssh_key.rb +44 -0
  38. data/lib/latitude/api/resources/storage_filesystem.rb +36 -0
  39. data/lib/latitude/api/resources/storage_object.rb +85 -0
  40. data/lib/latitude/api/resources/storage_volume.rb +64 -0
  41. data/lib/latitude/api/resources/tag.rb +34 -0
  42. data/lib/latitude/api/resources/team.rb +169 -0
  43. data/lib/latitude/api/resources/traffic.rb +215 -0
  44. data/lib/latitude/api/resources/user_data.rb +38 -0
  45. data/lib/latitude/api/resources/user_profile.rb +51 -0
  46. data/lib/latitude/api/resources/user_team.rb +90 -0
  47. data/lib/latitude/api/resources/virtual_machine.rb +146 -0
  48. data/lib/latitude/api/resources/virtual_network.rb +162 -0
  49. data/lib/latitude/api/resources/vpn_session.rb +80 -0
  50. data/lib/latitude/api/singleton_api_resource.rb +22 -0
  51. data/lib/latitude/api/util.rb +64 -0
  52. data/lib/latitude/api/version.rb +7 -0
  53. data/lib/latitude/api.rb +147 -0
  54. data/lib/latitude/metadata/errors.rb +18 -0
  55. data/lib/latitude/metadata/session.rb +109 -0
  56. data/lib/latitude/metadata.rb +43 -0
  57. data/lib/latitude.rb +4 -0
  58. metadata +132 -0
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ class RequestOptions
6
+ ALLOWED = %i[
7
+ api_key
8
+ api_version
9
+ api_base
10
+ open_timeout
11
+ read_timeout
12
+ write_timeout
13
+ max_network_retries
14
+ proxy
15
+ idempotency_key
16
+ headers
17
+ ].freeze
18
+
19
+ attr_reader :api_key, :api_version, :api_base,
20
+ :open_timeout, :read_timeout, :write_timeout,
21
+ :max_network_retries, :proxy,
22
+ :idempotency_key, :headers
23
+
24
+ def initialize(opts = {})
25
+ opts ||= {}
26
+ validate!(opts)
27
+ @api_key = opts[:api_key]
28
+ @api_version = opts[:api_version]
29
+ @api_base = opts[:api_base]
30
+ @open_timeout = opts[:open_timeout]
31
+ @read_timeout = opts[:read_timeout]
32
+ @write_timeout = opts[:write_timeout]
33
+ @max_network_retries = opts[:max_network_retries]
34
+ @proxy = opts[:proxy]
35
+ @idempotency_key = opts[:idempotency_key]
36
+ @headers = opts[:headers] || {}
37
+ end
38
+
39
+ def self.wrap(opts)
40
+ return opts if opts.is_a?(RequestOptions)
41
+
42
+ new(opts || {})
43
+ end
44
+
45
+ def merge(other)
46
+ other = self.class.wrap(other)
47
+ merged = to_h.merge(other.to_h) { |_k, a, b| b.nil? ? a : b }
48
+ merged[:headers] = @headers.merge(other.headers || {})
49
+ self.class.new(merged)
50
+ end
51
+
52
+ def to_h
53
+ {
54
+ api_key: @api_key,
55
+ api_version: @api_version,
56
+ api_base: @api_base,
57
+ open_timeout: @open_timeout,
58
+ read_timeout: @read_timeout,
59
+ write_timeout: @write_timeout,
60
+ max_network_retries: @max_network_retries,
61
+ proxy: @proxy,
62
+ idempotency_key: @idempotency_key,
63
+ headers: @headers,
64
+ }
65
+ end
66
+
67
+ def apply_to(config)
68
+ overrides = to_h.compact
69
+ overrides.delete(:idempotency_key)
70
+ overrides.delete(:headers)
71
+ return config if overrides.empty?
72
+
73
+ config.dup_with(**overrides)
74
+ end
75
+
76
+ private
77
+
78
+ def validate!(opts)
79
+ extras = opts.keys.map(&:to_sym) - ALLOWED
80
+ return if extras.empty?
81
+
82
+ raise ArgumentError, "unknown request option(s): #{extras.join(', ')}"
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module RequestParams
6
+ COMMA_JOIN_LEAVES = %w[tags extra_fields].freeze
7
+
8
+ module_function
9
+
10
+ def encode(params)
11
+ return "" if params.nil? || params.empty?
12
+
13
+ parts = []
14
+ flatten(params, nil, parts)
15
+ parts.map { |k, v| "#{URI.encode_www_form_component(k)}=#{URI.encode_www_form_component(v)}" }.join("&")
16
+ end
17
+
18
+ def flatten(value, parent_key, parts)
19
+ case value
20
+ when Hash
21
+ value.each do |k, v|
22
+ next if v.nil?
23
+
24
+ key = parent_key ? "#{parent_key}[#{k}]" : k.to_s
25
+ flatten(v, key, parts)
26
+ end
27
+ when Array
28
+ if comma_join?(parent_key, value)
29
+ parts << [parent_key, value.map(&:to_s).join(",")]
30
+ else
31
+ value.each_with_index do |v, _i|
32
+ flatten(v, "#{parent_key}[]", parts)
33
+ end
34
+ end
35
+ when nil
36
+ nil
37
+ when true, false
38
+ parts << [parent_key, value.to_s]
39
+ else
40
+ parts << [parent_key, scalar(value)]
41
+ end
42
+ end
43
+
44
+ def comma_join?(parent_key, value)
45
+ return false unless parent_key
46
+ return false unless value.all? { |v| v.is_a?(String) || v.is_a?(Symbol) || v.is_a?(Numeric) }
47
+
48
+ leaf = parent_key.split(/[\[\]]/).reject(&:empty?).last
49
+ COMMA_JOIN_LEAVES.include?(leaf)
50
+ end
51
+
52
+ def scalar(value)
53
+ case value
54
+ when Time, DateTime
55
+ value.utc.iso8601
56
+ when Date
57
+ value.iso8601
58
+ else
59
+ value.to_s
60
+ end
61
+ end
62
+
63
+ def encode_sort(value)
64
+ return nil if value.nil?
65
+
66
+ Array(value).map(&:to_s).reject(&:empty?).join(",")
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module Resources
6
+ # @!attribute [rw] name
7
+ # @return [String]
8
+ # @!attribute [rw] token
9
+ # @return [String]
10
+ # @!attribute [rw] token_last_slice
11
+ # @return [String]
12
+ # @!attribute [rw] api_version
13
+ # @return [String]
14
+ # @!attribute [rw] read_only
15
+ # @return [Boolean]
16
+ # @!attribute [rw] allowed_ips
17
+ # @return [String]
18
+ # @!attribute [r] created_at
19
+ # @return [Time]
20
+ # @!attribute [r] updated_at
21
+ # @return [Time]
22
+ # @!attribute [rw] last_used_at
23
+ # @return [Time]
24
+ # @!attribute [rw] user
25
+ # @return [Latitude::API::Objects::User]
26
+ class APIKey < APIResource
27
+ attribute :name, :string
28
+ attribute :token, :string
29
+ attribute :token_last_slice, :string
30
+ attribute :api_version, :string
31
+ attribute :read_only, :boolean
32
+ attribute :allowed_ips, :string
33
+ attribute :created_at, :time, read_only: true
34
+ attribute :updated_at, :time, read_only: true
35
+ attribute :last_used_at, :time
36
+ attribute :user, Objects::User
37
+
38
+ resource_type "api_keys"
39
+ resource_path "/auth/api_keys"
40
+ id_prefix "tok_"
41
+
42
+ extend Operations::List
43
+ extend Operations::Create
44
+ extend Operations::Update
45
+ extend Operations::Delete
46
+
47
+ class << self
48
+ def rotate(id, params = {}, opts = {})
49
+ body = JSONAPI.encode(type: resource_type, id: id, attributes: params)
50
+ parsed = execute_request(method: :put, path: instance_url(id), body: body, opts: opts)
51
+ construct_from(parsed, opts: opts)
52
+ end
53
+ end
54
+
55
+ def rotate(params = {}, opts = {})
56
+ self.class.rotate(id, params, opts)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module Resources
6
+ # @!attribute [rw] project
7
+ # @return [Latitude::API::Objects::Project]
8
+ # @!attribute [rw] period
9
+ # @return [Period]
10
+ # @!attribute [rw] available_credit_balance
11
+ # @return [Integer]
12
+ # @!attribute [rw] amount
13
+ # @return [Integer]
14
+ # @!attribute [rw] price
15
+ # @return [Integer]
16
+ # @!attribute [rw] products
17
+ # @return [Array]
18
+ # @!attribute [rw] threshold
19
+ # @return [Integer]
20
+ class BillingUsage < SingletonAPIResource
21
+ # @!attribute [r] start
22
+ # @return [Time]
23
+ # @!attribute [r] end
24
+ # @return [Time]
25
+ class Period < APIObject
26
+ attribute :start, :time, read_only: true
27
+ attribute :end, :time, read_only: true
28
+ end
29
+
30
+ attribute :project, Objects::Project
31
+ attribute :period, Period
32
+ attribute :available_credit_balance, :integer
33
+ attribute :amount, :integer
34
+ attribute :price, :integer
35
+ attribute :products, :array
36
+ attribute :threshold, :integer
37
+
38
+ resource_type "billing_usage"
39
+ resource_path "/billing/usage"
40
+
41
+ class << self
42
+ def retrieve(params = {}, opts = {})
43
+ query = RequestParams.encode(params)
44
+ parsed = execute_request(method: :get, path: resource_path, query: query, opts: opts)
45
+ construct_from(parsed, opts: opts)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module Resources
6
+ # @!attribute [rw] address
7
+ # @return [String]
8
+ # @!attribute [rw] family
9
+ # @return [String]
10
+ # @!attribute [rw] prefix_length
11
+ # @return [Integer]
12
+ # @!attribute [rw] mode
13
+ # @return [String]
14
+ # @!attribute [rw] status
15
+ # @return [String]
16
+ # @!attribute [r] created_at
17
+ # @return [Time]
18
+ # @!attribute [rw] server
19
+ # @return [Server]
20
+ # @!attribute [rw] project
21
+ # @return [Latitude::API::Objects::Project]
22
+ # @!attribute [rw] region
23
+ # @return [Region]
24
+ class ElasticIP < APIResource
25
+ # @!attribute [r] id
26
+ # @return [String]
27
+ # @!attribute [r] hostname
28
+ # @return [String]
29
+ # @!attribute [r] primary_ipv4
30
+ # @return [String]
31
+ # @!attribute [r] operating_system
32
+ # @return [String]
33
+ class Server < APIObject
34
+ attribute :id, :string, read_only: true
35
+ attribute :hostname, :string, read_only: true
36
+ attribute :primary_ipv4, :string, read_only: true
37
+ attribute :operating_system, :string, read_only: true
38
+ end
39
+
40
+ # @!attribute [r] id
41
+ # @return [String]
42
+ # @!attribute [r] name
43
+ # @return [String]
44
+ # @!attribute [r] slug
45
+ # @return [String]
46
+ class Location < APIObject
47
+ attribute :id, :string, read_only: true
48
+ attribute :name, :string, read_only: true
49
+ attribute :slug, :string, read_only: true
50
+ end
51
+
52
+ # @!attribute [r] id
53
+ # @return [String]
54
+ # @!attribute [r] name
55
+ # @return [String]
56
+ # @!attribute [r] location
57
+ # @return [Location]
58
+ class Region < APIObject
59
+ attribute :id, :string, read_only: true
60
+ attribute :name, :string, read_only: true
61
+ attribute :location, Location, read_only: true
62
+ end
63
+
64
+ attribute :address, :string
65
+ attribute :family, :string
66
+ attribute :prefix_length, :integer
67
+ attribute :mode, :string
68
+ attribute :status, :string
69
+ attribute :created_at, :time, read_only: true
70
+ attribute :server, Server
71
+ attribute :project, Objects::Project
72
+ attribute :region, Region
73
+
74
+ resource_type "elastic_ips"
75
+ resource_path "/elastic_ips"
76
+ id_prefix "eip_"
77
+
78
+ extend Operations::List
79
+ extend Operations::Create
80
+ extend Operations::Retrieve
81
+ extend Operations::Update
82
+ extend Operations::Delete
83
+
84
+ class << self
85
+ def move(id, server_id:, opts: {})
86
+ update(id, { server_id: server_id }, opts)
87
+ end
88
+ end
89
+
90
+ def move_to(server_id, opts = {})
91
+ self.class.move(id, server_id: server_id, opts: opts)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module Resources
6
+ # @!attribute [r] created_at
7
+ # @return [Time]
8
+ # @!attribute [rw] action
9
+ # @return [String]
10
+ # @!attribute [rw] target
11
+ # @return [Target]
12
+ # @!attribute [rw] project
13
+ # @return [Latitude::API::Objects::Project]
14
+ # @!attribute [rw] team
15
+ # @return [Latitude::API::Objects::Team]
16
+ # @!attribute [rw] author
17
+ # @return [Author]
18
+ class Event < APIResource
19
+ # @!attribute [r] id
20
+ # @return [String]
21
+ # @!attribute [r] name
22
+ # @return [String]
23
+ class Target < APIObject
24
+ attribute :id, :string, read_only: true
25
+ attribute :name, :string, read_only: true
26
+ end
27
+
28
+ # @!attribute [r] id
29
+ # @return [String]
30
+ # @!attribute [r] name
31
+ # @return [String]
32
+ # @!attribute [r] email
33
+ # @return [String]
34
+ class Author < APIObject
35
+ attribute :id, :string, read_only: true
36
+ attribute :name, :string, read_only: true
37
+ attribute :email, :string, read_only: true
38
+ end
39
+
40
+ attribute :created_at, :time, read_only: true
41
+ attribute :action, :string
42
+ attribute :target, Target
43
+ attribute :project, Objects::Project
44
+ attribute :team, Objects::Team
45
+ attribute :author, Author
46
+
47
+ resource_type "events"
48
+ resource_path "/events"
49
+ id_prefix "evt_"
50
+
51
+ extend Operations::List
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module Resources
6
+ # @!attribute [rw] name
7
+ # @return [String]
8
+ # @!attribute [rw] project
9
+ # @return [Latitude::API::Objects::Project]
10
+ # @!attribute [rw] rules
11
+ # @return [Array<Rule>]
12
+ class Firewall < APIResource
13
+ # @!attribute [r] from
14
+ # @return [String]
15
+ # @!attribute [r] to
16
+ # @return [String]
17
+ # @!attribute [r] port
18
+ # @return [String]
19
+ # @!attribute [r] protocol
20
+ # @return [String]
21
+ # @!attribute [r] default
22
+ # @return [Boolean]
23
+ class Rule < APIObject
24
+ attribute :from, :string, read_only: true
25
+ attribute :to, :string, read_only: true
26
+ attribute :port, :string, read_only: true
27
+ attribute :protocol, :string, read_only: true
28
+ attribute :default, :boolean, read_only: true
29
+ end
30
+
31
+ attribute :name, :string
32
+ attribute :project, Objects::Project
33
+ attribute :rules, [Rule]
34
+
35
+ resource_type "firewalls"
36
+ resource_path "/firewalls"
37
+ id_prefix "firewall_"
38
+
39
+ extend Operations::List
40
+ extend Operations::Create
41
+ extend Operations::Retrieve
42
+ extend Operations::Update
43
+ extend Operations::Delete
44
+
45
+ class << self
46
+ def assignments
47
+ Assignment
48
+ end
49
+ end
50
+
51
+ # @!attribute [rw] server
52
+
53
+ # @return [Server]
54
+
55
+ # @!attribute [rw] firewall
56
+
57
+ # @return [Firewall]
58
+
59
+ # @!attribute [rw] firewall_id
60
+
61
+ # @return [String]
62
+
63
+ class Assignment < APIResource
64
+
65
+ # @!attribute [r] id
66
+
67
+ # @return [String]
68
+
69
+ # @!attribute [r] hostname
70
+
71
+ # @return [String]
72
+
73
+ # @!attribute [r] primary_ipv4
74
+
75
+ # @return [String]
76
+
77
+ class Server < APIObject
78
+
79
+ attribute :id, :string, read_only: true
80
+
81
+ attribute :hostname, :string, read_only: true
82
+
83
+ attribute :primary_ipv4, :string, read_only: true
84
+
85
+ end
86
+
87
+
88
+ # @!attribute [r] id
89
+
90
+ # @return [String]
91
+
92
+ # @!attribute [r] name
93
+
94
+ # @return [String]
95
+
96
+ class Firewall < APIObject
97
+
98
+ attribute :id, :string, read_only: true
99
+
100
+ attribute :name, :string, read_only: true
101
+
102
+ end
103
+
104
+
105
+ attribute :server, Server
106
+
107
+ attribute :firewall, Firewall
108
+
109
+ attribute :firewall_id, :string
110
+
111
+ resource_type "firewall_assignments"
112
+ resource_path "/firewalls/:firewall_id/assignments"
113
+
114
+ extend Operations::List
115
+ extend Operations::Create
116
+ extend Operations::Delete
117
+
118
+ class << self
119
+ def list_all(params = {}, opts = {})
120
+ query = RequestParams.encode(params)
121
+ parsed = execute_request(method: :get, path: "/firewalls/assignments", query: query, opts: opts)
122
+ ListObject.new(resource_class: self, parsed: parsed, request_params: params, request_opts: opts)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Latitude
4
+ module API
5
+ module Resources
6
+ # @!attribute [rw] address
7
+ # @return [String]
8
+ # @!attribute [rw] cidr
9
+ # @return [String]
10
+ # @!attribute [rw] family
11
+ # @return [String]
12
+ # @!attribute [rw] gateway
13
+ # @return [String]
14
+ # @!attribute [rw] netmask
15
+ # @return [String]
16
+ # @!attribute [rw] type
17
+ # @return [String]
18
+ # @!attribute [rw] public
19
+ # @return [Boolean]
20
+ # @!attribute [rw] management
21
+ # @return [Boolean]
22
+ # @!attribute [rw] additional
23
+ # @return [Boolean]
24
+ # @!attribute [rw] project
25
+ # @return [Latitude::API::Objects::Project]
26
+ # @!attribute [rw] region
27
+ # @return [Region]
28
+ # @!attribute [rw] available
29
+ # @return [Boolean]
30
+ # @!attribute [rw] assignment
31
+ # @return [Assignment]
32
+ # @!attribute [rw] elastic
33
+ # @return [Elastic]
34
+ # @!attribute [r] created_at
35
+ # @return [Time]
36
+ class IP < APIResource
37
+ # @!attribute [r] id
38
+ # @return [String]
39
+ # @!attribute [r] name
40
+ # @return [String]
41
+ # @!attribute [r] slug
42
+ # @return [String]
43
+ class Location < APIObject
44
+ attribute :id, :string, read_only: true
45
+ attribute :name, :string, read_only: true
46
+ attribute :slug, :string, read_only: true
47
+ end
48
+
49
+ # @!attribute [r] id
50
+ # @return [String]
51
+ # @!attribute [r] name
52
+ # @return [String]
53
+ # @!attribute [r] location
54
+ # @return [Location]
55
+ class Region < APIObject
56
+ attribute :id, :string, read_only: true
57
+ attribute :name, :string, read_only: true
58
+ attribute :location, Location, read_only: true
59
+ end
60
+
61
+ # @!attribute [r] server_id
62
+ # @return [String]
63
+ # @!attribute [r] hostname
64
+ # @return [String]
65
+ # @!attribute [r] assigned_at
66
+ # @return [String]
67
+ class Assignment < APIObject
68
+ attribute :server_id, :string, read_only: true
69
+ attribute :hostname, :string, read_only: true
70
+ attribute :assigned_at, :string, read_only: true
71
+ end
72
+
73
+ # @!attribute [r] id
74
+ # @return [String]
75
+ # @!attribute [r] mode
76
+ # @return [String]
77
+ # @!attribute [r] status
78
+ # @return [String]
79
+ class Elastic < APIObject
80
+ attribute :id, :string, read_only: true
81
+ attribute :mode, :string, read_only: true
82
+ attribute :status, :string, read_only: true
83
+ end
84
+
85
+ attribute :address, :string
86
+ attribute :cidr, :string
87
+ attribute :family, :string
88
+ attribute :gateway, :string
89
+ attribute :netmask, :string
90
+ attribute :type, :string
91
+ attribute :public, :boolean
92
+ attribute :management, :boolean
93
+ attribute :additional, :boolean
94
+ attribute :project, Objects::Project
95
+ attribute :region, Region
96
+ attribute :available, :boolean
97
+ attribute :assignment, Assignment
98
+ attribute :elastic, Elastic
99
+ attribute :created_at, :time, read_only: true
100
+
101
+ resource_type "ip_addresses"
102
+ resource_path "/ips"
103
+ id_prefix "ip_"
104
+
105
+ extend Operations::List
106
+ extend Operations::Retrieve
107
+ end
108
+ end
109
+ end
110
+ end