aspera-cli 4.25.0.pre2 → 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 +3 -0
- data/CONTRIBUTING.md +60 -18
- data/README.md +164 -133
- data/lib/aspera/agent/factory.rb +9 -6
- data/lib/aspera/agent/transferd.rb +4 -4
- 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 +5 -5
- data/lib/aspera/cli/formatter.rb +15 -62
- data/lib/aspera/cli/manager.rb +8 -42
- data/lib/aspera/cli/plugins/aoc.rb +48 -30
- data/lib/aspera/cli/plugins/ats.rb +30 -36
- data/lib/aspera/cli/plugins/base.rb +6 -6
- data/lib/aspera/cli/plugins/config.rb +5 -4
- data/lib/aspera/cli/plugins/faspex.rb +5 -3
- data/lib/aspera/cli/plugins/faspex5.rb +10 -8
- data/lib/aspera/cli/plugins/faspio.rb +3 -1
- data/lib/aspera/cli/plugins/node.rb +9 -6
- data/lib/aspera/cli/plugins/oauth.rb +12 -11
- data/lib/aspera/cli/plugins/preview.rb +2 -2
- 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/dot_container.rb +108 -0
- data/lib/aspera/id_generator.rb +7 -10
- 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/rest.rb +5 -2
- data/lib/aspera/ssh.rb +6 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/transfer/parameters.rb +6 -6
- data/lib/aspera/transfer/spec.schema.yaml +3 -3
- data/lib/aspera/transfer/spec_doc.rb +11 -21
- data/lib/aspera/uri_reader.rb +17 -3
- data.tar.gz.sig +0 -0
- metadata +2 -1
- metadata.gz.sig +0 -0
|
@@ -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
|
|
@@ -136,12 +136,12 @@ module Aspera
|
|
|
136
136
|
# @param command [Symbol] command to execute: create show list modify delete
|
|
137
137
|
# @param display_fields [Array] fields to display by default
|
|
138
138
|
# @param items_key [String] result is in a sub key of the json
|
|
139
|
-
# @param delete_style [String]
|
|
140
|
-
# @param id_as_arg [String]
|
|
141
|
-
# @param is_singleton [Boolean]
|
|
142
|
-
# @param tclo [
|
|
143
|
-
# @param block [Proc]
|
|
144
|
-
# @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
|
|
145
145
|
def entity_execute(
|
|
146
146
|
api:,
|
|
147
147
|
entity:,
|
|
@@ -1170,16 +1170,17 @@ module Aspera
|
|
|
1170
1170
|
end
|
|
1171
1171
|
|
|
1172
1172
|
# Lookup the corresponding secret for the given URL and usernames
|
|
1173
|
-
# @
|
|
1174
|
-
|
|
1173
|
+
# @param url [String] Server URL
|
|
1174
|
+
# @param username [String] Username
|
|
1175
|
+
# @return [String, nil] Secret if found
|
|
1176
|
+
def lookup_secret(url:, username:)
|
|
1175
1177
|
secret = options.get_option(:secret)
|
|
1176
|
-
if secret.
|
|
1178
|
+
if secret.eql?('PRESET')
|
|
1177
1179
|
conf = lookup_preset(url: url, username: username)
|
|
1178
1180
|
if conf.is_a?(Hash)
|
|
1179
1181
|
Log.log.debug{"Found preset #{conf} with URL and username"}
|
|
1180
1182
|
secret = conf['password']
|
|
1181
1183
|
end
|
|
1182
|
-
raise "Please provide secret for #{username} using option: secret or by setting a preset for #{username}@#{url}." if secret.nil? && mandatory
|
|
1183
1184
|
end
|
|
1184
1185
|
return secret
|
|
1185
1186
|
end
|
|
@@ -150,7 +150,9 @@ module Aspera
|
|
|
150
150
|
grant_method: :generic,
|
|
151
151
|
base_url: "#{faspex_api_base}/auth/oauth2",
|
|
152
152
|
auth: {type: :basic, username: options.get_option(:username, mandatory: true), password: options.get_option(:password, mandatory: true)},
|
|
153
|
-
|
|
153
|
+
params: {
|
|
154
|
+
scope: 'admin'
|
|
155
|
+
},
|
|
154
156
|
grant_type: 'password'
|
|
155
157
|
}
|
|
156
158
|
)
|
|
@@ -337,12 +339,12 @@ module Aspera
|
|
|
337
339
|
skip_ids_persistency = PersistencyActionOnce.new(
|
|
338
340
|
manager: persistency,
|
|
339
341
|
data: skip_ids_data,
|
|
340
|
-
id: IdGenerator.from_list(
|
|
342
|
+
id: IdGenerator.from_list(
|
|
341
343
|
'faspex_recv',
|
|
342
344
|
options.get_option(:url, mandatory: true),
|
|
343
345
|
options.get_option(:username, mandatory: true),
|
|
344
346
|
options.get_option(:box, mandatory: true).to_s
|
|
345
|
-
|
|
347
|
+
)
|
|
346
348
|
)
|
|
347
349
|
end
|
|
348
350
|
# get command line parameters
|
|
@@ -79,12 +79,14 @@ module Aspera
|
|
|
79
79
|
)
|
|
80
80
|
return {
|
|
81
81
|
preset_value: {
|
|
82
|
-
url:
|
|
83
|
-
username:
|
|
84
|
-
auth:
|
|
85
|
-
private_key:
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
url: app_url,
|
|
83
|
+
username: wiz_username,
|
|
84
|
+
auth: :jwt.to_s,
|
|
85
|
+
private_key: "@file:#{private_key_path}",
|
|
86
|
+
params: {
|
|
87
|
+
client_id: client_id,
|
|
88
|
+
client_secret: client_secret
|
|
89
|
+
}
|
|
88
90
|
},
|
|
89
91
|
test_args: 'user profile show'
|
|
90
92
|
}
|
|
@@ -198,12 +200,12 @@ module Aspera
|
|
|
198
200
|
skip_ids_persistency = PersistencyActionOnce.new(
|
|
199
201
|
manager: persistency,
|
|
200
202
|
data: [],
|
|
201
|
-
id: IdGenerator.from_list(
|
|
203
|
+
id: IdGenerator.from_list(
|
|
202
204
|
'faspex_recv',
|
|
203
205
|
options.get_option(:url, mandatory: true),
|
|
204
206
|
options.get_option(:username, mandatory: true),
|
|
205
207
|
options.get_option(:box, mandatory: true)
|
|
206
|
-
|
|
208
|
+
)
|
|
207
209
|
)
|
|
208
210
|
end
|
|
209
211
|
packages = []
|
|
@@ -415,10 +415,12 @@ module Aspera
|
|
|
415
415
|
root_file_id = options.get_option(:root_id)
|
|
416
416
|
if root_file_id.nil?
|
|
417
417
|
ak_info = @api_node.read("access_keys/#{access_key_id}")
|
|
418
|
+
ak_secret = config.lookup_secret(url: @api_node.base_url, username: ak_info['id'])
|
|
418
419
|
# change API credentials if different access key
|
|
419
420
|
if !access_key_id.eql?('self')
|
|
421
|
+
Aspera.assert(ak_secret, type: Cli::MissingArgument){"Please provide secret for #{ak_info['id']} using option: secret or by setting a preset for #{ak_info['id']}@#{@api_node.base_url}."}
|
|
420
422
|
@api_node.auth_params[:username] = ak_info['id']
|
|
421
|
-
@api_node.auth_params[:password] =
|
|
423
|
+
@api_node.auth_params[:password] = ak_secret
|
|
422
424
|
end
|
|
423
425
|
root_file_id = ak_info['root_file_id']
|
|
424
426
|
end
|
|
@@ -517,7 +519,8 @@ module Aspera
|
|
|
517
519
|
else Aspera.error_unreachable_line
|
|
518
520
|
end
|
|
519
521
|
return Main.result_single_object(result) if command_repo.eql?(:node_info)
|
|
520
|
-
|
|
522
|
+
Log.dump(:result, result)
|
|
523
|
+
raise BadArgument, "Cannot get bearer token if authenticating with secret (#{apifid[:api].auth_params[:type]})" unless apifid[:api].auth_params[:type].eql?(:oauth2)
|
|
521
524
|
Aspera.assert(OAuth::Factory.bearer_auth?(result[:password])){'Not using bearer token auth'}
|
|
522
525
|
return Main.result_text(result[:password])
|
|
523
526
|
when :browse
|
|
@@ -728,12 +731,12 @@ module Aspera
|
|
|
728
731
|
skip_ids_persistency = PersistencyActionOnce.new(
|
|
729
732
|
manager: persistency,
|
|
730
733
|
data: iteration_data,
|
|
731
|
-
id: IdGenerator.from_list(
|
|
734
|
+
id: IdGenerator.from_list(
|
|
732
735
|
'sync_files',
|
|
733
736
|
options.get_option(:url, mandatory: true),
|
|
734
737
|
options.get_option(:username, mandatory: true),
|
|
735
738
|
async_id
|
|
736
|
-
|
|
739
|
+
)
|
|
737
740
|
)
|
|
738
741
|
data.select!{ |l| l['fnid'].to_i > iteration_data.first} unless iteration_data.first.nil?
|
|
739
742
|
iteration_data[0] = data.last['fnid'].to_i unless data.empty?
|
|
@@ -873,11 +876,11 @@ module Aspera
|
|
|
873
876
|
iteration_persistency = PersistencyActionOnce.new(
|
|
874
877
|
manager: persistency,
|
|
875
878
|
data: [],
|
|
876
|
-
id: IdGenerator.from_list(
|
|
879
|
+
id: IdGenerator.from_list(
|
|
877
880
|
'node_transfers',
|
|
878
881
|
options.get_option(:url, mandatory: true),
|
|
879
882
|
options.get_option(:username, mandatory: true)
|
|
880
|
-
|
|
883
|
+
)
|
|
881
884
|
)
|
|
882
885
|
if transfer_filter.delete('reset')
|
|
883
886
|
iteration_persistency.data.clear
|
|
@@ -10,7 +10,7 @@ module Aspera
|
|
|
10
10
|
# OAuth methods supported
|
|
11
11
|
AUTH_TYPES = %i[web jwt boot].freeze
|
|
12
12
|
# Options used for authentication
|
|
13
|
-
AUTH_OPTIONS = %i[url auth client_id client_secret
|
|
13
|
+
AUTH_OPTIONS = %i[url auth client_id client_secret redirect_uri private_key passphrase username password].freeze
|
|
14
14
|
def initialize(**_)
|
|
15
15
|
super
|
|
16
16
|
options.declare(:auth, 'OAuth type of authentication', allowed: AUTH_TYPES, default: :jwt)
|
|
@@ -19,22 +19,23 @@ module Aspera
|
|
|
19
19
|
options.declare(:redirect_uri, 'OAuth (Web) redirect URI for web authentication')
|
|
20
20
|
options.declare(:private_key, 'OAuth (JWT) RSA private key PEM value (prefix file path with @file:)')
|
|
21
21
|
options.declare(:passphrase, 'OAuth (JWT) RSA private key passphrase')
|
|
22
|
-
options.declare(:scope, 'OAuth scope for API calls')
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
# Get
|
|
26
|
-
# Adds those not nil to the `
|
|
24
|
+
# Get command line options specified by `AUTH_OPTIONS` and `option.keys` (value is default).
|
|
25
|
+
# Adds those not nil to the `kwargs`.
|
|
27
26
|
# Instantiate the provided `klass` with those kwargs.
|
|
28
|
-
# `
|
|
29
|
-
# @param klass
|
|
30
|
-
# @param
|
|
31
|
-
# @param
|
|
32
|
-
|
|
27
|
+
# `option` can specify a default value (not `nil`)
|
|
28
|
+
# @param klass [Class] API object to create
|
|
29
|
+
# @param kwargs [Hash] The fixed keyword arguments for creation
|
|
30
|
+
# @param option [Hash] Additional options, key=symbol, value:default value or nil
|
|
31
|
+
# @return [Object] instance of `klass`
|
|
32
|
+
# @raise [Cli::Error] if a required option is missing
|
|
33
|
+
def new_with_options(klass, kwargs: {}, option: {})
|
|
33
34
|
klass.new(**
|
|
34
|
-
(AUTH_OPTIONS +
|
|
35
|
+
(AUTH_OPTIONS + option.keys).each_with_object(kwargs) do |i, m|
|
|
35
36
|
v = options.get_option(i)
|
|
36
37
|
m[i] = v unless v.nil?
|
|
37
|
-
m[i] =
|
|
38
|
+
m[i] = option[i] unless !m[i].nil? || option[i].nil?
|
|
38
39
|
end)
|
|
39
40
|
rescue ::ArgumentError => e
|
|
40
41
|
if (m = e.message.match(/missing keyword: :(.*)$/))
|
|
@@ -451,12 +451,12 @@ module Aspera
|
|
|
451
451
|
iteration_persistency = PersistencyActionOnce.new(
|
|
452
452
|
manager: persistency,
|
|
453
453
|
data: [],
|
|
454
|
-
id: IdGenerator.from_list(
|
|
454
|
+
id: IdGenerator.from_list(
|
|
455
455
|
'preview_iteration',
|
|
456
456
|
command.to_s,
|
|
457
457
|
options.get_option(:url, mandatory: true),
|
|
458
458
|
options.get_option(:username, mandatory: true)
|
|
459
|
-
|
|
459
|
+
)
|
|
460
460
|
)
|
|
461
461
|
end
|
|
462
462
|
# call processing method specified by command line command
|
|
@@ -31,7 +31,6 @@ module Aspera
|
|
|
31
31
|
:FILE_LIST_FROM_TRANSFER_SPEC,
|
|
32
32
|
:FILE_LIST_OPTIONS,
|
|
33
33
|
:DEFAULT_TRANSFER_NOTIFY_TEMPLATE
|
|
34
|
-
TRANSFER_AGENTS = Agent::Factory.instance.list.freeze
|
|
35
34
|
|
|
36
35
|
class << self
|
|
37
36
|
# @return :success if all sessions statuses returned by "start" are success
|
|
@@ -65,7 +64,7 @@ module Aspera
|
|
|
65
64
|
@opt_mgr.declare(:to_folder, 'Destination folder for transferred files')
|
|
66
65
|
@opt_mgr.declare(:sources, "How list of transferred files is provided (#{FILE_LIST_OPTIONS.join(',')})", default: FILE_LIST_FROM_ARGS)
|
|
67
66
|
@opt_mgr.declare(:src_type, 'Type of file list', allowed: %i[list pair], default: :list)
|
|
68
|
-
@opt_mgr.declare(:transfer, 'Type of transfer agent', allowed:
|
|
67
|
+
@opt_mgr.declare(:transfer, 'Type of transfer agent', allowed: Agent::Factory::ALL.keys, default: :direct)
|
|
69
68
|
@opt_mgr.declare(:transfer_info, 'Parameters for transfer agent', allowed: Hash, handler: {o: self, m: :transfer_info})
|
|
70
69
|
@opt_mgr.parse_options!
|
|
71
70
|
@notification_cb = nil
|
data/lib/aspera/cli/version.rb
CHANGED
|
@@ -22,11 +22,11 @@ module Aspera
|
|
|
22
22
|
'x-cli-envvar', # [String] Name of env var
|
|
23
23
|
'x-cli-option', # [String] Command line option (starts with "-")
|
|
24
24
|
'x-cli-short', # [String] Command line option (starts with "-")
|
|
25
|
-
'x-cli-switch', # [
|
|
26
|
-
'x-cli-special', # [
|
|
25
|
+
'x-cli-switch', # [Boolean] `true` if option has no arg, else by default option has a value
|
|
26
|
+
'x-cli-special', # [Boolean] `true` if special handling (deferred)
|
|
27
27
|
'x-cli-convert', # [String,Hash] Method name for Convert object or Conversion for enum ts to arg
|
|
28
28
|
'x-agents', # [Array] Supported agents (for doc only), if not specified: all
|
|
29
|
-
'x-ts-name', # [
|
|
29
|
+
'x-ts-name', # [Boolean,String] (async) true if same name in transfer spec, else real name in transfer spec, else ignored
|
|
30
30
|
'x-ts-convert', # [String] (async) Name of methods to convert value from transfer spec to `conf` API.
|
|
31
31
|
'x-deprecation' # [String] Deprecation message for doc
|
|
32
32
|
].freeze
|
|
@@ -50,8 +50,8 @@ module Aspera
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
# Fill default values for some fields in the schema
|
|
53
|
-
# @param schema [Hash]
|
|
54
|
-
# @param ascp
|
|
53
|
+
# @param schema [Hash] The JSON schema
|
|
54
|
+
# @param ascp [Boolean] `true` if ascp
|
|
55
55
|
def validate_schema(schema, ascp: false)
|
|
56
56
|
direct_props = %w[x-cli-option x-cli-envvar x-cli-special].freeze
|
|
57
57
|
schema['properties'].each do |name, info|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/assert'
|
|
4
|
+
|
|
5
|
+
module Aspera
|
|
6
|
+
# Convert dotted-path to/from nested Hash/Array container
|
|
7
|
+
class DotContainer
|
|
8
|
+
class << self
|
|
9
|
+
# Insert extended value `value` into struct `result` at `path`
|
|
10
|
+
# @param path [String] Dotted path in container
|
|
11
|
+
# @param value [String] Last value to insert
|
|
12
|
+
# @param result [NilClass, Hash, Array] current value
|
|
13
|
+
# @return [Hash, Array]
|
|
14
|
+
def dotted_to_container(path, value, result = nil)
|
|
15
|
+
# Typed keys
|
|
16
|
+
keys = path.split(OPTION_DOTTED_SEPARATOR).map{ |k| int_or_string(k)}
|
|
17
|
+
# Create, or re-use first level container
|
|
18
|
+
current = (result ||= new_hash_or_array_from_key(keys.first))
|
|
19
|
+
# walk the path, and create sub-containers if necessary
|
|
20
|
+
keys.each_cons(2) do |k, next_k|
|
|
21
|
+
array_requires_integer_index!(current, k)
|
|
22
|
+
current = (current[k] ||= new_hash_or_array_from_key(next_k))
|
|
23
|
+
end
|
|
24
|
+
# Assign value at last index
|
|
25
|
+
array_requires_integer_index!(current, keys.last)
|
|
26
|
+
current[keys.last] = value
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Convert `String` to `Integer`, or keep `String` if not `Integer`
|
|
33
|
+
def int_or_string(value)
|
|
34
|
+
Integer(value, exception: false) || value
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Assert that if `container` is an `Array`, then `index` is an `Integer`
|
|
38
|
+
# @param container [Hash, Array]
|
|
39
|
+
# @param index [String, Integer]
|
|
40
|
+
def array_requires_integer_index!(container, index)
|
|
41
|
+
Aspera.assert(container.is_a?(Hash) || index.is_a?(Integer)){'Using String index when Integer index used previously'}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Create a new `Hash` or `Array` depending on type of `key`
|
|
45
|
+
def new_hash_or_array_from_key(key)
|
|
46
|
+
key.is_a?(Integer) ? [] : {}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param [Hash,Array] Container object
|
|
51
|
+
def initialize(container)
|
|
52
|
+
Aspera.assert_type(container, Hash)
|
|
53
|
+
# tail (pop,push) contains the next element to display
|
|
54
|
+
# elements are [path, value]
|
|
55
|
+
@stack = container.empty? ? [] : [[[], container]]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Convert nested Hash/Array container to dotted-path Hash
|
|
59
|
+
# @return [Hash] Dotted-path Hash
|
|
60
|
+
def to_dotted
|
|
61
|
+
result = {}
|
|
62
|
+
until @stack.empty?
|
|
63
|
+
path, current = @stack.pop
|
|
64
|
+
to_insert = nil
|
|
65
|
+
# empty things are left intact
|
|
66
|
+
if current.respond_to?(:empty?) && current.empty?
|
|
67
|
+
to_insert = current
|
|
68
|
+
else
|
|
69
|
+
case current
|
|
70
|
+
when Hash
|
|
71
|
+
add_elements(path, current)
|
|
72
|
+
when Array
|
|
73
|
+
# Array has no nested structures -> list of Strings
|
|
74
|
+
if current.none?{ |i| i.is_a?(Array) || i.is_a?(Hash)}
|
|
75
|
+
to_insert = current.map(&:to_s)
|
|
76
|
+
# Array of Hashes with only 'name' keys -> list of Strings
|
|
77
|
+
elsif current.all?{ |i| i.is_a?(Hash) && i.keys == ['name']}
|
|
78
|
+
to_insert = current.map{ |i| i['name']}
|
|
79
|
+
# Array of Hashes with only 'name' and 'value' keys -> Hash of key/values
|
|
80
|
+
elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
|
|
81
|
+
add_elements(path, current.each_with_object({}){ |i, h| h[i['name']] = i['value']})
|
|
82
|
+
else
|
|
83
|
+
add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
|
|
84
|
+
end
|
|
85
|
+
else
|
|
86
|
+
to_insert = current
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
result[path.map(&:to_s).join(OPTION_DOTTED_SEPARATOR)] = to_insert unless to_insert.nil?
|
|
90
|
+
end
|
|
91
|
+
result
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# Add elements of enumerator to the @stack, in reverse order
|
|
97
|
+
def add_elements(path, enum)
|
|
98
|
+
enum.reverse_each do |key, value|
|
|
99
|
+
@stack.push([path + [key], value])
|
|
100
|
+
end
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# "."
|
|
105
|
+
OPTION_DOTTED_SEPARATOR = '.'
|
|
106
|
+
private_constant :OPTION_DOTTED_SEPARATOR
|
|
107
|
+
end
|
|
108
|
+
end
|
data/lib/aspera/id_generator.rb
CHANGED
|
@@ -9,19 +9,16 @@ module Aspera
|
|
|
9
9
|
class << self
|
|
10
10
|
# Generate an ID from a list of object IDs
|
|
11
11
|
# The generated ID is safe as file name
|
|
12
|
-
# @param
|
|
12
|
+
# @param ids [Array] the object IDs (can be nested, will be flattened, and nils removed)
|
|
13
13
|
# @return [String] the generated ID
|
|
14
|
-
def from_list(
|
|
14
|
+
def from_list(*ids)
|
|
15
15
|
safe_char = Environment.instance.safe_filename_character
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end.join(safe_char)
|
|
21
|
-
end
|
|
22
|
-
Aspera.assert_type(object_id, String)
|
|
16
|
+
# compact: remove nils
|
|
17
|
+
id = ids.flatten.compact.map do |i|
|
|
18
|
+
i.is_a?(String) && i.start_with?('https://') ? URI.parse(i).host : i.to_s
|
|
19
|
+
end.join(safe_char)
|
|
23
20
|
# keep dot for extension only (nicer)
|
|
24
|
-
return Environment.instance.sanitized_filename(
|
|
21
|
+
return Environment.instance.sanitized_filename(id.gsub('.', safe_char)).downcase
|
|
25
22
|
end
|
|
26
23
|
end
|
|
27
24
|
end
|