xapixctl 1.1.2 → 1.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.
@@ -1,36 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
4
+ require 'psych'
5
+ require 'rest_client'
6
+ require 'xapixctl/phoenix_client/result_handler'
7
+ require 'xapixctl/phoenix_client/organization_connection'
8
+ require 'xapixctl/phoenix_client/project_connection'
9
+ require 'xapixctl/phoenix_client/connection'
10
+
3
11
  module Xapixctl
4
12
  module PhoenixClient
5
- class ResultHandler
6
- def initialize(default_success_handler:, default_error_handler:)
7
- @success_handler = default_success_handler
8
- @error_handler = default_error_handler
9
- @result_handler = nil
10
- yield self if block_given?
11
- end
12
-
13
- def on_success(&block); @success_handler = block; self; end
14
-
15
- def on_error(&block); @error_handler = block; self; end
16
-
17
- def prepare_data(proc); @result_handler = proc; self; end
18
-
19
- def formatter(proc); @formatter = proc; self; end
20
-
21
- def run
22
- res = yield
23
- res = res.present? ? JSON.parse(res) : res
24
- res = @result_handler ? @result_handler.call(res) : res
25
- res = @formatter ? @formatter.call(res) : res
26
- @success_handler.call(res)
27
- rescue RestClient::Exception => err
28
- response = JSON.parse(err.response) rescue {}
29
- @error_handler.call(err, response)
30
- rescue SocketError, Errno::ECONNREFUSED => err
31
- @error_handler.call(err, nil)
32
- end
33
- end
13
+ # sorting is intentional to reflect dependencies when exporting
14
+ SUPPORTED_RESOURCE_TYPES = %w[
15
+ Project
16
+ Ambassador
17
+ AuthScheme
18
+ Credential
19
+ Proxy
20
+ CacheConnection
21
+ Schema
22
+ DataSource
23
+ Pipeline
24
+ Service
25
+ ServiceInstall
26
+ EndpointGroup
27
+ Endpoint
28
+ StreamGroup
29
+ Stream
30
+ StreamProcessor
31
+ Scheduler
32
+ ApiPublishing
33
+ ApiPublishingRole
34
+ ].freeze
34
35
 
35
36
  TEXT_FORMATTERS = {
36
37
  all: ->(data) { "id : %<id>s\nkind: %<kind>s\nname: %<name>s\n\n" % { id: data.dig('metadata', 'id'), kind: data['kind'], name: data.dig('definition', 'name') } }
@@ -64,239 +65,15 @@ module Xapixctl
64
65
  end
65
66
  }.freeze
66
67
 
67
- class Connection
68
- DEFAULT_SUCCESS_HANDLER = ->(result) { result }
69
- DEFAULT_ERROR_HANDLER = ->(err, _response) { warn "Could not get data: #{err}" }
70
-
71
- # sorting is intentional to reflect dependencies when exporting
72
- SUPPORTED_RESOURCE_TYPES = %w[
73
- Project
74
- Ambassador
75
- AuthScheme
76
- Credential
77
- Proxy
78
- CacheConnection
79
- Schema
80
- DataSource
81
- Pipeline
82
- Service
83
- ServiceInstall
84
- EndpointGroup
85
- Endpoint
86
- StreamGroup
87
- Stream
88
- StreamProcessor
89
- Scheduler
90
- ApiPublishing
91
- ApiPublishingRole
92
- ].freeze
93
-
94
- def initialize(url, token)
95
- @client = RestClient::Resource.new(File.join(url, 'api/v1'), verify_ssl: false, accept: :json, content_type: :json, headers: { Authorization: "Bearer #{token}" })
96
- @default_success_handler = DEFAULT_SUCCESS_HANDLER
97
- @default_error_handler = DEFAULT_ERROR_HANDLER
98
- end
99
-
100
- def on_success(&block); @default_success_handler = block; self; end
101
-
102
- def on_error(&block); @default_error_handler = block; self; end
103
-
104
- def resource(resource_type, resource_id, org:, project: nil, format: :hash, &block)
105
- result_handler(block).
106
- formatter(FORMATTERS[format]).
107
- run { @client[resource_path(org, project, resource_type, resource_id)].get }
108
- end
109
-
110
- def resource_ids(resource_type, org:, project: nil, &block)
111
- result_handler(block).
112
- prepare_data(->(data) { data['resource_ids'] }).
113
- run { @client[resources_path(org, project, resource_type)].get }
114
- end
115
-
116
- def apply(resource_description, org:, project: nil, &block)
117
- result_handler(block).
118
- run { @client[generic_resource_path(org, project)].put(resource_description.to_json) }
119
- end
120
-
121
- def delete(resource_type, resource_id, org:, project: nil, &block)
122
- result_handler(block).
123
- run { @client[resource_path(org, project, resource_type, resource_id)].delete }
124
- end
125
-
126
- def pipeline_preview(pipeline_id, org:, project:, format: :hash, &block)
127
- result_handler(block).
128
- prepare_data(->(data) { data['pipeline_preview'] }).
129
- formatter(PREVIEW_FORMATTERS[format]).
130
- run { @client[pipeline_preview_path(org, project, pipeline_id)].get }
131
- end
132
-
133
- def endpoint_preview(endpoint_id, org:, project:, format: :hash, &block)
134
- result_handler(block).
135
- prepare_data(->(data) { data['endpoint_preview'] }).
136
- formatter(PREVIEW_FORMATTERS[format]).
137
- run { @client[endpoint_preview_path(org, project, endpoint_id)].get }
138
- end
139
-
140
- def stream_processor_preview(stream_processor_id, org:, project:, format: :hash, &block)
141
- result_handler(block).
142
- prepare_data(->(data) { data['stream_processor_preview'] }).
143
- formatter(PREVIEW_FORMATTERS[format]).
144
- run { @client[stream_processor_preview_path(org, project, stream_processor_id)].get }
145
- end
146
-
147
- def publish(org:, project:, &block)
148
- result_handler(block).
149
- run { @client[project_publications_path(org, project)].post('') }
150
- end
151
-
152
- def logs(correlation_id, org:, project:, &block)
153
- result_handler(block).
154
- run { @client[project_logss_path(org, project, correlation_id)].get }
155
- end
156
-
157
- def available_resource_types(&block)
158
- result_handler(block).
159
- prepare_data(->(data) { data['resource_types'] }).
160
- run { @client[resource_types_path].get }
161
- end
162
-
163
- def resource_types_for_export
164
- @resource_types_for_export ||=
165
- available_resource_types do |res|
166
- res.on_success { |available_types| SUPPORTED_RESOURCE_TYPES & available_types.map { |desc| desc['type'] } }
167
- res.on_error { |err, _response| raise err }
168
- end
169
- end
170
-
171
- def onboarding(org:, project:)
172
- OnboardingConnection.new(@client, @default_success_handler, @default_error_handler, org, project)
173
- end
174
-
175
- private
176
-
177
- def result_handler(block)
178
- ResultHandler.new(default_success_handler: @default_success_handler, default_error_handler: @default_error_handler, &block)
179
- end
180
-
181
- def resource_path(org, project, type, id)
182
- type = translate_type(type)
183
- project ? "/projects/#{org}/#{project}/#{type}/#{id}" : "/orgs/#{org}/#{type}/#{id}"
184
- end
185
-
186
- def resources_path(org, project, type)
187
- type = translate_type(type)
188
- project ? "/projects/#{org}/#{project}/#{type}" : "/orgs/#{org}/#{type}"
189
- end
190
-
191
- def generic_resource_path(org, project)
192
- project ? "projects/#{org}/#{project}/resource" : "orgs/#{org}/resource"
193
- end
194
-
195
- def pipeline_preview_path(org, project, pipeline)
196
- "/projects/#{org}/#{project}/pipelines/#{pipeline}/preview"
197
- end
198
-
199
- def endpoint_preview_path(org, project, endpoint)
200
- "/projects/#{org}/#{project}/endpoints/#{endpoint}/preview"
201
- end
202
-
203
- def stream_processor_preview_path(org, project, stream_processor)
204
- "/projects/#{org}/#{project}/stream_processors/#{stream_processor}/preview"
205
- end
206
-
207
- def project_publications_path(org, project)
208
- "/projects/#{org}/#{project}/publications"
209
- end
210
-
211
- def project_logss_path(org, project, correlation_id)
212
- "/projects/#{org}/#{project}/logs/#{correlation_id}"
213
- end
214
-
215
- def resource_types_path
216
- "/resource_types"
217
- end
68
+ DEFAULT_SUCCESS_HANDLER = ->(result) { result }
69
+ DEFAULT_ERROR_HANDLER = ->(err, _response) { warn "Could not get data: #{err}" }
218
70
 
219
- def translate_type(resource_type)
220
- return 'ApiPublishingRole' if resource_type == 'ApiPublishing/Role'
221
- resource_type.sub(%r[/.*], '') # cut off everything after first slash
222
- end
71
+ def self.connection(url, token, default_success_handler: DEFAULT_SUCCESS_HANDLER, default_error_handler: DEFAULT_ERROR_HANDLER, logging: nil)
72
+ Connection.new(url, token, default_success_handler, default_error_handler, logging)
223
73
  end
224
74
 
225
- class OnboardingConnection
226
- def initialize(client, default_success_handler, default_error_handler, org, project)
227
- @client = client
228
- @default_success_handler = default_success_handler
229
- @default_error_handler = default_error_handler
230
- @org = org
231
- @project = project
232
- end
233
-
234
- # Notes on parameters:
235
- # - Query parameters should be part of the URL
236
- # - Path parameters should be marked with `{name}` in the URL, and values should be given in path_params hash
237
- # - Headers should be given in headers hash
238
- # - Cookies should be given in cookies hash
239
- # - The body has to be given as a string
240
- # - The required authentication schemes should be listed, referring to previously created schemes
241
- #
242
- # This returns a hash like the following:
243
- # "data_source" => { "id" => id, "resource_description" => resource_description }
244
- #
245
- # To successfully onboard a DB using the API, the following steps are needed:
246
- # 1. setup the data source using add_rest_data_source.
247
- # 2. retrieve a preview using preview_data_source using the id returned by previous step
248
- # 3. confirm preview
249
- # 4. call accept_data_source_preview to complete onboarding
250
- #
251
- def add_rest_data_source(http_method:, url:, path_params: {}, headers: {}, cookies: {}, body: nil, auth_schemes: [], &block)
252
- data_source_details = {
253
- http_method: http_method, url: url,
254
- parameters: { path: path_params.to_query, header: headers.to_query, cookies: cookies.to_query, body: body },
255
- auth_schemes: auth_schemes
256
- }
257
- result_handler(block).
258
- run { @client[rest_data_source_path].post(data_source: data_source_details) }
259
- end
260
-
261
- # Notes on parameters:
262
- # - To call a data source which requires authentication, provide a hash with each required auth scheme as key and
263
- # as the value a reference to a previously created credential.
264
- # Example: { scheme_ref1 => credential_ref1, scheme_ref2 => credential_ref2 }
265
- #
266
- # This returns a hashified preview like the following:
267
- # { "preview" => {
268
- # "sample" => { "status" => integer, "body" => { ... }, "headers" => { ... }, "cookies" => { ... } },
269
- # "fetched_at" => Timestamp },
270
- # "data_source" => { "id" => id, "resource_description" => resource_description } }
271
- #
272
- def preview_data_source(data_source_id, authentications: {}, &block)
273
- preview_data = {
274
- authentications: authentications.map { |scheme, cred| { auth_scheme_id: scheme, auth_credential_id: cred } }
275
- }
276
- result_handler(block).
277
- run { @client[data_source_preview_path(data_source_id)].post(preview_data) }
278
- end
279
-
280
- # This returns a hashified preview like the following:
281
-
282
- def accept_data_source_preview(data_source_id, &block)
283
- result_handler(block).
284
- run { @client[data_source_preview_path(data_source_id)].patch('') }
285
- end
286
-
287
- private
288
-
289
- def result_handler(block)
290
- ResultHandler.new(default_success_handler: @default_success_handler, default_error_handler: @default_error_handler, &block)
291
- end
292
-
293
- def rest_data_source_path
294
- "/projects/#{@org}/#{@project}/onboarding/data_sources/rest"
295
- end
296
-
297
- def data_source_preview_path(id)
298
- "/projects/#{@org}/#{@project}/onboarding/data_sources/#{id}/preview"
299
- end
75
+ def self.supported_type?(type)
76
+ SUPPORTED_RESOURCE_TYPES.include?(type)
300
77
  end
301
78
  end
302
79
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xapixctl
4
+ module PhoenixClient
5
+ class Connection
6
+ DEFAULT_CLIENT_OPTS = { verify_ssl: false, headers: { accept: :json } }.freeze
7
+
8
+ attr_reader :xapix_url, :client
9
+
10
+ def initialize(url, token, default_success_handler, default_error_handler, logging)
11
+ @xapix_url = url
12
+ client_opts = DEFAULT_CLIENT_OPTS.deep_merge(headers: { Authorization: "Bearer #{token}" })
13
+ client_opts.merge!(log: RestClient.create_log(logging)) if logging
14
+ @client = RestClient::Resource.new(File.join(url, 'api/v1'), client_opts)
15
+ @default_success_handler = default_success_handler
16
+ @default_error_handler = default_error_handler
17
+ end
18
+
19
+ def on_success(&block); @default_success_handler = block; self; end
20
+
21
+ def on_error(&block); @default_error_handler = block; self; end
22
+
23
+ def available_resource_types(&block)
24
+ @available_resource_types ||= begin
25
+ result_handler(block).
26
+ prepare_data(->(data) { data['resource_types'].freeze }).
27
+ run { @client[resource_types_path].get }
28
+ end
29
+ end
30
+
31
+ def organization(org)
32
+ OrganizationConnection.new(self, org)
33
+ end
34
+
35
+ def project(org:, project:)
36
+ ProjectConnection.new(self, org, project)
37
+ end
38
+
39
+ def result_handler(block)
40
+ ResultHandler.new(default_success_handler: @default_success_handler, default_error_handler: @default_error_handler, &block)
41
+ end
42
+
43
+ private
44
+
45
+ def resource_types_path
46
+ "/resource_types"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xapixctl
4
+ module PhoenixClient
5
+ class OrganizationConnection
6
+ attr_reader :org
7
+
8
+ def initialize(connection, org)
9
+ @connection = connection
10
+ @client = connection.client
11
+ @org = org
12
+ end
13
+
14
+ def resource(resource_type, resource_id, format: :hash, &block)
15
+ result_handler(block).
16
+ formatter(FORMATTERS[format]).
17
+ run { @client[resource_path(resource_type, resource_id)].get }
18
+ end
19
+
20
+ def resource_ids(resource_type, &block)
21
+ result_handler(block).
22
+ prepare_data(->(data) { data['resource_ids'] }).
23
+ run { @client[resources_path(resource_type)].get }
24
+ end
25
+
26
+ def apply(resource_description, &block)
27
+ result_handler(block).
28
+ prepare_data(->(data) { data['resource_ids'] }).
29
+ run { @client[generic_resource_path].put(resource_description.to_json, content_type: :json) }
30
+ end
31
+
32
+ def delete(resource_type, resource_id, &block)
33
+ result_handler(block).
34
+ run { @client[resource_path(resource_type, resource_id)].delete }
35
+ end
36
+
37
+ private
38
+
39
+ def result_handler(block)
40
+ @connection.result_handler(block)
41
+ end
42
+
43
+ def resource_path(type, id)
44
+ "/orgs/#{@org}/#{translate_type(type)}/#{id}"
45
+ end
46
+
47
+ def resources_path(type)
48
+ "/orgs/#{@org}/#{translate_type(type)}"
49
+ end
50
+
51
+ def generic_resource_path
52
+ "orgs/#{@org}/resource"
53
+ end
54
+
55
+ def translate_type(resource_type)
56
+ return 'ApiPublishingRole' if resource_type == 'ApiPublishing/Role'
57
+ resource_type.sub(%r[/.*], '') # cut off everything after first slash
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xapixctl
4
+ module PhoenixClient
5
+ class ProjectConnection < OrganizationConnection
6
+ attr_reader :project
7
+
8
+ def initialize(connection, org, project)
9
+ super(connection, org)
10
+ @project = project
11
+ end
12
+
13
+ def project_resource(format: :hash, &block)
14
+ organization.resource('Project', @project, format: format, &block)
15
+ end
16
+
17
+ def organization
18
+ OrganizationConnection.new(@connection, @org)
19
+ end
20
+
21
+ def resource_types_for_export
22
+ @resource_types_for_export ||=
23
+ @connection.available_resource_types do |res|
24
+ res.on_success do |available_types|
25
+ prj_types = available_types.select { |desc| desc['context'] == 'Project' }
26
+ SUPPORTED_RESOURCE_TYPES & prj_types.map { |desc| desc['type'] }
27
+ end
28
+ end
29
+ end
30
+
31
+ # Notes on parameters:
32
+ # - Query parameters should be part of the URL
33
+ # - Path parameters should be marked with `{name}` in the URL, and values should be given in path_params hash
34
+ # - Headers should be given in headers hash
35
+ # - Cookies should be given in cookies hash
36
+ # - The body has to be given as a string
37
+ # - The required authentication schemes should be listed, referring to previously created schemes
38
+ #
39
+ # This returns a hash like the following:
40
+ # "data_source" => { "id" => id, "resource_description" => resource_description }
41
+ #
42
+ # To successfully onboard a DB using the API, the following steps are needed:
43
+ # 1. setup the data source using add_rest_data_source.
44
+ # 2. retrieve a preview using preview_data_source using the id returned by previous step
45
+ # 3. confirm preview
46
+ # 4. call accept_data_source_preview to complete onboarding
47
+ #
48
+ def add_rest_data_source(http_method:, url:, path_params: {}, headers: {}, cookies: {}, body: nil, auth_schemes: [], &block)
49
+ data_source_details = {
50
+ data_source: {
51
+ http_method: http_method, url: url,
52
+ parameters: { path: path_params.to_query, header: headers.to_query, cookies: cookies.to_query, body: body },
53
+ auth_schemes: auth_schemes
54
+ }
55
+ }
56
+ result_handler(block).
57
+ run { @client[rest_data_source_path].post(data_source_details.to_json, content_type: :json) }
58
+ end
59
+
60
+ # Notes on parameters:
61
+ # - To call a data source which requires authentication, provide a hash with each required auth scheme as key and
62
+ # as the value a reference to a previously created credential.
63
+ # Example: { scheme_ref1 => credential_ref1, scheme_ref2 => credential_ref2 }
64
+ #
65
+ # This returns a hashified preview like the following:
66
+ # { "preview" => {
67
+ # "sample" => { "status" => integer, "body" => { ... }, "headers" => { ... }, "cookies" => { ... } },
68
+ # "fetched_at" => Timestamp },
69
+ # "data_source" => { "id" => id, "resource_description" => resource_description } }
70
+ #
71
+ def data_source_preview(data_source_id, authentications: {}, &block)
72
+ preview_data = {
73
+ authentications: authentications.map { |scheme, cred| { auth_scheme_id: scheme, auth_credential_id: cred } }
74
+ }
75
+ result_handler(block).
76
+ run { @client[data_source_preview_path(data_source_id)].post(preview_data.to_json, content_type: :json) }
77
+ end
78
+
79
+ def pipeline_preview(pipeline_id, format: :hash, &block)
80
+ result_handler(block).
81
+ prepare_data(->(data) { data['pipeline_preview'] }).
82
+ formatter(PREVIEW_FORMATTERS[format]).
83
+ run { @client[pipeline_preview_path(pipeline_id)].get }
84
+ end
85
+
86
+ def endpoint_preview(endpoint_id, format: :hash, &block)
87
+ result_handler(block).
88
+ prepare_data(->(data) { data['endpoint_preview'] }).
89
+ formatter(PREVIEW_FORMATTERS[format]).
90
+ run { @client[endpoint_preview_path(endpoint_id)].get }
91
+ end
92
+
93
+ def stream_processor_preview(stream_processor_id, format: :hash, &block)
94
+ result_handler(block).
95
+ prepare_data(->(data) { data['stream_processor_preview'] }).
96
+ formatter(PREVIEW_FORMATTERS[format]).
97
+ run { @client[stream_processor_preview_path(stream_processor_id)].get }
98
+ end
99
+
100
+ def publish(&block)
101
+ result_handler(block).
102
+ run { @client[project_publications_path].post('') }
103
+ end
104
+
105
+ def logs(correlation_id, &block)
106
+ result_handler(block).
107
+ run { @client[project_logss_path(correlation_id)].get }
108
+ end
109
+
110
+ # This returns a hashified preview like the following:
111
+
112
+ def accept_data_source_preview(data_source_id, &block)
113
+ result_handler(block).
114
+ run { @client[data_source_preview_path(data_source_id)].patch('') }
115
+ end
116
+
117
+ def public_project_url
118
+ File.join(@connection.xapix_url, @org, @project)
119
+ end
120
+
121
+ private
122
+
123
+ def rest_data_source_path
124
+ "/projects/#{@org}/#{@project}/onboarding/data_sources/rest"
125
+ end
126
+
127
+ def data_source_preview_path(id)
128
+ "/projects/#{@org}/#{@project}/onboarding/data_sources/#{id}/preview"
129
+ end
130
+
131
+ def resource_path(type, id)
132
+ "/projects/#{@org}/#{@project}/#{translate_type(type)}/#{id}"
133
+ end
134
+
135
+ def resources_path(type)
136
+ "/projects/#{@org}/#{@project}/#{translate_type(type)}"
137
+ end
138
+
139
+ def generic_resource_path
140
+ "projects/#{@org}/#{@project}/resource"
141
+ end
142
+
143
+ def pipeline_preview_path(pipeline)
144
+ "/projects/#{@org}/#{@project}/pipelines/#{pipeline}/preview"
145
+ end
146
+
147
+ def endpoint_preview_path(endpoint)
148
+ "/projects/#{@org}/#{@project}/endpoints/#{endpoint}/preview"
149
+ end
150
+
151
+ def stream_processor_preview_path(stream_processor)
152
+ "/projects/#{@org}/#{@project}/stream_processors/#{stream_processor}/preview"
153
+ end
154
+
155
+ def project_publications_path
156
+ "/projects/#{@org}/#{@project}/publications"
157
+ end
158
+
159
+ def project_logss_path(correlation_id)
160
+ "/projects/#{@org}/#{@project}/logs/#{correlation_id}"
161
+ end
162
+ end
163
+ end
164
+ end