aspera-cli 4.24.1 → 4.24.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +15 -2
- data/README.md +745 -436
- data/bin/ascli +20 -1
- data/bin/asession +23 -27
- data/lib/aspera/agent/base.rb +10 -21
- data/lib/aspera/agent/connect.rb +2 -3
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +49 -32
- data/lib/aspera/agent/factory.rb +31 -0
- data/lib/aspera/api/aoc.rb +79 -49
- data/lib/aspera/api/faspex.rb +212 -0
- data/lib/aspera/api/node.rb +99 -84
- data/lib/aspera/ascp/installation.rb +22 -21
- data/lib/aspera/ascp/management.rb +119 -23
- data/lib/aspera/assert.rb +14 -8
- data/lib/aspera/cli/extended_value.rb +15 -15
- data/lib/aspera/cli/formatter.rb +7 -5
- data/lib/aspera/cli/hints.rb +8 -0
- data/lib/aspera/cli/info.rb +4 -4
- data/lib/aspera/cli/main.rb +55 -70
- data/lib/aspera/cli/manager.rb +7 -4
- data/lib/aspera/cli/plugins/alee.rb +2 -1
- data/lib/aspera/cli/plugins/aoc.rb +110 -186
- data/lib/aspera/cli/plugins/ats.rb +4 -4
- data/lib/aspera/cli/plugins/base.rb +335 -0
- data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
- data/lib/aspera/cli/plugins/config.rb +249 -220
- data/lib/aspera/cli/plugins/console.rb +15 -15
- data/lib/aspera/cli/plugins/cos.rb +2 -2
- data/lib/aspera/cli/plugins/factory.rb +78 -0
- data/lib/aspera/cli/plugins/faspex.rb +17 -20
- data/lib/aspera/cli/plugins/faspex5.rb +79 -193
- data/lib/aspera/cli/plugins/faspio.rb +14 -13
- data/lib/aspera/cli/plugins/httpgw.rb +13 -12
- data/lib/aspera/cli/plugins/node.rb +34 -32
- data/lib/aspera/cli/plugins/oauth.rb +48 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +15 -13
- data/lib/aspera/cli/plugins/preview.rb +4 -4
- data/lib/aspera/cli/plugins/server.rb +15 -13
- data/lib/aspera/cli/plugins/shares.rb +18 -15
- data/lib/aspera/cli/sync_actions.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +24 -20
- data/lib/aspera/cli/transfer_progress.rb +6 -6
- data/lib/aspera/cli/version.rb +3 -3
- data/lib/aspera/cli/wizard.rb +65 -53
- data/lib/aspera/colors.rb +6 -0
- data/lib/aspera/command_line_builder.rb +45 -50
- data/lib/aspera/command_line_converter.rb +2 -1
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +10 -7
- data/lib/aspera/faspex_gw.rb +6 -4
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +1 -1
- data/lib/aspera/log.rb +37 -9
- data/lib/aspera/nagios.rb +1 -1
- data/lib/aspera/oauth/base.rb +17 -10
- data/lib/aspera/oauth/factory.rb +8 -8
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/products/connect.rb +4 -3
- data/lib/aspera/products/desktop.rb +1 -4
- data/lib/aspera/products/other.rb +9 -1
- data/lib/aspera/products/transferd.rb +0 -1
- data/lib/aspera/rest.rb +126 -83
- data/lib/aspera/ssh.rb +3 -3
- data/lib/aspera/sync/args.schema.yaml +46 -3
- data/lib/aspera/sync/conf.schema.yaml +130 -94
- data/lib/aspera/sync/operations.rb +16 -16
- data/lib/aspera/temp_file_manager.rb +17 -5
- data/lib/aspera/transfer/error.rb +16 -7
- data/lib/aspera/transfer/parameters.rb +34 -20
- data/lib/aspera/transfer/resumer.rb +74 -0
- data/lib/aspera/transfer/spec.rb +4 -3
- data/lib/aspera/transfer/spec.schema.yaml +132 -51
- data/lib/aspera/transfer/spec_doc.rb +41 -35
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +6 -6
- data.tar.gz.sig +0 -0
- metadata +9 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
- data/lib/aspera/cli/plugin.rb +0 -333
- data/lib/aspera/cli/plugin_factory.rb +0 -81
- data/lib/aspera/resumer.rb +0 -77
- data/lib/aspera/transfer/error_info.rb +0 -91
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/cli/extended_value'
|
|
4
|
+
require 'aspera/assert'
|
|
5
|
+
|
|
6
|
+
module Aspera
|
|
7
|
+
module Cli
|
|
8
|
+
module Plugins
|
|
9
|
+
# Base class for command plugins
|
|
10
|
+
class Base
|
|
11
|
+
# Operations without id (create list)
|
|
12
|
+
GLOBAL_OPS = %i[create list].freeze
|
|
13
|
+
# Operations with id (modify delete show)
|
|
14
|
+
INSTANCE_OPS = %i[modify delete show].freeze
|
|
15
|
+
# All standard operations (create list modify delete show)
|
|
16
|
+
ALL_OPS = (GLOBAL_OPS + INSTANCE_OPS).freeze
|
|
17
|
+
# Special query parameter: `max`: max number of items for list command
|
|
18
|
+
MAX_ITEMS = 'max'
|
|
19
|
+
# Special query parameter: `pmax`: max number of pages for list command
|
|
20
|
+
MAX_PAGES = 'pmax'
|
|
21
|
+
# Special identifier format: look for this name to find where supported
|
|
22
|
+
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
def declare_options(options)
|
|
26
|
+
options.declare(:query, 'Additional filter for for some commands (list/delete)', types: [Hash, Array])
|
|
27
|
+
options.declare(:property, 'Name of property to set (modify operation)')
|
|
28
|
+
options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
|
|
29
|
+
options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(context:)
|
|
34
|
+
# Check presence in descendant of mandatory method and constant
|
|
35
|
+
Aspera.assert(respond_to?(:execute_action), type: InternalError){"Missing method 'execute_action' in #{self.class}"}
|
|
36
|
+
Aspera.assert(self.class.constants.include?(:ACTIONS), type: InternalError){"Missing constant 'ACTIONS' in #{self.class}"}
|
|
37
|
+
@context = context
|
|
38
|
+
add_manual_header if @context.man_header
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Global objects
|
|
42
|
+
attr_reader :context
|
|
43
|
+
|
|
44
|
+
def options; @context.options; end
|
|
45
|
+
def transfer; @context.transfer; end
|
|
46
|
+
def config; @context.config; end
|
|
47
|
+
def formatter; @context.formatter; end
|
|
48
|
+
def persistency; @context.persistency; end
|
|
49
|
+
|
|
50
|
+
def add_manual_header(has_options = true)
|
|
51
|
+
# Manual header for all plugins
|
|
52
|
+
options.parser.separator('')
|
|
53
|
+
options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
|
|
54
|
+
options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
|
|
55
|
+
options.parser.separator('OPTIONS:') if has_options
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Must be called AFTER the instance action:
|
|
59
|
+
# ... folder browse _call_instance_identifier
|
|
60
|
+
#
|
|
61
|
+
# @param description [String] description of the identifier
|
|
62
|
+
# @param as_option [Symbol] option name to use if identifier is an option
|
|
63
|
+
# @param block [Proc] block to search for identifier based on attribute value
|
|
64
|
+
# @return [String, Array] identifier or list of ids
|
|
65
|
+
def instance_identifier(description: 'identifier', as_option: nil, &block)
|
|
66
|
+
if as_option.nil?
|
|
67
|
+
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
68
|
+
else
|
|
69
|
+
res_id = options.get_option(as_option)
|
|
70
|
+
end
|
|
71
|
+
# Can be an Array
|
|
72
|
+
if res_id.is_a?(String) && (m = res_id.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
73
|
+
if block
|
|
74
|
+
res_id = yield(m[1], ExtendedValue.instance.evaluate(m[2]))
|
|
75
|
+
else
|
|
76
|
+
raise Cli::BadArgument, "Percent syntax for #{description} not supported in this context"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
return res_id
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# For create and delete operations: execute one action or multiple if bulk is yes
|
|
83
|
+
# @param command [Symbol] operation: :create, :delete, ...
|
|
84
|
+
# @param descr [String] description of the value
|
|
85
|
+
# @param values [Object] the value(s), or the type of value to get from user
|
|
86
|
+
# @param id_result [String] key in result hash to use as identifier
|
|
87
|
+
# @param fields [Array] fields to display
|
|
88
|
+
# @param &block [Proc] block to execute for each value
|
|
89
|
+
def do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default)
|
|
90
|
+
Aspera.assert(block_given?){'missing block'}
|
|
91
|
+
is_bulk = options.get_option(:bulk)
|
|
92
|
+
case values
|
|
93
|
+
when :identifier
|
|
94
|
+
values = instance_identifier(description: descr)
|
|
95
|
+
when Class
|
|
96
|
+
values = value_create_modify(command: command, description: descr, type: values, bulk: is_bulk)
|
|
97
|
+
end
|
|
98
|
+
# If not bulk, there is a single value
|
|
99
|
+
params = is_bulk ? values : [values]
|
|
100
|
+
Log.log.warn('Empty list given for bulk operation') if params.empty?
|
|
101
|
+
Log.dump(:bulk_operation, params)
|
|
102
|
+
result_list = []
|
|
103
|
+
params.each do |param|
|
|
104
|
+
# Init for delete
|
|
105
|
+
result = {id_result => param}
|
|
106
|
+
begin
|
|
107
|
+
# Execute custom code
|
|
108
|
+
res = yield(param)
|
|
109
|
+
# If block returns a hash, let's use this (create)
|
|
110
|
+
result = res if res.is_a?(Hash)
|
|
111
|
+
# TODO: remove when faspio gw api fixes this
|
|
112
|
+
result = res.first if res.is_a?(Array) && res.first.is_a?(Hash)
|
|
113
|
+
# Create -> created
|
|
114
|
+
result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
raise e if options.get_option(:bfail)
|
|
117
|
+
result['status'] = e.to_s
|
|
118
|
+
end
|
|
119
|
+
result_list.push(result)
|
|
120
|
+
end
|
|
121
|
+
display_fields = [id_result, 'status']
|
|
122
|
+
if is_bulk
|
|
123
|
+
return Main.result_object_list(result_list, fields: display_fields)
|
|
124
|
+
else
|
|
125
|
+
display_fields = fields unless fields.eql?(:default)
|
|
126
|
+
return Main.result_single_object(result_list.first, fields: display_fields)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Operations: Create, Delete, Show, List, Modify
|
|
131
|
+
# @param api [Rest] api to use
|
|
132
|
+
# @param entity [String] sub path in URL to resource relative to base url
|
|
133
|
+
# @param command [Symbol] command to execute: create show list modify delete
|
|
134
|
+
# @param display_fields [Array] fields to display by default
|
|
135
|
+
# @param items_key [String] result is in a sub key of the json
|
|
136
|
+
# @param delete_style [String] if set, the delete operation by array in payload
|
|
137
|
+
# @param id_as_arg [String] if set, the id is provided as url argument ?<id_as_arg>=<id>
|
|
138
|
+
# @param is_singleton [Boolean] if true, entity is the full path to the resource
|
|
139
|
+
# @param tclo [Bool] if set, :list use paging with total_count, limit, offset
|
|
140
|
+
# @param block [Proc] block to search for identifier based on attribute value
|
|
141
|
+
# @return result suitable for CLI result
|
|
142
|
+
def entity_execute(
|
|
143
|
+
api:,
|
|
144
|
+
entity:,
|
|
145
|
+
command: nil,
|
|
146
|
+
display_fields: nil,
|
|
147
|
+
items_key: nil,
|
|
148
|
+
delete_style: nil,
|
|
149
|
+
id_as_arg: false,
|
|
150
|
+
is_singleton: false,
|
|
151
|
+
list_query: nil,
|
|
152
|
+
tclo: false,
|
|
153
|
+
&block
|
|
154
|
+
)
|
|
155
|
+
command = options.get_next_command(ALL_OPS) if command.nil?
|
|
156
|
+
if is_singleton
|
|
157
|
+
one_res_path = entity
|
|
158
|
+
elsif INSTANCE_OPS.include?(command)
|
|
159
|
+
one_res_id = instance_identifier(&block)
|
|
160
|
+
one_res_path = "#{entity}/#{one_res_id}"
|
|
161
|
+
one_res_path = "#{entity}?#{id_as_arg}=#{one_res_id}" if id_as_arg
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
case command
|
|
165
|
+
when :create
|
|
166
|
+
raise BadArgument, 'cannot create singleton' if is_singleton
|
|
167
|
+
return do_bulk_operation(command: command, descr: 'data', fields: display_fields) do |params|
|
|
168
|
+
api.create(entity, params)
|
|
169
|
+
end
|
|
170
|
+
when :delete
|
|
171
|
+
raise BadArgument, 'cannot delete singleton' if is_singleton
|
|
172
|
+
if !delete_style.nil?
|
|
173
|
+
one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
|
|
174
|
+
Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
|
|
175
|
+
api.call(
|
|
176
|
+
operation: 'DELETE',
|
|
177
|
+
subpath: entity,
|
|
178
|
+
content_type: Rest::MIME_JSON,
|
|
179
|
+
body: {delete_style => one_res_id},
|
|
180
|
+
headers: {'Accept' => Rest::MIME_JSON}
|
|
181
|
+
)
|
|
182
|
+
return Main.result_status('deleted')
|
|
183
|
+
end
|
|
184
|
+
return do_bulk_operation(command: command, values: one_res_id) do |one_id|
|
|
185
|
+
api.delete("#{entity}/#{one_id}", query_read_delete)
|
|
186
|
+
{'id' => one_id}
|
|
187
|
+
end
|
|
188
|
+
when :show
|
|
189
|
+
return Main.result_single_object(api.read(one_res_path), fields: display_fields)
|
|
190
|
+
when :list
|
|
191
|
+
if tclo
|
|
192
|
+
data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: query_read_delete(default: list_query))
|
|
193
|
+
return Main.result_object_list(data, total: total, fields: display_fields)
|
|
194
|
+
end
|
|
195
|
+
resp = api.call(operation: 'GET', subpath: entity, headers: {'Accept' => Rest::MIME_JSON}, query: query_read_delete)
|
|
196
|
+
return Main.result_empty if resp[:http].code == '204'
|
|
197
|
+
data = resp[:data]
|
|
198
|
+
# TODO: not generic : which application is this for ?
|
|
199
|
+
if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
|
|
200
|
+
Log.log.debug('is vnd.api')
|
|
201
|
+
data = data[entity]
|
|
202
|
+
end
|
|
203
|
+
data = data[items_key] if items_key
|
|
204
|
+
case data
|
|
205
|
+
when Hash
|
|
206
|
+
return Main.result_single_object(data, fields: display_fields)
|
|
207
|
+
when Array
|
|
208
|
+
return Main.result_object_list(data, fields: display_fields) if data.empty? || data.first.is_a?(Hash)
|
|
209
|
+
return Main.result_value_list(data)
|
|
210
|
+
else
|
|
211
|
+
raise "An error occurred: unexpected result type for list: #{data.class}"
|
|
212
|
+
end
|
|
213
|
+
when :modify
|
|
214
|
+
parameters = value_create_modify(command: command)
|
|
215
|
+
property = options.get_option(:property)
|
|
216
|
+
parameters = {property => parameters} unless property.nil?
|
|
217
|
+
api.update(one_res_path, parameters)
|
|
218
|
+
return Main.result_status('modified')
|
|
219
|
+
else
|
|
220
|
+
raise "unknown action: #{command}"
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Query parameters in URL suitable for REST: list/GET and delete/DELETE
|
|
225
|
+
def query_read_delete(default: nil)
|
|
226
|
+
query = options.get_option(:query)
|
|
227
|
+
# Dup default, as it could be frozen
|
|
228
|
+
query = default.dup if query.nil?
|
|
229
|
+
Log.log.debug{"query_read_delete=#{query}".bg_red}
|
|
230
|
+
begin
|
|
231
|
+
# Check it is suitable
|
|
232
|
+
URI.encode_www_form(query) unless query.nil?
|
|
233
|
+
rescue StandardError => e
|
|
234
|
+
raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
|
|
235
|
+
end
|
|
236
|
+
return query
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Retrieves an extended value from command line, used for creation or modification of entities
|
|
240
|
+
# @param command [Symbol] command name for error message
|
|
241
|
+
# @param type [Class] expected type of value, either a Class, an Array of Class
|
|
242
|
+
# @param bulk [Boolean] if true, value must be an Array of <type>
|
|
243
|
+
# @param default [Object] default value if not provided
|
|
244
|
+
def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
|
|
245
|
+
value = options.get_next_argument(
|
|
246
|
+
"parameters for #{command}#{" (#{description})" unless description.nil?}", mandatory: default.nil?,
|
|
247
|
+
validation: bulk ? Array : type
|
|
248
|
+
)
|
|
249
|
+
value = default if value.nil?
|
|
250
|
+
unless type.nil?
|
|
251
|
+
type = [type] unless type.is_a?(Array)
|
|
252
|
+
Aspera.assert(type.all?(Class)){"check types must be a Class, not #{type.map(&:class).join(',')}"}
|
|
253
|
+
if bulk
|
|
254
|
+
Aspera.assert_type(value, Array, type: Cli::BadArgument)
|
|
255
|
+
value.each do |v|
|
|
256
|
+
Aspera.assert_values(v.class, type, type: Cli::BadArgument)
|
|
257
|
+
end
|
|
258
|
+
else
|
|
259
|
+
Aspera.assert_values(value.class, type, type: Cli::BadArgument)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
return value
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
266
|
+
# @param `api` [Rest] the API object
|
|
267
|
+
# @param `entity` [String,Symbol] the API endpoint of entity to list
|
|
268
|
+
# @param `items_key` [String] key in the result to get the list of items
|
|
269
|
+
# @param `query` [Hash,nil] additional query parameters
|
|
270
|
+
# @return [Array] items, total_count
|
|
271
|
+
def list_entities_limit_offset_total_count(
|
|
272
|
+
api:,
|
|
273
|
+
entity:,
|
|
274
|
+
items_key: nil,
|
|
275
|
+
query: nil
|
|
276
|
+
)
|
|
277
|
+
entity = entity.to_s if entity.is_a?(Symbol)
|
|
278
|
+
items_key = entity.split('/').last if items_key.nil?
|
|
279
|
+
query = {} if query.nil?
|
|
280
|
+
Aspera.assert_type(entity, String)
|
|
281
|
+
Aspera.assert_type(items_key, String)
|
|
282
|
+
Aspera.assert_type(query, Hash)
|
|
283
|
+
Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
|
|
284
|
+
result = []
|
|
285
|
+
offset = 0
|
|
286
|
+
max_items = query.delete(MAX_ITEMS)
|
|
287
|
+
remain_pages = query.delete(MAX_PAGES)
|
|
288
|
+
# Merge default parameters, by default 100 per page
|
|
289
|
+
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
290
|
+
total_count = nil
|
|
291
|
+
loop do
|
|
292
|
+
query['offset'] = offset
|
|
293
|
+
page_result = api.read(entity, query)
|
|
294
|
+
Aspera.assert_type(page_result[items_key], Array)
|
|
295
|
+
result.concat(page_result[items_key])
|
|
296
|
+
# Reach the limit set by user ?
|
|
297
|
+
if !max_items.nil? && (result.length >= max_items)
|
|
298
|
+
result = result.slice(0, max_items)
|
|
299
|
+
break
|
|
300
|
+
end
|
|
301
|
+
total_count ||= page_result['total_count']
|
|
302
|
+
break if result.length >= total_count
|
|
303
|
+
remain_pages -= 1 unless remain_pages.nil?
|
|
304
|
+
break if remain_pages == 0
|
|
305
|
+
offset += page_result[items_key].length
|
|
306
|
+
formatter.long_operation_running
|
|
307
|
+
end
|
|
308
|
+
formatter.long_operation_terminated
|
|
309
|
+
return result, total_count
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Lookup an entity id from its name
|
|
313
|
+
# @param entity [String] the type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
314
|
+
# @param value [String] the value to lookup
|
|
315
|
+
# @param field [String] the field to match, by default it is 'name'
|
|
316
|
+
# @param items_key [String] key in the result to get the list of items (override entity)
|
|
317
|
+
# @param query [Hash] additional query parameters
|
|
318
|
+
def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
319
|
+
if query.eql?(:default)
|
|
320
|
+
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
321
|
+
query = {'q'=> value}
|
|
322
|
+
end
|
|
323
|
+
found = list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first.select{ |i| i[field].eql?(value)}
|
|
324
|
+
case found.length
|
|
325
|
+
when 0 then raise "No #{entity} with #{field} = #{value}"
|
|
326
|
+
when 1 then return found.first
|
|
327
|
+
else raise "Found #{found.length} #{entity} with #{field} = #{value}"
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
PER_PAGE_DEFAULT = 1000
|
|
331
|
+
private_constant :PER_PAGE_DEFAULT
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/rest'
|
|
4
|
+
require 'aspera/cli/plugins/base'
|
|
5
|
+
|
|
6
|
+
module Aspera
|
|
7
|
+
module Cli
|
|
8
|
+
module Plugins
|
|
9
|
+
# base class for applications supporting basic authentication
|
|
10
|
+
class BasicAuth < Base
|
|
11
|
+
class << self
|
|
12
|
+
def declare_options(options)
|
|
13
|
+
options.declare(:url, 'URL of application, e.g. https://app.example.com/aspera/app')
|
|
14
|
+
options.declare(:username, "User's identifier")
|
|
15
|
+
options.declare(:password, "User's password")
|
|
16
|
+
options.parse_options!
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(context:, basic_options: true)
|
|
21
|
+
super(context: context)
|
|
22
|
+
BasicAuth.declare_options(options) if basic_options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# returns a Rest object with basic auth
|
|
26
|
+
def basic_auth_params(subpath = nil)
|
|
27
|
+
api_url = options.get_option(:url, mandatory: true)
|
|
28
|
+
api_url = "#{api_url}/#{subpath}" unless subpath.nil?
|
|
29
|
+
return {
|
|
30
|
+
base_url: api_url,
|
|
31
|
+
auth: {
|
|
32
|
+
type: :basic,
|
|
33
|
+
username: options.get_option(:username, mandatory: true),
|
|
34
|
+
password: options.get_option(:password, mandatory: true)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def basic_auth_api(subpath = nil)
|
|
40
|
+
return Rest.new(**basic_auth_params(subpath))
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|