aspera-cli 4.25.0.pre → 4.25.0
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 +23 -17
- data/CONTRIBUTING.md +119 -47
- data/README.md +325 -239
- data/lib/aspera/agent/direct.rb +14 -12
- data/lib/aspera/agent/factory.rb +9 -6
- data/lib/aspera/agent/transferd.rb +8 -8
- data/lib/aspera/api/aoc.rb +33 -24
- data/lib/aspera/api/ats.rb +1 -0
- data/lib/aspera/api/faspex.rb +11 -5
- data/lib/aspera/ascmd.rb +1 -1
- data/lib/aspera/ascp/installation.rb +7 -7
- data/lib/aspera/ascp/management.rb +9 -5
- data/lib/aspera/assert.rb +3 -3
- data/lib/aspera/cli/extended_value.rb +10 -2
- data/lib/aspera/cli/formatter.rb +15 -62
- data/lib/aspera/cli/manager.rb +9 -43
- data/lib/aspera/cli/plugins/aoc.rb +71 -66
- data/lib/aspera/cli/plugins/ats.rb +30 -36
- data/lib/aspera/cli/plugins/base.rb +11 -6
- data/lib/aspera/cli/plugins/config.rb +21 -16
- data/lib/aspera/cli/plugins/console.rb +2 -1
- data/lib/aspera/cli/plugins/faspex.rb +7 -4
- data/lib/aspera/cli/plugins/faspex5.rb +12 -9
- data/lib/aspera/cli/plugins/faspio.rb +5 -2
- data/lib/aspera/cli/plugins/httpgw.rb +2 -1
- data/lib/aspera/cli/plugins/node.rb +10 -6
- data/lib/aspera/cli/plugins/oauth.rb +12 -11
- data/lib/aspera/cli/plugins/orchestrator.rb +2 -1
- data/lib/aspera/cli/plugins/preview.rb +2 -2
- data/lib/aspera/cli/plugins/server.rb +3 -2
- data/lib/aspera/cli/plugins/shares.rb +59 -20
- data/lib/aspera/cli/transfer_agent.rb +1 -2
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +5 -5
- data/lib/aspera/coverage.rb +5 -1
- data/lib/aspera/dot_container.rb +108 -0
- data/lib/aspera/environment.rb +69 -89
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/id_generator.rb +7 -10
- data/lib/aspera/keychain/macos_security.rb +2 -2
- data/lib/aspera/log.rb +2 -1
- data/lib/aspera/oauth/base.rb +25 -38
- data/lib/aspera/oauth/factory.rb +5 -6
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/url_json.rb +4 -3
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/preview/file_types.rb +1 -1
- data/lib/aspera/preview/terminal.rb +95 -29
- data/lib/aspera/preview/utils.rb +6 -5
- data/lib/aspera/rest.rb +5 -2
- data/lib/aspera/ssh.rb +6 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/operations.rb +3 -3
- data/lib/aspera/transfer/parameters.rb +6 -6
- data/lib/aspera/transfer/spec.schema.yaml +4 -4
- data/lib/aspera/transfer/spec_doc.rb +11 -21
- data/lib/aspera/uri_reader.rb +17 -3
- data.tar.gz.sig +0 -0
- metadata +17 -2
- metadata.gz.sig +0 -0
data/lib/aspera/cli/manager.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'aspera/colors'
|
|
|
6
6
|
require 'aspera/secret_hider'
|
|
7
7
|
require 'aspera/log'
|
|
8
8
|
require 'aspera/assert'
|
|
9
|
+
require 'aspera/dot_container'
|
|
9
10
|
require 'io/console'
|
|
10
11
|
require 'optparse'
|
|
11
12
|
|
|
@@ -345,7 +346,7 @@ module Aspera
|
|
|
345
346
|
raise Cli::BadArgument, "Invalid integer: #{result}" if int_result.nil?
|
|
346
347
|
result = int_result
|
|
347
348
|
end
|
|
348
|
-
Log.log.
|
|
349
|
+
Log.log.trace1{"#{descr}=#{result}"}
|
|
349
350
|
result = aliases[result] if aliases&.key?(result)
|
|
350
351
|
# if value comes from JSON/YAML, it may come as Integer
|
|
351
352
|
result = result.to_s if result.is_a?(Integer) && validation&.eql?(Allowed::TYPES_STRING)
|
|
@@ -486,11 +487,12 @@ module Aspera
|
|
|
486
487
|
Log.log.trace1('After parse')
|
|
487
488
|
rescue OptionParser::InvalidOption => e
|
|
488
489
|
Log.log.trace1{"InvalidOption #{e}".red}
|
|
490
|
+
# An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
|
|
489
491
|
if (m = e.args.first.match(/^--([a-z\-]+)\.([^=]+)=(.+)$/))
|
|
490
492
|
option, path, value = m.captures
|
|
491
493
|
option_sym = self.class.option_line_to_name(option).to_sym
|
|
492
494
|
if @declared_options.key?(option_sym)
|
|
493
|
-
set_option(option_sym,
|
|
495
|
+
set_option(option_sym, DotContainer.dotted_to_container(path, smart_convert(value), get_option(option_sym)), where: 'dotted')
|
|
494
496
|
retry
|
|
495
497
|
end
|
|
496
498
|
end
|
|
@@ -561,14 +563,14 @@ module Aspera
|
|
|
561
563
|
|
|
562
564
|
# Read remaining args and build an Array or Hash
|
|
563
565
|
# @param value [nil] Argument to `@:` extended value
|
|
564
|
-
def args_as_extended(
|
|
566
|
+
def args_as_extended(arg)
|
|
565
567
|
# This extended value does not take args (`@:`)
|
|
566
|
-
ExtendedValue.assert_no_value(
|
|
568
|
+
ExtendedValue.assert_no_value(arg, :p)
|
|
567
569
|
result = nil
|
|
568
570
|
get_next_argument(:args, multiple: true).each do |arg|
|
|
569
571
|
Aspera.assert(arg.include?(OPTION_VALUE_SEPARATOR)){"Positional argument: #{arg} does not inlude #{OPTION_VALUE_SEPARATOR}"}
|
|
570
|
-
path,
|
|
571
|
-
result =
|
|
572
|
+
path, value = arg.split(OPTION_VALUE_SEPARATOR, 2)
|
|
573
|
+
result = DotContainer.dotted_to_container(path, smart_convert(value), result)
|
|
572
574
|
end
|
|
573
575
|
result
|
|
574
576
|
end
|
|
@@ -590,40 +592,6 @@ module Aspera
|
|
|
590
592
|
end
|
|
591
593
|
end
|
|
592
594
|
|
|
593
|
-
# Convert `String` to `Integer`, or keep `String` if not `Integer`
|
|
594
|
-
def int_or_string(value)
|
|
595
|
-
Integer(value, exception: false) || value
|
|
596
|
-
end
|
|
597
|
-
|
|
598
|
-
def new_hash_or_array_from_key(key)
|
|
599
|
-
key.is_a?(Integer) ? [] : {}
|
|
600
|
-
end
|
|
601
|
-
|
|
602
|
-
def array_requires_integer_index!(container, index)
|
|
603
|
-
Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer)){'Using String index when Integer index used previously'}
|
|
604
|
-
end
|
|
605
|
-
|
|
606
|
-
# Insert extended value `value` into struct `result` at `path`
|
|
607
|
-
# @param path [String]
|
|
608
|
-
# @param value [String]
|
|
609
|
-
# @param result [NilClass, Hash, Array]
|
|
610
|
-
# @return [Hash, Array]
|
|
611
|
-
def dotted_to_extended(path, value, result = nil)
|
|
612
|
-
# Typed keys
|
|
613
|
-
keys = path.split(OPTION_DOTTED_SEPARATOR).map{ |k| int_or_string(k)}
|
|
614
|
-
# Create, or re-used higher level container
|
|
615
|
-
current = (result ||= new_hash_or_array_from_key(keys.first))
|
|
616
|
-
# walk the path, and create sub-containers if necessary
|
|
617
|
-
keys.each_cons(2) do |k, next_k|
|
|
618
|
-
array_requires_integer_index!(current, k)
|
|
619
|
-
current = (current[k] ||= new_hash_or_array_from_key(next_k))
|
|
620
|
-
end
|
|
621
|
-
# Assign value at last index
|
|
622
|
-
array_requires_integer_index!(current, keys.last)
|
|
623
|
-
current[keys.last] = smart_convert(value)
|
|
624
|
-
result
|
|
625
|
-
end
|
|
626
|
-
|
|
627
595
|
# generate command line option from option symbol
|
|
628
596
|
def symbol_to_option(symbol, opt_val = nil)
|
|
629
597
|
result = [OPTION_PREFIX, symbol.to_s.gsub(OPTION_SEP_SYMBOL, OPTION_SEP_LINE)].join
|
|
@@ -677,15 +645,13 @@ module Aspera
|
|
|
677
645
|
OPTION_SEP_SYMBOL = '_'
|
|
678
646
|
# Option value separator on command line, e.g. in --option-blah=foo, the "="
|
|
679
647
|
OPTION_VALUE_SEPARATOR = '='
|
|
680
|
-
# "." : An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
|
|
681
|
-
OPTION_DOTTED_SEPARATOR = '.'
|
|
682
648
|
# Starts an option, e.g. in --option-blah, the two first "--"
|
|
683
649
|
OPTION_PREFIX = '--'
|
|
684
650
|
# when this is alone, this stops option processing
|
|
685
651
|
OPTIONS_STOP = '--'
|
|
686
652
|
SOURCE_USER = 'cmdline' # cspell:disable-line
|
|
687
653
|
|
|
688
|
-
private_constant :BOOL_YES, :BOOL_NO, :FALSE_VALUES, :TRUE_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :
|
|
654
|
+
private_constant :BOOL_YES, :BOOL_NO, :FALSE_VALUES, :TRUE_VALUES, :OPTION_SEP_LINE, :OPTION_SEP_SYMBOL, :OPTION_VALUE_SEPARATOR, :OPTION_PREFIX, :OPTIONS_STOP, :SOURCE_USER
|
|
689
655
|
end
|
|
690
656
|
end
|
|
691
657
|
end
|
|
@@ -62,6 +62,7 @@ module Aspera
|
|
|
62
62
|
'Aspera on Cloud'
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# @return [Hash,NilClass]
|
|
65
66
|
def detect(base_url)
|
|
66
67
|
# no protocol ?
|
|
67
68
|
base_url = "https://#{base_url}" unless base_url.match?(%r{^[a-z]{1,6}://})
|
|
@@ -81,8 +82,10 @@ module Aspera
|
|
|
81
82
|
}
|
|
82
83
|
end
|
|
83
84
|
|
|
84
|
-
#
|
|
85
|
-
# @
|
|
85
|
+
# Get folder path that does not exist
|
|
86
|
+
# @param base [String] Base folder path
|
|
87
|
+
# @param always [Boolean] `true` always add number, `false` only if base folder already exists
|
|
88
|
+
# @return [String] Folder path that does not exist, with possible .<number> extension
|
|
86
89
|
def next_available_folder(base, always: false)
|
|
87
90
|
counter = always ? 1 : 0
|
|
88
91
|
loop do
|
|
@@ -95,27 +98,26 @@ module Aspera
|
|
|
95
98
|
# Get folder path that does not exist
|
|
96
99
|
# If it exists, an extension is added
|
|
97
100
|
# or a sequential number if extension == :seq
|
|
98
|
-
# @param
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
else
|
|
111
|
-
folder
|
|
112
|
-
end
|
|
101
|
+
# @param package_info [Hash] Package information
|
|
102
|
+
# @param destination_folder [String] Base folder
|
|
103
|
+
# @param fld. [Array] List of fields of package
|
|
104
|
+
def unique_folder(package_info, destination_folder, fld: nil, seq: false, opt: false)
|
|
105
|
+
Aspera.assert_array_all(fld, String, type: Cli::BadArgument){'fld'}
|
|
106
|
+
Aspera.assert([1, 2].include?(fld.length)){'fld must have 1 or 2 elements'}
|
|
107
|
+
folder = Environment.instance.sanitized_filename(package_info[fld[0]])
|
|
108
|
+
if seq
|
|
109
|
+
folder = next_available_folder(folder, always: !opt)
|
|
110
|
+
elsif fld[1] && (Dir.exist?(folder) || !opt)
|
|
111
|
+
# NOTE: it might already exist
|
|
112
|
+
folder = "#{folder}.#{Environment.instance.sanitized_filename(fld[1])}"
|
|
113
113
|
end
|
|
114
|
+
puts("sub= #{folder}")
|
|
115
|
+
File.join(destination_folder, folder)
|
|
114
116
|
end
|
|
115
117
|
end
|
|
116
118
|
|
|
117
119
|
# @param wizard [Wizard] The wizard object
|
|
118
|
-
# @param app_url [
|
|
120
|
+
# @param app_url [String] Tested URL
|
|
119
121
|
# @return [Hash] :preset_value, :test_args
|
|
120
122
|
def wizard(wizard, app_url)
|
|
121
123
|
pub_link_info = Api::AoC.link_info(app_url)
|
|
@@ -174,7 +176,7 @@ module Aspera
|
|
|
174
176
|
auto_set_jwt = true
|
|
175
177
|
Aspera.error_not_implemented
|
|
176
178
|
# aoc_api.oauth.grant_method = :web
|
|
177
|
-
# aoc_api.oauth.scope = Api::AoC::
|
|
179
|
+
# aoc_api.oauth.scope = Api::AoC::Scope::ADMIN
|
|
178
180
|
# aoc_api.oauth.specific_parameters[:redirect_uri] = REDIRECT_LOCALHOST
|
|
179
181
|
end
|
|
180
182
|
myself = aoc_api.read('self')
|
|
@@ -205,48 +207,58 @@ module Aspera
|
|
|
205
207
|
@cache_workspace_info = nil
|
|
206
208
|
@cache_home_node_file = nil
|
|
207
209
|
@cache_api_aoc = nil
|
|
210
|
+
@scope = Api::AoC::Scope::USER
|
|
208
211
|
options.declare(:workspace, 'Name of workspace', allowed: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
|
|
209
212
|
options.declare(:new_user_option, 'New user creation option for unknown package recipients', allowed: Hash)
|
|
210
213
|
options.declare(:validate_metadata, 'Validate shared inbox metadata', allowed: Allowed::TYPES_BOOLEAN, default: true)
|
|
211
|
-
options.declare(:package_folder, '
|
|
214
|
+
options.declare(:package_folder, 'Handling of reception of packages in folders', allowed: Hash, default: {})
|
|
212
215
|
options.parse_options!
|
|
213
216
|
# add node plugin options (for manual)
|
|
214
217
|
Node.declare_options(options)
|
|
215
218
|
end
|
|
216
219
|
|
|
220
|
+
# Change API scope for subsequent calls, re-instantiate API object
|
|
221
|
+
# @param new_scope [String] New scope
|
|
222
|
+
def change_api_scope(new_scope)
|
|
223
|
+
@cache_api_aoc = nil
|
|
224
|
+
@scope = new_scope
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# create an API object with the same options, but with a different subpath
|
|
228
|
+
# @param aoc_base_path [String] New subpath
|
|
229
|
+
# @return [Api::AoC] API object for AoC (is Rest)
|
|
217
230
|
def api_from_options(aoc_base_path)
|
|
218
|
-
# create an API object with the same options, but with a different subpath
|
|
219
231
|
return new_with_options(
|
|
220
232
|
Api::AoC,
|
|
221
|
-
|
|
233
|
+
kwargs: {
|
|
234
|
+
scope: @scope,
|
|
222
235
|
subpath: aoc_base_path,
|
|
223
236
|
secret_finder: config
|
|
224
237
|
},
|
|
225
|
-
|
|
226
|
-
scope: Api::AoC::SCOPE_FILES_USER,
|
|
238
|
+
option: {
|
|
227
239
|
workspace: nil
|
|
228
240
|
}
|
|
229
241
|
)
|
|
230
242
|
end
|
|
231
243
|
|
|
232
244
|
# AoC Rest object
|
|
233
|
-
# @return [Rest
|
|
245
|
+
# @return [Api::AoC] API object for AoC (is Rest)
|
|
234
246
|
def aoc_api
|
|
235
247
|
if @cache_api_aoc.nil?
|
|
236
248
|
@cache_api_aoc = api_from_options(Api::AoC::API_V1)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
transfer.httpgw_url_cb = lambda{organization['http_gateway_server_url']}
|
|
249
|
+
transfer.httpgw_url_cb = lambda do
|
|
250
|
+
organization = @cache_api_aoc.read('organization')
|
|
240
251
|
# @cache_api_aoc.current_user_info['connect_disabled']
|
|
252
|
+
organization['http_gateway_server_url'] if organization['http_gateway_enabled'] && organization['http_gateway_server_url']
|
|
241
253
|
end
|
|
242
254
|
end
|
|
243
255
|
return @cache_api_aoc
|
|
244
256
|
end
|
|
245
257
|
|
|
246
258
|
# Generate or update Hash with workspace id and name (option), if not already set
|
|
247
|
-
# @param hash [Hash,
|
|
248
|
-
# @param string [
|
|
249
|
-
# @param name [
|
|
259
|
+
# @param hash [Hash,nil] Optional base hash (modified)
|
|
260
|
+
# @param string [Boolean] true to set key as string, else as symbol
|
|
261
|
+
# @param name [Boolean] include name
|
|
250
262
|
# @return [Hash] with key `workspace_[id,name]` (symbol or string) only if defined
|
|
251
263
|
def workspace_id_hash(hash: nil, string: false, name: false)
|
|
252
264
|
info = aoc_api.workspace
|
|
@@ -319,8 +331,11 @@ module Aspera
|
|
|
319
331
|
NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
|
|
320
332
|
private_constant :NODE4_EXT_COMMANDS
|
|
321
333
|
|
|
322
|
-
#
|
|
323
|
-
# @param
|
|
334
|
+
# Execute a node gen4 command
|
|
335
|
+
# @param command_repo [Symbol] command to execute
|
|
336
|
+
# @param node_id [String] Node identifier
|
|
337
|
+
# @param file_id [String] Root file id for the operation (can be AK root, or other, e.g. package, or link). If `nil` use AK root file id.
|
|
338
|
+
# @param scope [String] node scope (Node::SCOPE_<USER|ADMIN>), or nil (requires secret)
|
|
324
339
|
def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil)
|
|
325
340
|
top_node_api = aoc_api.node_api_from(
|
|
326
341
|
node_id: node_id,
|
|
@@ -422,7 +437,7 @@ module Aspera
|
|
|
422
437
|
when :client_registration_token then default_fields.push('value', 'data.client_subject_scopes', 'created_at')
|
|
423
438
|
when :contact
|
|
424
439
|
default_fields = %w[source_type source_id name email]
|
|
425
|
-
default_query = {'include_only_user_personal_contacts' => true} if
|
|
440
|
+
default_query = {'include_only_user_personal_contacts' => true} if @scope == Api::AoC::Scope::USER
|
|
426
441
|
when :node then default_fields.push('name', 'host', 'access_key')
|
|
427
442
|
when :operation then default_fields = nil
|
|
428
443
|
when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
|
|
@@ -455,7 +470,7 @@ module Aspera
|
|
|
455
470
|
return Main.result_success
|
|
456
471
|
when :do
|
|
457
472
|
command_repo = options.get_next_command(NODE4_EXT_COMMANDS)
|
|
458
|
-
return execute_nodegen4_command(command_repo, res_id)
|
|
473
|
+
return execute_nodegen4_command(command_repo, res_id, scope: Api::Node::SCOPE_ADMIN)
|
|
459
474
|
when :bearer_token
|
|
460
475
|
node_api = aoc_api.node_api_from(
|
|
461
476
|
node_id: res_id,
|
|
@@ -514,13 +529,15 @@ module Aspera
|
|
|
514
529
|
end
|
|
515
530
|
end
|
|
516
531
|
|
|
517
|
-
ADMIN_ACTIONS = %i[ats resource usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
|
|
532
|
+
ADMIN_ACTIONS = %i[ats bearer_token resource usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
|
|
518
533
|
|
|
519
534
|
def execute_admin_action
|
|
520
|
-
#
|
|
521
|
-
|
|
535
|
+
# change scope to admin
|
|
536
|
+
change_api_scope(Api::AoC::Scope::ADMIN)
|
|
522
537
|
command_admin = options.get_next_command(ADMIN_ACTIONS)
|
|
523
538
|
case command_admin
|
|
539
|
+
when :bearer_token
|
|
540
|
+
return Main.result_text(aoc_api.oauth.authorization)
|
|
524
541
|
when :resource
|
|
525
542
|
Log.log.warn('resource command is deprecated (4.18), directly use the specific command instead')
|
|
526
543
|
return execute_resource_action(options.get_next_argument('resource', accept_list: ADMIN_OBJECTS))
|
|
@@ -649,13 +666,13 @@ module Aspera
|
|
|
649
666
|
when :ats
|
|
650
667
|
ats_api = Rest.new(**aoc_api.params.deep_merge({
|
|
651
668
|
base_url: "#{aoc_api.base_url}/admin/ats/pub/v1",
|
|
652
|
-
auth: {scope: Api::AoC::
|
|
669
|
+
auth: {params: {scope: Api::AoC::Scope::ADMIN_USER}}
|
|
653
670
|
}))
|
|
654
|
-
return Ats.new(context: context).
|
|
671
|
+
return Ats.new(context: context, api: ats_api).execute_action
|
|
655
672
|
when :analytics
|
|
656
673
|
analytics_api = Rest.new(**aoc_api.params.deep_merge({
|
|
657
674
|
base_url: "#{aoc_api.base_url.gsub('/api/v1', '')}/analytics/v2",
|
|
658
|
-
auth: {scope: Api::AoC::
|
|
675
|
+
auth: {params: {scope: Api::AoC::Scope::ADMIN_USER}}
|
|
659
676
|
}))
|
|
660
677
|
command_analytics = options.get_next_command(%i[application_events transfers files])
|
|
661
678
|
case command_analytics
|
|
@@ -680,13 +697,13 @@ module Aspera
|
|
|
680
697
|
start_date_persistency = PersistencyActionOnce.new(
|
|
681
698
|
manager: persistency,
|
|
682
699
|
data: saved_date,
|
|
683
|
-
id: IdGenerator.from_list(
|
|
700
|
+
id: IdGenerator.from_list(
|
|
684
701
|
'aoc_ana_date',
|
|
685
702
|
options.get_option(:url, mandatory: true),
|
|
686
703
|
aoc_api.workspace[:name],
|
|
687
704
|
event_resource_type.to_s,
|
|
688
705
|
event_resource_id
|
|
689
|
-
|
|
706
|
+
)
|
|
690
707
|
)
|
|
691
708
|
start_date_time = saved_date.first
|
|
692
709
|
stop_date_time = Time.now.utc.strftime('%FT%T.%LZ')
|
|
@@ -839,9 +856,10 @@ module Aspera
|
|
|
839
856
|
manager: persistency,
|
|
840
857
|
data: [],
|
|
841
858
|
id: IdGenerator.from_list(
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
859
|
+
'aoc_recv',
|
|
860
|
+
options.get_option(:url, mandatory: true),
|
|
861
|
+
aoc_api.workspace[:id],
|
|
862
|
+
aoc_api.additional_persistence_ids
|
|
845
863
|
)
|
|
846
864
|
)
|
|
847
865
|
end
|
|
@@ -977,13 +995,8 @@ module Aspera
|
|
|
977
995
|
end
|
|
978
996
|
# download all files, or specified list only
|
|
979
997
|
ts_paths = transfer.ts_source_paths(default: ['.'])
|
|
980
|
-
per_package_def = options.get_option(:package_folder)
|
|
981
|
-
|
|
982
|
-
raise Cli::BadArgument, "Invalid package folder option : #{per_package_def}" unless per_package_def =~ /\A([^+]+)(?:\+([^?]+)(\?)?)?\z/
|
|
983
|
-
per_package_field1 = Regexp.last_match(1)
|
|
984
|
-
per_package_field2 = Regexp.last_match(2)
|
|
985
|
-
per_package_sub_always = Regexp.last_match(3).nil?
|
|
986
|
-
end
|
|
998
|
+
per_package_def = options.get_option(:package_folder).symbolize_keys
|
|
999
|
+
save_metadata = per_package_def.delete(:inf)
|
|
987
1000
|
# get value outside of loop
|
|
988
1001
|
destination_folder = transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE)
|
|
989
1002
|
result_transfer = []
|
|
@@ -999,23 +1012,14 @@ module Aspera
|
|
|
999
1012
|
Transfer::Spec::DIRECTION_RECEIVE,
|
|
1000
1013
|
{'paths'=> ts_paths}
|
|
1001
1014
|
)
|
|
1002
|
-
unless per_package_def.
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
destination_folder,
|
|
1006
|
-
Environment.instance.sanitized_filename(package_info[per_package_field1])
|
|
1007
|
-
)
|
|
1008
|
-
transfer.user_transfer_spec['destination_root'] = self.class.unique_folder(
|
|
1009
|
-
folder,
|
|
1010
|
-
extension: per_package_field2.eql?('seq') ? :seq : package_info[per_package_field2],
|
|
1011
|
-
always: per_package_sub_always
|
|
1012
|
-
)
|
|
1013
|
-
end
|
|
1014
|
-
formatter.display_status(%Q{Downloading package: [#{package_info['id']}] "#{package_info['name']}" to [#{destination_folder}]})
|
|
1015
|
+
transfer.user_transfer_spec['destination_root'] = self.class.unique_folder(package_info, destination_folder, **per_package_def) unless per_package_def.empty?
|
|
1016
|
+
dest_folder = transfer.user_transfer_spec['destination_root'] || destination_folder
|
|
1017
|
+
formatter.display_status(%Q{Downloading package: [#{package_info['id']}] "#{package_info['name']}" to [#{dest_folder}]})
|
|
1015
1018
|
statuses = transfer.start(
|
|
1016
1019
|
transfer_spec,
|
|
1017
1020
|
rest_token: package_node_api
|
|
1018
1021
|
)
|
|
1022
|
+
File.write(File.join(dest_folder, "#{package_id}.info.json"), package_info.to_json) if save_metadata
|
|
1019
1023
|
result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
|
|
1020
1024
|
# update skip list only if all transfer sessions completed
|
|
1021
1025
|
if skip_ids_persistency && TransferAgent.session_status(statuses).eql?(:success)
|
|
@@ -1092,6 +1096,7 @@ module Aspera
|
|
|
1092
1096
|
end
|
|
1093
1097
|
end
|
|
1094
1098
|
when :automation
|
|
1099
|
+
change_api_scope(Api::AoC::Scope::ADMIN_USER)
|
|
1095
1100
|
Log.log.warn('BETA: work under progress')
|
|
1096
1101
|
# automation api is not in the same place
|
|
1097
1102
|
automation_api = Rest.new(**aoc_api.params, base_url: aoc_api.base_url.gsub('/api/', '/automation/'))
|
|
@@ -18,10 +18,10 @@ module Aspera
|
|
|
18
18
|
# columns for list of cloud providers
|
|
19
19
|
CLOUD_TABLE = %w[id name].freeze
|
|
20
20
|
private_constant :CLOUD_TABLE
|
|
21
|
-
def initialize(**
|
|
22
|
-
super
|
|
23
|
-
@
|
|
24
|
-
@
|
|
21
|
+
def initialize(api: nil, **base_args)
|
|
22
|
+
super(**base_args)
|
|
23
|
+
@ats_api_open = Api::Ats.new
|
|
24
|
+
@ats_api_auth = api
|
|
25
25
|
options.declare(:ibm_api_key, 'IBM API key, see https://cloud.ibm.com/iam/apikeys')
|
|
26
26
|
options.declare(:instance, 'ATS instance in ibm cloud')
|
|
27
27
|
options.declare(:ats_key, 'ATS key identifier (ats_xxx)')
|
|
@@ -36,13 +36,13 @@ module Aspera
|
|
|
36
36
|
# TODO: provide list ?
|
|
37
37
|
cloud = options.get_option(:cloud, mandatory: true).upcase
|
|
38
38
|
region = options.get_option(:region, mandatory: true)
|
|
39
|
-
return @
|
|
39
|
+
return @ats_api_open.read("servers/#{cloud}/#{region}")
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# require api key only if needed
|
|
43
|
-
def
|
|
44
|
-
return @
|
|
45
|
-
@
|
|
43
|
+
def ats_api
|
|
44
|
+
return @ats_api_auth unless @ats_api_auth.nil?
|
|
45
|
+
@ats_api_auth = Rest.new(
|
|
46
46
|
base_url: "#{Api::Ats::SERVICE_BASE_URL}/pub/v1",
|
|
47
47
|
auth: {
|
|
48
48
|
type: :basic,
|
|
@@ -73,10 +73,10 @@ module Aspera
|
|
|
73
73
|
when 'ibm-s3'
|
|
74
74
|
server_data2 = nil
|
|
75
75
|
if server_data.nil?
|
|
76
|
-
server_data2 = @
|
|
76
|
+
server_data2 = @ats_api_open.all_servers.find{ |s| s['id'].eql?(params['transfer_server_id'])}
|
|
77
77
|
raise "no such transfer server id: #{params['transfer_server_id']}" if server_data2.nil?
|
|
78
78
|
else
|
|
79
|
-
server_data2 = @
|
|
79
|
+
server_data2 = @ats_api_open.all_servers.find do |s|
|
|
80
80
|
s['cloud'].eql?(server_data['cloud']) &&
|
|
81
81
|
s['region'].eql?(server_data['region']) &&
|
|
82
82
|
s.key?('s3_authentication_endpoint')
|
|
@@ -88,31 +88,31 @@ module Aspera
|
|
|
88
88
|
params['storage']['endpoint'] = server_data2['s3_authentication_endpoint'] if !params['storage'].key?('authentication_endpoint')
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
|
-
res =
|
|
91
|
+
res = ats_api.create('access_keys', params)
|
|
92
92
|
return Main.result_single_object(res)
|
|
93
93
|
# TODO : action : modify, with "PUT"
|
|
94
94
|
when :list
|
|
95
95
|
params = query_read_delete(default: {'offset' => 0, 'max_results' => 1000})
|
|
96
|
-
res =
|
|
96
|
+
res = ats_api.read('access_keys', params)
|
|
97
97
|
return Main.result_object_list(res['data'], fields: ['name', 'id', 'created.at', 'modified.at'])
|
|
98
98
|
when :show
|
|
99
|
-
res =
|
|
99
|
+
res = ats_api.read("access_keys/#{access_key_id}")
|
|
100
100
|
return Main.result_single_object(res)
|
|
101
101
|
when :modify
|
|
102
102
|
params = value_create_modify(command: command)
|
|
103
103
|
params['id'] = access_key_id
|
|
104
|
-
|
|
104
|
+
ats_api.update("access_keys/#{access_key_id}", params)
|
|
105
105
|
return Main.result_status('modified')
|
|
106
106
|
when :entitlement
|
|
107
|
-
ak =
|
|
107
|
+
ak = ats_api.read("access_keys/#{access_key_id}")
|
|
108
108
|
api_bss = Api::Alee.new(ak['license']['entitlement_id'], ak['license']['customer_id'])
|
|
109
109
|
return Main.result_single_object(api_bss.read('entitlement'))
|
|
110
110
|
when :delete
|
|
111
|
-
|
|
111
|
+
ats_api.delete("access_keys/#{access_key_id}")
|
|
112
112
|
return Main.result_status("deleted #{access_key_id}")
|
|
113
113
|
when :node
|
|
114
|
-
ak_data =
|
|
115
|
-
server_data = @
|
|
114
|
+
ak_data = ats_api.read("access_keys/#{access_key_id}")
|
|
115
|
+
server_data = @ats_api_open.all_servers.find{ |i| i['id'].start_with?(ak_data['transfer_server_id'])}
|
|
116
116
|
raise Cli::Error, 'no such server found' if server_data.nil?
|
|
117
117
|
node_url = server_data['transfer_setup_url']
|
|
118
118
|
api_node = Api::Node.new(
|
|
@@ -126,7 +126,7 @@ module Aspera
|
|
|
126
126
|
command = options.get_next_command(Node::COMMANDS_GEN4)
|
|
127
127
|
return Node.new(context: context, api: api_node).execute_command_gen4(command, ak_data['root_file_id'])
|
|
128
128
|
when :cluster
|
|
129
|
-
ats_url =
|
|
129
|
+
ats_url = ats_api.base_url
|
|
130
130
|
api_ak_auth = Rest.new(
|
|
131
131
|
base_url: ats_url,
|
|
132
132
|
auth: {
|
|
@@ -140,19 +140,19 @@ module Aspera
|
|
|
140
140
|
end
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
def
|
|
143
|
+
def execute_action_cluster_open
|
|
144
144
|
command = options.get_next_command(%i[clouds list show])
|
|
145
145
|
case command
|
|
146
146
|
when :clouds
|
|
147
|
-
return Main.result_object_list(@
|
|
147
|
+
return Main.result_object_list(@ats_api_open.cloud_names.map{ |k, v| CLOUD_TABLE.zip([k, v]).to_h})
|
|
148
148
|
when :list
|
|
149
|
-
return Main.result_object_list(@
|
|
149
|
+
return Main.result_object_list(@ats_api_open.all_servers, fields: %w[id cloud region])
|
|
150
150
|
when :show
|
|
151
151
|
if options.get_option(:cloud) || options.get_option(:region)
|
|
152
152
|
server_data = server_by_cloud_region
|
|
153
153
|
else
|
|
154
154
|
server_id = instance_identifier
|
|
155
|
-
server_data = @
|
|
155
|
+
server_data = @ats_api_open.all_servers.find{ |i| i['id'].eql?(server_id)}
|
|
156
156
|
raise BadIdentifier.new('server', server_id) if server_data.nil?
|
|
157
157
|
end
|
|
158
158
|
return Main.result_single_object(server_data)
|
|
@@ -170,7 +170,9 @@ module Aspera
|
|
|
170
170
|
# does not work: base_url: 'https://iam.cloud.ibm.com/identity',
|
|
171
171
|
grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
|
|
172
172
|
response_type: 'cloud_iam',
|
|
173
|
-
|
|
173
|
+
params: {
|
|
174
|
+
apikey: options.get_option(:ibm_api_key, mandatory: true)
|
|
175
|
+
}
|
|
174
176
|
}
|
|
175
177
|
)
|
|
176
178
|
end
|
|
@@ -213,31 +215,23 @@ module Aspera
|
|
|
213
215
|
ACTIONS = %i[cluster access_key api_key aws_trust_policy].freeze
|
|
214
216
|
|
|
215
217
|
# called for legacy and AoC
|
|
216
|
-
def
|
|
218
|
+
def execute_action
|
|
217
219
|
actions = ACTIONS.dup
|
|
218
|
-
actions.delete(:api_key) unless
|
|
220
|
+
actions.delete(:api_key) unless @ats_api_auth.nil?
|
|
219
221
|
command = options.get_next_command(actions)
|
|
220
|
-
@ats_api_pub_v1_cache = ats_api_arg
|
|
221
|
-
# keep as member variable as we may want to use the api in AoC name space
|
|
222
|
-
@ats_api_pub = Api::Ats.new
|
|
223
222
|
case command
|
|
224
223
|
when :cluster # display general ATS cluster information, this uses public API, no auth
|
|
225
|
-
return
|
|
224
|
+
return execute_action_cluster_open
|
|
226
225
|
when :access_key
|
|
227
226
|
return execute_action_access_key
|
|
228
227
|
when :api_key # manage credential to access ATS API
|
|
229
228
|
return execute_action_api_key
|
|
230
229
|
when :aws_trust_policy
|
|
231
|
-
res =
|
|
230
|
+
res = ats_api.read('aws/trustpolicy', {region: options.get_option(:region, mandatory: true)})
|
|
232
231
|
return Main.result_single_object(res)
|
|
233
232
|
else Aspera.error_unexpected_value(command)
|
|
234
233
|
end
|
|
235
234
|
end
|
|
236
|
-
|
|
237
|
-
# called for legacy ATS only
|
|
238
|
-
def execute_action
|
|
239
|
-
execute_action_gen(nil)
|
|
240
|
-
end
|
|
241
235
|
end
|
|
242
236
|
end
|
|
243
237
|
end
|
|
@@ -48,10 +48,15 @@ module Aspera
|
|
|
48
48
|
# Global objects
|
|
49
49
|
attr_reader :context
|
|
50
50
|
|
|
51
|
+
# @return [Manager]
|
|
51
52
|
def options; @context.options; end
|
|
53
|
+
# @return [TransferAgent]
|
|
52
54
|
def transfer; @context.transfer; end
|
|
55
|
+
# @return [Config]
|
|
53
56
|
def config; @context.config; end
|
|
57
|
+
# @return [Formatter]
|
|
54
58
|
def formatter; @context.formatter; end
|
|
59
|
+
# @return [PersistencyFolder]
|
|
55
60
|
def persistency; @context.persistency; end
|
|
56
61
|
|
|
57
62
|
def add_manual_header(has_options = true)
|
|
@@ -131,12 +136,12 @@ module Aspera
|
|
|
131
136
|
# @param command [Symbol] command to execute: create show list modify delete
|
|
132
137
|
# @param display_fields [Array] fields to display by default
|
|
133
138
|
# @param items_key [String] result is in a sub key of the json
|
|
134
|
-
# @param delete_style [String]
|
|
135
|
-
# @param id_as_arg [String]
|
|
136
|
-
# @param is_singleton [Boolean]
|
|
137
|
-
# @param tclo [
|
|
138
|
-
# @param block [Proc]
|
|
139
|
-
# @return
|
|
139
|
+
# @param delete_style [String] If set, the delete operation by array in payload
|
|
140
|
+
# @param id_as_arg [String] If set, the id is provided as url argument ?<id_as_arg>=<id>
|
|
141
|
+
# @param is_singleton [Boolean] If `true`, entity is the full path to the resource
|
|
142
|
+
# @param tclo [Boolean] If `true`, :list use paging with total_count, limit, offset
|
|
143
|
+
# @param block [Proc] Block to search for identifier based on attribute value
|
|
144
|
+
# @return [Hash] Result suitable for CLI result
|
|
140
145
|
def entity_execute(
|
|
141
146
|
api:,
|
|
142
147
|
entity:,
|