aspera-cli 4.24.1 → 4.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +15 -2
- data/README.md +745 -436
- data/bin/ascli +20 -1
- data/bin/asession +23 -27
- data/lib/aspera/agent/base.rb +10 -21
- data/lib/aspera/agent/connect.rb +2 -3
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +49 -32
- data/lib/aspera/agent/factory.rb +31 -0
- data/lib/aspera/api/aoc.rb +79 -49
- data/lib/aspera/api/faspex.rb +212 -0
- data/lib/aspera/api/node.rb +99 -84
- data/lib/aspera/ascp/installation.rb +22 -21
- data/lib/aspera/ascp/management.rb +119 -23
- data/lib/aspera/assert.rb +14 -8
- data/lib/aspera/cli/extended_value.rb +15 -15
- data/lib/aspera/cli/formatter.rb +7 -5
- data/lib/aspera/cli/hints.rb +8 -0
- data/lib/aspera/cli/info.rb +4 -4
- data/lib/aspera/cli/main.rb +55 -70
- data/lib/aspera/cli/manager.rb +7 -4
- data/lib/aspera/cli/plugins/alee.rb +2 -1
- data/lib/aspera/cli/plugins/aoc.rb +110 -186
- data/lib/aspera/cli/plugins/ats.rb +4 -4
- data/lib/aspera/cli/plugins/base.rb +335 -0
- data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
- data/lib/aspera/cli/plugins/config.rb +249 -220
- data/lib/aspera/cli/plugins/console.rb +15 -15
- data/lib/aspera/cli/plugins/cos.rb +2 -2
- data/lib/aspera/cli/plugins/factory.rb +78 -0
- data/lib/aspera/cli/plugins/faspex.rb +17 -20
- data/lib/aspera/cli/plugins/faspex5.rb +79 -193
- data/lib/aspera/cli/plugins/faspio.rb +14 -13
- data/lib/aspera/cli/plugins/httpgw.rb +13 -12
- data/lib/aspera/cli/plugins/node.rb +34 -32
- data/lib/aspera/cli/plugins/oauth.rb +48 -0
- data/lib/aspera/cli/plugins/orchestrator.rb +15 -13
- data/lib/aspera/cli/plugins/preview.rb +4 -4
- data/lib/aspera/cli/plugins/server.rb +15 -13
- data/lib/aspera/cli/plugins/shares.rb +18 -15
- data/lib/aspera/cli/sync_actions.rb +1 -1
- data/lib/aspera/cli/transfer_agent.rb +24 -20
- data/lib/aspera/cli/transfer_progress.rb +6 -6
- data/lib/aspera/cli/version.rb +3 -3
- data/lib/aspera/cli/wizard.rb +65 -53
- data/lib/aspera/colors.rb +6 -0
- data/lib/aspera/command_line_builder.rb +45 -50
- data/lib/aspera/command_line_converter.rb +2 -1
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/environment.rb +10 -7
- data/lib/aspera/faspex_gw.rb +6 -4
- data/lib/aspera/faspex_postproc.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +1 -1
- data/lib/aspera/log.rb +37 -9
- data/lib/aspera/nagios.rb +1 -1
- data/lib/aspera/oauth/base.rb +17 -10
- data/lib/aspera/oauth/factory.rb +8 -8
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/products/connect.rb +4 -3
- data/lib/aspera/products/desktop.rb +1 -4
- data/lib/aspera/products/other.rb +9 -1
- data/lib/aspera/products/transferd.rb +0 -1
- data/lib/aspera/rest.rb +126 -83
- data/lib/aspera/ssh.rb +3 -3
- data/lib/aspera/sync/args.schema.yaml +46 -3
- data/lib/aspera/sync/conf.schema.yaml +130 -94
- data/lib/aspera/sync/operations.rb +16 -16
- data/lib/aspera/temp_file_manager.rb +17 -5
- data/lib/aspera/transfer/error.rb +16 -7
- data/lib/aspera/transfer/parameters.rb +34 -20
- data/lib/aspera/transfer/resumer.rb +74 -0
- data/lib/aspera/transfer/spec.rb +4 -3
- data/lib/aspera/transfer/spec.schema.yaml +132 -51
- data/lib/aspera/transfer/spec_doc.rb +41 -35
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +6 -6
- data.tar.gz.sig +0 -0
- metadata +9 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
- data/lib/aspera/cli/plugin.rb +0 -333
- data/lib/aspera/cli/plugin_factory.rb +0 -81
- data/lib/aspera/resumer.rb +0 -77
- data/lib/aspera/transfer/error_info.rb +0 -91
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'aspera/agent/
|
|
3
|
+
require 'aspera/agent/factory'
|
|
4
4
|
|
|
5
5
|
module Aspera
|
|
6
6
|
module Transfer
|
|
@@ -13,33 +13,38 @@ module Aspera
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
# @param formatter [Cli::Formatter] Formatter to use, methods: special_format, check_row
|
|
16
|
-
# @param include_option [Boolean]
|
|
16
|
+
# @param include_option [Boolean] `true` : include CLI options
|
|
17
|
+
# @param agent_columns [Boolean] `true` : include agents columns
|
|
18
|
+
# @param schema [Hash] The JSON spec
|
|
17
19
|
# @return [Array] a table suitable to display in manual
|
|
18
|
-
def man_table(formatter, include_option: false, agent_columns: true)
|
|
20
|
+
def man_table(formatter, include_option: false, agent_columns: true, schema: Spec::SCHEMA)
|
|
19
21
|
col_local = agent_to_short(:direct)
|
|
20
|
-
cols =
|
|
21
|
-
|
|
22
|
+
cols = %i[name type description]
|
|
23
|
+
cols.insert(-2, *AGENT_LIST.map(&:last)) if agent_columns
|
|
24
|
+
rows = []
|
|
25
|
+
schema['properties'].each do |name, info|
|
|
26
|
+
if info['type'].eql?('object') && info['properties']
|
|
27
|
+
rows.concat(man_table(formatter, include_option: include_option, agent_columns: agent_columns, schema: info).last.map { |h| h.merge(name: "#{name}.#{h[:name]}") })
|
|
28
|
+
end
|
|
22
29
|
# manual table
|
|
23
30
|
columns = {
|
|
24
31
|
name: name,
|
|
25
|
-
type:
|
|
32
|
+
type: info['type'],
|
|
26
33
|
description: []
|
|
27
34
|
}
|
|
28
|
-
# replace "back solidus" HTML entity with its text value
|
|
29
|
-
#
|
|
30
|
-
# split lines
|
|
35
|
+
# replace "back solidus" HTML entity with its text value, highlight keywords, and split lines
|
|
31
36
|
columns[:description] =
|
|
32
|
-
|
|
37
|
+
info['description']
|
|
33
38
|
.gsub('\', '\\')
|
|
34
39
|
.gsub(/`([a-z0-9_.+-]+)`/){formatter.keyword_highlight(Regexp.last_match(1))}
|
|
35
|
-
.split("\n") if
|
|
36
|
-
columns[:description].unshift("DEPRECATED: #{
|
|
40
|
+
.split("\n") if info.key?('description')
|
|
41
|
+
columns[:description].unshift("DEPRECATED: #{info['x-deprecation']}") if info.key?('x-deprecation')
|
|
37
42
|
# add flags for supported agents in doc
|
|
38
43
|
agents = []
|
|
39
44
|
AGENT_LIST.each do |agent_info|
|
|
40
|
-
agents.push(agent_info.last) if
|
|
45
|
+
agents.push(agent_info.last) if info['x-agents'].nil? || info['x-agents'].include?(agent_info.first.to_s)
|
|
41
46
|
end
|
|
42
|
-
agents.
|
|
47
|
+
Aspera.assert(agents.include?(col_local)){"#{name}: x-cli-option requires agent direct (or nil)"} if info['x-cli-option']
|
|
43
48
|
if agent_columns
|
|
44
49
|
AGENT_LIST.each do |agent_info|
|
|
45
50
|
columns[agent_info.last] = formatter.tick(agents.include?(agent_info.last))
|
|
@@ -49,30 +54,31 @@ module Aspera
|
|
|
49
54
|
end
|
|
50
55
|
# only keep lines that are usable in supported agents
|
|
51
56
|
next false if agents.empty?
|
|
52
|
-
columns[:description].push("Allowed values: #{
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
''
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
columns[:description].push("Allowed values: #{info['enum'].map{ |v| formatter.keyword_highlight(v)}.join(', ')}") if info.key?('enum')
|
|
58
|
+
if include_option
|
|
59
|
+
envvar_prefix = ''
|
|
60
|
+
cli_option =
|
|
61
|
+
if info.key?('x-cli-envvar')
|
|
62
|
+
envvar_prefix = 'env:'
|
|
63
|
+
info['x-cli-envvar']
|
|
64
|
+
elsif info['x-cli-switch']
|
|
65
|
+
info['x-cli-option']
|
|
66
|
+
elsif info['x-cli-option']
|
|
67
|
+
arg_type = info.key?('enum') ? '{enum}' : "{#{[info['type']].flatten.join('|')}}"
|
|
68
|
+
# conversion_tag = info['x-cli-convert']
|
|
69
|
+
conversion_tag = info.key?('x-cli-convert') ? 'conversion' : nil
|
|
70
|
+
sep = info['x-cli-option'].start_with?('--') ? '=' : ' '
|
|
71
|
+
"#{info['x-cli-option']}#{sep}#{"(#{conversion_tag})" if conversion_tag}#{arg_type}"
|
|
72
|
+
end
|
|
73
|
+
columns[:description].push("(#{'special:' if info['x-cli-special']}#{envvar_prefix}#{formatter.keyword_highlight(cli_option)})") if cli_option
|
|
74
|
+
end
|
|
75
|
+
rows.push(formatter.check_row(columns))
|
|
76
|
+
end
|
|
77
|
+
[cols, rows.sort_by{ |i| i[:name]}]
|
|
72
78
|
end
|
|
73
79
|
end
|
|
74
80
|
# Agents shown in manual for parameters (sub list)
|
|
75
|
-
AGENT_LIST = Agent::
|
|
81
|
+
AGENT_LIST = Agent::Factory.instance.list.map do |agent_sym|
|
|
76
82
|
[agent_sym, agent_sym.to_s.capitalize, agent_to_short(agent_sym)]
|
|
77
83
|
end.sort_by(&:last).freeze
|
|
78
84
|
end
|
data/lib/aspera/uri_reader.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Aspera
|
|
|
37
37
|
return File.expand_path(url[SCHEME_FILE_PFX2.length..-1])
|
|
38
38
|
else
|
|
39
39
|
# download to temp file
|
|
40
|
-
#
|
|
40
|
+
# auto-delete on exit
|
|
41
41
|
sdk_archive_path = TempFileManager.instance.new_file_path_global(suffix: File.basename(url))
|
|
42
42
|
Aspera::Rest.new(base_url: url, redirect_max: 3).call(operation: 'GET', save_to_file: sdk_archive_path)
|
|
43
43
|
return sdk_archive_path
|
data/lib/aspera/web_auth.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Aspera
|
|
|
17
17
|
def service(request, response)
|
|
18
18
|
Log.log.debug{"received request from browser #{request.request_method} #{request.path}"}
|
|
19
19
|
Aspera.assert_values(request.request_method, ['GET'], type: WEBrick::HTTPStatus::MethodNotAllowed){'HTTP verb'}
|
|
20
|
-
|
|
20
|
+
additional_info = @web_auth.signal_request(request)
|
|
21
21
|
response.status = 200
|
|
22
22
|
response.content_type = 'text/html'
|
|
23
23
|
response.body = <<~HTML
|
|
@@ -91,7 +91,7 @@ module Aspera
|
|
|
91
91
|
<body>
|
|
92
92
|
<h1>Thank You!</h1>
|
|
93
93
|
<p>You can close this window.</p>
|
|
94
|
-
<p>#{
|
|
94
|
+
<p>#{additional_info}</p>
|
|
95
95
|
|
|
96
96
|
<!-- JavaScript to generate IBM logos -->
|
|
97
97
|
<script>
|
|
@@ -170,15 +170,15 @@ module Aspera
|
|
|
170
170
|
# store the final query
|
|
171
171
|
class WebAuth < WebServerSimple
|
|
172
172
|
# @param endpoint_url [String] e.g. 'https://127.0.0.1:12345'
|
|
173
|
-
# @param
|
|
174
|
-
def initialize(endpoint_url,
|
|
173
|
+
# @param additional_info [String] Information in web page
|
|
174
|
+
def initialize(endpoint_url, additional_info = nil)
|
|
175
175
|
uri = URI.parse(endpoint_url)
|
|
176
176
|
super(uri)
|
|
177
177
|
@mutex = Mutex.new
|
|
178
178
|
@cond = ConditionVariable.new
|
|
179
179
|
@expected_path = uri.path.empty? ? '/' : uri.path
|
|
180
180
|
@query = nil
|
|
181
|
-
@
|
|
181
|
+
@additional_info = additional_info
|
|
182
182
|
# last argument (self) is provided to constructor of servlet
|
|
183
183
|
mount(@expected_path, WebAuthServlet, self)
|
|
184
184
|
# server runs in thread
|
|
@@ -194,7 +194,7 @@ module Aspera
|
|
|
194
194
|
@query = request.query
|
|
195
195
|
@cond.signal
|
|
196
196
|
end
|
|
197
|
-
return @
|
|
197
|
+
return @additional_info
|
|
198
198
|
end
|
|
199
199
|
|
|
200
200
|
# wait for request on web server (main thread)
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aspera-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.24.
|
|
4
|
+
version: 4.24.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Laurent Martin
|
|
@@ -37,7 +37,7 @@ cert_chain:
|
|
|
37
37
|
eTf9kxhVM40wGQOECVNA8UsEEZHD48eF+csUYZtAJOF5oxTI8UyV9T/o6CgO0c9/
|
|
38
38
|
Gzz+Qm5ULOUcPiJLjSpaiTrkiIVYiDGnqNSr6R1Hb1c=
|
|
39
39
|
-----END CERTIFICATE-----
|
|
40
|
-
date: 2025-10-
|
|
40
|
+
date: 2025-10-23 00:00:00.000000000 Z
|
|
41
41
|
dependencies:
|
|
42
42
|
- !ruby/object:Gem::Dependency
|
|
43
43
|
name: blankslate
|
|
@@ -282,6 +282,7 @@ files:
|
|
|
282
282
|
- lib/aspera/agent/connect.rb
|
|
283
283
|
- lib/aspera/agent/desktop.rb
|
|
284
284
|
- lib/aspera/agent/direct.rb
|
|
285
|
+
- lib/aspera/agent/factory.rb
|
|
285
286
|
- lib/aspera/agent/httpgw.rb
|
|
286
287
|
- lib/aspera/agent/node.rb
|
|
287
288
|
- lib/aspera/agent/transferd.rb
|
|
@@ -289,13 +290,13 @@ files:
|
|
|
289
290
|
- lib/aspera/api/aoc.rb
|
|
290
291
|
- lib/aspera/api/ats.rb
|
|
291
292
|
- lib/aspera/api/cos_node.rb
|
|
293
|
+
- lib/aspera/api/faspex.rb
|
|
292
294
|
- lib/aspera/api/httpgw.rb
|
|
293
295
|
- lib/aspera/api/node.rb
|
|
294
296
|
- lib/aspera/ascmd.rb
|
|
295
297
|
- lib/aspera/ascp/installation.rb
|
|
296
298
|
- lib/aspera/ascp/management.rb
|
|
297
299
|
- lib/aspera/assert.rb
|
|
298
|
-
- lib/aspera/cli/basic_auth_plugin.rb
|
|
299
300
|
- lib/aspera/cli/error.rb
|
|
300
301
|
- lib/aspera/cli/extended_value.rb
|
|
301
302
|
- lib/aspera/cli/formatter.rb
|
|
@@ -303,19 +304,21 @@ files:
|
|
|
303
304
|
- lib/aspera/cli/info.rb
|
|
304
305
|
- lib/aspera/cli/main.rb
|
|
305
306
|
- lib/aspera/cli/manager.rb
|
|
306
|
-
- lib/aspera/cli/plugin.rb
|
|
307
|
-
- lib/aspera/cli/plugin_factory.rb
|
|
308
307
|
- lib/aspera/cli/plugins/alee.rb
|
|
309
308
|
- lib/aspera/cli/plugins/aoc.rb
|
|
310
309
|
- lib/aspera/cli/plugins/ats.rb
|
|
310
|
+
- lib/aspera/cli/plugins/base.rb
|
|
311
|
+
- lib/aspera/cli/plugins/basic_auth.rb
|
|
311
312
|
- lib/aspera/cli/plugins/config.rb
|
|
312
313
|
- lib/aspera/cli/plugins/console.rb
|
|
313
314
|
- lib/aspera/cli/plugins/cos.rb
|
|
315
|
+
- lib/aspera/cli/plugins/factory.rb
|
|
314
316
|
- lib/aspera/cli/plugins/faspex.rb
|
|
315
317
|
- lib/aspera/cli/plugins/faspex5.rb
|
|
316
318
|
- lib/aspera/cli/plugins/faspio.rb
|
|
317
319
|
- lib/aspera/cli/plugins/httpgw.rb
|
|
318
320
|
- lib/aspera/cli/plugins/node.rb
|
|
321
|
+
- lib/aspera/cli/plugins/oauth.rb
|
|
319
322
|
- lib/aspera/cli/plugins/orchestrator.rb
|
|
320
323
|
- lib/aspera/cli/plugins/preview.rb
|
|
321
324
|
- lib/aspera/cli/plugins/server.rb
|
|
@@ -378,7 +381,6 @@ files:
|
|
|
378
381
|
- lib/aspera/rest_call_error.rb
|
|
379
382
|
- lib/aspera/rest_error_analyzer.rb
|
|
380
383
|
- lib/aspera/rest_errors_aspera.rb
|
|
381
|
-
- lib/aspera/resumer.rb
|
|
382
384
|
- lib/aspera/secret_hider.rb
|
|
383
385
|
- lib/aspera/ssh.rb
|
|
384
386
|
- lib/aspera/sync/args.schema.yaml
|
|
@@ -388,9 +390,9 @@ files:
|
|
|
388
390
|
- lib/aspera/temp_file_manager.rb
|
|
389
391
|
- lib/aspera/timer_limiter.rb
|
|
390
392
|
- lib/aspera/transfer/error.rb
|
|
391
|
-
- lib/aspera/transfer/error_info.rb
|
|
392
393
|
- lib/aspera/transfer/faux_file.rb
|
|
393
394
|
- lib/aspera/transfer/parameters.rb
|
|
395
|
+
- lib/aspera/transfer/resumer.rb
|
|
394
396
|
- lib/aspera/transfer/spec.rb
|
|
395
397
|
- lib/aspera/transfer/spec.schema.yaml
|
|
396
398
|
- lib/aspera/transfer/spec_doc.rb
|
metadata.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'aspera/rest'
|
|
4
|
-
require 'aspera/cli/plugin'
|
|
5
|
-
|
|
6
|
-
module Aspera
|
|
7
|
-
module Cli
|
|
8
|
-
# base class for applications supporting basic authentication
|
|
9
|
-
class BasicAuthPlugin < Cli::Plugin
|
|
10
|
-
class << self
|
|
11
|
-
def declare_options(options)
|
|
12
|
-
options.declare(:url, 'URL of application, e.g. https://app.example.com/aspera/app')
|
|
13
|
-
options.declare(:username, "User's identifier")
|
|
14
|
-
options.declare(:password, "User's password")
|
|
15
|
-
options.parse_options!
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def initialize(context:, basic_options: true)
|
|
20
|
-
super(context: context)
|
|
21
|
-
BasicAuthPlugin.declare_options(options) if basic_options
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# returns a Rest object with basic auth
|
|
25
|
-
def basic_auth_params(subpath = nil)
|
|
26
|
-
api_url = options.get_option(:url, mandatory: true)
|
|
27
|
-
api_url = "#{api_url}/#{subpath}" unless subpath.nil?
|
|
28
|
-
return {
|
|
29
|
-
base_url: api_url,
|
|
30
|
-
auth: {
|
|
31
|
-
type: :basic,
|
|
32
|
-
username: options.get_option(:username, mandatory: true),
|
|
33
|
-
password: options.get_option(:password, mandatory: true)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def basic_auth_api(subpath = nil)
|
|
39
|
-
return Rest.new(**basic_auth_params(subpath))
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
data/lib/aspera/cli/plugin.rb
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'aspera/cli/extended_value'
|
|
4
|
-
require 'aspera/assert'
|
|
5
|
-
|
|
6
|
-
module Aspera
|
|
7
|
-
module Cli
|
|
8
|
-
# Base class for plugins
|
|
9
|
-
class Plugin
|
|
10
|
-
# operations without id
|
|
11
|
-
GLOBAL_OPS = %i[create list].freeze
|
|
12
|
-
# operations with id
|
|
13
|
-
INSTANCE_OPS = %i[modify delete show].freeze
|
|
14
|
-
# all standard operations
|
|
15
|
-
ALL_OPS = (GLOBAL_OPS + INSTANCE_OPS).freeze
|
|
16
|
-
# special query parameter: max number of items for list command
|
|
17
|
-
MAX_ITEMS = 'max'
|
|
18
|
-
# special query parameter: max number of pages for list command
|
|
19
|
-
MAX_PAGES = 'pmax'
|
|
20
|
-
# special identifier format: look for this name to find where supported
|
|
21
|
-
REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
|
|
22
|
-
PER_PAGE_DEFAULT = 1000
|
|
23
|
-
private_constant :PER_PAGE_DEFAULT
|
|
24
|
-
|
|
25
|
-
class << self
|
|
26
|
-
def declare_generic_options(options)
|
|
27
|
-
options.declare(:query, 'Additional filter for for some commands (list/delete)', types: [Hash, Array])
|
|
28
|
-
options.declare(:property, 'Name of property to set (modify operation)')
|
|
29
|
-
options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
|
|
30
|
-
options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def initialize(context:)
|
|
35
|
-
# check presence in descendant of mandatory method and constant
|
|
36
|
-
Aspera.assert(respond_to?(:execute_action), type: InternalError){"Missing method 'execute_action' in #{self.class}"}
|
|
37
|
-
Aspera.assert(self.class.constants.include?(:ACTIONS), type: InternalError){"Missing constant 'ACTIONS' in #{self.class}"}
|
|
38
|
-
@context = context
|
|
39
|
-
add_manual_header if @context.man_header
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Global objects
|
|
43
|
-
attr_reader :context
|
|
44
|
-
|
|
45
|
-
def options; @context.options; end
|
|
46
|
-
def transfer; @context.transfer; end
|
|
47
|
-
def config; @context.config; end
|
|
48
|
-
def formatter; @context.formatter; end
|
|
49
|
-
def persistency; @context.persistency; end
|
|
50
|
-
|
|
51
|
-
def add_manual_header(has_options = true)
|
|
52
|
-
# manual header for all plugins
|
|
53
|
-
options.parser.separator('')
|
|
54
|
-
options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
|
|
55
|
-
options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
|
|
56
|
-
options.parser.separator('OPTIONS:') if has_options
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Must be called AFTER the instance action:
|
|
60
|
-
# ... folder browse _call_instance_identifier
|
|
61
|
-
#
|
|
62
|
-
# @param description [String] description of the identifier
|
|
63
|
-
# @param as_option [Symbol] option name to use if identifier is an option
|
|
64
|
-
# @param block [Proc] block to search for identifier based on attribute value
|
|
65
|
-
# @return [String, Array] identifier or list of ids
|
|
66
|
-
def instance_identifier(description: 'identifier', as_option: nil, &block)
|
|
67
|
-
if as_option.nil?
|
|
68
|
-
res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
|
|
69
|
-
else
|
|
70
|
-
res_id = options.get_option(as_option)
|
|
71
|
-
end
|
|
72
|
-
# can be an Array
|
|
73
|
-
if res_id.is_a?(String) && (m = res_id.match(REGEX_LOOKUP_ID_BY_FIELD))
|
|
74
|
-
if block
|
|
75
|
-
res_id = yield(m[1], ExtendedValue.instance.evaluate(m[2]))
|
|
76
|
-
else
|
|
77
|
-
raise Cli::BadArgument, "Percent syntax for #{description} not supported in this context"
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
return res_id
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# For create and delete operations: execute one actin or multiple if bulk is yes
|
|
84
|
-
# @param command [Symbol] operation: :create, :delete, ...
|
|
85
|
-
# @param descr [String] description of the value
|
|
86
|
-
# @param values [Object] the value(s), or the type of value to get from user
|
|
87
|
-
# @param id_result [String] key in result hash to use as identifier
|
|
88
|
-
# @param fields [Array] fields to display
|
|
89
|
-
# @param &block [Proc] block to execute for each value
|
|
90
|
-
def do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default)
|
|
91
|
-
Aspera.assert(block_given?){'missing block'}
|
|
92
|
-
is_bulk = options.get_option(:bulk)
|
|
93
|
-
case values
|
|
94
|
-
when :identifier
|
|
95
|
-
values = instance_identifier(description: descr)
|
|
96
|
-
when Class
|
|
97
|
-
values = value_create_modify(command: command, description: descr, type: values, bulk: is_bulk)
|
|
98
|
-
end
|
|
99
|
-
# if not bulk, there is a single value
|
|
100
|
-
params = is_bulk ? values : [values]
|
|
101
|
-
Log.log.warn('Empty list given for bulk operation') if params.empty?
|
|
102
|
-
Log.dump(:bulk_operation, params)
|
|
103
|
-
result_list = []
|
|
104
|
-
params.each do |param|
|
|
105
|
-
# init for delete
|
|
106
|
-
result = {id_result => param}
|
|
107
|
-
begin
|
|
108
|
-
# execute custom code
|
|
109
|
-
res = yield(param)
|
|
110
|
-
# if block returns a hash, let's use this (create)
|
|
111
|
-
result = res if res.is_a?(Hash)
|
|
112
|
-
# TODO: remove when faspio gw api fixes this
|
|
113
|
-
result = res.first if res.is_a?(Array) && res.first.is_a?(Hash)
|
|
114
|
-
# create -> created
|
|
115
|
-
result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
|
|
116
|
-
rescue StandardError => e
|
|
117
|
-
raise e if options.get_option(:bfail)
|
|
118
|
-
result['status'] = e.to_s
|
|
119
|
-
end
|
|
120
|
-
result_list.push(result)
|
|
121
|
-
end
|
|
122
|
-
display_fields = [id_result, 'status']
|
|
123
|
-
if is_bulk
|
|
124
|
-
return Main.result_object_list(result_list, fields: display_fields)
|
|
125
|
-
else
|
|
126
|
-
display_fields = fields unless fields.eql?(:default)
|
|
127
|
-
return Main.result_single_object(result_list.first, fields: display_fields)
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
# Operations: Create, Delete, Show, List, Modify
|
|
132
|
-
# @param api [Rest] api to use
|
|
133
|
-
# @param entity [String] sub path in URL to resource relative to base url
|
|
134
|
-
# @param command [Symbol] command to execute: create show list modify delete
|
|
135
|
-
# @param display_fields [Array] fields to display by default
|
|
136
|
-
# @param items_key [String] result is in a sub key of the json
|
|
137
|
-
# @param delete_style [String] if set, the delete operation by array in payload
|
|
138
|
-
# @param id_as_arg [String] if set, the id is provided as url argument ?<id_as_arg>=<id>
|
|
139
|
-
# @param is_singleton [Boolean] if true, entity is the full path to the resource
|
|
140
|
-
# @param tclo [Bool] if set, :list use paging with total_count, limit, offset
|
|
141
|
-
# @param block [Proc] block to search for identifier based on attribute value
|
|
142
|
-
# @return result suitable for CLI result
|
|
143
|
-
def entity_execute(
|
|
144
|
-
api:,
|
|
145
|
-
entity:,
|
|
146
|
-
command: nil,
|
|
147
|
-
display_fields: nil,
|
|
148
|
-
items_key: nil,
|
|
149
|
-
delete_style: nil,
|
|
150
|
-
id_as_arg: false,
|
|
151
|
-
is_singleton: false,
|
|
152
|
-
list_query: nil,
|
|
153
|
-
tclo: false,
|
|
154
|
-
&block
|
|
155
|
-
)
|
|
156
|
-
command = options.get_next_command(ALL_OPS) if command.nil?
|
|
157
|
-
if is_singleton
|
|
158
|
-
one_res_path = entity
|
|
159
|
-
elsif INSTANCE_OPS.include?(command)
|
|
160
|
-
one_res_id = instance_identifier(&block)
|
|
161
|
-
one_res_path = "#{entity}/#{one_res_id}"
|
|
162
|
-
one_res_path = "#{entity}?#{id_as_arg}=#{one_res_id}" if id_as_arg
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
case command
|
|
166
|
-
when :create
|
|
167
|
-
raise BadArgument, 'cannot create singleton' if is_singleton
|
|
168
|
-
return do_bulk_operation(command: command, descr: 'data', fields: display_fields) do |params|
|
|
169
|
-
api.create(entity, params)
|
|
170
|
-
end
|
|
171
|
-
when :delete
|
|
172
|
-
raise BadArgument, 'cannot delete singleton' if is_singleton
|
|
173
|
-
if !delete_style.nil?
|
|
174
|
-
one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
|
|
175
|
-
Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
|
|
176
|
-
api.call(
|
|
177
|
-
operation: 'DELETE',
|
|
178
|
-
subpath: entity,
|
|
179
|
-
content_type: Rest::MIME_JSON,
|
|
180
|
-
body: {delete_style => one_res_id},
|
|
181
|
-
headers: {'Accept' => Rest::MIME_JSON}
|
|
182
|
-
)
|
|
183
|
-
return Main.result_status('deleted')
|
|
184
|
-
end
|
|
185
|
-
return do_bulk_operation(command: command, values: one_res_id) do |one_id|
|
|
186
|
-
api.delete("#{entity}/#{one_id}", query_read_delete)
|
|
187
|
-
{'id' => one_id}
|
|
188
|
-
end
|
|
189
|
-
when :show
|
|
190
|
-
return Main.result_single_object(api.read(one_res_path), fields: display_fields)
|
|
191
|
-
when :list
|
|
192
|
-
if tclo
|
|
193
|
-
data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: list_query)
|
|
194
|
-
return Main.result_object_list(data, total: total, fields: display_fields)
|
|
195
|
-
end
|
|
196
|
-
resp = api.call(operation: 'GET', subpath: entity, headers: {'Accept' => Rest::MIME_JSON}, query: query_read_delete)
|
|
197
|
-
return Main.result_empty if resp[:http].code == '204'
|
|
198
|
-
data = resp[:data]
|
|
199
|
-
# TODO: not generic : which application is this for ?
|
|
200
|
-
if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
|
|
201
|
-
Log.log.debug('is vnd.api')
|
|
202
|
-
data = data[entity]
|
|
203
|
-
end
|
|
204
|
-
data = data[items_key] if items_key
|
|
205
|
-
case data
|
|
206
|
-
when Hash
|
|
207
|
-
return Main.result_single_object(data, fields: display_fields)
|
|
208
|
-
when Array
|
|
209
|
-
return Main.result_object_list(data, fields: display_fields) if data.empty? || data.first.is_a?(Hash)
|
|
210
|
-
return Main.result_value_list(data)
|
|
211
|
-
else
|
|
212
|
-
raise "An error occurred: unexpected result type for list: #{data.class}"
|
|
213
|
-
end
|
|
214
|
-
when :modify
|
|
215
|
-
parameters = value_create_modify(command: command)
|
|
216
|
-
property = options.get_option(:property)
|
|
217
|
-
parameters = {property => parameters} unless property.nil?
|
|
218
|
-
api.update(one_res_path, parameters)
|
|
219
|
-
return Main.result_status('modified')
|
|
220
|
-
else
|
|
221
|
-
raise "unknown action: #{command}"
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Query parameters in URL suitable for REST: list/GET and delete/DELETE
|
|
226
|
-
def query_read_delete(default: nil)
|
|
227
|
-
query = options.get_option(:query)
|
|
228
|
-
# dup default, as it could be frozen
|
|
229
|
-
query = default.dup if query.nil?
|
|
230
|
-
Log.log.debug{"query_read_delete=#{query}".bg_red}
|
|
231
|
-
begin
|
|
232
|
-
# check it is suitable
|
|
233
|
-
URI.encode_www_form(query) unless query.nil?
|
|
234
|
-
rescue StandardError => e
|
|
235
|
-
raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
|
|
236
|
-
end
|
|
237
|
-
return query
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
# Retrieves an extended value from command line, used for creation or modification of entities
|
|
241
|
-
# @param command [Symbol] command name for error message
|
|
242
|
-
# @param type [Class] expected type of value, either a Class, an Array of Class
|
|
243
|
-
# @param bulk [Boolean] if true, value must be an Array of <type>
|
|
244
|
-
# @param default [Object] default value if not provided
|
|
245
|
-
def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
|
|
246
|
-
value = options.get_next_argument(
|
|
247
|
-
"parameters for #{command}#{" (#{description})" unless description.nil?}", mandatory: default.nil?,
|
|
248
|
-
validation: bulk ? Array : type
|
|
249
|
-
)
|
|
250
|
-
value = default if value.nil?
|
|
251
|
-
unless type.nil?
|
|
252
|
-
type = [type] unless type.is_a?(Array)
|
|
253
|
-
Aspera.assert(type.all?(Class)){"check types must be a Class, not #{type.map(&:class).join(',')}"}
|
|
254
|
-
if bulk
|
|
255
|
-
Aspera.assert_type(value, Array, type: Cli::BadArgument)
|
|
256
|
-
value.each do |v|
|
|
257
|
-
Aspera.assert_values(v.class, type, type: Cli::BadArgument)
|
|
258
|
-
end
|
|
259
|
-
else
|
|
260
|
-
Aspera.assert_values(value.class, type, type: Cli::BadArgument)
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
return value
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# Get a (full or partial) list of all entities of a given type with query: offset/limit
|
|
267
|
-
# @param `api` [Rest] the API object
|
|
268
|
-
# @param `entity` [String,Symbol] the API endpoint of entity to list
|
|
269
|
-
# @param `items_key` [String] key in the result to get the list of items
|
|
270
|
-
# @param `query` [Hash,nil] additional query parameters
|
|
271
|
-
# @return [Array] items, total_count
|
|
272
|
-
def list_entities_limit_offset_total_count(
|
|
273
|
-
api:,
|
|
274
|
-
entity:,
|
|
275
|
-
items_key: nil,
|
|
276
|
-
query: nil
|
|
277
|
-
)
|
|
278
|
-
entity = entity.to_s if entity.is_a?(Symbol)
|
|
279
|
-
items_key = entity.split('/').last if items_key.nil?
|
|
280
|
-
query = {} if query.nil?
|
|
281
|
-
Aspera.assert_type(entity, String)
|
|
282
|
-
Aspera.assert_type(items_key, String)
|
|
283
|
-
Aspera.assert_type(query, Hash)
|
|
284
|
-
Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
|
|
285
|
-
result = []
|
|
286
|
-
offset = 0
|
|
287
|
-
max_items = query.delete(MAX_ITEMS)
|
|
288
|
-
remain_pages = query.delete(MAX_PAGES)
|
|
289
|
-
# merge default parameters, by default 100 per page
|
|
290
|
-
query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
|
|
291
|
-
total_count = nil
|
|
292
|
-
loop do
|
|
293
|
-
query['offset'] = offset
|
|
294
|
-
page_result = api.read(entity, query)
|
|
295
|
-
Aspera.assert_type(page_result[items_key], Array)
|
|
296
|
-
result.concat(page_result[items_key])
|
|
297
|
-
# reach the limit set by user ?
|
|
298
|
-
if !max_items.nil? && (result.length >= max_items)
|
|
299
|
-
result = result.slice(0, max_items)
|
|
300
|
-
break
|
|
301
|
-
end
|
|
302
|
-
total_count ||= page_result['total_count']
|
|
303
|
-
break if result.length >= total_count
|
|
304
|
-
remain_pages -= 1 unless remain_pages.nil?
|
|
305
|
-
break if remain_pages == 0
|
|
306
|
-
offset += page_result[items_key].length
|
|
307
|
-
formatter.long_operation_running
|
|
308
|
-
end
|
|
309
|
-
formatter.long_operation_terminated
|
|
310
|
-
return result, total_count
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# Lookup an entity id from its name
|
|
314
|
-
# @param entity [String] the type of entity to lookup, by default it is the path, and it is also the field name in result
|
|
315
|
-
# @param value [String] the value to lookup
|
|
316
|
-
# @param field [String] the field to match, by default it is 'name'
|
|
317
|
-
# @param items_key [String] key in the result to get the list of items (override entity)
|
|
318
|
-
# @param query [Hash] additional query parameters
|
|
319
|
-
def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
|
|
320
|
-
if query.eql?(:default)
|
|
321
|
-
Aspera.assert(field.eql?('name')){'Default query is on name only'}
|
|
322
|
-
query = {'q'=> value}
|
|
323
|
-
end
|
|
324
|
-
found = list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first.select{ |i| i[field].eql?(value)}
|
|
325
|
-
case found.length
|
|
326
|
-
when 0 then raise "No #{entity} with #{field} = #{value}"
|
|
327
|
-
when 1 then return found.first
|
|
328
|
-
else raise "Found #{found.length} #{entity} with #{field} = #{value}"
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
end
|
|
332
|
-
end
|
|
333
|
-
end
|