xapixctl 1.1.1 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ puts "uploading to update schema import '#{options[:schema_import]}': #{spec_filename}..."
24
+ result = prj_connection.update_schema_import(options[:schema_import], spec_filename)
25
+ puts "updated #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
26
+ else
27
+ puts "uploading as new import: #{spec_filename}..."
28
+ result = prj_connection.add_schema_import(spec_filename)
29
+ puts "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
+ puts "\n#{name} issues:"
36
+ issues.each { |issue| puts " - #{issue}" }
37
+ end
38
+ end
39
+
40
+ updated_resources = result.dig('schema_import', 'updated_resources')
41
+ if updated_resources.any?
42
+ puts "\nconnectors:"
43
+ updated_resources.each { |resource| puts " - #{resource['kind']} #{resource['id']}" }
44
+ else
45
+ puts "\nno connectors created/updated."
46
+ end
47
+ end
48
+ end
49
+ end
@@ -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,238 +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
- EndpointGroup
84
- Endpoint
85
- StreamGroup
86
- Stream
87
- Scheduler
88
- StreamProcessor
89
- ApiPublishing
90
- ApiPublishingRole
91
- ].freeze
92
-
93
- def initialize(url, token)
94
- @client = RestClient::Resource.new(File.join(url, 'api/v1'), verify_ssl: false, accept: :json, content_type: :json, headers: { Authorization: "Bearer #{token}" })
95
- @default_success_handler = DEFAULT_SUCCESS_HANDLER
96
- @default_error_handler = DEFAULT_ERROR_HANDLER
97
- end
98
-
99
- def on_success(&block); @default_success_handler = block; self; end
100
-
101
- def on_error(&block); @default_error_handler = block; self; end
102
-
103
- def resource(resource_type, resource_id, org:, project: nil, format: :hash, &block)
104
- result_handler(block).
105
- formatter(FORMATTERS[format]).
106
- run { @client[resource_path(org, project, resource_type, resource_id)].get }
107
- end
108
-
109
- def resource_ids(resource_type, org:, project: nil, &block)
110
- result_handler(block).
111
- prepare_data(->(data) { data['resource_ids'] }).
112
- run { @client[resources_path(org, project, resource_type)].get }
113
- end
114
-
115
- def apply(resource_description, org:, project: nil, &block)
116
- result_handler(block).
117
- run { @client[generic_resource_path(org, project)].put(resource_description.to_json) }
118
- end
119
-
120
- def delete(resource_type, resource_id, org:, project: nil, &block)
121
- result_handler(block).
122
- run { @client[resource_path(org, project, resource_type, resource_id)].delete }
123
- end
124
-
125
- def pipeline_preview(pipeline_id, org:, project:, format: :hash, &block)
126
- result_handler(block).
127
- prepare_data(->(data) { data['pipeline_preview'] }).
128
- formatter(PREVIEW_FORMATTERS[format]).
129
- run { @client[pipeline_preview_path(org, project, pipeline_id)].get }
130
- end
131
-
132
- def endpoint_preview(endpoint_id, org:, project:, format: :hash, &block)
133
- result_handler(block).
134
- prepare_data(->(data) { data['endpoint_preview'] }).
135
- formatter(PREVIEW_FORMATTERS[format]).
136
- run { @client[endpoint_preview_path(org, project, endpoint_id)].get }
137
- end
138
-
139
- def stream_processor_preview(stream_processor_id, org:, project:, format: :hash, &block)
140
- result_handler(block).
141
- prepare_data(->(data) { data['stream_processor_preview'] }).
142
- formatter(PREVIEW_FORMATTERS[format]).
143
- run { @client[stream_processor_preview_path(org, project, stream_processor_id)].get }
144
- end
145
-
146
- def publish(org:, project:, &block)
147
- result_handler(block).
148
- run { @client[project_publications_path(org, project)].post('') }
149
- end
150
-
151
- def logs(correlation_id, org:, project:, &block)
152
- result_handler(block).
153
- run { @client[project_logss_path(org, project, correlation_id)].get }
154
- end
155
-
156
- def available_resource_types(&block)
157
- result_handler(block).
158
- prepare_data(->(data) { data['resource_types'] }).
159
- run { @client[resource_types_path].get }
160
- end
161
-
162
- def resource_types_for_export
163
- @resource_types_for_export ||=
164
- available_resource_types do |res|
165
- res.on_success { |available_types| SUPPORTED_RESOURCE_TYPES & available_types.map { |desc| desc['type'] } }
166
- res.on_error { |err, _response| raise err }
167
- end
168
- end
169
-
170
- def onboarding(org:, project:)
171
- OnboardingConnection.new(@client, @default_success_handler, @default_error_handler, org, project)
172
- end
173
-
174
- private
175
-
176
- def result_handler(block)
177
- ResultHandler.new(default_success_handler: @default_success_handler, default_error_handler: @default_error_handler, &block)
178
- end
179
-
180
- def resource_path(org, project, type, id)
181
- type = translate_type(type)
182
- project ? "/projects/#{org}/#{project}/#{type}/#{id}" : "/orgs/#{org}/#{type}/#{id}"
183
- end
184
-
185
- def resources_path(org, project, type)
186
- type = translate_type(type)
187
- project ? "/projects/#{org}/#{project}/#{type}" : "/orgs/#{org}/#{type}"
188
- end
189
-
190
- def generic_resource_path(org, project)
191
- project ? "projects/#{org}/#{project}/resource" : "orgs/#{org}/resource"
192
- end
193
-
194
- def pipeline_preview_path(org, project, pipeline)
195
- "/projects/#{org}/#{project}/pipelines/#{pipeline}/preview"
196
- end
197
-
198
- def endpoint_preview_path(org, project, endpoint)
199
- "/projects/#{org}/#{project}/endpoints/#{endpoint}/preview"
200
- end
201
-
202
- def stream_processor_preview_path(org, project, stream_processor)
203
- "/projects/#{org}/#{project}/stream_processors/#{stream_processor}/preview"
204
- end
205
-
206
- def project_publications_path(org, project)
207
- "/projects/#{org}/#{project}/publications"
208
- end
209
-
210
- def project_logss_path(org, project, correlation_id)
211
- "/projects/#{org}/#{project}/logs/#{correlation_id}"
212
- end
213
-
214
- def resource_types_path
215
- "/resource_types"
216
- end
68
+ DEFAULT_SUCCESS_HANDLER = ->(result) { result }
69
+ DEFAULT_ERROR_HANDLER = ->(err, _response) { warn "Could not get data: #{err}" }
217
70
 
218
- def translate_type(resource_type)
219
- return 'ApiPublishingRole' if resource_type == 'ApiPublishing/Role'
220
- resource_type.sub(%r[/.*], '') # cut off everything after first slash
221
- 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)
222
73
  end
223
74
 
224
- class OnboardingConnection
225
- def initialize(client, default_success_handler, default_error_handler, org, project)
226
- @client = client
227
- @default_success_handler = default_success_handler
228
- @default_error_handler = default_error_handler
229
- @org = org
230
- @project = project
231
- end
232
-
233
- # Notes on parameters:
234
- # - Query parameters should be part of the URL
235
- # - Path parameters should be marked with `{name}` in the URL, and values should be given in path_params hash
236
- # - Headers should be given in headers hash
237
- # - Cookies should be given in cookies hash
238
- # - The body has to be given as a string
239
- # - The required authentication schemes should be listed, referring to previously created schemes
240
- #
241
- # This returns a hash like the following:
242
- # "data_source" => { "id" => id, "resource_description" => resource_description }
243
- #
244
- # To successfully onboard a DB using the API, the following steps are needed:
245
- # 1. setup the data source using add_rest_data_source.
246
- # 2. retrieve a preview using preview_data_source using the id returned by previous step
247
- # 3. confirm preview
248
- # 4. call accept_data_source_preview to complete onboarding
249
- #
250
- def add_rest_data_source(http_method:, url:, path_params: {}, headers: {}, cookies: {}, body: nil, auth_schemes: [], &block)
251
- data_source_details = {
252
- http_method: http_method, url: url,
253
- parameters: { path: path_params.to_query, header: headers.to_query, cookies: cookies.to_query, body: body },
254
- auth_schemes: auth_schemes
255
- }
256
- result_handler(block).
257
- run { @client[rest_data_source_path].post(data_source: data_source_details) }
258
- end
259
-
260
- # Notes on parameters:
261
- # - To call a data source which requires authentication, provide a hash with each required auth scheme as key and
262
- # as the value a reference to a previously created credential.
263
- # Example: { scheme_ref1 => credential_ref1, scheme_ref2 => credential_ref2 }
264
- #
265
- # This returns a hashified preview like the following:
266
- # { "preview" => {
267
- # "sample" => { "status" => integer, "body" => { ... }, "headers" => { ... }, "cookies" => { ... } },
268
- # "fetched_at" => Timestamp },
269
- # "data_source" => { "id" => id, "resource_description" => resource_description } }
270
- #
271
- def preview_data_source(data_source_id, authentications: {}, &block)
272
- preview_data = {
273
- authentications: authentications.map { |scheme, cred| { auth_scheme_id: scheme, auth_credential_id: cred } }
274
- }
275
- result_handler(block).
276
- run { @client[data_source_preview_path(data_source_id)].post(preview_data) }
277
- end
278
-
279
- # This returns a hashified preview like the following:
280
-
281
- def accept_data_source_preview(data_source_id, &block)
282
- result_handler(block).
283
- run { @client[data_source_preview_path(data_source_id)].patch('') }
284
- end
285
-
286
- private
287
-
288
- def result_handler(block)
289
- ResultHandler.new(default_success_handler: @default_success_handler, default_error_handler: @default_error_handler, &block)
290
- end
291
-
292
- def rest_data_source_path
293
- "/projects/#{@org}/#{@project}/onboarding/data_sources/rest"
294
- end
295
-
296
- def data_source_preview_path(id)
297
- "/projects/#{@org}/#{@project}/onboarding/data_sources/#{id}/preview"
298
- end
75
+ def self.supported_type?(type)
76
+ SUPPORTED_RESOURCE_TYPES.include?(type)
299
77
  end
300
78
  end
301
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