xapixctl 1.1.0 → 1.2.2

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