aspera-cli 4.26.2 → 4.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +281 -21
- data/CONTRIBUTING.md +98 -7
- data/bin/ascli +3 -3
- data/docs/README.md +10757 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +20 -6
- data/lib/aspera/agent/connect.rb +53 -16
- data/lib/aspera/agent/desktop.rb +52 -17
- data/lib/aspera/agent/direct.rb +26 -24
- data/lib/aspera/agent/httpgw.rb +1 -1
- data/lib/aspera/agent/node.rb +32 -0
- data/lib/aspera/agent/transferd.rb +39 -3
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +14 -16
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/faspex.rb +15 -14
- data/lib/aspera/api/httpgw.rb +4 -4
- data/lib/aspera/api/node.rb +26 -23
- data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
- data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
- data/lib/aspera/ascmd.rb +21 -19
- data/lib/aspera/ascp/installation.rb +37 -38
- data/lib/aspera/ascp/management.rb +2 -3
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +81 -0
- data/lib/aspera/cli/bootstrapper.rb +197 -0
- data/lib/aspera/cli/command_registry.rb +145 -0
- data/lib/aspera/cli/command_spec.rb +138 -0
- data/lib/aspera/cli/context.rb +16 -3
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +2 -1
- data/lib/aspera/cli/formatter.rb +41 -27
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +2 -2
- data/lib/aspera/cli/http.rb +70 -19
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +198 -0
- data/lib/aspera/cli/options.schema.yaml +364 -7
- data/lib/aspera/cli/{manager.rb → parser.rb} +334 -142
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1166 -853
- data/lib/aspera/cli/plugins/ats.rb +200 -161
- data/lib/aspera/cli/plugins/base.rb +457 -100
- data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
- data/lib/aspera/cli/plugins/config.rb +428 -754
- data/lib/aspera/cli/plugins/console.rb +106 -64
- data/lib/aspera/cli/plugins/cos.rb +44 -32
- data/lib/aspera/cli/plugins/factory.rb +4 -4
- data/lib/aspera/cli/plugins/faspex.rb +294 -257
- data/lib/aspera/cli/plugins/faspex5.rb +585 -357
- data/lib/aspera/cli/plugins/faspio.rb +49 -51
- data/lib/aspera/cli/plugins/httpgw.rb +18 -25
- data/lib/aspera/cli/plugins/mcp.rb +279 -0
- data/lib/aspera/cli/plugins/node.rb +998 -794
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +111 -134
- data/lib/aspera/cli/plugins/preview.rb +180 -139
- data/lib/aspera/cli/plugins/server.rb +124 -79
- data/lib/aspera/cli/plugins/shares.rb +301 -107
- data/lib/aspera/cli/preset_actions.rb +139 -0
- data/lib/aspera/cli/preset_manager.rb +13 -12
- data/lib/aspera/cli/result.rb +61 -11
- data/lib/aspera/cli/runner.rb +275 -141
- data/lib/aspera/cli/secret_finder.rb +40 -0
- data/lib/aspera/cli/special_values.rb +1 -0
- data/lib/aspera/cli/sync_actions.rb +83 -59
- data/lib/aspera/cli/terminal_formatter.rb +1 -1
- data/lib/aspera/cli/transfer_actions.rb +83 -0
- data/lib/aspera/cli/transfer_agent.rb +116 -51
- data/lib/aspera/cli/transfer_progress.rb +3 -3
- data/lib/aspera/cli/vault_manager.rb +57 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +21 -20
- data/lib/aspera/command_line_builder.rb +22 -22
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +4 -4
- data/lib/aspera/environment.rb +18 -11
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +1 -1
- data/lib/aspera/faspex_postproc.rb +4 -3
- data/lib/aspera/graphql.rb +35 -0
- data/lib/aspera/hash_ext.rb +6 -0
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/encrypted_hash.rb +1 -1
- data/lib/aspera/keychain/factory.rb +3 -3
- data/lib/aspera/keychain/macos_security.rb +3 -3
- data/lib/aspera/link_header.rb +82 -0
- data/lib/aspera/log.rb +17 -3
- data/lib/aspera/markdown.rb +85 -2
- data/lib/aspera/node_simulator.rb +3 -1
- data/lib/aspera/oauth/base.rb +5 -5
- data/lib/aspera/oauth/boot.rb +2 -2
- data/lib/aspera/oauth/factory.rb +7 -5
- data/lib/aspera/oauth/jwt.rb +4 -4
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_action_once.rb +8 -8
- data/lib/aspera/persistency_folder.rb +10 -3
- data/lib/aspera/preview/file_types.rb +3 -3
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/utils.rb +1 -1
- data/lib/aspera/products/connect.rb +1 -1
- data/lib/aspera/products/other.rb +1 -1
- data/lib/aspera/products/transferd.rb +1 -1
- data/lib/aspera/proxy_auto_config.rb +7 -6
- data/lib/aspera/rest.rb +10 -36
- data/lib/aspera/rest_error_analyzer.rb +3 -3
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +3 -3
- data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/documentation.rb +57 -26
- data/lib/aspera/schema/reader.rb +59 -10
- data/lib/aspera/schema/registry.rb +39 -6
- data/lib/aspera/secret_hider.rb +7 -2
- data/lib/aspera/ssl.rb +4 -4
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/operations.rb +10 -13
- data/lib/aspera/temp_file_manager.rb +1 -1
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +23 -21
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +6 -6
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +2 -2
- data/lib/aspera/web_auth.rb +2 -2
- data/lib/aspera/web_server_simple.rb +8 -5
- data/lib/aspera/yaml.rb +2 -1
- data.tar.gz.sig +0 -0
- metadata +25 -3
- metadata.gz.sig +0 -0
- data/lib/aspera/json_rpc.rb +0 -52
|
@@ -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
|
-
|
|
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]+)/))
|
|
@@ -91,129 +91,323 @@ module Aspera
|
|
|
91
91
|
|
|
92
92
|
def initialize(**_)
|
|
93
93
|
super
|
|
94
|
+
@api_shares_admin = nil
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
SAML_IMPORT_MANDATORY = %w[id name_id].freeze
|
|
97
98
|
SAML_IMPORT_ALLOWED = %w[email given_name surname].concat(SAML_IMPORT_MANDATORY).freeze
|
|
98
99
|
|
|
99
|
-
ACTIONS = %i[health info files admin].freeze
|
|
100
100
|
# common to users and groups
|
|
101
101
|
USR_GRP_SETTINGS = %i[transfer_settings app_authorizations share_permissions].freeze
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
# --- DSL ---
|
|
104
|
+
|
|
105
|
+
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'}))}
|
|
107
|
+
command :files, description: 'Browse and transfer files on Shares', aliases: [:repository], setup: :setup_shares_node
|
|
108
|
+
command :admin, description: 'Administer Shares', setup: :setup_admin
|
|
109
|
+
|
|
110
|
+
commands_under(:admin) do
|
|
111
|
+
entity_command :node, api: :@api_shares_admin, entity: 'data/nodes'
|
|
112
|
+
command :share, description: 'Manage shares'
|
|
113
|
+
command :transfer_settings, description: 'Manage transfer settings'
|
|
114
|
+
command :user, description: 'Manage users'
|
|
115
|
+
command :group, description: 'Manage groups'
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# admin > user / group: sub-commands per location, generated for both entity types
|
|
119
|
+
%i[user group].each do |entity_type|
|
|
120
|
+
commands_under([:admin, entity_type]) do
|
|
121
|
+
command :all, description: "#{entity_type.capitalize}s from all sources"
|
|
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
|
+
end
|
|
126
|
+
|
|
127
|
+
# all: list/show/delete + USR_GRP_SETTINGS [+ users for group]
|
|
128
|
+
commands_under([:admin, entity_type, :all]) do
|
|
129
|
+
(Operations::ALL - [:create]).each do |op|
|
|
130
|
+
command(op, description: "#{op.capitalize} #{entity_type}(s)")
|
|
115
131
|
end
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
USR_GRP_SETTINGS.each do |setting|
|
|
133
|
+
setting_ops = setting.eql?(:share_permissions) ? Operations::ALL : %i[show modify]
|
|
134
|
+
lookup_method = :"lookup_shares_#{entity_type}_all_id"
|
|
135
|
+
command(
|
|
136
|
+
setting, description: "Manage #{setting} for a #{entity_type}",
|
|
137
|
+
arguments: [{name: :entity_id, type: :identifier, lookup: lookup_method}]
|
|
138
|
+
)
|
|
139
|
+
commands_under([:admin, entity_type, :all, setting]) do
|
|
140
|
+
setting_ops.each do |op|
|
|
141
|
+
command(op, description: "#{op.capitalize} #{setting} for a #{entity_type}")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
120
144
|
end
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
when :admin
|
|
131
|
-
api_shares_admin = basic_auth_api(ADMIN_API_PATH)
|
|
132
|
-
admin_command = options.get_next_command(%i[node share transfer_settings user group].freeze)
|
|
133
|
-
lookup_share = ->(field, value){RestList.lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
|
|
134
|
-
case admin_command
|
|
135
|
-
when :node
|
|
136
|
-
return entity_execute(api: api_shares_admin, entity: 'data/nodes')
|
|
137
|
-
when :share
|
|
138
|
-
share_command = options.get_next_command(%i[user_permissions group_permissions].concat(Operations::ALL))
|
|
139
|
-
case share_command
|
|
140
|
-
when *Operations::ALL
|
|
141
|
-
return entity_execute(
|
|
142
|
-
api: api_shares_admin,
|
|
143
|
-
entity: 'data/shares',
|
|
144
|
-
command: share_command,
|
|
145
|
-
display_fields: %w[id name node_id directory percent_free],
|
|
146
|
-
&lookup_share
|
|
147
|
-
)
|
|
148
|
-
when :user_permissions, :group_permissions
|
|
149
|
-
share_id = options.instance_identifier(&lookup_share)
|
|
150
|
-
return entity_execute(api: api_shares_admin, entity: "data/shares/#{share_id}/#{share_command}")
|
|
145
|
+
if entity_type.eql?(:group)
|
|
146
|
+
command(
|
|
147
|
+
:users, description: 'Manage users of a group',
|
|
148
|
+
arguments: [{name: :group_id, type: :identifier}]
|
|
149
|
+
)
|
|
150
|
+
commands_under([:admin, entity_type, :all, :users]) do
|
|
151
|
+
Operations::ALL.each do |op|
|
|
152
|
+
command(op, description: "#{op.capitalize} users of a group")
|
|
153
|
+
end
|
|
151
154
|
end
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# local: list/show/delete/create/modify [+ users for group]
|
|
159
|
+
commands_under([:admin, entity_type, :local]) do
|
|
160
|
+
Operations::ALL.each do |op|
|
|
161
|
+
command(op, description: "#{op.capitalize} #{entity_type}(s)")
|
|
162
|
+
end
|
|
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}]
|
|
159
169
|
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
entities_path = "data/#{entities_prefix}#{entity_type}s"
|
|
165
|
-
entity_commands = nil
|
|
166
|
-
case entities_location
|
|
167
|
-
when :all
|
|
168
|
-
entity_commands = %i[list show delete]
|
|
169
|
-
entity_commands.concat(USR_GRP_SETTINGS)
|
|
170
|
-
entity_commands.push(:users) if entity_type.eql?(:group)
|
|
171
|
-
entity_commands.freeze
|
|
172
|
-
when :local
|
|
173
|
-
entity_commands = %i[list show delete create modify]
|
|
174
|
-
entity_commands.push(:users) if entity_type.eql?(:group)
|
|
175
|
-
entity_commands.freeze
|
|
176
|
-
when :ldap
|
|
177
|
-
entity_commands = %i[add].freeze
|
|
178
|
-
when :saml
|
|
179
|
-
entity_commands = %i[import].freeze
|
|
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}")
|
|
173
|
+
end
|
|
180
174
|
end
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
command: entity_verb,
|
|
191
|
-
display_fields: display_fields,
|
|
192
|
-
&lookup_block
|
|
193
|
-
)
|
|
194
|
-
when *USR_GRP_SETTINGS # transfer_settings, app_authorizations, share_permissions
|
|
195
|
-
group_id = options.instance_identifier(&lookup_block)
|
|
196
|
-
entities_path = "#{entities_path}/#{group_id}/#{entity_verb}"
|
|
197
|
-
return entity_execute(api: api_shares_admin, entity: entities_path, is_singleton: !entity_verb.eql?(:share_permissions), &lookup_share)
|
|
198
|
-
when :import # saml
|
|
199
|
-
return do_bulk_operation(command: entity_verb, descr: 'user information') do |entity_parameters|
|
|
200
|
-
entity_parameters = entity_parameters.transform_keys{ |k| k.gsub(/\s+/, '_').downcase}
|
|
201
|
-
Aspera.assert_type(entity_parameters, Hash)
|
|
202
|
-
SAML_IMPORT_MANDATORY.each{ |p| raise "missing mandatory field: #{p}" if entity_parameters[p].nil?}
|
|
203
|
-
entity_parameters.each_key do |p|
|
|
204
|
-
raise "unsupported field: #{p}, use: #{SAML_IMPORT_ALLOWED.join(',')}" unless SAML_IMPORT_ALLOWED.include?(p)
|
|
205
|
-
end
|
|
206
|
-
api_shares_admin.create("#{entities_path}/import", entity_parameters)
|
|
175
|
+
end
|
|
176
|
+
if entity_type.eql?(:group)
|
|
177
|
+
command(
|
|
178
|
+
:users, description: 'Manage users of a group',
|
|
179
|
+
arguments: [{name: :group_id, type: :identifier}]
|
|
180
|
+
)
|
|
181
|
+
commands_under([:admin, entity_type, :local, :users]) do
|
|
182
|
+
Operations::ALL.each do |op|
|
|
183
|
+
command(op, description: "#{op.capitalize} users of a group")
|
|
207
184
|
end
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# ldap: add only
|
|
190
|
+
commands_under([:admin, entity_type, :ldap]) do
|
|
191
|
+
command :add, description: "Add a LDAP #{entity_type}",
|
|
192
|
+
arguments: [{name: :entity_name, type: String, bulk: true}]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# saml: import only
|
|
196
|
+
commands_under([:admin, entity_type, :saml]) do
|
|
197
|
+
command :import, description: "Import a SAML #{entity_type}",
|
|
198
|
+
arguments: [{name: :entity_parameters, type: Hash, bulk: true}]
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
ENTITY_LOCATIONS = %i[all local].freeze
|
|
203
|
+
SHARE_DISPLAY_FIELDS = %w[id name node_id directory percent_free].freeze
|
|
204
|
+
private_constant :ENTITY_LOCATIONS, :SHARE_DISPLAY_FIELDS
|
|
205
|
+
|
|
206
|
+
# Lookup methods used by arguments:(:identifier, lookup:) on setting nodes.
|
|
207
|
+
# One method per entity_type/location combination; each returns the entity id.
|
|
208
|
+
%i[user group].each do |entity_type|
|
|
209
|
+
ENTITY_LOCATIONS.each do |location|
|
|
210
|
+
lookup_method = :"lookup_shares_#{entity_type}_#{location}_id"
|
|
211
|
+
define_method(lookup_method) do |field, value, **|
|
|
212
|
+
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']
|
|
214
|
+
end
|
|
215
|
+
private lookup_method
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
commands_under(%i[admin share]) do
|
|
220
|
+
Operations::ALL.each do |op|
|
|
221
|
+
command(op, description: "#{op.capitalize} share(s)", action: lambda do
|
|
222
|
+
entity_execute(
|
|
223
|
+
api: @api_shares_admin, entity: 'data/shares', command: op,
|
|
224
|
+
display_fields: SHARE_DISPLAY_FIELDS
|
|
225
|
+
){ |f, v| lookup_share_id(f, v)}
|
|
226
|
+
end)
|
|
227
|
+
end
|
|
228
|
+
command(
|
|
229
|
+
:user_permissions, description: 'Manage user permissions on a share',
|
|
230
|
+
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}]
|
|
231
|
+
)
|
|
232
|
+
command(
|
|
233
|
+
:group_permissions, description: 'Manage group permissions on a share',
|
|
234
|
+
arguments: [{name: :share_id, type: :identifier, lookup: :lookup_share_id}]
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
%i[user_permissions group_permissions].each do |perm_type|
|
|
239
|
+
commands_under([:admin, :share, perm_type]) do
|
|
240
|
+
Operations::ALL.each do |op|
|
|
241
|
+
command(op, description: "#{op.capitalize} #{perm_type}")
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
commands_under(%i[admin transfer_settings]) do
|
|
247
|
+
entity_command :show, description: 'Show transfer settings', api: :@api_shares_admin, entity: 'data/transfer_settings', command: :show, is_singleton: true
|
|
248
|
+
entity_command :modify, description: 'Modify transfer settings', api: :@api_shares_admin, entity: 'data/transfer_settings', command: :modify, is_singleton: true
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# --- setup ---
|
|
252
|
+
|
|
253
|
+
# Build the Shares node API plugin and inject into ctx.
|
|
254
|
+
# @return [Hash] context hash containing :shares_node_plugin
|
|
255
|
+
def setup_shares_node(**)
|
|
256
|
+
api_shares_node = basic_auth_api(NODE_API_PATH)
|
|
257
|
+
{shares_node_plugin: Node.new(context: context, api: api_shares_node)}
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Build the admin REST API and store in ivar.
|
|
261
|
+
# @return [Hash] empty ctx (state stored in @api_shares_admin)
|
|
262
|
+
def setup_admin(**)
|
|
263
|
+
@api_shares_admin = basic_auth_api(ADMIN_API_PATH)
|
|
264
|
+
{}
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Lookup a share id by field/value using the admin API.
|
|
268
|
+
def lookup_share_id(field, value, **)
|
|
269
|
+
RestList.lookup_entity_generic(entity: 'share', field: field, value: value){@api_shares_admin.read('data/shares')}['id']
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# --- health ---
|
|
273
|
+
|
|
274
|
+
def action_health
|
|
275
|
+
nagios = Nagios.new
|
|
276
|
+
shares_url = options.get_option(:url, mandatory: true)
|
|
277
|
+
health = self.class.health_check(shares_url)
|
|
278
|
+
nagios.add_ok('version', health[:version]) if health[:version].is_a?(String)
|
|
279
|
+
if health[:ping].is_a?(String)
|
|
280
|
+
nagios.add_ok('ping', health[:ping])
|
|
281
|
+
else
|
|
282
|
+
nagios.add_critical('ping', health[:ping].to_s)
|
|
283
|
+
end
|
|
284
|
+
if health[:api].is_a?(String)
|
|
285
|
+
nagios.add_ok('API', health[:api])
|
|
286
|
+
else
|
|
287
|
+
nagios.add_critical('API', health[:api].to_s)
|
|
288
|
+
end
|
|
289
|
+
Result::ObjectList.new(nagios.status_list)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# --- files sub-commands (restricted to COMMANDS_SHARES, delegated to Node) ---
|
|
293
|
+
|
|
294
|
+
commands_under(:files) do
|
|
295
|
+
Node::COMMANDS_SHARES.each do |cmd|
|
|
296
|
+
command(cmd, description: "Node #{cmd} command")
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
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)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
# @return [String] admin API path for the given entity_type and location
|
|
310
|
+
def admin_entity_path(entity_type, location)
|
|
311
|
+
prefix = location.eql?(:all) ? '' : "#{location}_"
|
|
312
|
+
"data/#{prefix}#{entity_type}s"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# --- admin > user|group handlers ---
|
|
316
|
+
|
|
317
|
+
# Shared handler for list/show/delete/create/modify on user or group.
|
|
318
|
+
# @param entity_type [Symbol] :user or :group
|
|
319
|
+
# @param location [Symbol] :all or :local
|
|
320
|
+
# @param op [Symbol] CRUD operation
|
|
321
|
+
def action_admin_entity_crud(entity_type, location, op)
|
|
322
|
+
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']}
|
|
324
|
+
display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
|
|
325
|
+
display_fields&.push('directory_user') if entity_type.eql?(:user) && location.eql?(:all)
|
|
326
|
+
entity_execute(api: @api_shares_admin, entity: path, command: op, display_fields: display_fields, &lookup)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Shared handler for USR_GRP_SETTINGS (transfer_settings, app_authorizations, share_permissions)
|
|
330
|
+
# @param entity_type [Symbol] :user or :group
|
|
331
|
+
# @param location [Symbol] :all or :local
|
|
332
|
+
# @param setting [Symbol] one of USR_GRP_SETTINGS
|
|
333
|
+
# @param op [Symbol] CRUD operation
|
|
334
|
+
# entity_id: resolved by Phase A via arguments:(:identifier) on the setting node
|
|
335
|
+
def action_admin_entity_setting(entity_type, location, setting, op, entity_id:, **)
|
|
336
|
+
path = admin_entity_path(entity_type, location)
|
|
337
|
+
entity_execute(
|
|
338
|
+
api: @api_shares_admin,
|
|
339
|
+
entity: "#{path}/#{entity_id}/#{setting}",
|
|
340
|
+
command: op,
|
|
341
|
+
is_singleton: !setting.eql?(:share_permissions)
|
|
342
|
+
){ |f, v| lookup_share_id(f, v)}
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Shared handler for :users (group only)
|
|
346
|
+
# group_id: resolved by Phase A via arguments:(:identifier) on the :users node
|
|
347
|
+
def action_admin_entity_users(entity_type, location, op, group_id:, **)
|
|
348
|
+
path = admin_entity_path(entity_type, location)
|
|
349
|
+
prefix = location.eql?(:all) ? '' : "#{location}_"
|
|
350
|
+
entity_execute(api: @api_shares_admin, entity: "#{path}/#{group_id}/#{prefix}users", command: op)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
# Generate action_admin_<user|group>_<location>_<verb> for all combinations
|
|
354
|
+
%i[user group].each do |entity_type|
|
|
355
|
+
# all + local: CRUD + USR_GRP_SETTINGS [+ users for group]
|
|
356
|
+
ENTITY_LOCATIONS.each do |location|
|
|
357
|
+
ops = location.eql?(:all) ? (Operations::ALL - [:create]) : Operations::ALL
|
|
358
|
+
ops.each do |op|
|
|
359
|
+
define_action_method([:admin, entity_type, location, op]) do
|
|
360
|
+
action_admin_entity_crud(entity_type, location, op)
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
USR_GRP_SETTINGS.each do |setting|
|
|
364
|
+
setting_ops = setting.eql?(:share_permissions) ? Operations::ALL : %i[show modify]
|
|
365
|
+
setting_ops.each do |op|
|
|
366
|
+
define_action_method([:admin, entity_type, location, setting, op]) do |entity_id:, **|
|
|
367
|
+
action_admin_entity_setting(entity_type, location, setting, op, entity_id: entity_id)
|
|
211
368
|
end
|
|
212
|
-
when :users # group
|
|
213
|
-
return entity_execute(api: api_shares_admin, entity: "#{entities_path}/#{options.instance_identifier(&lookup_block)}/#{entities_prefix}users")
|
|
214
|
-
else Aspera.error_unexpected_value(entity_verb)
|
|
215
369
|
end
|
|
216
370
|
end
|
|
371
|
+
next unless entity_type.eql?(:group)
|
|
372
|
+
Operations::ALL.each do |op|
|
|
373
|
+
define_action_method([:admin, entity_type, location, :users, op]) do |group_id:, **|
|
|
374
|
+
action_admin_entity_users(entity_type, location, op, group_id: group_id)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# ldap: add
|
|
380
|
+
define_action_method([:admin, entity_type, :ldap, :add]) do |entity_name:, **|
|
|
381
|
+
path = admin_entity_path(entity_type, :ldap)
|
|
382
|
+
is_bulk = options.get_option(:bulk)
|
|
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|
|
|
384
|
+
@api_shares_admin.create(path, {entity_type => entity_name})
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# saml: import
|
|
389
|
+
define_action_method([:admin, entity_type, :saml, :import]) do |entity_parameters:, **|
|
|
390
|
+
path = admin_entity_path(entity_type, :saml)
|
|
391
|
+
is_bulk = options.get_option(:bulk)
|
|
392
|
+
Result.bulk(entity_parameters, is_bulk: is_bulk, command: :import, bfail: options.get_option(:bfail)) do |entity_parameters|
|
|
393
|
+
entity_parameters = entity_parameters.transform_keys{ |k| k.gsub(/\s+/, '_').downcase}
|
|
394
|
+
Aspera.assert_type(entity_parameters, Hash)
|
|
395
|
+
SAML_IMPORT_MANDATORY.each{ |p| raise "missing mandatory field: #{p}" if entity_parameters[p].nil?}
|
|
396
|
+
entity_parameters.each_key do |p|
|
|
397
|
+
Aspera.assert_values(p, SAML_IMPORT_ALLOWED){'SAML field'}
|
|
398
|
+
end
|
|
399
|
+
@api_shares_admin.create("#{path}/import", entity_parameters)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Handlers for admin > share > user_permissions|group_permissions > op
|
|
405
|
+
# share_id: resolved by Phase A via arguments:(:identifier, lookup: :lookup_share_id) on the perm_type node
|
|
406
|
+
%i[user_permissions group_permissions].each do |perm_type|
|
|
407
|
+
Operations::ALL.each do |op|
|
|
408
|
+
define_action_method([:admin, :share, perm_type, op]) do |share_id:, **|
|
|
409
|
+
entity_execute(api: @api_shares_admin, entity: "data/shares/#{share_id}/#{perm_type}", command: op)
|
|
410
|
+
end
|
|
217
411
|
end
|
|
218
412
|
end
|
|
219
413
|
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/assert'
|
|
4
|
+
|
|
5
|
+
module Aspera
|
|
6
|
+
module Cli
|
|
7
|
+
# Mixin for Config plugin: preset CRUD actions
|
|
8
|
+
module PresetActions
|
|
9
|
+
# Used to identify the global default preset keyword
|
|
10
|
+
GLOBAL_DEFAULT_KEYWORD = 'GLOBAL'
|
|
11
|
+
# Display columns for preset overview
|
|
12
|
+
CONF_OVERVIEW_KEYS = %w[preset parameter value].freeze
|
|
13
|
+
|
|
14
|
+
def action_preset_list(**)
|
|
15
|
+
Result::ValueList.new(presets.config_presets.keys, name: 'name')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def action_preset_overview(**)
|
|
19
|
+
cp = presets.config_presets
|
|
20
|
+
# Display process modifies the value (hide secrets): we do not want to save removed secrets
|
|
21
|
+
data = PresetManager.deep_clone(cp)
|
|
22
|
+
formatter.hide_secrets(data)
|
|
23
|
+
result = []
|
|
24
|
+
data.each do |config, preset|
|
|
25
|
+
preset.each do |parameter, value|
|
|
26
|
+
result.push(CONF_OVERVIEW_KEYS.zip([config, parameter, value]).to_h)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
Result::ObjectList.new(result, fields: CONF_OVERVIEW_KEYS)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def action_preset_lookup(**)
|
|
33
|
+
Plugins::BasicAuth.declare_options(options)
|
|
34
|
+
url = options.get_option(:url, mandatory: true)
|
|
35
|
+
user = options.get_option(:username, mandatory: true)
|
|
36
|
+
result = presets.lookup_preset(url: url, username: user)
|
|
37
|
+
Aspera.assert(!result.nil?, type: Error){'no such config found'}
|
|
38
|
+
Result::SingleObject.new(result)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def action_preset_secure(config_name: nil, **)
|
|
42
|
+
cp = presets.config_presets
|
|
43
|
+
preset_names = config_name.nil? ? cp.keys : [config_name]
|
|
44
|
+
secret_keywords = %w[password secret].freeze
|
|
45
|
+
preset_names.each do |preset_name|
|
|
46
|
+
preset = cp[preset_name]
|
|
47
|
+
next unless preset.is_a?(Hash)
|
|
48
|
+
preset.each_key do |option_name|
|
|
49
|
+
secret_keywords.each do |keyword|
|
|
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
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
Result::Status.new('Secrets secured in vault: Make sure to save the vault password securely.')
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def action_preset_show(name:, **)
|
|
68
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
69
|
+
cp = presets.config_presets
|
|
70
|
+
raise "no such preset: #{name}" unless cp.key?(name)
|
|
71
|
+
Result::SingleObject.new(PresetManager.deep_clone(cp[name]))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def action_preset_delete(name:, **)
|
|
75
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
76
|
+
cp = presets.config_presets
|
|
77
|
+
raise "no such preset: #{name}" unless cp.key?(name)
|
|
78
|
+
cp.delete(name)
|
|
79
|
+
Result::Status.new("Deleted: #{name}")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def action_preset_get(name:, param_name:, **)
|
|
83
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
84
|
+
cp = presets.config_presets
|
|
85
|
+
raise "no such preset: #{name}" unless cp.key?(name)
|
|
86
|
+
value = cp[name][param_name]
|
|
87
|
+
raise "no such option in preset #{name} : #{param_name}" if value.nil?
|
|
88
|
+
case value
|
|
89
|
+
when Numeric, String then return Result::Text.new(ExtendedValue.instance.evaluate(value.to_s, context: 'preset'))
|
|
90
|
+
end
|
|
91
|
+
Result::SingleObject.new(value)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def action_preset_unset(name:, param_name:, **)
|
|
95
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
96
|
+
cp = presets.config_presets
|
|
97
|
+
raise "no such preset: #{name}" unless cp.key?(name)
|
|
98
|
+
cp[name].delete(param_name)
|
|
99
|
+
Result::Status.new("Removed: #{name}: #{param_name}")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def action_preset_set(name:, param_name:, param_value:, **)
|
|
103
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
104
|
+
param_name = Parser.option_line_to_name(param_name)
|
|
105
|
+
presets.set_key(name, param_name, param_value)
|
|
106
|
+
Result::Nothing.new
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def action_preset_initialize(name:, config_value:, **)
|
|
110
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
111
|
+
cp = presets.config_presets
|
|
112
|
+
Log.log.warn{"configuration already exists: #{name}, overwriting"} if cp.key?(name)
|
|
113
|
+
cp[name] = config_value
|
|
114
|
+
Result::Status.new("Modified: #{@option_config_file}")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def action_preset_update(name:, **)
|
|
118
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
119
|
+
unprocessed_options = options.unprocessed_options_with_value
|
|
120
|
+
Log.log.debug{"opts=#{unprocessed_options}"}
|
|
121
|
+
cp = presets.config_presets
|
|
122
|
+
cp[name] ||= {}
|
|
123
|
+
cp[name].merge!(unprocessed_options)
|
|
124
|
+
Result::Status.new("Updated: #{name}")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def action_preset_ask(name:, option_names:, **)
|
|
128
|
+
name = presets.global_default_preset if name.eql?(GLOBAL_DEFAULT_KEYWORD)
|
|
129
|
+
cp = presets.config_presets
|
|
130
|
+
cp[name] ||= {}
|
|
131
|
+
option_names.each do |option_name|
|
|
132
|
+
option_value = options.get_interactive(option_name, check_option: true)
|
|
133
|
+
cp[name][option_name] = option_value
|
|
134
|
+
end
|
|
135
|
+
Result::Status.new("Updated: #{name}")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|