aspera-cli 4.14.0 → 4.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/BUGS.md +29 -3
- data/CHANGELOG.md +300 -185
- data/CONTRIBUTING.md +74 -23
- data/README.md +2346 -1619
- data/bin/ascli +16 -25
- data/bin/asession +15 -15
- data/examples/dascli +2 -2
- data/examples/proxy.pac +1 -1
- data/lib/aspera/aoc.rb +216 -150
- data/lib/aspera/ascmd.rb +25 -18
- data/lib/aspera/assert.rb +45 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
- data/lib/aspera/cli/error.rb +17 -0
- data/lib/aspera/cli/extended_value.rb +51 -16
- data/lib/aspera/cli/formatter.rb +276 -174
- data/lib/aspera/cli/hints.rb +81 -0
- data/lib/aspera/cli/main.rb +114 -147
- data/lib/aspera/cli/manager.rb +181 -136
- data/lib/aspera/cli/plugin.rb +82 -64
- data/lib/aspera/cli/plugins/alee.rb +0 -1
- data/lib/aspera/cli/plugins/aoc.rb +327 -331
- data/lib/aspera/cli/plugins/ats.rb +12 -8
- data/lib/aspera/cli/plugins/bss.rb +2 -2
- data/lib/aspera/cli/plugins/config.rb +575 -439
- data/lib/aspera/cli/plugins/console.rb +40 -0
- data/lib/aspera/cli/plugins/cos.rb +4 -5
- data/lib/aspera/cli/plugins/faspex.rb +111 -92
- data/lib/aspera/cli/plugins/faspex5.rb +245 -182
- data/lib/aspera/cli/plugins/node.rb +239 -160
- data/lib/aspera/cli/plugins/orchestrator.rb +56 -19
- data/lib/aspera/cli/plugins/preview.rb +54 -38
- data/lib/aspera/cli/plugins/server.rb +63 -20
- data/lib/aspera/cli/plugins/shares.rb +64 -38
- data/lib/aspera/cli/sync_actions.rb +68 -0
- data/lib/aspera/cli/transfer_agent.rb +64 -67
- data/lib/aspera/cli/transfer_progress.rb +73 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/colors.rb +3 -1
- data/lib/aspera/command_line_builder.rb +27 -22
- data/lib/aspera/cos_node.rb +6 -4
- data/lib/aspera/coverage.rb +22 -0
- data/lib/aspera/data_repository.rb +33 -2
- data/lib/aspera/environment.rb +21 -8
- data/lib/aspera/fasp/agent_alpha.rb +116 -0
- data/lib/aspera/fasp/agent_base.rb +40 -76
- data/lib/aspera/fasp/agent_connect.rb +21 -22
- data/lib/aspera/fasp/agent_direct.rb +169 -179
- data/lib/aspera/fasp/agent_httpgw.rb +200 -195
- data/lib/aspera/fasp/agent_node.rb +43 -35
- data/lib/aspera/fasp/agent_trsdk.rb +124 -41
- data/lib/aspera/fasp/error_info.rb +2 -2
- data/lib/aspera/fasp/faux_file.rb +52 -0
- data/lib/aspera/fasp/installation.rb +89 -191
- data/lib/aspera/fasp/management.rb +249 -0
- data/lib/aspera/fasp/parameters.rb +86 -47
- data/lib/aspera/fasp/parameters.yaml +75 -8
- data/lib/aspera/fasp/products.rb +162 -0
- data/lib/aspera/fasp/resume_policy.rb +7 -5
- data/lib/aspera/fasp/sync.rb +273 -0
- data/lib/aspera/fasp/transfer_spec.rb +10 -8
- data/lib/aspera/fasp/uri.rb +6 -6
- data/lib/aspera/faspex_gw.rb +11 -8
- data/lib/aspera/faspex_postproc.rb +8 -7
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/id_generator.rb +3 -1
- data/lib/aspera/json_rpc.rb +51 -0
- data/lib/aspera/keychain/encrypted_hash.rb +46 -11
- data/lib/aspera/keychain/macos_security.rb +15 -13
- data/lib/aspera/line_logger.rb +23 -0
- data/lib/aspera/log.rb +61 -19
- data/lib/aspera/nagios.rb +7 -2
- data/lib/aspera/node.rb +105 -21
- data/lib/aspera/node_simulator.rb +214 -0
- data/lib/aspera/oauth.rb +57 -36
- data/lib/aspera/open_application.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +13 -14
- data/lib/aspera/persistency_folder.rb +5 -4
- data/lib/aspera/preview/file_types.rb +56 -268
- data/lib/aspera/preview/generator.rb +28 -39
- data/lib/aspera/preview/options.rb +2 -0
- data/lib/aspera/preview/terminal.rb +36 -16
- data/lib/aspera/preview/utils.rb +23 -29
- data/lib/aspera/proxy_auto_config.rb +6 -3
- data/lib/aspera/rest.rb +127 -80
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +16 -14
- data/lib/aspera/rest_errors_aspera.rb +39 -34
- data/lib/aspera/secret_hider.rb +18 -17
- data/lib/aspera/ssh.rb +10 -5
- data/lib/aspera/temp_file_manager.rb +11 -4
- data/lib/aspera/web_auth.rb +10 -7
- data/lib/aspera/web_server_simple.rb +11 -5
- data.tar.gz.sig +0 -0
- metadata +108 -39
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/listener/line_dump.rb +0 -19
- data/lib/aspera/cli/listener/logger.rb +0 -22
- data/lib/aspera/cli/listener/progress.rb +0 -50
- data/lib/aspera/cli/listener/progress_multi.rb +0 -84
- data/lib/aspera/cli/plugins/sync.rb +0 -44
- data/lib/aspera/fasp/listener.rb +0 -13
- data/lib/aspera/sync.rb +0 -213
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# spellchecker: ignore workgroups
|
3
|
+
# spellchecker: ignore workgroups mypackages passcode
|
4
4
|
|
5
5
|
require 'aspera/cli/basic_auth_plugin'
|
6
|
+
require 'aspera/cli/extended_value'
|
6
7
|
require 'aspera/persistency_action_once'
|
7
8
|
require 'aspera/id_generator'
|
8
9
|
require 'aspera/nagios'
|
9
10
|
require 'aspera/environment'
|
11
|
+
require 'aspera/assert'
|
10
12
|
require 'securerandom'
|
11
|
-
require 'ruby-progressbar'
|
12
13
|
require 'tty-spinner'
|
13
14
|
|
14
15
|
module Aspera
|
@@ -20,80 +21,132 @@ module Aspera
|
|
20
21
|
API_DETECT = 'api/v5/configuration/ping'
|
21
22
|
# list of supported mailbox types (to list packages)
|
22
23
|
API_LIST_MAILBOX_TYPES = %w[inbox inbox_history inbox_all inbox_all_history outbox outbox_history pending pending_history all].freeze
|
23
|
-
PACKAGE_ALL_INIT = 'INIT'
|
24
24
|
PACKAGE_SEND_FROM_REMOTE_SOURCE = 'remote_source'
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
# Faspex API v5: get transfer spec for connect
|
26
|
+
TRANSFER_CONNECT = 'connect'
|
27
|
+
ADMIN_RESOURCES = %i[
|
28
|
+
accounts contacts jobs workgroups shared_inboxes nodes oauth_clients registrations saml_configs
|
29
|
+
metadata_profiles email_notifications alternate_addresses
|
30
|
+
].freeze
|
31
|
+
JOB_RUNNING = %w[queued working].freeze
|
32
|
+
STANDARD_PATH = '/aspera/faspex'
|
33
|
+
PER_PAGE_DEFAULT = 100
|
34
|
+
private_constant(*%i[JOB_RUNNING RECIPIENT_TYPES PACKAGE_TERMINATED API_DETECT API_LIST_MAILBOX_TYPES PACKAGE_SEND_FROM_REMOTE_SOURCE PER_PAGE_DEFAULT])
|
28
35
|
class << self
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
36
|
+
def application_name
|
37
|
+
'Faspex'
|
38
|
+
end
|
39
|
+
|
40
|
+
def detect(address_or_url)
|
41
|
+
address_or_url = "https://#{address_or_url}" unless address_or_url.match?(%r{^[a-z]{1,6}://})
|
42
|
+
urls = [address_or_url]
|
43
|
+
urls.push("#{address_or_url}#{STANDARD_PATH}") unless address_or_url.end_with?(STANDARD_PATH)
|
44
|
+
|
45
|
+
urls.each do |base_url|
|
46
|
+
next unless base_url.start_with?('https://')
|
47
|
+
api = Rest.new(base_url: base_url, redirect_max: 1)
|
48
|
+
result = api.read(API_DETECT)
|
49
|
+
next unless result[:http].code.start_with?('2') && result[:http].body.strip.empty?
|
50
|
+
url_length = -2 - API_DETECT.length
|
51
|
+
# take redirect if any
|
34
52
|
return {
|
35
53
|
version: result[:http]['x-ibm-aspera'] || '5',
|
36
|
-
url: result[:http].uri.to_s[0..
|
37
|
-
name: 'Faspex 5'
|
54
|
+
url: result[:http].uri.to_s[0..url_length]
|
38
55
|
}
|
56
|
+
rescue StandardError => e
|
57
|
+
Log.log.debug{"detect error: #{e}"}
|
39
58
|
end
|
40
59
|
return nil
|
41
60
|
end
|
42
|
-
end
|
43
61
|
|
44
|
-
|
45
|
-
|
62
|
+
def wizard(object:, private_key_path:, pub_key_pem:)
|
63
|
+
options = object.options
|
64
|
+
formatter = object.formatter
|
65
|
+
instance_url = options.get_option(:url, mandatory: true)
|
66
|
+
wiz_username = options.get_option(:username, mandatory: true)
|
67
|
+
raise "Username shall be an email in Faspex: #{wiz_username}" if !(wiz_username =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
|
68
|
+
if options.get_option(:client_id).nil? || options.get_option(:client_secret).nil?
|
69
|
+
formatter.display_status('Ask the ascli client id and secret to your Administrator.'.red)
|
70
|
+
formatter.display_status("Admin should login to: #{instance_url}")
|
71
|
+
OpenApplication.instance.uri(instance_url)
|
72
|
+
formatter.display_status('Navigate to: 𓃑 → Admin → Configurations → API clients')
|
73
|
+
formatter.display_status('Create an API client with:')
|
74
|
+
formatter.display_status('- name: ascli')
|
75
|
+
formatter.display_status('- JWT: enabled')
|
76
|
+
formatter.display_status("Then, logged in as #{wiz_username.red} go to your profile:")
|
77
|
+
formatter.display_status('👤 → Account Settings → Preferences -> Public Key in PEM:')
|
78
|
+
formatter.display_status(pub_key_pem)
|
79
|
+
formatter.display_status('Once set, fill in the parameters:')
|
80
|
+
end
|
81
|
+
return {
|
82
|
+
preset_value: {
|
83
|
+
url: instance_url,
|
84
|
+
username: wiz_username,
|
85
|
+
auth: :jwt.to_s,
|
86
|
+
private_key: "@file:#{private_key_path}",
|
87
|
+
client_id: options.get_option(:client_id, mandatory: true),
|
88
|
+
client_secret: options.get_option(:client_secret, mandatory: true)
|
89
|
+
},
|
90
|
+
test_args: 'user profile show'
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def public_link?(url)
|
95
|
+
url.include?('/public/')
|
96
|
+
end
|
97
|
+
end
|
46
98
|
|
47
99
|
def initialize(env)
|
48
100
|
super(env)
|
49
101
|
options.declare(:client_id, 'OAuth client identifier')
|
50
102
|
options.declare(:client_secret, 'OAuth client secret')
|
51
103
|
options.declare(:redirect_uri, 'OAuth redirect URI for web authentication')
|
52
|
-
options.declare(:auth, 'OAuth type of authentication', values: %i[boot
|
104
|
+
options.declare(:auth, 'OAuth type of authentication', values: %i[boot].concat(Oauth::STD_AUTH_TYPES), default: :jwt)
|
53
105
|
options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
|
54
106
|
options.declare(:passphrase, 'OAuth JWT RSA private key passphrase')
|
55
|
-
options.declare(:
|
56
|
-
options.declare(:box, "Package inbox, either shared inbox name or one of #{API_LIST_MAILBOX_TYPES} or #{VAL_ALL}", default: 'inbox')
|
107
|
+
options.declare(:box, "Package inbox, either shared inbox name or one of: #{API_LIST_MAILBOX_TYPES.join(', ')} or #{ExtendedValue::ALL}", default: 'inbox')
|
57
108
|
options.declare(:shared_folder, 'Send package with files from shared folder')
|
58
|
-
options.declare(:group_type, '
|
109
|
+
options.declare(:group_type, 'Type of shared box', values: %i[shared_inboxes workgroups], default: :shared_inboxes)
|
59
110
|
options.parse_options!
|
60
111
|
end
|
61
112
|
|
113
|
+
def api_url
|
114
|
+
return "#{@faspex5_api_base_url}/api/v5"
|
115
|
+
end
|
116
|
+
|
117
|
+
def auth_api_url
|
118
|
+
return "#{@faspex5_api_base_url}/auth"
|
119
|
+
end
|
120
|
+
|
62
121
|
def set_api
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
args = URI.decode_www_form(uri.query).each_with_object({}){|v, h|h[v.first] = v.last; }
|
75
|
-
Log.dump(:args, args)
|
76
|
-
context = args['context']
|
77
|
-
raise 'missing context' if context.nil?
|
78
|
-
@pub_link_context = JSON.parse(Base64.decode64(context))
|
79
|
-
Log.dump(:@pub_link_context, @pub_link_context)
|
122
|
+
# get endpoint, remove unnecessary trailing slashes
|
123
|
+
@faspex5_api_base_url = options.get_option(:url, mandatory: true).gsub(%r{/+$}, '')
|
124
|
+
auth_type = self.class.public_link?(@faspex5_api_base_url) ? :public_link : options.get_option(:auth, mandatory: true)
|
125
|
+
case auth_type
|
126
|
+
when :public_link
|
127
|
+
encoded_context = Rest.decode_query(URI.parse(@faspex5_api_base_url).query)['context']
|
128
|
+
raise 'Bad faspex5 public link, missing context in query' if encoded_context.nil?
|
129
|
+
@pub_link_context = JSON.parse(Base64.decode64(encoded_context))
|
130
|
+
Log.log.trace1{Log.dump(:@pub_link_context, @pub_link_context)}
|
131
|
+
# ok, we have the additional parameters, get the base url
|
132
|
+
@faspex5_api_base_url = @faspex5_api_base_url.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
|
80
133
|
@api_v5 = Rest.new({
|
81
|
-
base_url:
|
134
|
+
base_url: api_url,
|
82
135
|
headers: {'Passcode' => @pub_link_context['passcode']}
|
83
136
|
})
|
84
137
|
when :boot
|
85
138
|
# the password here is the token copied directly from browser in developer mode
|
86
139
|
@api_v5 = Rest.new({
|
87
|
-
base_url:
|
140
|
+
base_url: api_url,
|
88
141
|
headers: {'Authorization' => options.get_option(:password, mandatory: true)}
|
89
142
|
})
|
90
143
|
when :web
|
91
144
|
# opens a browser and ask user to auth using web
|
92
145
|
@api_v5 = Rest.new({
|
93
|
-
base_url:
|
146
|
+
base_url: api_url,
|
94
147
|
auth: {
|
95
148
|
type: :oauth2,
|
96
|
-
base_url:
|
149
|
+
base_url: auth_api_url,
|
97
150
|
grant_method: :web,
|
98
151
|
client_id: options.get_option(:client_id, mandatory: true),
|
99
152
|
web: {redirect_uri: options.get_option(:redirect_uri, mandatory: true)}
|
@@ -101,10 +154,10 @@ module Aspera
|
|
101
154
|
when :jwt
|
102
155
|
app_client_id = options.get_option(:client_id, mandatory: true)
|
103
156
|
@api_v5 = Rest.new({
|
104
|
-
base_url:
|
157
|
+
base_url: api_url,
|
105
158
|
auth: {
|
106
159
|
type: :oauth2,
|
107
|
-
base_url:
|
160
|
+
base_url: auth_api_url,
|
108
161
|
grant_method: :jwt,
|
109
162
|
client_id: app_client_id,
|
110
163
|
jwt: {
|
@@ -117,14 +170,14 @@ module Aspera
|
|
117
170
|
headers: {typ: 'JWT'}
|
118
171
|
}
|
119
172
|
}})
|
120
|
-
else
|
173
|
+
else error_unexpected_value(auth_type)
|
121
174
|
end
|
122
175
|
end
|
123
176
|
|
124
177
|
# if recipient is just an email, then convert to expected API hash : name and type
|
125
178
|
def normalize_recipients(parameters)
|
126
179
|
return unless parameters.key?('recipients')
|
127
|
-
|
180
|
+
assert_type(parameters['recipients'], Array){'recipients'}
|
128
181
|
recipient_types = RECIPIENT_TYPES
|
129
182
|
if parameters.key?('recipient_types')
|
130
183
|
recipient_types = parameters['recipient_types']
|
@@ -147,52 +200,63 @@ module Aspera
|
|
147
200
|
|
148
201
|
# wait for package status to be in provided list
|
149
202
|
def wait_package_status(id, status_list: PACKAGE_TERMINATED)
|
150
|
-
|
151
|
-
|
152
|
-
while true
|
203
|
+
total_sent = false
|
204
|
+
loop do
|
153
205
|
status = @api_v5.read("packages/#{id}/upload_details")[:data]
|
206
|
+
status['id'] = id
|
154
207
|
# user asked to not follow
|
155
|
-
|
208
|
+
return status if status_list.nil?
|
156
209
|
if status['upload_status'].eql?('submitted')
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
spinner.spin
|
163
|
-
elsif progress.nil?
|
164
|
-
progress = ProgressBar.create(
|
165
|
-
format: '%a %B %p%% %r Mbps %e',
|
166
|
-
rate_scale: lambda{|rate|rate / Environment::BYTES_PER_MEBIBIT},
|
167
|
-
title: 'progress',
|
168
|
-
total: status['bytes_total'].to_i)
|
210
|
+
config.progress_bar&.event(session_id: nil, type: :pre_start, info: status['upload_status'])
|
211
|
+
elsif !total_sent
|
212
|
+
config.progress_bar&.event(session_id: id, type: :session_start)
|
213
|
+
config.progress_bar&.event(session_id: id, type: :session_size, info: status['bytes_total'].to_i)
|
214
|
+
total_sent = true
|
169
215
|
else
|
170
|
-
|
216
|
+
config.progress_bar&.event(session_id: id, type: :transfer, info: status['bytes_written'].to_i)
|
217
|
+
end
|
218
|
+
if status_list.include?(status['upload_status'])
|
219
|
+
# if status['upload_status'].eql?('completed')
|
220
|
+
config.progress_bar&.event(session_id: id, type: :end)
|
221
|
+
return status
|
222
|
+
# end
|
171
223
|
end
|
224
|
+
sleep(1.0)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def wait_for_job(job_id)
|
229
|
+
spinner = nil
|
230
|
+
loop do
|
231
|
+
status = @api_v5.read("jobs/#{job_id}", {type: :formatted})[:data]
|
232
|
+
return status unless JOB_RUNNING.include?(status['status'])
|
233
|
+
if spinner.nil?
|
234
|
+
spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
|
235
|
+
spinner.start
|
236
|
+
end
|
237
|
+
spinner.update(title: status['status'])
|
238
|
+
spinner.spin
|
172
239
|
sleep(0.5)
|
173
240
|
end
|
174
|
-
|
175
|
-
return status
|
241
|
+
error_unreachable_line
|
176
242
|
end
|
177
243
|
|
178
|
-
#
|
244
|
+
# Get a (full or partial) list of all entities of a given type
|
179
245
|
# @param type [String] the type of entity to list (just a name)
|
180
246
|
# @param query [Hash,nil] additional query parameters
|
181
|
-
# @param
|
247
|
+
# @param real_path [String] real path if it's n ot just the type
|
182
248
|
# @param item_list_key [String] key in the result to get the list of items
|
183
|
-
def list_entities(type:,
|
184
|
-
query = {} if query.nil?
|
249
|
+
def list_entities(type:, real_path: nil, query: {}, item_list_key: nil)
|
185
250
|
type = type.to_s if type.is_a?(Symbol)
|
251
|
+
assert_type(type, String)
|
186
252
|
item_list_key = type if item_list_key.nil?
|
187
|
-
|
188
|
-
full_path = type
|
189
|
-
full_path = "#{path}/#{full_path}" unless path.nil? || path.empty?
|
253
|
+
full_path = real_path.nil? ? type : real_path
|
190
254
|
result = []
|
191
255
|
offset = 0
|
192
256
|
max_items = query.delete(MAX_ITEMS)
|
193
257
|
remain_pages = query.delete(MAX_PAGES)
|
194
258
|
# merge default parameters, by default 100 per page
|
195
|
-
query = {'limit'=>
|
259
|
+
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
196
260
|
loop do
|
197
261
|
query['offset'] = offset
|
198
262
|
page_result = @api_v5.read(full_path, query)[:data]
|
@@ -211,36 +275,39 @@ module Aspera
|
|
211
275
|
end
|
212
276
|
|
213
277
|
# lookup an entity id from its name
|
214
|
-
def lookup_entity_by_field(type:, value:, field: 'name', query: :default,
|
215
|
-
|
216
|
-
|
278
|
+
def lookup_entity_by_field(type:, value:, field: 'name', query: :default, real_path: nil, item_list_key: nil)
|
279
|
+
if query.eql?(:default)
|
280
|
+
assert(field.eql?('name')){'Default query is on name only'}
|
281
|
+
query = {'q'=> value}
|
282
|
+
end
|
283
|
+
found = list_entities(type: type, real_path: real_path, query: query, item_list_key: item_list_key).select{|i|i[field].eql?(value)}
|
217
284
|
case found.length
|
218
285
|
when 0 then raise "No #{type} with #{field} = #{value}"
|
219
286
|
when 1 then return found.first
|
220
|
-
else raise "Found #{found.length} #{
|
287
|
+
else raise "Found #{found.length} #{real_path} with #{field} = #{value}"
|
221
288
|
end
|
222
289
|
end
|
223
290
|
|
224
291
|
# list all packages with optional filter
|
225
|
-
def list_packages_with_filter
|
292
|
+
def list_packages_with_filter(query: {})
|
226
293
|
filter = options.get_next_argument('filter', mandatory: false, type: Proc, default: ->(_x){true})
|
227
294
|
# translate box name to API prefix (with ending slash)
|
228
295
|
box = options.get_option(:box)
|
229
|
-
|
296
|
+
real_path =
|
230
297
|
case box
|
231
|
-
when
|
232
|
-
when *API_LIST_MAILBOX_TYPES then box
|
298
|
+
when ExtendedValue::ALL then 'packages' # only admin can list all packages globally
|
299
|
+
when *API_LIST_MAILBOX_TYPES then "#{box}/packages"
|
233
300
|
else
|
234
301
|
group_type = options.get_option(:group_type)
|
235
|
-
"#{group_type}/#{lookup_entity_by_field(type: group_type, value: box)['id']}"
|
302
|
+
"#{group_type}/#{lookup_entity_by_field(type: group_type, value: box)['id']}/packages"
|
236
303
|
end
|
237
304
|
return list_entities(
|
238
305
|
type: 'packages',
|
239
|
-
query: query_read_delete(default:
|
240
|
-
|
306
|
+
query: query_read_delete(default: query),
|
307
|
+
real_path: real_path).select(&filter)
|
241
308
|
end
|
242
309
|
|
243
|
-
def package_receive
|
310
|
+
def package_receive(package_ids)
|
244
311
|
# prepare persistency if needed
|
245
312
|
skip_ids_persistency = nil
|
246
313
|
if options.get_option(:once_only, mandatory: true)
|
@@ -251,52 +318,53 @@ module Aspera
|
|
251
318
|
id: IdGenerator.from_list([
|
252
319
|
'faspex_recv',
|
253
320
|
options.get_option(:url, mandatory: true),
|
254
|
-
options.get_option(:username, mandatory: true)
|
321
|
+
options.get_option(:username, mandatory: true),
|
322
|
+
options.get_option(:box, mandatory: true)
|
323
|
+
]))
|
255
324
|
end
|
256
|
-
|
257
|
-
if @pub_link_context&.key?('package_id')
|
258
|
-
@pub_link_context['package_id']
|
259
|
-
else
|
260
|
-
# one or several packages
|
261
|
-
instance_identifier
|
262
|
-
end
|
325
|
+
packages = []
|
263
326
|
case package_ids
|
264
|
-
when
|
265
|
-
|
327
|
+
when ExtendedValue::INIT
|
328
|
+
assert(skip_ids_persistency){'Only with option once_only'}
|
266
329
|
skip_ids_persistency.data.clear.concat(list_packages_with_filter.map{|p|p['id']})
|
267
330
|
skip_ids_persistency.save
|
268
331
|
return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
|
269
|
-
when
|
332
|
+
when ExtendedValue::ALL
|
270
333
|
# TODO: if packages have same name, they will overwrite ?
|
271
|
-
|
272
|
-
Log.dump(:package_ids,
|
273
|
-
Log.dump(:
|
274
|
-
|
275
|
-
Log.dump(:package_ids,
|
334
|
+
packages = list_packages_with_filter(query: {'status' => 'completed'})
|
335
|
+
Log.log.trace1{Log.dump(:package_ids, packages.map{|p|p['id']})}
|
336
|
+
Log.log.trace1{Log.dump(:skip_ids, skip_ids_persistency.data)}
|
337
|
+
packages.reject!{|p|skip_ids_persistency.data.include?(p['id'])} if skip_ids_persistency
|
338
|
+
Log.log.trace1{Log.dump(:package_ids, packages.map{|p|p['id']})}
|
339
|
+
else
|
340
|
+
# a single id was provided, or a list of ids
|
341
|
+
package_ids = [package_ids] unless package_ids.is_a?(Array)
|
342
|
+
assert_type(package_ids, Array){'Expecting a single package id or a list of ids'}
|
343
|
+
assert(package_ids.all?(String)){'Package id shall be String'}
|
344
|
+
# packages = package_ids.map{|pkg_id|@api_v5.read("packages/#{pkg_id}")[:data]}
|
345
|
+
packages = package_ids.map{|pkg_id|{'id'=>pkg_id}}
|
276
346
|
end
|
277
|
-
# a single id was provided
|
278
|
-
# TODO: check package_ids is a list of strings
|
279
|
-
package_ids = [package_ids] if package_ids.is_a?(String)
|
280
347
|
result_transfer = []
|
281
|
-
|
348
|
+
param_file_list = {}
|
349
|
+
begin
|
350
|
+
param_file_list['paths'] = transfer.source_list.map{|source|{'path'=>source}}
|
351
|
+
rescue Cli::BadArgument
|
352
|
+
# paths is optional
|
353
|
+
end
|
354
|
+
download_params = {
|
355
|
+
type: 'received',
|
356
|
+
transfer_type: TRANSFER_CONNECT
|
357
|
+
}
|
358
|
+
box = options.get_option(:box)
|
359
|
+
case box
|
360
|
+
when /outbox/ then download_params[:type] = 'sent'
|
361
|
+
when *API_LIST_MAILBOX_TYPES then nil # nothing to do
|
362
|
+
else # shared inbox / workgroup
|
363
|
+
download_params[:recipient_workgroup_id] = lookup_entity_by_field(type: options.get_option(:group_type), value: box)['id']
|
364
|
+
end
|
365
|
+
packages.each do |package|
|
366
|
+
pkg_id = package['id']
|
282
367
|
formatter.display_status("Receiving package #{pkg_id}")
|
283
|
-
param_file_list = {}
|
284
|
-
begin
|
285
|
-
param_file_list['paths'] = transfer.source_list.map{|source|{'path'=>source}}
|
286
|
-
rescue Aspera::Cli::CliBadArgument
|
287
|
-
# paths is optional
|
288
|
-
end
|
289
|
-
download_params = {
|
290
|
-
type: 'received',
|
291
|
-
transfer_type: TRANSFER_CONNECT
|
292
|
-
}
|
293
|
-
box = options.get_option(:box)
|
294
|
-
case box
|
295
|
-
when /outbox/ then download_params[:type] = 'sent'
|
296
|
-
when *API_LIST_MAILBOX_TYPES then nil # nothing to do
|
297
|
-
else # shared inbox / workgroup
|
298
|
-
download_params[:recipient_workgroup_id] = lookup_entity_by_field(type: options.get_option(:group_type), value: box)['id']
|
299
|
-
end
|
300
368
|
# TODO: allow from sent as well ?
|
301
369
|
transfer_spec = @api_v5.call(
|
302
370
|
operation: 'POST',
|
@@ -319,21 +387,15 @@ module Aspera
|
|
319
387
|
end
|
320
388
|
|
321
389
|
def package_action
|
322
|
-
command = options.get_next_command(%i[
|
390
|
+
command = options.get_next_command(%i[show browse status delete receive send list])
|
391
|
+
package_id =
|
392
|
+
if %i[receive show browse status delete].include?(command)
|
393
|
+
@pub_link_context&.key?('package_id') ? @pub_link_context['package_id'] : instance_identifier
|
394
|
+
end
|
323
395
|
case command
|
324
|
-
when :list
|
325
|
-
return {
|
326
|
-
type: :object_list,
|
327
|
-
data: list_packages_with_filter,
|
328
|
-
fields: %w[id title release_date total_bytes total_files created_time state]
|
329
|
-
}
|
330
396
|
when :show
|
331
|
-
|
332
|
-
id ||= instance_identifier
|
333
|
-
return {type: :single_object, data: @api_v5.read("packages/#{id}")[:data]}
|
397
|
+
return {type: :single_object, data: @api_v5.read("packages/#{package_id}")[:data]}
|
334
398
|
when :browse
|
335
|
-
id = @pub_link_context['package_id'] if @pub_link_context&.key?('package_id')
|
336
|
-
id ||= instance_identifier
|
337
399
|
path = options.get_next_argument('path', expected: :single, mandatory: false) || '/'
|
338
400
|
# TODO: support multi-page listing ?
|
339
401
|
params = {
|
@@ -343,29 +405,32 @@ module Aspera
|
|
343
405
|
}
|
344
406
|
result = @api_v5.call({
|
345
407
|
operation: 'POST',
|
346
|
-
subpath: "packages/#{
|
408
|
+
subpath: "packages/#{package_id}/files/received",
|
347
409
|
headers: {'Accept' => 'application/json'},
|
348
410
|
url_params: params,
|
349
411
|
json_params: {'path' => path, 'filters' => {'basenames'=>[]}}})[:data]
|
350
412
|
formatter.display_item_count(result['item_count'], result['total_count'])
|
351
413
|
return {type: :object_list, data: result['items']}
|
352
414
|
when :status
|
353
|
-
status = wait_package_status(
|
415
|
+
status = wait_package_status(package_id, status_list: nil)
|
354
416
|
return {type: :single_object, data: status}
|
355
417
|
when :delete
|
356
|
-
ids =
|
418
|
+
ids = package_id
|
357
419
|
ids = [ids] unless ids.is_a?(Array)
|
358
|
-
|
420
|
+
assert_type(ids, Array){'Package identifier'}
|
421
|
+
assert(ids.all?(String)){'Package id shall be String'}
|
359
422
|
# API returns 204, empty on success
|
360
423
|
@api_v5.call({operation: 'DELETE', subpath: 'packages', headers: {'Accept' => 'application/json'}, json_params: {ids: ids}})
|
361
424
|
return Main.result_status('Package(s) deleted')
|
425
|
+
when :receive
|
426
|
+
return package_receive(package_id)
|
362
427
|
when :send
|
363
|
-
parameters = value_create_modify(command: command
|
428
|
+
parameters = value_create_modify(command: command)
|
364
429
|
normalize_recipients(parameters)
|
365
430
|
package = @api_v5.create('packages', parameters)[:data]
|
366
431
|
shared_folder = options.get_option(:shared_folder)
|
367
432
|
if shared_folder.nil?
|
368
|
-
#
|
433
|
+
# send from local files
|
369
434
|
transfer_spec = @api_v5.call(
|
370
435
|
operation: 'POST',
|
371
436
|
subpath: "packages/#{package['id']}/transfer_spec/upload",
|
@@ -377,6 +442,7 @@ module Aspera
|
|
377
442
|
transfer_spec.delete('authentication')
|
378
443
|
return Main.result_transfer(transfer.start(transfer_spec))
|
379
444
|
else
|
445
|
+
# send from remote shared folder
|
380
446
|
if (m = shared_folder.match(REGEX_LOOKUP_ID_BY_FIELD))
|
381
447
|
shared_folder = lookup_entity_by_field(type: 'shared_folders', value: m[2])['id']
|
382
448
|
end
|
@@ -390,8 +456,12 @@ module Aspera
|
|
390
456
|
end
|
391
457
|
return {type: :single_object, data: result}
|
392
458
|
end
|
393
|
-
when :
|
394
|
-
return
|
459
|
+
when :list
|
460
|
+
return {
|
461
|
+
type: :object_list,
|
462
|
+
data: list_packages_with_filter,
|
463
|
+
fields: %w[id title release_date total_bytes total_files created_time state]
|
464
|
+
}
|
395
465
|
end # case package
|
396
466
|
end
|
397
467
|
|
@@ -465,26 +535,44 @@ module Aspera
|
|
465
535
|
id_as_arg = false
|
466
536
|
display_fields = nil
|
467
537
|
adm_api = @api_v5
|
538
|
+
special_query = :default
|
468
539
|
available_commands = [].concat(Plugin::ALL_OPS)
|
469
540
|
case res_type
|
470
541
|
when :metadata_profiles
|
471
542
|
res_path = 'configuration/metadata_profiles'
|
472
543
|
list_key = 'profiles'
|
544
|
+
when :alternate_addresses
|
545
|
+
res_path = 'configuration/alternate_addresses'
|
473
546
|
when :email_notifications
|
474
547
|
list_key = false
|
475
548
|
id_as_arg = 'type'
|
476
549
|
when :accounts
|
477
|
-
display_fields =
|
550
|
+
display_fields = Formatter.all_but('user_profile_data_attributes')
|
478
551
|
when :oauth_clients
|
479
|
-
display_fields =
|
480
|
-
adm_api = Rest.new(@api_v5.params.merge({base_url:
|
552
|
+
display_fields = Formatter.all_but('public_key')
|
553
|
+
adm_api = Rest.new(@api_v5.params.merge({base_url: auth_api_url}))
|
481
554
|
when :shared_inboxes, :workgroups
|
482
555
|
available_commands.push(:members, :saml_groups, :invite_external_collaborator)
|
556
|
+
special_query = {'all': true}
|
557
|
+
when :nodes
|
558
|
+
available_commands.push(:shared_folders)
|
483
559
|
end
|
484
560
|
res_command = options.get_next_command(available_commands)
|
485
561
|
case res_command
|
486
562
|
when *Plugin::ALL_OPS
|
487
|
-
return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg)
|
563
|
+
return entity_command(res_command, adm_api, res_path, item_list_key: list_key, display_fields: display_fields, id_as_arg: id_as_arg) do |field, value|
|
564
|
+
lookup_entity_by_field(
|
565
|
+
type: res_type, real_path: res_path, field: field, value: value, query: special_query)['id']
|
566
|
+
end
|
567
|
+
when :shared_folders
|
568
|
+
node_id = instance_identifier do |field, value|
|
569
|
+
lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']
|
570
|
+
end
|
571
|
+
sh_path = "#{res_path}/#{node_id}/shared_folders"
|
572
|
+
return entity_action(adm_api, sh_path, item_list_key: 'shared_folders') do |field, value|
|
573
|
+
lookup_entity_by_field(
|
574
|
+
type: 'shared_folders', real_path: sh_path, field: field, value: value)['id']
|
575
|
+
end
|
488
576
|
when :invite_external_collaborator
|
489
577
|
shared_inbox_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
|
490
578
|
creation_payload = value_create_modify(command: res_command, type: [Hash, String])
|
@@ -492,7 +580,9 @@ module Aspera
|
|
492
580
|
res_path = "#{res_type}/#{shared_inbox_id}/external_collaborator"
|
493
581
|
result = adm_api.create(res_path, creation_payload)[:data]
|
494
582
|
formatter.display_status(result['message'])
|
495
|
-
result = lookup_entity_by_field(
|
583
|
+
result = lookup_entity_by_field(
|
584
|
+
type: 'members', real_path: "#{res_type}/#{shared_inbox_id}/members", value: creation_payload['email_address'],
|
585
|
+
query: {})
|
496
586
|
return {type: :single_object, data: result}
|
497
587
|
when :members, :saml_groups
|
498
588
|
res_id = instance_identifier { |field, value| lookup_entity_by_field(type: res_type.to_s, field: field, value: value)['id']}
|
@@ -533,20 +623,23 @@ module Aspera
|
|
533
623
|
when :show
|
534
624
|
return { type: :single_object, data: @api_v5.read(smtp_path)[:data] }
|
535
625
|
when :create
|
536
|
-
return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd
|
626
|
+
return { type: :single_object, data: @api_v5.create(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
|
537
627
|
when :modify
|
538
|
-
return { type: :single_object, data: @api_v5.
|
628
|
+
return { type: :single_object, data: @api_v5.update(smtp_path, value_create_modify(command: smtp_cmd))[:data] }
|
539
629
|
when :delete
|
540
630
|
return { type: :single_object, data: @api_v5.delete(smtp_path)[:data] }
|
541
631
|
when :test
|
542
632
|
test_data = options.get_next_argument('Email or test data, see API')
|
543
633
|
test_data = {test_email_recipient: test_data} if test_data.is_a?(String)
|
544
|
-
|
634
|
+
creation = @api_v5.create(File.join(smtp_path, 'test'), test_data)[:data]
|
635
|
+
result = wait_for_job(creation['job_id'])
|
636
|
+
result['serialized_args'] = JSON.parse(result['serialized_args']) rescue result['serialized_args']
|
637
|
+
return { type: :single_object, data: result }
|
545
638
|
end
|
546
639
|
end
|
547
640
|
when :gateway
|
548
641
|
require 'aspera/faspex_gw'
|
549
|
-
url = value_create_modify(type: String)
|
642
|
+
url = value_create_modify(command: command, description: 'listening url (e.g. https://localhost:12345)', type: String)
|
550
643
|
uri = URI.parse(url)
|
551
644
|
server = WebServerSimple.new(uri)
|
552
645
|
server.mount(uri.path, Faspex4GWServlet, @api_v5, nil)
|
@@ -559,9 +652,9 @@ module Aspera
|
|
559
652
|
return Main.result_status('Gateway terminated')
|
560
653
|
when :postprocessing
|
561
654
|
require 'aspera/faspex_postproc' # cspell:disable-line
|
562
|
-
parameters = value_create_modify(
|
655
|
+
parameters = value_create_modify(command: command)
|
563
656
|
parameters = parameters.symbolize_keys
|
564
|
-
|
657
|
+
assert(parameters.key?(:url)){'Missing key: url'}
|
565
658
|
uri = URI.parse(parameters[:url])
|
566
659
|
parameters[:processing] ||= {}
|
567
660
|
parameters[:processing][:root] = uri.path
|
@@ -576,36 +669,6 @@ module Aspera
|
|
576
669
|
return Main.result_status('Gateway terminated')
|
577
670
|
end # case command
|
578
671
|
end # action
|
579
|
-
|
580
|
-
def wizard(params)
|
581
|
-
if params[:prepare]
|
582
|
-
# if not defined by user, generate unique name
|
583
|
-
params[:preset_name] ||= [params[:plugin_sym]].concat(URI.parse(params[:instance_url]).host.gsub(/[^a-z0-9.]/, '').split('.')).join('_')
|
584
|
-
params[:need_private_key] = true
|
585
|
-
return
|
586
|
-
end
|
587
|
-
formatter.display_status('Ask the ascli client id and secret to your Administrator, or ask them to go to:'.red)
|
588
|
-
OpenApplication.instance.uri(params[:instance_url])
|
589
|
-
formatter.display_status('Then: 𓃑 → Admin → Configurations → API clients')
|
590
|
-
formatter.display_status('Create an API client with:')
|
591
|
-
formatter.display_status('- name: ascli')
|
592
|
-
formatter.display_status('- JWT: enabled')
|
593
|
-
formatter.display_status('Then, logged in as user go to your profile:')
|
594
|
-
formatter.display_status('👤 → Account Settings → Preferences -> Public Key in PEM:')
|
595
|
-
formatter.display_status(params[:pub_key_pem])
|
596
|
-
formatter.display_status('Once set, fill in the parameters:')
|
597
|
-
return {
|
598
|
-
preset_value: {
|
599
|
-
url: params[:instance_url],
|
600
|
-
username: options.get_option(:username, mandatory: true),
|
601
|
-
auth: :jwt.to_s,
|
602
|
-
private_key: '@file:' + params[:private_key_path],
|
603
|
-
client_id: options.get_option(:client_id, mandatory: true),
|
604
|
-
client_secret: options.get_option(:client_secret, mandatory: true)
|
605
|
-
},
|
606
|
-
test_args: "#{params[:plugin_sym]} user profile show"
|
607
|
-
}
|
608
|
-
end
|
609
672
|
end # Faspex5
|
610
673
|
end # Plugins
|
611
674
|
end # Cli
|