aspera-cli 4.27.0 → 4.27.1
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 +29 -0
- data/CONTRIBUTING.md +1 -7
- data/TODO.md +50 -0
- data/docs/README.md +708 -488
- data/lib/aspera/agent/base.rb +10 -2
- data/lib/aspera/agent/connect.rb +7 -7
- data/lib/aspera/agent/desktop.rb +3 -3
- data/lib/aspera/agent/direct.rb +48 -19
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +2 -2
- data/lib/aspera/agent/transferd.rb +12 -12
- data/lib/aspera/api/aoc.rb +28 -28
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +2 -2
- data/lib/aspera/api/httpgw.rb +20 -20
- data/lib/aspera/api/node.rb +31 -53
- data/lib/aspera/ascmd.rb +13 -13
- data/lib/aspera/ascp/installation.rb +43 -36
- data/lib/aspera/ascp/management.rb +6 -6
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +7 -7
- data/lib/aspera/cli/async_transfer_store.rb +49 -5
- data/lib/aspera/cli/bootstrapper.rb +9 -9
- data/lib/aspera/cli/command_registry.rb +1 -1
- data/lib/aspera/cli/command_spec.rb +6 -3
- data/lib/aspera/cli/context.rb +3 -3
- data/lib/aspera/cli/extended_value.rb +26 -26
- data/lib/aspera/cli/formatter.rb +76 -54
- data/lib/aspera/cli/gem_checker.rb +1 -1
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +22 -29
- data/lib/aspera/cli/mailer.rb +7 -7
- data/lib/aspera/cli/mcp_tool.rb +31 -4
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +273 -3
- data/lib/aspera/cli/parser.rb +539 -280
- data/lib/aspera/cli/plugins/alee.rb +1 -1
- data/lib/aspera/cli/plugins/aoc.rb +297 -313
- data/lib/aspera/cli/plugins/ats.rb +25 -21
- data/lib/aspera/cli/plugins/base.rb +357 -186
- data/lib/aspera/cli/plugins/basic_auth.rb +0 -12
- data/lib/aspera/cli/plugins/config.rb +76 -73
- data/lib/aspera/cli/plugins/console.rb +8 -8
- data/lib/aspera/cli/plugins/cos.rb +4 -3
- data/lib/aspera/cli/plugins/factory.rb +2 -2
- data/lib/aspera/cli/plugins/faspex5.rb +259 -248
- data/lib/aspera/cli/plugins/faspio.rb +7 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/mcp.rb +11 -11
- data/lib/aspera/cli/plugins/node.rb +227 -283
- data/lib/aspera/cli/plugins/orchestrator.rb +23 -23
- data/lib/aspera/cli/plugins/preview.rb +61 -64
- data/lib/aspera/cli/plugins/server.rb +36 -31
- data/lib/aspera/cli/plugins/shares.rb +174 -135
- data/lib/aspera/cli/preset_actions.rb +39 -19
- data/lib/aspera/cli/preset_manager.rb +74 -32
- data/lib/aspera/cli/result.rb +18 -16
- data/lib/aspera/cli/runner.rb +75 -39
- data/lib/aspera/cli/secret_finder.rb +1 -1
- data/lib/aspera/cli/sync_actions.rb +36 -35
- data/lib/aspera/cli/terminal_formatter.rb +2 -2
- data/lib/aspera/cli/transfer_actions.rb +17 -10
- data/lib/aspera/cli/transfer_agent.rb +44 -44
- data/lib/aspera/cli/transfer_progress.rb +7 -7
- data/lib/aspera/cli/vault_manager.rb +34 -12
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +17 -20
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +16 -16
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +1 -1
- data/lib/aspera/dot_container.rb +8 -8
- data/lib/aspera/environment.rb +12 -12
- data/lib/aspera/faspex_gw.rb +4 -4
- data/lib/aspera/faspex_postproc.rb +14 -9
- data/lib/aspera/graphql.rb +4 -2
- data/lib/aspera/hash_ext.rb +2 -2
- data/lib/aspera/json_rpc/client.rb +1 -1
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +17 -5
- data/lib/aspera/keychain/hashicorp_vault.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +23 -25
- data/lib/aspera/keychain/one_password_api.rb +86 -0
- data/lib/aspera/keychain/one_password_base.rb +34 -0
- data/lib/aspera/keychain/one_password_cli.rb +98 -0
- data/lib/aspera/link_header.rb +2 -2
- data/lib/aspera/log.rb +13 -13
- data/lib/aspera/markdown.rb +5 -5
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +33 -27
- data/lib/aspera/oauth/base.rb +4 -5
- data/lib/aspera/oauth/boot.rb +3 -3
- data/lib/aspera/oauth/factory.rb +5 -5
- data/lib/aspera/oauth/jwt.rb +5 -7
- data/lib/aspera/oauth/web.rb +4 -4
- data/lib/aspera/persistency_action_once.rb +4 -4
- data/lib/aspera/persistency_folder.rb +8 -8
- data/lib/aspera/preview/file_types.rb +4 -4
- data/lib/aspera/preview/generator.rb +49 -46
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +65 -18
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +2 -2
- data/lib/aspera/products/transferd.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +10 -10
- data/lib/aspera/rest.rb +149 -115
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_list.rb +10 -9
- data/lib/aspera/schema/IBM Aspera Node API-4.4.6.yaml +6232 -0
- data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +249 -0
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +17 -11
- data/lib/aspera/schema/reader.rb +78 -4
- data/lib/aspera/schema/registry.rb +26 -7
- data/lib/aspera/secret_hider.rb +5 -5
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +3 -3
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +13 -13
- data/lib/aspera/temp_file_manager.rb +24 -4
- data/lib/aspera/transfer/faux_file.rb +2 -2
- data/lib/aspera/transfer/parameters.rb +15 -15
- data/lib/aspera/transfer/resumer.rb +9 -9
- data/lib/aspera/transfer/spec.rb +1 -1
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +6 -5
- data/lib/aspera/web_server_simple.rb +9 -9
- data/lib/aspera/yaml.rb +4 -4
- data.tar.gz.sig +0 -0
- metadata +10 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/faspex.rb +0 -576
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -26,7 +26,7 @@ module Aspera
|
|
|
26
26
|
login_page = Rest
|
|
27
27
|
.new(base_url: url, redirect_max: 2)
|
|
28
28
|
.read('', headers: {'Accept'=>'text/html'})
|
|
29
|
-
Aspera.assert(login_page.include?('aspera-Shares')){'not Shares'}
|
|
29
|
+
Aspera.assert(login_page.include?('aspera-Shares')) { 'not Shares' }
|
|
30
30
|
if (m = login_page.match(/\(v([0-9a-f\.]+)\)/))
|
|
31
31
|
version = m[1]
|
|
32
32
|
if (m = login_page.match(/Patch level ([0-9]+)/))
|
|
@@ -95,107 +95,118 @@ module Aspera
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
SAML_IMPORT_MANDATORY = %w[id name_id].freeze
|
|
98
|
-
SAML_IMPORT_ALLOWED = %w[email given_name surname]
|
|
98
|
+
SAML_IMPORT_ALLOWED = (%w[email given_name surname] + SAML_IMPORT_MANDATORY).freeze
|
|
99
99
|
|
|
100
100
|
# common to users and groups
|
|
101
101
|
USR_GRP_SETTINGS = %i[transfer_settings app_authorizations share_permissions].freeze
|
|
102
|
+
# share_permissions is read-only (Rails only exposes index+show)
|
|
103
|
+
SHARE_PERMISSIONS_OPS = %i[list show].freeze
|
|
104
|
+
# group users: Rails only exposes index+show+update+destroy (no create)
|
|
105
|
+
GROUP_USERS_OPS = (Operations::ALL - %i[create]).freeze
|
|
102
106
|
|
|
103
107
|
# --- DSL ---
|
|
104
108
|
|
|
105
109
|
command :health, description: 'Check Shares health'
|
|
106
|
-
command :info, description: 'Show server information', action: ->{Result::SingleObject.new(basic_auth_api(NODE_API_PATH).read('info', headers: {'Content-Type'=>'application/json'}))}
|
|
110
|
+
command :info, description: 'Show server information', action: -> { Result::SingleObject.new(basic_auth_api(NODE_API_PATH).read('info', headers: {'Content-Type'=>'application/json'})) }
|
|
107
111
|
command :files, description: 'Browse and transfer files on Shares', aliases: [:repository], setup: :setup_shares_node
|
|
108
112
|
command :admin, description: 'Administer Shares', setup: :setup_admin
|
|
109
113
|
|
|
110
|
-
commands_under
|
|
111
|
-
|
|
114
|
+
commands_under :admin do
|
|
115
|
+
commands_under :node do
|
|
116
|
+
crud_commands entity: 'data/nodes', api: :@api_shares_admin
|
|
117
|
+
end
|
|
118
|
+
commands_under :ldap_config, description: 'LDAP configuration' do
|
|
119
|
+
crud_commands entity: 'data/ldap_configs', api: :@api_shares_admin
|
|
120
|
+
end
|
|
112
121
|
command :share, description: 'Manage shares'
|
|
113
122
|
command :transfer_settings, description: 'Manage transfer settings'
|
|
114
123
|
command :user, description: 'Manage users'
|
|
115
124
|
command :group, description: 'Manage groups'
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
command :local, description: "Local #{entity_type}s"
|
|
123
|
-
command :ldap, description: "LDAP #{entity_type}s"
|
|
124
|
-
command :saml, description: "SAML #{entity_type}s"
|
|
125
|
+
commands_under :smtp_server, description: 'SMTP server' do
|
|
126
|
+
crud_commands entity: 'data/smtp_server',
|
|
127
|
+
api: :@api_shares_admin,
|
|
128
|
+
is_singleton: true,
|
|
129
|
+
body_component: Schema::Registry::SHARES,
|
|
130
|
+
operations: %i[show create modify]
|
|
125
131
|
end
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
command
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
setting_ops.each do |op|
|
|
141
|
-
command(op, description: "#{op.capitalize} #{setting} for a #{entity_type}")
|
|
132
|
+
# user / group: sub-commands per location, generated for both entity types
|
|
133
|
+
%i[user group].each do |entity_type|
|
|
134
|
+
commands_under entity_type do
|
|
135
|
+
command :all, description: "#{entity_type.capitalize}s from all sources"
|
|
136
|
+
command :local, description: "Local #{entity_type}s"
|
|
137
|
+
command :ldap, description: "LDAP #{entity_type}s"
|
|
138
|
+
command :saml, description: "SAML #{entity_type}s"
|
|
139
|
+
|
|
140
|
+
# all: list/show/delete only (no create, no modify — users/groups/:all has no update route)
|
|
141
|
+
lookup_method_all = :"lookup_shares_#{entity_type}_all_id"
|
|
142
|
+
commands_under :all do
|
|
143
|
+
(Operations::ALL - %i[create modify]).each do |op|
|
|
144
|
+
id_args = Operations::GLOBAL.include?(op) ? {} : {arguments: [{name: :"#{entity_type}_id", type: :identifier, lookup: lookup_method_all}]}
|
|
145
|
+
command op, description: "#{op.capitalize} #{entity_type}s", **id_args
|
|
142
146
|
end
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
USR_GRP_SETTINGS.each do |setting|
|
|
148
|
+
# share_permissions: Rails only exposes index+show (read-only)
|
|
149
|
+
setting_ops = setting.eql?(:share_permissions) ? SHARE_PERMISSIONS_OPS : %i[show modify]
|
|
150
|
+
command setting,
|
|
151
|
+
description: "Manage #{setting} for a #{entity_type}",
|
|
152
|
+
arguments: [{name: :"#{entity_type}_id", type: :identifier, lookup: lookup_method_all}]
|
|
153
|
+
commands_under setting do
|
|
154
|
+
setting_ops.each do |op|
|
|
155
|
+
# share_permissions > show needs a permission identifier (list does not)
|
|
156
|
+
perm_id_arg = if setting.eql?(:share_permissions) && op.eql?(:show)
|
|
157
|
+
[{name: :permission_id, type: :identifier, lookup: :lookup_share_id}]
|
|
158
|
+
else
|
|
159
|
+
[]
|
|
160
|
+
end
|
|
161
|
+
command op,
|
|
162
|
+
description: "#{op.capitalize} #{setting} for a #{entity_type}",
|
|
163
|
+
arguments: perm_id_arg.empty? ? nil : perm_id_arg
|
|
164
|
+
end
|
|
165
|
+
end
|
|
153
166
|
end
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
USR_GRP_SETTINGS.each do |setting|
|
|
164
|
-
setting_ops = setting.eql?(:share_permissions) ? Operations::ALL : %i[show modify]
|
|
165
|
-
lookup_method = :"lookup_shares_#{entity_type}_local_id"
|
|
166
|
-
command(
|
|
167
|
-
setting, description: "Manage #{setting} for a #{entity_type}",
|
|
168
|
-
arguments: [{name: :entity_id, type: :identifier, lookup: lookup_method}]
|
|
169
|
-
)
|
|
170
|
-
commands_under([:admin, entity_type, :local, setting]) do
|
|
171
|
-
setting_ops.each do |op|
|
|
172
|
-
command(op, description: "#{op.capitalize} #{setting} for a #{entity_type}")
|
|
167
|
+
if entity_type.eql?(:group)
|
|
168
|
+
command :users,
|
|
169
|
+
description: 'Manage users of a group',
|
|
170
|
+
arguments: [{name: :group_id, type: :identifier, lookup: :lookup_shares_group_all_id}]
|
|
171
|
+
commands_under :users do
|
|
172
|
+
GROUP_USERS_OPS.each do |op|
|
|
173
|
+
command op, description: "#{op.capitalize} users of a group"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
173
176
|
end
|
|
174
177
|
end
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
arguments: [{name: :group_id, type: :identifier}]
|
|
180
|
-
)
|
|
181
|
-
commands_under([:admin, entity_type, :local, :users]) do
|
|
178
|
+
|
|
179
|
+
# local: full CRUD only — no nested settings (Rails does not nest transfer_settings/app_authorizations/share_permissions under local_users/local_groups)
|
|
180
|
+
commands_under :local do
|
|
181
|
+
lookup_method_local = :"lookup_shares_#{entity_type}_local_id"
|
|
182
182
|
Operations::ALL.each do |op|
|
|
183
|
-
|
|
183
|
+
id_args = Operations::GLOBAL.include?(op) ? {} : {arguments: [{name: :"#{entity_type}_id", type: :identifier, lookup: lookup_method_local}]}
|
|
184
|
+
command op, description: "#{op.capitalize} #{entity_type}s", **id_args
|
|
185
|
+
end
|
|
186
|
+
if entity_type.eql?(:group)
|
|
187
|
+
command :users,
|
|
188
|
+
description: 'Manage users of a group',
|
|
189
|
+
arguments: [{name: :group_id, type: :identifier, lookup: :lookup_shares_group_local_id}]
|
|
190
|
+
commands_under :users do
|
|
191
|
+
GROUP_USERS_OPS.each do |op|
|
|
192
|
+
command op, description: "#{op.capitalize} users of a group"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
184
195
|
end
|
|
185
196
|
end
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
197
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
# ldap: add only
|
|
199
|
+
commands_under :ldap do
|
|
200
|
+
command :add, description: "Add a LDAP #{entity_type}",
|
|
201
|
+
arguments: [{name: :entity_name, type: String, bulk: true}]
|
|
202
|
+
end
|
|
194
203
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
# saml: import only
|
|
205
|
+
commands_under :saml do
|
|
206
|
+
command :import, description: "Import a SAML #{entity_type}",
|
|
207
|
+
arguments: [{name: :entity_parameters, type: Hash, bulk: true}]
|
|
208
|
+
end
|
|
209
|
+
end
|
|
199
210
|
end
|
|
200
211
|
end
|
|
201
212
|
|
|
@@ -210,42 +221,40 @@ module Aspera
|
|
|
210
221
|
lookup_method = :"lookup_shares_#{entity_type}_#{location}_id"
|
|
211
222
|
define_method(lookup_method) do |field, value, **|
|
|
212
223
|
path = admin_entity_path(entity_type, location)
|
|
213
|
-
RestList.lookup_entity_generic(entity: entity_type, field: field, value: value){@api_shares_admin.read(path)}['id']
|
|
224
|
+
RestList.lookup_entity_generic(entity: entity_type, field: field, value: value) { @api_shares_admin.read(path) }['id']
|
|
214
225
|
end
|
|
215
226
|
private lookup_method
|
|
216
227
|
end
|
|
217
228
|
end
|
|
218
229
|
|
|
219
|
-
commands_under
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
end
|
|
228
|
-
command(
|
|
229
|
-
:user_permissions, description: 'Manage user permissions on a share',
|
|
230
|
+
commands_under %i[admin share] do
|
|
231
|
+
crud_commands entity: 'data/shares',
|
|
232
|
+
api: :@api_shares_admin,
|
|
233
|
+
display_fields: SHARE_DISPLAY_FIELDS,
|
|
234
|
+
body_component: Schema::Registry::SHARES,
|
|
235
|
+
lookup: :lookup_share_id
|
|
236
|
+
command :user_permissions,
|
|
237
|
+
description: 'Manage user permissions on a share',
|
|
230
238
|
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}]
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
:group_permissions, description: 'Manage group permissions on a share',
|
|
239
|
+
command :group_permissions,
|
|
240
|
+
description: 'Manage group permissions on a share',
|
|
234
241
|
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}]
|
|
235
|
-
)
|
|
236
242
|
end
|
|
237
243
|
|
|
244
|
+
# share user_permissions and group_permissions: Rails only exposes index+show (read-only)
|
|
238
245
|
%i[user_permissions group_permissions].each do |perm_type|
|
|
239
|
-
commands_under
|
|
240
|
-
|
|
241
|
-
command
|
|
246
|
+
commands_under [:admin, :share, perm_type] do
|
|
247
|
+
SHARE_PERMISSIONS_OPS.each do |op|
|
|
248
|
+
command op, description: "#{op.capitalize} #{perm_type}"
|
|
242
249
|
end
|
|
243
250
|
end
|
|
244
251
|
end
|
|
245
252
|
|
|
246
|
-
commands_under
|
|
247
|
-
|
|
248
|
-
|
|
253
|
+
commands_under %i[admin transfer_settings] do
|
|
254
|
+
crud_commands entity: 'data/transfer_settings',
|
|
255
|
+
api: :@api_shares_admin,
|
|
256
|
+
is_singleton: true,
|
|
257
|
+
operations: %i[show modify]
|
|
249
258
|
end
|
|
250
259
|
|
|
251
260
|
# --- setup ---
|
|
@@ -266,7 +275,7 @@ module Aspera
|
|
|
266
275
|
|
|
267
276
|
# Lookup a share id by field/value using the admin API.
|
|
268
277
|
def lookup_share_id(field, value, **)
|
|
269
|
-
RestList.lookup_entity_generic(entity: 'share', field: field, value: value){@api_shares_admin.read('data/shares')}['id']
|
|
278
|
+
RestList.lookup_entity_generic(entity: 'share', field: field, value: value) { @api_shares_admin.read('data/shares') }['id']
|
|
270
279
|
end
|
|
271
280
|
|
|
272
281
|
# --- health ---
|
|
@@ -291,16 +300,39 @@ module Aspera
|
|
|
291
300
|
|
|
292
301
|
# --- files sub-commands (restricted to COMMANDS_SHARES, delegated to Node) ---
|
|
293
302
|
|
|
294
|
-
commands_under
|
|
303
|
+
commands_under :files do
|
|
295
304
|
Node::COMMANDS_SHARES.each do |cmd|
|
|
296
|
-
|
|
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)
|
|
297
316
|
end
|
|
298
317
|
end
|
|
299
318
|
|
|
300
|
-
# One handler per COMMANDS_SHARES command.
|
|
301
|
-
Node::COMMANDS_SHARES.each do |cmd|
|
|
302
|
-
define_action_method([:files, cmd]) do |shares_node_plugin
|
|
303
|
-
shares_node_plugin.dispatch_v3_command(cmd)
|
|
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)
|
|
304
336
|
end
|
|
305
337
|
end
|
|
306
338
|
|
|
@@ -320,10 +352,13 @@ module Aspera
|
|
|
320
352
|
# @param op [Symbol] CRUD operation
|
|
321
353
|
def action_admin_entity_crud(entity_type, location, op)
|
|
322
354
|
path = admin_entity_path(entity_type, location)
|
|
323
|
-
lookup = ->(f, v){RestList.lookup_entity_generic(entity: entity_type, field: f, value: v){@api_shares_admin.read(path)}['id']}
|
|
355
|
+
lookup = ->(f, v) { RestList.lookup_entity_generic(entity: entity_type, field: f, value: v) { @api_shares_admin.read(path) }['id'] }
|
|
324
356
|
display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
|
|
325
357
|
display_fields&.push('directory_user') if entity_type.eql?(:user) && location.eql?(:all)
|
|
326
|
-
|
|
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)
|
|
327
362
|
end
|
|
328
363
|
|
|
329
364
|
# Shared handler for USR_GRP_SETTINGS (transfer_settings, app_authorizations, share_permissions)
|
|
@@ -331,15 +366,13 @@ module Aspera
|
|
|
331
366
|
# @param location [Symbol] :all or :local
|
|
332
367
|
# @param setting [Symbol] one of USR_GRP_SETTINGS
|
|
333
368
|
# @param op [Symbol] CRUD operation
|
|
334
|
-
# entity_id: resolved
|
|
335
|
-
def action_admin_entity_setting(entity_type, location, setting, op, entity_id:, **)
|
|
369
|
+
# entity_id: resolved via arguments:(:identifier) on each setting leaf command
|
|
370
|
+
def action_admin_entity_setting(entity_type, location, setting, op, entity_id:, permission_id: nil, **)
|
|
336
371
|
path = admin_entity_path(entity_type, location)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
is_singleton: !setting.eql?(:share_permissions)
|
|
342
|
-
){ |f, v| lookup_share_id(f, v)}
|
|
372
|
+
entity_path = "#{path}/#{entity_id}/#{setting}"
|
|
373
|
+
is_singleton = !setting.eql?(:share_permissions)
|
|
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)
|
|
343
376
|
end
|
|
344
377
|
|
|
345
378
|
# Shared handler for :users (group only)
|
|
@@ -347,29 +380,35 @@ module Aspera
|
|
|
347
380
|
def action_admin_entity_users(entity_type, location, op, group_id:, **)
|
|
348
381
|
path = admin_entity_path(entity_type, location)
|
|
349
382
|
prefix = location.eql?(:all) ? '' : "#{location}_"
|
|
350
|
-
|
|
383
|
+
id = Operations::INSTANCE.include?(op) ? options.instance_identifier : nil
|
|
384
|
+
send(:"entity_#{op}", api: @api_shares_admin, entity: "#{path}/#{group_id}/#{prefix}users", id: id)
|
|
351
385
|
end
|
|
352
386
|
|
|
353
387
|
# Generate action_admin_<user|group>_<location>_<verb> for all combinations
|
|
354
388
|
%i[user group].each do |entity_type|
|
|
355
|
-
# all
|
|
389
|
+
# all: list/show/delete only (no create, no modify — no update route on /users and /groups)
|
|
390
|
+
# local: full CRUD only — no nested settings under local_users/local_groups
|
|
356
391
|
ENTITY_LOCATIONS.each do |location|
|
|
357
|
-
ops = location.eql?(:all) ? (Operations::ALL - [
|
|
392
|
+
ops = location.eql?(:all) ? (Operations::ALL - %i[create modify]) : Operations::ALL
|
|
358
393
|
ops.each do |op|
|
|
359
394
|
define_action_method([:admin, entity_type, location, op]) do
|
|
360
395
|
action_admin_entity_crud(entity_type, location, op)
|
|
361
396
|
end
|
|
362
397
|
end
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
398
|
+
unless location.eql?(:local)
|
|
399
|
+
USR_GRP_SETTINGS.each do |setting|
|
|
400
|
+
# share_permissions: Rails only exposes index+show (read-only)
|
|
401
|
+
setting_ops = setting.eql?(:share_permissions) ? SHARE_PERMISSIONS_OPS : %i[show modify]
|
|
402
|
+
setting_ops.each do |op|
|
|
403
|
+
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])
|
|
405
|
+
end
|
|
368
406
|
end
|
|
369
407
|
end
|
|
370
408
|
end
|
|
371
409
|
next unless entity_type.eql?(:group)
|
|
372
|
-
|
|
410
|
+
# group users: no create route (Rails only exposes index+show+update+destroy)
|
|
411
|
+
GROUP_USERS_OPS.each do |op|
|
|
373
412
|
define_action_method([:admin, entity_type, location, :users, op]) do |group_id:, **|
|
|
374
413
|
action_admin_entity_users(entity_type, location, op, group_id: group_id)
|
|
375
414
|
end
|
|
@@ -379,8 +418,7 @@ module Aspera
|
|
|
379
418
|
# ldap: add
|
|
380
419
|
define_action_method([:admin, entity_type, :ldap, :add]) do |entity_name:, **|
|
|
381
420
|
path = admin_entity_path(entity_type, :ldap)
|
|
382
|
-
|
|
383
|
-
Result.bulk(entity_name, is_bulk: is_bulk, command: :add, id_result: entity_type.to_s, bfail: options.get_option(:bfail)) do |entity_name|
|
|
421
|
+
bulk_result(entity_name, command: :add, id_result: entity_type.to_s) do |entity_name|
|
|
384
422
|
@api_shares_admin.create(path, {entity_type => entity_name})
|
|
385
423
|
end
|
|
386
424
|
end
|
|
@@ -388,13 +426,12 @@ module Aspera
|
|
|
388
426
|
# saml: import
|
|
389
427
|
define_action_method([:admin, entity_type, :saml, :import]) do |entity_parameters:, **|
|
|
390
428
|
path = admin_entity_path(entity_type, :saml)
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
entity_parameters = entity_parameters.transform_keys{ |k| k.gsub(/\s+/, '_').downcase}
|
|
429
|
+
bulk_result(entity_parameters, command: :import) do |entity_parameters|
|
|
430
|
+
entity_parameters = entity_parameters.transform_keys { |k| k.gsub(/\s+/, '_').downcase }
|
|
394
431
|
Aspera.assert_type(entity_parameters, Hash)
|
|
395
|
-
SAML_IMPORT_MANDATORY.each{ |p| raise "missing mandatory field: #{p}" if entity_parameters[p].nil?}
|
|
432
|
+
SAML_IMPORT_MANDATORY.each { |p| raise "missing mandatory field: #{p}" if entity_parameters[p].nil? }
|
|
396
433
|
entity_parameters.each_key do |p|
|
|
397
|
-
Aspera.assert_values(p, SAML_IMPORT_ALLOWED){'SAML field'}
|
|
434
|
+
Aspera.assert_values(p, SAML_IMPORT_ALLOWED) { 'SAML field' }
|
|
398
435
|
end
|
|
399
436
|
@api_shares_admin.create("#{path}/import", entity_parameters)
|
|
400
437
|
end
|
|
@@ -403,10 +440,12 @@ module Aspera
|
|
|
403
440
|
|
|
404
441
|
# Handlers for admin > share > user_permissions|group_permissions > op
|
|
405
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)
|
|
406
444
|
%i[user_permissions group_permissions].each do |perm_type|
|
|
407
|
-
|
|
445
|
+
SHARE_PERMISSIONS_OPS.each do |op|
|
|
408
446
|
define_action_method([:admin, :share, perm_type, op]) do |share_id:, **|
|
|
409
|
-
|
|
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)
|
|
410
449
|
end
|
|
411
450
|
end
|
|
412
451
|
end
|
|
@@ -30,40 +30,57 @@ module Aspera
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def action_preset_lookup(**)
|
|
33
|
-
Plugins::BasicAuth.declare_options(options)
|
|
33
|
+
Plugins::BasicAuth.declare_options(options, parse: true)
|
|
34
34
|
url = options.get_option(:url, mandatory: true)
|
|
35
35
|
user = options.get_option(:username, mandatory: true)
|
|
36
36
|
result = presets.lookup_preset(url: url, username: user)
|
|
37
|
-
Aspera.assert(!result.nil?, type: Error){'no such config found'}
|
|
37
|
+
Aspera.assert(!result.nil?, type: Error) { 'no such config found' }
|
|
38
38
|
Result::SingleObject.new(result)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def action_preset_secure(config_name: nil, **)
|
|
42
42
|
cp = presets.config_presets
|
|
43
43
|
preset_names = config_name.nil? ? cp.keys : [config_name]
|
|
44
|
-
secret_keywords = %w[password secret].freeze
|
|
45
44
|
preset_names.each do |preset_name|
|
|
46
45
|
preset = cp[preset_name]
|
|
47
46
|
next unless preset.is_a?(Hash)
|
|
48
47
|
preset.each_key do |option_name|
|
|
49
|
-
|
|
50
|
-
next unless option_name.end_with?(keyword)
|
|
51
|
-
vault_label = preset_name
|
|
52
|
-
incr = 0
|
|
53
|
-
until vault.get(label: vault_label, exception: false).nil?
|
|
54
|
-
vault_label = "#{preset_name}#{incr}"
|
|
55
|
-
incr += 1
|
|
56
|
-
end
|
|
57
|
-
to_set = {label: vault_label, password: preset[option_name]}
|
|
58
|
-
puts "need to encode #{preset_name}.#{option_name} -> #{vault_label} -> #{to_set}"
|
|
59
|
-
vault.set(to_set)
|
|
60
|
-
preset[option_name] = "@vault:#{vault_label}.password"
|
|
61
|
-
end
|
|
48
|
+
secure_preset_option(preset, preset_name, option_name)
|
|
62
49
|
end
|
|
63
50
|
end
|
|
64
51
|
Result::Status.new('Secrets secured in vault: Make sure to save the vault password securely.')
|
|
65
52
|
end
|
|
66
53
|
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
SECRET_KEYWORDS = %w[password secret].freeze
|
|
57
|
+
|
|
58
|
+
# If +option_name+ ends with a secret keyword and the vault is configured,
|
|
59
|
+
# move the clear-text value into the vault and replace it with a @vault: reference.
|
|
60
|
+
# @param preset [Hash] the preset hash (modified in place)
|
|
61
|
+
# @param preset_name [String] name used as base for the vault label
|
|
62
|
+
# @param option_name [String] option key to inspect
|
|
63
|
+
def secure_preset_option(preset, preset_name, option_name)
|
|
64
|
+
return unless SECRET_KEYWORDS.any? { |kw| option_name.end_with?(kw) }
|
|
65
|
+
# Never auto-secure the global preset: it holds vault credentials themselves
|
|
66
|
+
return if preset_name.eql?(presets.global_default_preset)
|
|
67
|
+
return if vault.nil?
|
|
68
|
+
value = preset[option_name]
|
|
69
|
+
# Already a vault reference or nil — nothing to do
|
|
70
|
+
return if value.nil? || value.to_s.start_with?('@vault:')
|
|
71
|
+
vault_label = preset_name
|
|
72
|
+
incr = 0
|
|
73
|
+
until vault.get(label: vault_label, exception: false).nil?
|
|
74
|
+
vault_label = "#{preset_name}#{incr}"
|
|
75
|
+
incr += 1
|
|
76
|
+
end
|
|
77
|
+
Log.log.info { "Securing #{preset_name}.#{option_name} -> vault label: #{vault_label}" }
|
|
78
|
+
vault.set({label: vault_label, password: value})
|
|
79
|
+
preset[option_name] = "@vault:#{vault_label}.password"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
public
|
|
83
|
+
|
|
67
84
|
def action_preset_show(name:, **)
|
|
68
85
|
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
69
86
|
cp = presets.config_presets
|
|
@@ -102,14 +119,15 @@ module Aspera
|
|
|
102
119
|
def action_preset_set(name:, param_name:, param_value:, **)
|
|
103
120
|
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
104
121
|
param_name = Parser.option_line_to_name(param_name)
|
|
105
|
-
presets.set_key(name, param_name, param_value)
|
|
122
|
+
presets.set_key(name, param_name, Parser.smart_convert(param_value))
|
|
123
|
+
secure_preset_option(presets.config_presets[name], name, param_name)
|
|
106
124
|
Result::Nothing.new
|
|
107
125
|
end
|
|
108
126
|
|
|
109
127
|
def action_preset_initialize(name:, config_value:, **)
|
|
110
128
|
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
111
129
|
cp = presets.config_presets
|
|
112
|
-
Log.log.warn{"configuration already exists: #{name}, overwriting"} if cp.key?(name)
|
|
130
|
+
Log.log.warn { "configuration already exists: #{name}, overwriting" } if cp.key?(name)
|
|
113
131
|
cp[name] = config_value
|
|
114
132
|
Result::Status.new("Modified: #{@option_config_file}")
|
|
115
133
|
end
|
|
@@ -117,10 +135,11 @@ module Aspera
|
|
|
117
135
|
def action_preset_update(name:, **)
|
|
118
136
|
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
119
137
|
unprocessed_options = options.unprocessed_options_with_value
|
|
120
|
-
Log.
|
|
138
|
+
Log.dump(:opts, unprocessed_options)
|
|
121
139
|
cp = presets.config_presets
|
|
122
140
|
cp[name] ||= {}
|
|
123
141
|
cp[name].merge!(unprocessed_options)
|
|
142
|
+
unprocessed_options.each_key { |k| secure_preset_option(cp[name], name, k) }
|
|
124
143
|
Result::Status.new("Updated: #{name}")
|
|
125
144
|
end
|
|
126
145
|
|
|
@@ -131,6 +150,7 @@ module Aspera
|
|
|
131
150
|
option_names.each do |option_name|
|
|
132
151
|
option_value = options.get_interactive(option_name, check_option: true)
|
|
133
152
|
cp[name][option_name] = option_value
|
|
153
|
+
secure_preset_option(cp[name], name, option_name)
|
|
134
154
|
end
|
|
135
155
|
Result::Status.new("Updated: #{name}")
|
|
136
156
|
end
|