aspera-cli 4.27.2 → 4.27.3
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 +50 -0
- data/bin/ascli +2 -1
- data/docs/README.md +804 -746
- data/lib/aspera/agent/connect.rb +6 -4
- data/lib/aspera/agent/desktop.rb +2 -2
- data/lib/aspera/agent/direct.rb +3 -1
- data/lib/aspera/agent/node.rb +3 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -12
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +2 -2
- data/lib/aspera/api/faspex.rb +9 -7
- data/lib/aspera/api/httpgw.rb +37 -33
- data/lib/aspera/api/node.rb +38 -33
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +62 -27
- data/lib/aspera/ascp/management.rb +1 -0
- data/lib/aspera/assert.rb +4 -0
- data/lib/aspera/cli/ascp_actions.rb +20 -41
- data/lib/aspera/cli/async_transfer_store.rb +2 -2
- data/lib/aspera/cli/bootstrapper.rb +11 -15
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +149 -33
- data/lib/aspera/cli/command_spec.rb +103 -14
- data/lib/aspera/cli/completion/ascli.bash +12 -0
- data/lib/aspera/cli/completion/ascli.fish +16 -0
- data/lib/aspera/cli/completion/ascli.zsh +19 -0
- data/lib/aspera/cli/context.rb +3 -0
- data/lib/aspera/cli/deprecation.rb +37 -0
- data/lib/aspera/cli/extended_value.rb +2 -0
- data/lib/aspera/cli/formatter.rb +87 -75
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +21 -21
- data/lib/aspera/cli/info.rb +3 -0
- data/lib/aspera/cli/mcp_tool.rb +47 -83
- data/lib/aspera/cli/option_declarator.rb +33 -42
- data/lib/aspera/cli/option_registry.rb +69 -0
- data/lib/aspera/cli/option_types.rb +103 -0
- data/lib/aspera/cli/option_value.rb +281 -0
- data/lib/aspera/cli/options.schema.yaml +38 -5
- data/lib/aspera/cli/parser.rb +307 -848
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +430 -380
- data/lib/aspera/cli/plugins/ats.rb +58 -73
- data/lib/aspera/cli/plugins/base.rb +190 -240
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +244 -178
- data/lib/aspera/cli/plugins/console.rb +102 -38
- data/lib/aspera/cli/plugins/cos.rb +6 -23
- data/lib/aspera/cli/plugins/factory.rb +3 -0
- data/lib/aspera/cli/plugins/faspex5.rb +176 -173
- data/lib/aspera/cli/plugins/faspio.rb +5 -10
- data/lib/aspera/cli/plugins/httpgw.rb +8 -11
- data/lib/aspera/cli/plugins/mcp.rb +20 -55
- data/lib/aspera/cli/plugins/node.rb +277 -311
- data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
- data/lib/aspera/cli/plugins/preview.rb +79 -90
- data/lib/aspera/cli/plugins/server.rb +76 -50
- data/lib/aspera/cli/plugins/shares.rb +68 -116
- data/lib/aspera/cli/preset_actions.rb +17 -10
- data/lib/aspera/cli/preset_manager.rb +12 -2
- data/lib/aspera/cli/prompt.rb +35 -0
- data/lib/aspera/cli/result.rb +13 -18
- data/lib/aspera/cli/runner.rb +31 -54
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +41 -37
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +0 -6
- data/lib/aspera/cli/transfer_agent.rb +29 -35
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +4 -2
- data/lib/aspera/coverage.rb +1 -0
- data/lib/aspera/environment.rb +6 -0
- data/lib/aspera/faspex_gw.rb +2 -1
- data/lib/aspera/faspex_postproc.rb +1 -0
- data/lib/aspera/graphql.rb +5 -5
- data/lib/aspera/json_rpc/client.rb +5 -5
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +22 -25
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_simulator.rb +1 -0
- data/lib/aspera/oauth/base.rb +35 -25
- data/lib/aspera/oauth/factory.rb +1 -0
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/jwt.rb +1 -1
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +7 -0
- data/lib/aspera/preview/options.rb +4 -4
- data/lib/aspera/preview/terminal.rb +4 -3
- data/lib/aspera/preview/utils.rb +9 -6
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/rainbow.rb +7 -0
- data/lib/aspera/rest/aspera_errors.rb +60 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +514 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +143 -0
- data/lib/aspera/rest/parameters.rb +55 -0
- data/lib/aspera/rest/util.rb +176 -0
- data/lib/aspera/rest.rb +7 -621
- data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/registry.rb +18 -1
- data/lib/aspera/schema/validator.rb +92 -0
- data/lib/aspera/secret_hider.rb +36 -25
- data/lib/aspera/string_ext.rb +15 -0
- data/lib/aspera/temp_file_manager.rb +6 -5
- data/lib/aspera/transfer/parameters.rb +2 -0
- data/lib/aspera/transfer/spec.rb +1 -0
- data/lib/aspera/uri_reader.rb +11 -11
- data/lib/aspera/web_auth/index.html +147 -0
- data/lib/aspera/web_auth/server.rb +81 -0
- data.tar.gz.sig +0 -0
- metadata +39 -7
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/rest_call_error.rb +0 -25
- data/lib/aspera/rest_error_analyzer.rb +0 -111
- data/lib/aspera/rest_errors_aspera.rb +0 -58
- data/lib/aspera/rest_list.rb +0 -136
- data/lib/aspera/web_auth.rb +0 -211
|
@@ -81,7 +81,6 @@ module Aspera
|
|
|
81
81
|
@connection_type = :ssh
|
|
82
82
|
@ascmd_executor = nil
|
|
83
83
|
@server_transfer_spec = nil
|
|
84
|
-
options.parse_options!
|
|
85
84
|
@ssh_opts = options.get_option(:ssh_options).symbolize_keys
|
|
86
85
|
end
|
|
87
86
|
|
|
@@ -160,7 +159,7 @@ module Aspera
|
|
|
160
159
|
end
|
|
161
160
|
|
|
162
161
|
Sync::Operations::DIRECTIONS.each do |dir|
|
|
163
|
-
define_method(:"action_sync_#{dir}") { |path:, sync_info: {}, **| run_sync_transfer(dir, path: path, sync_info: sync_info) {
|
|
162
|
+
define_method(:"action_sync_#{dir}") { |path:, sync_info: {}, **| run_sync_transfer(dir, path: path, sync_info: sync_info) { server_transfer_spec } }
|
|
164
163
|
end
|
|
165
164
|
|
|
166
165
|
# --- DSL ---
|
|
@@ -169,22 +168,18 @@ module Aspera
|
|
|
169
168
|
option :passphrase, description: 'SSH private key passphrase'
|
|
170
169
|
option :ssh_options, description: 'SSH options', allowed: Hash
|
|
171
170
|
|
|
172
|
-
# root_setup runs once before any command argument is consumed, populating
|
|
173
|
-
# @server_transfer_spec and @ascmd_executor so that condition methods work.
|
|
174
|
-
root_setup :setup_server
|
|
175
|
-
|
|
176
171
|
command :health, description: 'Check transfer health'
|
|
177
|
-
command(:upload, description: 'Upload files to server', transfer_paths: :send, action: lambda do
|
|
178
|
-
|
|
179
|
-
Runner.result_transfer(transfer.start(
|
|
172
|
+
command(:upload, description: 'Upload files to server', transfer_paths: :send, action: lambda do |**|
|
|
173
|
+
server_transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(:upload)
|
|
174
|
+
Runner.result_transfer(transfer.start(server_transfer_spec))
|
|
180
175
|
end)
|
|
181
176
|
command(
|
|
182
177
|
:download,
|
|
183
178
|
description: 'Download files from server',
|
|
184
179
|
transfer_paths: :receive,
|
|
185
|
-
action: lambda do
|
|
186
|
-
|
|
187
|
-
Runner.result_transfer(transfer.start(
|
|
180
|
+
action: lambda do |**|
|
|
181
|
+
server_transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(:download)
|
|
182
|
+
Runner.result_transfer(transfer.start(server_transfer_spec))
|
|
188
183
|
end
|
|
189
184
|
)
|
|
190
185
|
command :sync, description: 'Synchronize files with server'
|
|
@@ -201,43 +196,76 @@ module Aspera
|
|
|
201
196
|
end
|
|
202
197
|
|
|
203
198
|
# AsCmd operations (available only when SSH/local - not WSS)
|
|
204
|
-
|
|
205
|
-
|
|
199
|
+
# Arguments follow AsCmd::OPS_ARGS: one path per operation, or source and destination
|
|
200
|
+
PATHS_ARGS = [{name: :paths, multiple: true}].freeze
|
|
201
|
+
private_constant :PATHS_ARGS
|
|
206
202
|
|
|
207
|
-
command
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
command :
|
|
203
|
+
command(
|
|
204
|
+
:ls, description: 'List files', condition: :ascmd_available?, aliases: [:browse], arguments: PATHS_ARGS,
|
|
205
|
+
action: lambda do |paths:, **|
|
|
206
|
+
execute_ascmd(:ls, paths) do |result|
|
|
207
|
+
Result::ObjectList.new(result.map(&:stringify_keys), fields: %w[zmode zuid zgid size mtime name])
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
)
|
|
211
|
+
command :rm, description: 'Delete files', condition: :ascmd_available?, aliases: [:delete], arguments: PATHS_ARGS
|
|
212
|
+
command :mv, description: 'Rename/move files: source destination pairs, or sources with --to-folder', condition: :ascmd_available?, aliases: [:rename], arguments: PATHS_ARGS
|
|
213
|
+
command :cp, description: 'Copy files: source destination pairs, or sources with --to-folder', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
214
|
+
command :mkdir, description: 'Create directory', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
215
|
+
command(
|
|
216
|
+
:df, description: 'Show disk usage', condition: :ascmd_available?,
|
|
217
|
+
action: lambda do |**|
|
|
218
|
+
execute_ascmd(:df, []) do |result|
|
|
219
|
+
Result::ObjectList.new(result.map(&:stringify_keys))
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
)
|
|
223
|
+
command :du, description: 'Show file sizes', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
224
|
+
command :md5sum, description: 'Compute MD5 checksums', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
225
|
+
command :info, description: 'Show server system information', condition: :ascmd_available?,
|
|
226
|
+
action: ->(**) { execute_ascmd(:info, []) { |r| Result::SingleObject.new(r.stringify_keys) } }
|
|
216
227
|
|
|
217
228
|
# Generate ascmd handlers - convention: action_<op>
|
|
218
|
-
%i[rm
|
|
219
|
-
define_action_method([op]) do |
|
|
220
|
-
execute_ascmd(op,
|
|
229
|
+
%i[rm mkdir].each do |op|
|
|
230
|
+
define_action_method([op]) do |paths:, **|
|
|
231
|
+
execute_ascmd(op, paths) { Result::Success.new }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
%i[mv cp].each do |op|
|
|
236
|
+
define_action_method([op]) do |paths:, **|
|
|
237
|
+
execute_ascmd(op, source_destination_pairs(paths)) { Result::Success.new }
|
|
221
238
|
end
|
|
222
239
|
end
|
|
223
240
|
|
|
224
|
-
%i[du md5sum
|
|
225
|
-
define_action_method([op]) do |
|
|
226
|
-
execute_ascmd(op,
|
|
241
|
+
%i[du md5sum].each do |op|
|
|
242
|
+
define_action_method([op]) do |paths:, **|
|
|
243
|
+
execute_ascmd(op, paths) { |r| Result::SingleObject.new(r.stringify_keys) }
|
|
227
244
|
end
|
|
228
245
|
end
|
|
229
246
|
|
|
230
247
|
# --- conditions ---
|
|
231
248
|
|
|
232
249
|
def ascmd_available?
|
|
233
|
-
|
|
250
|
+
!ascmd_executor.nil?
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# --- server access ---
|
|
254
|
+
|
|
255
|
+
# @return [Hash] base transfer spec, built from CLI options on first use
|
|
256
|
+
def server_transfer_spec
|
|
257
|
+
build_server_access if @server_transfer_spec.nil?
|
|
258
|
+
@server_transfer_spec
|
|
234
259
|
end
|
|
235
260
|
|
|
236
|
-
#
|
|
261
|
+
# @return [LocalExecutor, Ssh, nil] ascmd executor, nil with WSS (ascmd not available)
|
|
262
|
+
def ascmd_executor
|
|
263
|
+
build_server_access if @server_transfer_spec.nil?
|
|
264
|
+
@ascmd_executor
|
|
265
|
+
end
|
|
237
266
|
|
|
238
267
|
# Build the transfer spec and ascmd executor from CLI options.
|
|
239
|
-
|
|
240
|
-
def setup_server(**)
|
|
268
|
+
def build_server_access
|
|
241
269
|
@server_transfer_spec = options_to_base_transfer_spec
|
|
242
270
|
@ascmd_executor = case @connection_type
|
|
243
271
|
when :local then LocalExecutor.new
|
|
@@ -245,14 +273,13 @@ module Aspera
|
|
|
245
273
|
when :ssh then Ssh.new(@server_transfer_spec['remote_host'], @server_transfer_spec['remote_user'], @ssh_opts)
|
|
246
274
|
else Aspera.error_unexpected_value(@connection_type) { 'connection type' }
|
|
247
275
|
end
|
|
248
|
-
{}
|
|
249
276
|
end
|
|
250
277
|
|
|
251
278
|
# --- health ---
|
|
252
279
|
|
|
253
|
-
def action_health_transfer
|
|
280
|
+
def action_health_transfer(**)
|
|
254
281
|
nagios = Nagios.new
|
|
255
|
-
probe_ts =
|
|
282
|
+
probe_ts = server_transfer_spec.merge({
|
|
256
283
|
'direction' => 'send',
|
|
257
284
|
'cookie' => 'aspera.sync', # hide in console
|
|
258
285
|
'resume_policy' => 'none',
|
|
@@ -269,24 +296,23 @@ module Aspera
|
|
|
269
296
|
|
|
270
297
|
# --- ascmd handlers ---
|
|
271
298
|
|
|
272
|
-
|
|
273
|
-
execute_ascmd(:ls, command_arguments) do |result|
|
|
274
|
-
Result::ObjectList.new(result.map(&:stringify_keys), fields: %w[zmode zuid zgid size mtime name])
|
|
275
|
-
end
|
|
276
|
-
end
|
|
299
|
+
private
|
|
277
300
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
301
|
+
# AsCmd `cp` and `mv` take source and destination pairs.
|
|
302
|
+
# With option `to_folder`, paths are only sources, copied or moved into that folder.
|
|
303
|
+
# @param paths [Array<String>] command line arguments
|
|
304
|
+
# @return [Array<String>] flat list of source and destination pairs
|
|
305
|
+
def source_destination_pairs(paths)
|
|
306
|
+
to_folder = options.get_option(:to_folder)
|
|
307
|
+
return paths.flat_map { |source| [source, "#{to_folder.chomp('/')}/#{File.basename(source)}"] } unless to_folder.nil?
|
|
308
|
+
Aspera.assert(paths.length.even?, type: Cli::BadArgument) { 'expecting pairs: <source> <destination> ..., or sources with option to_folder' }
|
|
309
|
+
paths
|
|
282
310
|
end
|
|
283
311
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def execute_ascmd(op, command_arguments)
|
|
287
|
-
ascmd = AsCmd.new(@ascmd_executor)
|
|
312
|
+
def execute_ascmd(op, arguments)
|
|
313
|
+
ascmd = AsCmd.new(ascmd_executor)
|
|
288
314
|
begin
|
|
289
|
-
result = ascmd.execute_single(op,
|
|
315
|
+
result = ascmd.execute_single(op, arguments)
|
|
290
316
|
yield(result)
|
|
291
317
|
rescue AsCmd::Error => e
|
|
292
318
|
raise Cli::BadArgument, e.extended_message
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require 'aspera/cli/plugins/basic_auth'
|
|
4
4
|
require 'aspera/cli/plugins/node'
|
|
5
5
|
require 'aspera/assert'
|
|
6
|
-
require 'aspera/
|
|
6
|
+
require 'aspera/rest/list'
|
|
7
7
|
|
|
8
8
|
module Aspera
|
|
9
9
|
module Cli
|
|
@@ -23,7 +23,7 @@ module Aspera
|
|
|
23
23
|
result[:version] =
|
|
24
24
|
begin
|
|
25
25
|
version = nil
|
|
26
|
-
login_page = Rest
|
|
26
|
+
login_page = Rest::Client
|
|
27
27
|
.new(base_url: url, redirect_max: 2)
|
|
28
28
|
.read('', headers: {'Accept'=>'text/html'})
|
|
29
29
|
Aspera.assert(login_page.include?('aspera-Shares')) { 'not Shares' }
|
|
@@ -39,16 +39,16 @@ module Aspera
|
|
|
39
39
|
end
|
|
40
40
|
result[:ping] =
|
|
41
41
|
begin
|
|
42
|
-
Rest
|
|
42
|
+
Rest::Client
|
|
43
43
|
.new(base_url: "#{url}/#{NODE_API_PATH}")
|
|
44
|
-
.read('ping', headers: {'Content-Type'=>
|
|
44
|
+
.read('ping', headers: {'Content-Type' => Mime::JSON})
|
|
45
45
|
'ping ok'
|
|
46
46
|
rescue => e
|
|
47
47
|
e
|
|
48
48
|
end
|
|
49
49
|
result[:api] =
|
|
50
50
|
begin
|
|
51
|
-
data, resp = Rest
|
|
51
|
+
data, resp = Rest::Client
|
|
52
52
|
.new(base_url: "#{url}/#{NODE_API_PATH}", redirect_max: 1)
|
|
53
53
|
.read('info', exception: false, ret: :both)
|
|
54
54
|
# shall fail: shares requires auth, but we check error message
|
|
@@ -103,12 +103,19 @@ module Aspera
|
|
|
103
103
|
SHARE_PERMISSIONS_OPS = %i[list show].freeze
|
|
104
104
|
# group users: Rails only exposes index+show+update+destroy (no create)
|
|
105
105
|
GROUP_USERS_OPS = (Operations::ALL - %i[create]).freeze
|
|
106
|
+
# Arguments of group users commands, by operation
|
|
107
|
+
GROUP_USERS_ARGS = {
|
|
108
|
+
show: [{name: :user_id, type: :identifier}],
|
|
109
|
+
modify: [{name: :user_id, type: :identifier}, {name: :user, type: Hash}],
|
|
110
|
+
delete: [{name: :user_id, type: :identifier}]
|
|
111
|
+
}.freeze
|
|
106
112
|
|
|
107
113
|
# --- DSL ---
|
|
108
114
|
|
|
109
115
|
command :health, description: 'Check Shares health'
|
|
110
|
-
command :info, description: 'Show server information', action: -> { Result::SingleObject.new(basic_auth_api(NODE_API_PATH).read('info', headers: {'Content-Type'=>
|
|
111
|
-
command :files, description: 'Browse and transfer files on Shares', aliases: [:repository],
|
|
116
|
+
command :info, description: 'Show server information', action: ->(**) { Result::SingleObject.new(basic_auth_api(NODE_API_PATH).read('info', headers: {'Content-Type' => Mime::JSON})) }
|
|
117
|
+
command :files, description: 'Browse and transfer files on Shares', aliases: [:repository],
|
|
118
|
+
mount: {plugin: Node, instance: :shares_node_plugin, only: Node::COMMANDS_SHARES}
|
|
112
119
|
command :admin, description: 'Administer Shares', setup: :setup_admin
|
|
113
120
|
|
|
114
121
|
commands_under :admin do
|
|
@@ -137,30 +144,33 @@ module Aspera
|
|
|
137
144
|
command :ldap, description: "LDAP #{entity_type}s"
|
|
138
145
|
command :saml, description: "SAML #{entity_type}s"
|
|
139
146
|
|
|
147
|
+
user_fields = %w[id user_id username first_name last_name email] if entity_type.eql?(:user)
|
|
140
148
|
# all: list/show/delete only (no create, no modify — users/groups/:all has no update route)
|
|
141
149
|
lookup_method_all = :"lookup_shares_#{entity_type}_all_id"
|
|
142
150
|
commands_under :all do
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
crud_commands entity: "data/#{entity_type}s",
|
|
152
|
+
api: :@api_shares_admin,
|
|
153
|
+
name: entity_type.to_s,
|
|
154
|
+
operations: Operations::ALL - %i[create modify],
|
|
155
|
+
lookup: lookup_method_all,
|
|
156
|
+
display_fields: user_fields && (user_fields + %w[directory_user])
|
|
147
157
|
USR_GRP_SETTINGS.each do |setting|
|
|
148
158
|
# share_permissions: Rails only exposes index+show (read-only)
|
|
149
159
|
setting_ops = setting.eql?(:share_permissions) ? SHARE_PERMISSIONS_OPS : %i[show modify]
|
|
150
160
|
command setting,
|
|
151
|
-
description: "Manage #{setting} for a #{entity_type}",
|
|
161
|
+
description: "Manage #{entity_noun(setting, singular: false)} for a #{entity_type}",
|
|
152
162
|
arguments: [{name: :"#{entity_type}_id", type: :identifier, lookup: lookup_method_all}]
|
|
153
163
|
commands_under setting do
|
|
154
164
|
setting_ops.each do |op|
|
|
155
|
-
# share_permissions > show needs a permission identifier (list does not)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
[]
|
|
160
|
-
|
|
165
|
+
# share_permissions > show needs a permission identifier (list does not), modify needs data
|
|
166
|
+
op_args =
|
|
167
|
+
case op
|
|
168
|
+
when :show then setting.eql?(:share_permissions) ? [{name: :permission_id, type: :identifier, lookup: :lookup_share_id}] : nil
|
|
169
|
+
when :modify then [{name: setting, type: Hash}]
|
|
170
|
+
end
|
|
161
171
|
command op,
|
|
162
|
-
description: "#{op.capitalize} #{setting} for a #{entity_type}",
|
|
163
|
-
arguments:
|
|
172
|
+
description: "#{op.capitalize} #{entity_noun(setting, singular: false)} for a #{entity_type}",
|
|
173
|
+
arguments: op_args
|
|
164
174
|
end
|
|
165
175
|
end
|
|
166
176
|
end
|
|
@@ -170,7 +180,7 @@ module Aspera
|
|
|
170
180
|
arguments: [{name: :group_id, type: :identifier, lookup: :lookup_shares_group_all_id}]
|
|
171
181
|
commands_under :users do
|
|
172
182
|
GROUP_USERS_OPS.each do |op|
|
|
173
|
-
command op, description: "#{op.capitalize} users of a group"
|
|
183
|
+
command op, description: "#{op.capitalize} users of a group", arguments: GROUP_USERS_ARGS[op]
|
|
174
184
|
end
|
|
175
185
|
end
|
|
176
186
|
end
|
|
@@ -179,17 +189,19 @@ module Aspera
|
|
|
179
189
|
# local: full CRUD only — no nested settings (Rails does not nest transfer_settings/app_authorizations/share_permissions under local_users/local_groups)
|
|
180
190
|
commands_under :local do
|
|
181
191
|
lookup_method_local = :"lookup_shares_#{entity_type}_local_id"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
crud_commands entity: "data/local_#{entity_type}s",
|
|
193
|
+
api: :@api_shares_admin,
|
|
194
|
+
name: "local #{entity_type}",
|
|
195
|
+
lookup: lookup_method_local,
|
|
196
|
+
display_fields: user_fields,
|
|
197
|
+
body_component: Schema::Registry::SHARES
|
|
186
198
|
if entity_type.eql?(:group)
|
|
187
199
|
command :users,
|
|
188
200
|
description: 'Manage users of a group',
|
|
189
201
|
arguments: [{name: :group_id, type: :identifier, lookup: :lookup_shares_group_local_id}]
|
|
190
202
|
commands_under :users do
|
|
191
203
|
GROUP_USERS_OPS.each do |op|
|
|
192
|
-
command op, description: "#{op.capitalize} users of a group"
|
|
204
|
+
command op, description: "#{op.capitalize} users of a group", arguments: GROUP_USERS_ARGS[op]
|
|
193
205
|
end
|
|
194
206
|
end
|
|
195
207
|
end
|
|
@@ -198,13 +210,13 @@ module Aspera
|
|
|
198
210
|
# ldap: add only
|
|
199
211
|
commands_under :ldap do
|
|
200
212
|
command :add, description: "Add a LDAP #{entity_type}",
|
|
201
|
-
arguments: [{name:
|
|
213
|
+
arguments: [{name: entity_type, bulk: true}]
|
|
202
214
|
end
|
|
203
215
|
|
|
204
216
|
# saml: import only
|
|
205
217
|
commands_under :saml do
|
|
206
218
|
command :import, description: "Import a SAML #{entity_type}",
|
|
207
|
-
arguments: [{name:
|
|
219
|
+
arguments: [{name: entity_type, type: Hash, bulk: true}]
|
|
208
220
|
end
|
|
209
221
|
end
|
|
210
222
|
end
|
|
@@ -221,7 +233,7 @@ module Aspera
|
|
|
221
233
|
lookup_method = :"lookup_shares_#{entity_type}_#{location}_id"
|
|
222
234
|
define_method(lookup_method) do |field, value, **|
|
|
223
235
|
path = admin_entity_path(entity_type, location)
|
|
224
|
-
|
|
236
|
+
Rest::List.lookup_entity_generic(entity: entity_type, field: field, value: value) { @api_shares_admin.read(path) }['id']
|
|
225
237
|
end
|
|
226
238
|
private lookup_method
|
|
227
239
|
end
|
|
@@ -245,7 +257,8 @@ module Aspera
|
|
|
245
257
|
%i[user_permissions group_permissions].each do |perm_type|
|
|
246
258
|
commands_under [:admin, :share, perm_type] do
|
|
247
259
|
SHARE_PERMISSIONS_OPS.each do |op|
|
|
248
|
-
command op, description: "#{op.capitalize} #{perm_type}"
|
|
260
|
+
command op, description: "#{op.capitalize} #{entity_noun(perm_type, singular: false)}",
|
|
261
|
+
arguments: op.eql?(:show) ? [{name: :permission_id, type: :identifier}] : nil
|
|
249
262
|
end
|
|
250
263
|
end
|
|
251
264
|
end
|
|
@@ -259,11 +272,11 @@ module Aspera
|
|
|
259
272
|
|
|
260
273
|
# --- setup ---
|
|
261
274
|
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
275
|
+
# files - mount target: Node plugin on the Shares node API.
|
|
276
|
+
# Api::Node (not plain Rest::Client) so that Node commands using its helpers work (e.g. spec, transport).
|
|
277
|
+
# @return [Node]
|
|
278
|
+
def shares_node_plugin(**)
|
|
279
|
+
Node.new(context: context, api: Api::Node.new(**basic_auth_params(NODE_API_PATH)))
|
|
267
280
|
end
|
|
268
281
|
|
|
269
282
|
# Build the admin REST API and store in ivar.
|
|
@@ -275,12 +288,12 @@ module Aspera
|
|
|
275
288
|
|
|
276
289
|
# Lookup a share id by field/value using the admin API.
|
|
277
290
|
def lookup_share_id(field, value, **)
|
|
278
|
-
|
|
291
|
+
Rest::List.lookup_entity_generic(entity: 'share', field: field, value: value) { @api_shares_admin.read('data/shares') }['id']
|
|
279
292
|
end
|
|
280
293
|
|
|
281
294
|
# --- health ---
|
|
282
295
|
|
|
283
|
-
def action_health
|
|
296
|
+
def action_health(**)
|
|
284
297
|
nagios = Nagios.new
|
|
285
298
|
shares_url = options.get_option(:url, mandatory: true)
|
|
286
299
|
health = self.class.health_check(shares_url)
|
|
@@ -298,44 +311,6 @@ module Aspera
|
|
|
298
311
|
Result::ObjectList.new(nagios.status_list)
|
|
299
312
|
end
|
|
300
313
|
|
|
301
|
-
# --- files sub-commands (restricted to COMMANDS_SHARES, delegated to Node) ---
|
|
302
|
-
|
|
303
|
-
commands_under :files do
|
|
304
|
-
Node::COMMANDS_SHARES.each do |cmd|
|
|
305
|
-
next if cmd.eql?(:sync) # intermediate node with sub-commands, declared separately below
|
|
306
|
-
spec = Node::COMMANDS_GEN3_SPEC[cmd] || {description: "Node #{cmd} command"}
|
|
307
|
-
command cmd, **spec
|
|
308
|
-
end
|
|
309
|
-
command :sync, description: 'Synchronize folders (Gen3)'
|
|
310
|
-
commands_under :sync do
|
|
311
|
-
Sync::Operations::DIRECTIONS.each do |dir|
|
|
312
|
-
command dir, description: "#{dir.capitalize}-sync (Gen3)", transfer_paths: :send, arguments: SyncActions::PATH_AND_INFO_ARGS
|
|
313
|
-
end
|
|
314
|
-
command :admin, description: 'Manage sync database (admin operations)'
|
|
315
|
-
SyncActions.register_sync_admin_commands(self, :admin)
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
|
|
319
|
-
# One handler per COMMANDS_SHARES command (except :sync which is an intermediate node).
|
|
320
|
-
Node::COMMANDS_SHARES.reject { |cmd| cmd.eql?(:sync) }.each do |cmd|
|
|
321
|
-
define_action_method([:files, cmd]) do |shares_node_plugin:, **ctx|
|
|
322
|
-
shares_node_plugin.dispatch_v3_command(cmd, **ctx)
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# Handlers for files > sync > <direction>: delegate to Node's [:sync, dir] leaf.
|
|
327
|
-
Sync::Operations::DIRECTIONS.each do |dir|
|
|
328
|
-
define_action_method([:files, :sync, dir]) do |shares_node_plugin:, **ctx|
|
|
329
|
-
shares_node_plugin.dispatch_from_registry([:sync, dir], ctx, skip_setup: true)
|
|
330
|
-
end
|
|
331
|
-
end
|
|
332
|
-
# Handlers for files > sync > admin > <op>: delegate to Node's [:sync, :admin, op] leaf.
|
|
333
|
-
SyncActions::ADMIN_COMMANDS.each do |op|
|
|
334
|
-
define_action_method([:files, :sync, :admin, op]) do |shares_node_plugin:, **ctx|
|
|
335
|
-
shares_node_plugin.dispatch_from_registry([:sync, :admin, op], ctx, skip_setup: true)
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
314
|
private
|
|
340
315
|
|
|
341
316
|
# @return [String] admin API path for the given entity_type and location
|
|
@@ -346,42 +321,25 @@ module Aspera
|
|
|
346
321
|
|
|
347
322
|
# --- admin > user|group handlers ---
|
|
348
323
|
|
|
349
|
-
# Shared handler for list/show/delete/create/modify on user or group.
|
|
350
|
-
# @param entity_type [Symbol] :user or :group
|
|
351
|
-
# @param location [Symbol] :all or :local
|
|
352
|
-
# @param op [Symbol] CRUD operation
|
|
353
|
-
def action_admin_entity_crud(entity_type, location, op)
|
|
354
|
-
path = admin_entity_path(entity_type, location)
|
|
355
|
-
lookup = ->(f, v) { RestList.lookup_entity_generic(entity: entity_type, field: f, value: v) { @api_shares_admin.read(path) }['id'] }
|
|
356
|
-
display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
|
|
357
|
-
display_fields&.push('directory_user') if entity_type.eql?(:user) && location.eql?(:all)
|
|
358
|
-
# :all excludes :create; :local has a documented POST+PUT requestBody for both users and groups
|
|
359
|
-
body_component = location.eql?(:local) ? Schema::Registry::SHARES : nil
|
|
360
|
-
id = Operations::INSTANCE.include?(op) ? options.instance_identifier(&lookup) : nil
|
|
361
|
-
send(:"entity_#{op}", api: @api_shares_admin, entity: path, id: id, display_fields: display_fields, body_component: body_component)
|
|
362
|
-
end
|
|
363
|
-
|
|
364
324
|
# Shared handler for USR_GRP_SETTINGS (transfer_settings, app_authorizations, share_permissions)
|
|
365
325
|
# @param entity_type [Symbol] :user or :group
|
|
366
326
|
# @param location [Symbol] :all or :local
|
|
367
327
|
# @param setting [Symbol] one of USR_GRP_SETTINGS
|
|
368
328
|
# @param op [Symbol] CRUD operation
|
|
369
|
-
# entity_id: resolved via arguments:(:identifier) on
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
entity_path = "#{
|
|
373
|
-
is_singleton
|
|
374
|
-
id = permission_id || (Operations::INSTANCE.include?(op) && !is_singleton ? options.instance_identifier { |f, v| lookup_share_id(f, v) } : nil)
|
|
375
|
-
send(:"entity_#{op}", api: @api_shares_admin, entity: entity_path, id: id, is_singleton: is_singleton)
|
|
329
|
+
# entity_id: resolved via arguments:(:identifier) on the setting node
|
|
330
|
+
# permission_id: (share_permissions show) and data: (modify) resolved via arguments: on the leaf
|
|
331
|
+
def action_admin_entity_setting(entity_type, location, setting, op, entity_id:, permission_id: nil, data: nil)
|
|
332
|
+
entity_path = "#{admin_entity_path(entity_type, location)}/#{entity_id}/#{setting}"
|
|
333
|
+
send(:"entity_#{op}", api: @api_shares_admin, entity: entity_path, id: permission_id, is_singleton: !setting.eql?(:share_permissions), data: data)
|
|
376
334
|
end
|
|
377
335
|
|
|
378
336
|
# Shared handler for :users (group only)
|
|
379
337
|
# group_id: resolved by Phase A via arguments:(:identifier) on the :users node
|
|
380
|
-
|
|
338
|
+
# user_id: and data: resolved via arguments: on the leaf (GROUP_USERS_ARGS)
|
|
339
|
+
def action_admin_entity_users(entity_type, location, op, group_id:, user_id: nil, data: nil)
|
|
381
340
|
path = admin_entity_path(entity_type, location)
|
|
382
341
|
prefix = location.eql?(:all) ? '' : "#{location}_"
|
|
383
|
-
|
|
384
|
-
send(:"entity_#{op}", api: @api_shares_admin, entity: "#{path}/#{group_id}/#{prefix}users", id: id)
|
|
342
|
+
send(:"entity_#{op}", api: @api_shares_admin, entity: "#{path}/#{group_id}/#{prefix}users", id: user_id, data: data)
|
|
385
343
|
end
|
|
386
344
|
|
|
387
345
|
# Generate action_admin_<user|group>_<location>_<verb> for all combinations
|
|
@@ -389,19 +347,13 @@ module Aspera
|
|
|
389
347
|
# all: list/show/delete only (no create, no modify — no update route on /users and /groups)
|
|
390
348
|
# local: full CRUD only — no nested settings under local_users/local_groups
|
|
391
349
|
ENTITY_LOCATIONS.each do |location|
|
|
392
|
-
ops = location.eql?(:all) ? (Operations::ALL - %i[create modify]) : Operations::ALL
|
|
393
|
-
ops.each do |op|
|
|
394
|
-
define_action_method([:admin, entity_type, location, op]) do
|
|
395
|
-
action_admin_entity_crud(entity_type, location, op)
|
|
396
|
-
end
|
|
397
|
-
end
|
|
398
350
|
unless location.eql?(:local)
|
|
399
351
|
USR_GRP_SETTINGS.each do |setting|
|
|
400
352
|
# share_permissions: Rails only exposes index+show (read-only)
|
|
401
353
|
setting_ops = setting.eql?(:share_permissions) ? SHARE_PERMISSIONS_OPS : %i[show modify]
|
|
402
354
|
setting_ops.each do |op|
|
|
403
355
|
define_action_method([:admin, entity_type, location, setting, op]) do |**kwargs|
|
|
404
|
-
action_admin_entity_setting(entity_type, location, setting, op, entity_id: kwargs[:"#{entity_type}_id"], permission_id: kwargs[:permission_id])
|
|
356
|
+
action_admin_entity_setting(entity_type, location, setting, op, entity_id: kwargs[:"#{entity_type}_id"], permission_id: kwargs[:permission_id], data: kwargs[setting])
|
|
405
357
|
end
|
|
406
358
|
end
|
|
407
359
|
end
|
|
@@ -409,24 +361,24 @@ module Aspera
|
|
|
409
361
|
next unless entity_type.eql?(:group)
|
|
410
362
|
# group users: no create route (Rails only exposes index+show+update+destroy)
|
|
411
363
|
GROUP_USERS_OPS.each do |op|
|
|
412
|
-
define_action_method([:admin, entity_type, location, :users, op]) do |group_id:, **|
|
|
413
|
-
action_admin_entity_users(entity_type, location, op, group_id: group_id)
|
|
364
|
+
define_action_method([:admin, entity_type, location, :users, op]) do |group_id:, user_id: nil, user: nil, **|
|
|
365
|
+
action_admin_entity_users(entity_type, location, op, group_id: group_id, user_id: user_id, data: user)
|
|
414
366
|
end
|
|
415
367
|
end
|
|
416
368
|
end
|
|
417
369
|
|
|
418
370
|
# ldap: add
|
|
419
|
-
define_action_method([:admin, entity_type, :ldap, :add]) do |
|
|
371
|
+
define_action_method([:admin, entity_type, :ldap, :add]) do |**kwargs|
|
|
420
372
|
path = admin_entity_path(entity_type, :ldap)
|
|
421
|
-
bulk_result(
|
|
373
|
+
bulk_result(kwargs.fetch(entity_type), command: :add, id_result: entity_type.to_s) do |entity_name|
|
|
422
374
|
@api_shares_admin.create(path, {entity_type => entity_name})
|
|
423
375
|
end
|
|
424
376
|
end
|
|
425
377
|
|
|
426
378
|
# saml: import
|
|
427
|
-
define_action_method([:admin, entity_type, :saml, :import]) do |
|
|
379
|
+
define_action_method([:admin, entity_type, :saml, :import]) do |**kwargs|
|
|
428
380
|
path = admin_entity_path(entity_type, :saml)
|
|
429
|
-
bulk_result(
|
|
381
|
+
bulk_result(kwargs.fetch(entity_type), command: :import) do |entity_parameters|
|
|
430
382
|
entity_parameters = entity_parameters.transform_keys { |k| k.gsub(/\s+/, '_').downcase }
|
|
431
383
|
Aspera.assert_type(entity_parameters, Hash)
|
|
432
384
|
SAML_IMPORT_MANDATORY.each { |p| raise "missing mandatory field: #{p}" if entity_parameters[p].nil? }
|
|
@@ -440,12 +392,12 @@ module Aspera
|
|
|
440
392
|
|
|
441
393
|
# Handlers for admin > share > user_permissions|group_permissions > op
|
|
442
394
|
# share_id: resolved by Phase A via arguments:(:identifier, lookup: :lookup_share_id) on the perm_type node
|
|
395
|
+
# permission_id: resolved via arguments: on the show leaf
|
|
443
396
|
# Rails only exposes index+show for share permissions (read-only)
|
|
444
397
|
%i[user_permissions group_permissions].each do |perm_type|
|
|
445
398
|
SHARE_PERMISSIONS_OPS.each do |op|
|
|
446
|
-
define_action_method([:admin, :share, perm_type, op]) do |share_id:, **|
|
|
447
|
-
|
|
448
|
-
send(:"entity_#{op}", api: @api_shares_admin, entity: "data/shares/#{share_id}/#{perm_type}", id: id)
|
|
399
|
+
define_action_method([:admin, :share, perm_type, op]) do |share_id:, permission_id: nil, **|
|
|
400
|
+
send(:"entity_#{op}", api: @api_shares_admin, entity: "data/shares/#{share_id}/#{perm_type}", id: permission_id)
|
|
449
401
|
end
|
|
450
402
|
end
|
|
451
403
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/assert'
|
|
4
|
+
require 'aspera/cli/preset_manager'
|
|
4
5
|
|
|
5
6
|
module Aspera
|
|
6
7
|
module Cli
|
|
@@ -11,10 +12,6 @@ module Aspera
|
|
|
11
12
|
# Display columns for preset overview
|
|
12
13
|
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
|
13
14
|
|
|
14
|
-
def action_preset_list(**)
|
|
15
|
-
Result::ValueList.new(presets.config_presets.keys, name: 'name')
|
|
16
|
-
end
|
|
17
|
-
|
|
18
15
|
def action_preset_overview(**)
|
|
19
16
|
cp = presets.config_presets
|
|
20
17
|
# Display process modifies the value (hide secrets): we do not want to save removed secrets
|
|
@@ -55,18 +52,21 @@ module Aspera
|
|
|
55
52
|
|
|
56
53
|
SECRET_KEYWORDS = %w[password secret].freeze
|
|
57
54
|
|
|
58
|
-
# If
|
|
55
|
+
# If `option_name` ends with a secret keyword and the vault is configured,
|
|
59
56
|
# move the clear-text value into the vault and replace it with a @vault: reference.
|
|
60
57
|
# @param preset [Hash] the preset hash (modified in place)
|
|
61
58
|
# @param preset_name [String] name used as base for the vault label
|
|
62
59
|
# @param option_name [String] option key to inspect
|
|
63
60
|
def secure_preset_option(preset, preset_name, option_name)
|
|
64
61
|
return unless SECRET_KEYWORDS.any? { |kw| option_name.end_with?(kw) }
|
|
62
|
+
# Vault password is not saved in vault
|
|
63
|
+
return if option_name.eql?('vault_password')
|
|
65
64
|
# Never auto-secure the global preset: it holds vault credentials themselves
|
|
66
|
-
|
|
65
|
+
# Checked before opening the vault, which may not be usable yet (e.g. vault_password not set)
|
|
66
|
+
return if presets.global_preset?(preset_name)
|
|
67
67
|
return if vault.nil?
|
|
68
68
|
value = preset[option_name]
|
|
69
|
-
# Already a vault reference or nil
|
|
69
|
+
# Already a vault reference or nil - nothing to do
|
|
70
70
|
return if value.nil? || value.to_s.start_with?('@vault:')
|
|
71
71
|
vault_label = preset_name
|
|
72
72
|
incr = 0
|
|
@@ -93,6 +93,12 @@ module Aspera
|
|
|
93
93
|
cp = presets.config_presets
|
|
94
94
|
raise "no such preset: #{name}" unless cp.key?(name)
|
|
95
95
|
cp.delete(name)
|
|
96
|
+
# Remove default declarations referring to the deleted preset
|
|
97
|
+
defaults = cp[PresetManager::Key::DEFAULTS]
|
|
98
|
+
defaults&.select { |_, preset_name| preset_name.eql?(name) }&.each_key do |plugin|
|
|
99
|
+
defaults.delete(plugin)
|
|
100
|
+
Log.log.info { "Removed: #{PresetManager::Key::DEFAULTS}: #{plugin}" }
|
|
101
|
+
end
|
|
96
102
|
Result::Status.new("Deleted: #{name}")
|
|
97
103
|
end
|
|
98
104
|
|
|
@@ -124,11 +130,11 @@ module Aspera
|
|
|
124
130
|
Result::Nothing.new
|
|
125
131
|
end
|
|
126
132
|
|
|
127
|
-
def action_preset_initialize(name:,
|
|
133
|
+
def action_preset_initialize(name:, preset:, **)
|
|
128
134
|
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
129
135
|
cp = presets.config_presets
|
|
130
136
|
Log.log.warn { "configuration already exists: #{name}, overwriting" } if cp.key?(name)
|
|
131
|
-
cp[name] =
|
|
137
|
+
cp[name] = preset
|
|
132
138
|
Result::Status.new("Modified: #{@option_config_file}")
|
|
133
139
|
end
|
|
134
140
|
|
|
@@ -147,8 +153,9 @@ module Aspera
|
|
|
147
153
|
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
148
154
|
cp = presets.config_presets
|
|
149
155
|
cp[name] ||= {}
|
|
156
|
+
# Option names are only known at runtime: cannot be declared as interactive arguments
|
|
150
157
|
option_names.each do |option_name|
|
|
151
|
-
option_value = options.get_interactive(option_name
|
|
158
|
+
option_value = options.get_interactive(option_name)
|
|
152
159
|
cp[name][option_name] = option_value
|
|
153
160
|
secure_preset_option(cp[name], name, option_name)
|
|
154
161
|
end
|