xapixctl 1.1.2 → 1.2.0

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