aspera-cli 4.14.0 → 4.16.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/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
|