xapixctl 1.1.2 → 1.2.4

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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xapixctl/base_cli'
4
+
5
+ module Xapixctl
6
+ class ConnectorCli < BaseCli
7
+ option :schema_import, desc: "Resource id of an existing Schema import"
8
+ desc "import SPECFILE", "Create HTTP Connectors from Swagger / OpenAPI or SOAP WSDL files"
9
+ long_desc <<-LONGDESC
10
+ `xapixctl connectors import SPECFILE` will create HTTP Connectors from the given Swagger / OpenAPI or SOAP WSDL file.
11
+
12
+ Examples:
13
+ \x5> $ xapixctl connectors import ./swagger.json -p xapix/some-project
14
+ \x5> $ xapixctl connectors import ./swagger.json -p xapix/some-project --schema-import=existing-schema
15
+ LONGDESC
16
+ def import(spec_filename)
17
+ path = Pathname.new(spec_filename)
18
+ unless path.file? && path.readable?
19
+ warn "Cannot read #{path}, please check file exists and is readable"
20
+ exit 1
21
+ end
22
+ if options[:schema_import]
23
+ say "uploading to update schema import '#{options[:schema_import]}': #{spec_filename}..."
24
+ result = prj_connection.update_schema_import(options[:schema_import], spec_filename)
25
+ say "updated #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
26
+ else
27
+ say "uploading as new import: #{spec_filename}..."
28
+ result = prj_connection.add_schema_import(spec_filename)
29
+ say "created #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
30
+ end
31
+
32
+ [['issues', 'import'], ['validation_issues', 'validation']].each do |key, name|
33
+ issues = result.dig('schema_import', 'report', key)
34
+ if issues.any?
35
+ say "\n#{name} issues:"
36
+ shell.indent { issues.each { |issue| say "- #{issue}" } }
37
+ end
38
+ end
39
+
40
+ updated_resources = result.dig('schema_import', 'updated_resources')
41
+ if updated_resources.any?
42
+ say "\nconnectors:"
43
+ shell.indent { updated_resources.each { |resource| say "- #{resource['kind']} #{resource['id']}" } }
44
+ else
45
+ say "\nno connectors created/updated."
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,36 +1,38 @@
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
+ Service
24
+ ServiceInstall
25
+ Pipeline
26
+ EndpointGroup
27
+ Endpoint
28
+ StreamGroup
29
+ Stream
30
+ StreamProcessor
31
+ Scheduler
32
+ ApiPublishing
33
+ ApiPublishingRole
34
+ ApiPublishingAccessRule
35
+ ].freeze
34
36
 
35
37
  TEXT_FORMATTERS = {
36
38
  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 +66,15 @@ module Xapixctl
64
66
  end
65
67
  }.freeze
66
68
 
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
69
+ DEFAULT_SUCCESS_HANDLER = ->(result) { result }
70
+ DEFAULT_ERROR_HANDLER = ->(err, _response) { warn "Could not get data: #{err}" }
218
71
 
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
72
+ def self.connection(url, token, default_success_handler: DEFAULT_SUCCESS_HANDLER, default_error_handler: DEFAULT_ERROR_HANDLER, logging: nil)
73
+ Connection.new(url, token, default_success_handler, default_error_handler, logging)
223
74
  end
224
75
 
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
76
+ def self.supported_type?(type)
77
+ SUPPORTED_RESOURCE_TYPES.include?(type)
300
78
  end
301
79
  end
302
80
  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