aspera-cli 4.27.2 → 4.27.4
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 +133 -0
- data/CONTRIBUTING.md +5 -2
- data/bin/ascli +3 -1
- data/docs/README.md +1006 -766
- data/lib/aspera/agent/base.rb +7 -2
- data/lib/aspera/agent/connect.rb +6 -8
- data/lib/aspera/agent/desktop.rb +2 -6
- data/lib/aspera/agent/direct.rb +52 -22
- data/lib/aspera/agent/node.rb +9 -8
- data/lib/aspera/agent/transferd.rb +2 -2
- 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 +11 -7
- data/lib/aspera/api/httpgw.rb +38 -35
- data/lib/aspera/api/node.rb +39 -34
- data/lib/aspera/ascmd.rb +3 -1
- data/lib/aspera/ascp/installation.rb +63 -28
- 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 +12 -11
- data/lib/aspera/cli/bootstrapper.rb +14 -16
- data/lib/aspera/cli/command_line.rb +252 -0
- data/lib/aspera/cli/command_registry.rb +215 -37
- data/lib/aspera/cli/command_spec.rb +104 -15
- 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 +6 -3
- data/lib/aspera/cli/formatter.rb +94 -80
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +7 -6
- data/lib/aspera/cli/http.rb +22 -34
- 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 +105 -0
- data/lib/aspera/cli/option_value.rb +278 -0
- data/lib/aspera/cli/options.schema.yaml +124 -15
- data/lib/aspera/cli/parser.rb +333 -862
- data/lib/aspera/cli/plugins/alee.rb +7 -4
- data/lib/aspera/cli/plugins/aoc.rb +545 -518
- data/lib/aspera/cli/plugins/ats.rb +59 -80
- data/lib/aspera/cli/plugins/base.rb +221 -265
- data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
- data/lib/aspera/cli/plugins/config.rb +263 -184
- data/lib/aspera/cli/plugins/console.rb +103 -39
- 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 +204 -182
- data/lib/aspera/cli/plugins/faspio.rb +6 -11
- 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 +300 -327
- data/lib/aspera/cli/plugins/orchestrator.rb +152 -110
- data/lib/aspera/cli/plugins/preview.rb +96 -105
- data/lib/aspera/cli/plugins/server.rb +78 -53
- data/lib/aspera/cli/plugins/shares.rb +80 -131
- data/lib/aspera/cli/preset_actions.rb +44 -27
- data/lib/aspera/cli/preset_manager.rb +44 -19
- data/lib/aspera/cli/prompt.rb +36 -0
- data/lib/aspera/cli/result.rb +42 -36
- data/lib/aspera/cli/runner.rb +32 -59
- data/lib/aspera/cli/special_values.rb +5 -0
- data/lib/aspera/cli/sync_actions.rb +51 -46
- data/lib/aspera/cli/terminal_formatter.rb +9 -3
- data/lib/aspera/cli/transfer_actions.rb +14 -9
- data/lib/aspera/cli/transfer_agent.rb +34 -38
- data/lib/aspera/cli/transfer_progress.rb +290 -55
- data/lib/aspera/cli/vault_manager.rb +0 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +5 -3
- data/lib/aspera/coverage.rb +1 -1
- data/lib/aspera/environment.rb +35 -5
- 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 +2 -2
- data/lib/aspera/keychain/factory.rb +2 -1
- data/lib/aspera/keychain/one_password_api.rb +1 -1
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +47 -27
- data/lib/aspera/markdown.rb +2 -0
- data/lib/aspera/mime.rb +25 -0
- data/lib/aspera/node_emulator.rb +759 -0
- data/lib/aspera/oauth/base.rb +37 -26
- data/lib/aspera/oauth/factory.rb +7 -3
- data/lib/aspera/oauth/generic.rb +1 -1
- data/lib/aspera/oauth/json_credentials.rb +34 -0
- data/lib/aspera/oauth/jwt.rb +4 -5
- data/lib/aspera/oauth/web.rb +9 -8
- data/lib/aspera/oauth.rb +1 -0
- data/lib/aspera/persistency_folder.rb +1 -3
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +11 -1
- data/lib/aspera/preview/options.schema.yaml +119 -0
- 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 +72 -0
- data/lib/aspera/rest/call_error.rb +27 -0
- data/lib/aspera/rest/client.rb +523 -0
- data/lib/aspera/rest/error_analyzer.rb +113 -0
- data/lib/aspera/rest/list.rb +149 -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 Faspex API-5.0-enhanced.yaml +0 -20
- data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
- data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
- data/lib/aspera/schema/documentation.rb +13 -3
- data/lib/aspera/schema/reader.rb +12 -18
- data/lib/aspera/schema/registry.rb +23 -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 +43 -9
- metadata.gz.sig +0 -0
- data/lib/aspera/colors.rb +0 -79
- data/lib/aspera/node_simulator.rb +0 -344
- data/lib/aspera/preview/options.rb +0 -45
- 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
|
@@ -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
|
|
@@ -235,18 +247,22 @@ module Aspera
|
|
|
235
247
|
lookup: :lookup_share_id
|
|
236
248
|
command :user_permissions,
|
|
237
249
|
description: 'Manage user permissions on a share',
|
|
238
|
-
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}]
|
|
250
|
+
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}],
|
|
251
|
+
setup: :setup_share_permissions
|
|
239
252
|
command :group_permissions,
|
|
240
253
|
description: 'Manage group permissions on a share',
|
|
241
|
-
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}]
|
|
254
|
+
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}],
|
|
255
|
+
setup: :setup_share_permissions
|
|
242
256
|
end
|
|
243
257
|
|
|
244
|
-
# share user_permissions and group_permissions:
|
|
245
|
-
|
|
258
|
+
# share user_permissions and group_permissions: full CRUD, identified by the user or group id
|
|
259
|
+
{user_permissions: :user, group_permissions: :group}.each do |perm_type, entity_type|
|
|
246
260
|
commands_under [:admin, :share, perm_type] do
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
261
|
+
crud_commands entity: :"share_#{perm_type}",
|
|
262
|
+
api: :@api_shares_admin,
|
|
263
|
+
name: entity_noun(perm_type),
|
|
264
|
+
id_name: :"#{entity_type}_id",
|
|
265
|
+
lookup: :"lookup_shares_#{entity_type}_all_id"
|
|
250
266
|
end
|
|
251
267
|
end
|
|
252
268
|
|
|
@@ -259,11 +275,11 @@ module Aspera
|
|
|
259
275
|
|
|
260
276
|
# --- setup ---
|
|
261
277
|
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
278
|
+
# files - mount target: Node plugin on the Shares node API.
|
|
279
|
+
# Api::Node (not plain Rest::Client) so that Node commands using its helpers work (e.g. spec, transport).
|
|
280
|
+
# @return [Node]
|
|
281
|
+
def shares_node_plugin(**)
|
|
282
|
+
Node.new(context: context, api: Api::Node.new(**basic_auth_params(NODE_API_PATH)))
|
|
267
283
|
end
|
|
268
284
|
|
|
269
285
|
# Build the admin REST API and store in ivar.
|
|
@@ -273,14 +289,20 @@ module Aspera
|
|
|
273
289
|
{}
|
|
274
290
|
end
|
|
275
291
|
|
|
292
|
+
# share_id: resolved by Phase A via arguments:(:identifier, lookup: :lookup_share_id) on the perm_type node
|
|
293
|
+
# @return [Hash] ctx with API path of user and group permissions of the share
|
|
294
|
+
def setup_share_permissions(share_id:, **)
|
|
295
|
+
%i[user_permissions group_permissions].to_h { |perm_type| [:"share_#{perm_type}", "data/shares/#{share_id}/#{perm_type}"] }
|
|
296
|
+
end
|
|
297
|
+
|
|
276
298
|
# Lookup a share id by field/value using the admin API.
|
|
277
299
|
def lookup_share_id(field, value, **)
|
|
278
|
-
|
|
300
|
+
Rest::List.lookup_entity_generic(entity: 'share', field: field, value: value) { @api_shares_admin.read('data/shares') }['id']
|
|
279
301
|
end
|
|
280
302
|
|
|
281
303
|
# --- health ---
|
|
282
304
|
|
|
283
|
-
def action_health
|
|
305
|
+
def action_health(**)
|
|
284
306
|
nagios = Nagios.new
|
|
285
307
|
shares_url = options.get_option(:url, mandatory: true)
|
|
286
308
|
health = self.class.health_check(shares_url)
|
|
@@ -298,44 +320,6 @@ module Aspera
|
|
|
298
320
|
Result::ObjectList.new(nagios.status_list)
|
|
299
321
|
end
|
|
300
322
|
|
|
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
323
|
private
|
|
340
324
|
|
|
341
325
|
# @return [String] admin API path for the given entity_type and location
|
|
@@ -346,42 +330,25 @@ module Aspera
|
|
|
346
330
|
|
|
347
331
|
# --- admin > user|group handlers ---
|
|
348
332
|
|
|
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
333
|
# Shared handler for USR_GRP_SETTINGS (transfer_settings, app_authorizations, share_permissions)
|
|
365
334
|
# @param entity_type [Symbol] :user or :group
|
|
366
335
|
# @param location [Symbol] :all or :local
|
|
367
336
|
# @param setting [Symbol] one of USR_GRP_SETTINGS
|
|
368
337
|
# @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)
|
|
338
|
+
# entity_id: resolved via arguments:(:identifier) on the setting node
|
|
339
|
+
# permission_id: (share_permissions show) and data: (modify) resolved via arguments: on the leaf
|
|
340
|
+
def admin_entity_setting(entity_type, location, setting, op, entity_id:, permission_id: nil, data: nil)
|
|
341
|
+
entity_path = "#{admin_entity_path(entity_type, location)}/#{entity_id}/#{setting}"
|
|
342
|
+
send(:"entity_#{op}", api: @api_shares_admin, entity: entity_path, id: permission_id, is_singleton: !setting.eql?(:share_permissions), data: data)
|
|
376
343
|
end
|
|
377
344
|
|
|
378
345
|
# Shared handler for :users (group only)
|
|
379
346
|
# group_id: resolved by Phase A via arguments:(:identifier) on the :users node
|
|
380
|
-
|
|
347
|
+
# user_id: and data: resolved via arguments: on the leaf (GROUP_USERS_ARGS)
|
|
348
|
+
def admin_entity_users(entity_type, location, op, group_id:, user_id: nil, data: nil)
|
|
381
349
|
path = admin_entity_path(entity_type, location)
|
|
382
350
|
prefix = location.eql?(:all) ? '' : "#{location}_"
|
|
383
|
-
|
|
384
|
-
send(:"entity_#{op}", api: @api_shares_admin, entity: "#{path}/#{group_id}/#{prefix}users", id: id)
|
|
351
|
+
send(:"entity_#{op}", api: @api_shares_admin, entity: "#{path}/#{group_id}/#{prefix}users", id: user_id, data: data)
|
|
385
352
|
end
|
|
386
353
|
|
|
387
354
|
# Generate action_admin_<user|group>_<location>_<verb> for all combinations
|
|
@@ -389,19 +356,13 @@ module Aspera
|
|
|
389
356
|
# all: list/show/delete only (no create, no modify — no update route on /users and /groups)
|
|
390
357
|
# local: full CRUD only — no nested settings under local_users/local_groups
|
|
391
358
|
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
359
|
unless location.eql?(:local)
|
|
399
360
|
USR_GRP_SETTINGS.each do |setting|
|
|
400
361
|
# share_permissions: Rails only exposes index+show (read-only)
|
|
401
362
|
setting_ops = setting.eql?(:share_permissions) ? SHARE_PERMISSIONS_OPS : %i[show modify]
|
|
402
363
|
setting_ops.each do |op|
|
|
403
364
|
define_action_method([:admin, entity_type, location, setting, op]) do |**kwargs|
|
|
404
|
-
|
|
365
|
+
admin_entity_setting(entity_type, location, setting, op, entity_id: kwargs[:"#{entity_type}_id"], permission_id: kwargs[:permission_id], data: kwargs[setting])
|
|
405
366
|
end
|
|
406
367
|
end
|
|
407
368
|
end
|
|
@@ -409,24 +370,24 @@ module Aspera
|
|
|
409
370
|
next unless entity_type.eql?(:group)
|
|
410
371
|
# group users: no create route (Rails only exposes index+show+update+destroy)
|
|
411
372
|
GROUP_USERS_OPS.each do |op|
|
|
412
|
-
define_action_method([:admin, entity_type, location, :users, op]) do |group_id:, **|
|
|
413
|
-
|
|
373
|
+
define_action_method([:admin, entity_type, location, :users, op]) do |group_id:, user_id: nil, user: nil, **|
|
|
374
|
+
admin_entity_users(entity_type, location, op, group_id: group_id, user_id: user_id, data: user)
|
|
414
375
|
end
|
|
415
376
|
end
|
|
416
377
|
end
|
|
417
378
|
|
|
418
379
|
# ldap: add
|
|
419
|
-
define_action_method([:admin, entity_type, :ldap, :add]) do |
|
|
380
|
+
define_action_method([:admin, entity_type, :ldap, :add]) do |**kwargs|
|
|
420
381
|
path = admin_entity_path(entity_type, :ldap)
|
|
421
|
-
bulk_result(
|
|
382
|
+
bulk_result(kwargs.fetch(entity_type), command: :add, id_result: entity_type.to_s) do |entity_name|
|
|
422
383
|
@api_shares_admin.create(path, {entity_type => entity_name})
|
|
423
384
|
end
|
|
424
385
|
end
|
|
425
386
|
|
|
426
387
|
# saml: import
|
|
427
|
-
define_action_method([:admin, entity_type, :saml, :import]) do |
|
|
388
|
+
define_action_method([:admin, entity_type, :saml, :import]) do |**kwargs|
|
|
428
389
|
path = admin_entity_path(entity_type, :saml)
|
|
429
|
-
bulk_result(
|
|
390
|
+
bulk_result(kwargs.fetch(entity_type), command: :import) do |entity_parameters|
|
|
430
391
|
entity_parameters = entity_parameters.transform_keys { |k| k.gsub(/\s+/, '_').downcase }
|
|
431
392
|
Aspera.assert_type(entity_parameters, Hash)
|
|
432
393
|
SAML_IMPORT_MANDATORY.each { |p| raise "missing mandatory field: #{p}" if entity_parameters[p].nil? }
|
|
@@ -437,18 +398,6 @@ module Aspera
|
|
|
437
398
|
end
|
|
438
399
|
end
|
|
439
400
|
end
|
|
440
|
-
|
|
441
|
-
# Handlers for admin > share > user_permissions|group_permissions > op
|
|
442
|
-
# share_id: resolved by Phase A via arguments:(:identifier, lookup: :lookup_share_id) on the perm_type node
|
|
443
|
-
# Rails only exposes index+show for share permissions (read-only)
|
|
444
|
-
%i[user_permissions group_permissions].each do |perm_type|
|
|
445
|
-
SHARE_PERMISSIONS_OPS.each do |op|
|
|
446
|
-
define_action_method([:admin, :share, perm_type, op]) do |share_id:, **|
|
|
447
|
-
id = Operations::INSTANCE.include?(op) ? options.instance_identifier : nil
|
|
448
|
-
send(:"entity_#{op}", api: @api_shares_admin, entity: "data/shares/#{share_id}/#{perm_type}", id: id)
|
|
449
|
-
end
|
|
450
|
-
end
|
|
451
|
-
end
|
|
452
401
|
end
|
|
453
402
|
end
|
|
454
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,29 @@ module Aspera
|
|
|
55
52
|
|
|
56
53
|
SECRET_KEYWORDS = %w[password secret].freeze
|
|
57
54
|
|
|
58
|
-
#
|
|
55
|
+
# @param name [String] preset name, or `GLOBAL` for the global default preset
|
|
56
|
+
# @param create [Boolean] `true` for an action writing in the preset: the global default preset is declared and created if needed
|
|
57
|
+
# @return [String] preset name
|
|
58
|
+
def preset_name(name, create: false)
|
|
59
|
+
return name unless name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
60
|
+
presets.global_default_preset(create: create)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# If `option_name` ends with a secret keyword and the vault is configured,
|
|
59
64
|
# move the clear-text value into the vault and replace it with a @vault: reference.
|
|
60
65
|
# @param preset [Hash] the preset hash (modified in place)
|
|
61
66
|
# @param preset_name [String] name used as base for the vault label
|
|
62
67
|
# @param option_name [String] option key to inspect
|
|
63
68
|
def secure_preset_option(preset, preset_name, option_name)
|
|
64
69
|
return unless SECRET_KEYWORDS.any? { |kw| option_name.end_with?(kw) }
|
|
70
|
+
# Vault password is not saved in vault
|
|
71
|
+
return if option_name.eql?('vault_password')
|
|
65
72
|
# Never auto-secure the global preset: it holds vault credentials themselves
|
|
66
|
-
|
|
73
|
+
# Checked before opening the vault, which may not be usable yet (e.g. vault_password not set)
|
|
74
|
+
return if presets.global_preset?(preset_name)
|
|
67
75
|
return if vault.nil?
|
|
68
76
|
value = preset[option_name]
|
|
69
|
-
# Already a vault reference or nil
|
|
77
|
+
# Already a vault reference or nil - nothing to do
|
|
70
78
|
return if value.nil? || value.to_s.start_with?('@vault:')
|
|
71
79
|
vault_label = preset_name
|
|
72
80
|
incr = 0
|
|
@@ -82,26 +90,32 @@ module Aspera
|
|
|
82
90
|
public
|
|
83
91
|
|
|
84
92
|
def action_preset_show(name:, **)
|
|
85
|
-
name =
|
|
93
|
+
name = preset_name(name)
|
|
86
94
|
cp = presets.config_presets
|
|
87
|
-
raise "no such preset: #{name}" unless cp.key?(name)
|
|
95
|
+
raise Cli::BadArgument, "no such preset: #{name}" unless cp.key?(name)
|
|
88
96
|
Result::SingleObject.new(PresetManager.deep_clone(cp[name]))
|
|
89
97
|
end
|
|
90
98
|
|
|
91
99
|
def action_preset_delete(name:, **)
|
|
92
|
-
name =
|
|
100
|
+
name = preset_name(name)
|
|
93
101
|
cp = presets.config_presets
|
|
94
|
-
raise "no such preset: #{name}" unless cp.key?(name)
|
|
102
|
+
raise Cli::BadArgument, "no such preset: #{name}" unless cp.key?(name)
|
|
95
103
|
cp.delete(name)
|
|
104
|
+
# Remove default declarations referring to the deleted preset
|
|
105
|
+
defaults = cp[PresetManager::Key::DEFAULTS]
|
|
106
|
+
defaults&.select { |_, preset_name| preset_name.eql?(name) }&.each_key do |plugin|
|
|
107
|
+
defaults.delete(plugin)
|
|
108
|
+
Log.log.info { "Removed: #{PresetManager::Key::DEFAULTS}: #{plugin}" }
|
|
109
|
+
end
|
|
96
110
|
Result::Status.new("Deleted: #{name}")
|
|
97
111
|
end
|
|
98
112
|
|
|
99
113
|
def action_preset_get(name:, param_name:, **)
|
|
100
|
-
name =
|
|
114
|
+
name = preset_name(name)
|
|
101
115
|
cp = presets.config_presets
|
|
102
|
-
raise "no such preset: #{name}" unless cp.key?(name)
|
|
116
|
+
raise Cli::BadArgument, "no such preset: #{name}" unless cp.key?(name)
|
|
103
117
|
value = cp[name][param_name]
|
|
104
|
-
raise "no such option in preset #{name}
|
|
118
|
+
raise Cli::BadArgument, "no such option in preset #{name}: #{param_name}" if value.nil?
|
|
105
119
|
case value
|
|
106
120
|
when Numeric, String then return Result::Text.new(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
|
|
107
121
|
end
|
|
@@ -109,31 +123,31 @@ module Aspera
|
|
|
109
123
|
end
|
|
110
124
|
|
|
111
125
|
def action_preset_unset(name:, param_name:, **)
|
|
112
|
-
name =
|
|
126
|
+
name = preset_name(name)
|
|
113
127
|
cp = presets.config_presets
|
|
114
|
-
raise "no such preset: #{name}" unless cp.key?(name)
|
|
128
|
+
raise Cli::BadArgument, "no such preset: #{name}" unless cp.key?(name)
|
|
115
129
|
cp[name].delete(param_name)
|
|
116
130
|
Result::Status.new("Removed: #{name}: #{param_name}")
|
|
117
131
|
end
|
|
118
132
|
|
|
119
133
|
def action_preset_set(name:, param_name:, param_value:, **)
|
|
120
|
-
name =
|
|
134
|
+
name = preset_name(name, create: true)
|
|
121
135
|
param_name = Parser.option_line_to_name(param_name)
|
|
122
136
|
presets.set_key(name, param_name, Parser.smart_convert(param_value))
|
|
123
137
|
secure_preset_option(presets.config_presets[name], name, param_name)
|
|
124
138
|
Result::Nothing.new
|
|
125
139
|
end
|
|
126
140
|
|
|
127
|
-
def action_preset_initialize(name:,
|
|
128
|
-
name =
|
|
141
|
+
def action_preset_initialize(name:, preset:, **)
|
|
142
|
+
name = preset_name(name, create: true)
|
|
129
143
|
cp = presets.config_presets
|
|
130
144
|
Log.log.warn { "configuration already exists: #{name}, overwriting" } if cp.key?(name)
|
|
131
|
-
cp[name] =
|
|
132
|
-
Result::Status.new("
|
|
145
|
+
cp[name] = preset
|
|
146
|
+
Result::Status.new("Initialized: #{name}")
|
|
133
147
|
end
|
|
134
148
|
|
|
135
149
|
def action_preset_update(name:, **)
|
|
136
|
-
name =
|
|
150
|
+
name = preset_name(name, create: true)
|
|
137
151
|
unprocessed_options = options.unprocessed_options_with_value
|
|
138
152
|
Log.dump(:opts, unprocessed_options)
|
|
139
153
|
cp = presets.config_presets
|
|
@@ -144,13 +158,16 @@ module Aspera
|
|
|
144
158
|
end
|
|
145
159
|
|
|
146
160
|
def action_preset_ask(name:, option_names:, **)
|
|
147
|
-
name =
|
|
161
|
+
name = preset_name(name, create: true)
|
|
148
162
|
cp = presets.config_presets
|
|
149
163
|
cp[name] ||= {}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
# Option names are only known at runtime: cannot be declared as interactive arguments
|
|
165
|
+
options.with_interactive do
|
|
166
|
+
option_names.each do |option_name|
|
|
167
|
+
option_value = options.get_interactive(option_name)
|
|
168
|
+
cp[name][option_name] = option_value
|
|
169
|
+
secure_preset_option(cp[name], name, option_name)
|
|
170
|
+
end
|
|
154
171
|
end
|
|
155
172
|
Result::Status.new("Updated: #{name}")
|
|
156
173
|
end
|