xapixctl 1.1.2 → 1.2.4

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
+ 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