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,11 +3,11 @@
|
|
|
3
3
|
# cspell:ignore trevents
|
|
4
4
|
require 'aspera/cli/plugins/basic_auth'
|
|
5
5
|
require 'aspera/preview/generator'
|
|
6
|
-
require 'aspera/preview/options'
|
|
7
6
|
require 'aspera/preview/utils'
|
|
8
7
|
require 'aspera/preview/file_types'
|
|
9
8
|
require 'aspera/preview/terminal'
|
|
10
9
|
require 'aspera/transfer/spec'
|
|
10
|
+
require 'aspera/schema/registry'
|
|
11
11
|
require 'aspera/persistency_action_once'
|
|
12
12
|
require 'aspera/temp_file_manager'
|
|
13
13
|
require 'aspera/api/node'
|
|
@@ -18,6 +18,8 @@ require 'aspera/uri_reader'
|
|
|
18
18
|
require 'aspera/log'
|
|
19
19
|
require 'aspera/assert'
|
|
20
20
|
require 'securerandom'
|
|
21
|
+
require 'aspera/rainbow'
|
|
22
|
+
using Rainbow
|
|
21
23
|
|
|
22
24
|
module Aspera
|
|
23
25
|
module Cli
|
|
@@ -46,53 +48,59 @@ module Aspera
|
|
|
46
48
|
:LOG_LIMITER_SEC,
|
|
47
49
|
:REMOTE_ACCESS
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# DSL option declarations (Pattern 1 and Pattern 2 - handler: Symbol resolved to self)
|
|
51
|
+
# DSL option declarations
|
|
52
52
|
option :skip_format, description: 'Skip this preview format', allowed: Aspera::Preview::Generator::PREVIEW_FORMATS
|
|
53
53
|
# TODO: use the same option as in `node` plugin
|
|
54
|
-
option :folder_reset_cache, description: 'Force detection of generated preview by refresh cache', allowed: %i[no header read],
|
|
55
|
-
option :skip_types, description: 'Skip generation for those types of files', allowed: Type::SYMBOL_ARRAY + Aspera::Preview::FileTypes::CONVERSION_TYPES
|
|
56
|
-
option :previews_folder, description: 'Preview folder in storage root',
|
|
57
|
-
option :skip_folders, description: 'List of folder to skip', allowed: Type::STRING_ARRAY
|
|
54
|
+
option :folder_reset_cache, description: 'Force detection of generated preview by refresh cache', allowed: %i[no header read], default: :no
|
|
55
|
+
option :skip_types, description: 'Skip generation for those types of files', allowed: Type::SYMBOL_ARRAY + Aspera::Preview::FileTypes::CONVERSION_TYPES
|
|
56
|
+
option :previews_folder, description: 'Preview folder in storage root', default: DEFAULT_PREVIEWS_FOLDER
|
|
57
|
+
option :skip_folders, description: 'List of folder to skip', allowed: Type::STRING_ARRAY
|
|
58
58
|
option :base, description: 'Basename of output for for test'
|
|
59
|
-
option :filter,
|
|
60
|
-
option :
|
|
61
|
-
option :overwrite, description: 'When to overwrite result file',
|
|
59
|
+
option :filter, description: 'File name filter: String (glob), Regexp, or Proc', allowed: [String, Regexp, Proc, NilClass]
|
|
60
|
+
option :detect_mime, description: 'Detect Mime type by analyzing file', allowed: Type::BOOLEAN, default: false
|
|
61
|
+
option :overwrite, description: 'When to overwrite result file', allowed: %i[always never mtime], default: :mtime
|
|
62
62
|
option :root_url, description: "How to read and write files on storage (#{REMOTE_ACCESS}, or #{UriReader.file_url('<folder>')})", default: REMOTE_ACCESS
|
|
63
|
-
# Generator
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
# Generator options, defined in schema: values set in @gen_options
|
|
64
|
+
Schema::Registry.instance.reader(Schema::Registry::PREVIEW_OPTIONS).current['properties'].each do |name, property|
|
|
65
|
+
key = name.to_sym
|
|
66
|
+
option(
|
|
67
|
+
key,
|
|
68
|
+
description: property['description'].lines.first.strip.delete_suffix('.'),
|
|
69
|
+
allowed: property.key?('enum') ? property['enum'].map(&:to_sym) : {'integer' => Type::INTEGER, 'number' => Type::FLOAT, 'boolean' => Type::BOOLEAN}[property['type']],
|
|
70
|
+
default: property['default'],
|
|
71
|
+
# `Hash` value: validated with schema
|
|
72
|
+
schema: ("#{Schema::Registry::PREVIEW_OPTIONS}:properties.#{name}" if property['type'].eql?('object')),
|
|
73
|
+
on_set: ->(value) { @gen_options[key] = value }
|
|
74
|
+
)
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
def initialize(**_)
|
|
78
|
+
# Generator options, see `options.schema.yaml`
|
|
79
|
+
# Created before `super`, which declares options: values are set through `on_set`.
|
|
80
|
+
@gen_options = Aspera::Preview::Generator::Options.new
|
|
74
81
|
super
|
|
75
|
-
# Generator configuration populated from CLI options.
|
|
76
|
-
@gen_options = Aspera::Preview::Options.new
|
|
77
82
|
# Used to rate-limit periodic progress logging and checkpoint persistence.
|
|
78
83
|
@periodic = TimerLimiter.new(LOG_LIMITER_SEC)
|
|
79
84
|
# Optional callback used to filter entries before generation.
|
|
80
85
|
@filter_block = nil
|
|
81
86
|
@access_remote = true
|
|
82
|
-
#
|
|
83
|
-
Aspera::Preview::Options::DESCRIPTIONS.each do |opt|
|
|
84
|
-
options.set_handler(opt[:name], object: @gen_options, method: opt[:name])
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
+
# Values set through `on_set` callbacks are used below
|
|
87
88
|
options.parse_options!
|
|
89
|
+
@option_skip_types = options.get_option(:skip_types)
|
|
90
|
+
@option_previews_folder = options.get_option(:previews_folder)
|
|
91
|
+
@option_folder_reset_cache = options.get_option(:folder_reset_cache)
|
|
92
|
+
# Modified during scan
|
|
93
|
+
@option_skip_folders = options.get_option(:skip_folders).dup
|
|
94
|
+
@option_overwrite = options.get_option(:overwrite)
|
|
95
|
+
# Tell which tool we will use for office docs
|
|
96
|
+
Aspera::Preview::Utils.office_tool = @gen_options.office_conversion
|
|
88
97
|
Api::Node.api_options[:cache] = !@option_folder_reset_cache.eql?(:header)
|
|
89
98
|
# Start from the full supported format list, then remove any skipped format.
|
|
90
99
|
@preview_formats_to_generate = Aspera::Preview::Generator::PREVIEW_FORMATS.clone
|
|
91
100
|
skip = options.get_option(:skip_format)
|
|
92
101
|
@preview_formats_to_generate.delete(skip) if skip
|
|
102
|
+
# Created by `tools_cleanup`, only for commands that use it
|
|
93
103
|
@tmp_folder = File.join(TempFileManager.instance.global_temp, "#{TMP_DIR_PREFIX}.#{SecureRandom.uuid}")
|
|
94
|
-
FileUtils.mkdir_p(@tmp_folder)
|
|
95
|
-
Log.log.debug { "tmpdir: #{@tmp_folder}" }
|
|
96
104
|
end
|
|
97
105
|
|
|
98
106
|
# Process legacy transfer events and trigger preview generation for completed downloads.
|
|
@@ -108,7 +116,7 @@ module Aspera
|
|
|
108
116
|
events_filter['iteration_token'] = iteration_persistency.data.first unless iteration_persistency.nil?
|
|
109
117
|
begin
|
|
110
118
|
events = @api_node.read('events', events_filter)
|
|
111
|
-
rescue
|
|
119
|
+
rescue Rest::CallError => e
|
|
112
120
|
if e.message.include?('Invalid iteration_token')
|
|
113
121
|
Log.log.warn { "Retrying without iteration token: #{e}" }
|
|
114
122
|
events_filter.delete('iteration_token')
|
|
@@ -360,7 +368,7 @@ module Aspera
|
|
|
360
368
|
Log.log.debug { "processing entry #{entry_path_with_slash}" } if @periodic.trigger?
|
|
361
369
|
entry_path_with_slash = "#{entry_path_with_slash}/" unless entry_path_with_slash.end_with?('/')
|
|
362
370
|
if !top_path.nil? && !top_path.start_with?(entry_path_with_slash) && !entry_path_with_slash.start_with?(top_path)
|
|
363
|
-
Log.log.debug { "#{entry['path']} folder (skip start)".
|
|
371
|
+
Log.log.debug { "#{entry['path']} folder (skip start)".bg(:red) }
|
|
364
372
|
next
|
|
365
373
|
end
|
|
366
374
|
Log.log.debug { "item:#{entry}" }
|
|
@@ -376,7 +384,7 @@ module Aspera
|
|
|
376
384
|
Log.log.debug('Ignoring link.')
|
|
377
385
|
when 'folder'
|
|
378
386
|
if @option_skip_folders.include?(entry['path'])
|
|
379
|
-
Log.log.debug { "#{entry['path']} folder (skip list)".
|
|
387
|
+
Log.log.debug { "#{entry['path']} folder (skip list)".bg(:red) }
|
|
380
388
|
else
|
|
381
389
|
Log.log.debug { "#{entry['path']} folder".green }
|
|
382
390
|
# get folder content
|
|
@@ -402,14 +410,16 @@ module Aspera
|
|
|
402
410
|
|
|
403
411
|
# scan, events and trevents connect to the Node API (setup: :setup_node_api).
|
|
404
412
|
# `check`, `test` and `show` work without Node API authentication.
|
|
405
|
-
command :scan, description: 'Scan a file or folder and generate previews',
|
|
406
|
-
|
|
407
|
-
command :
|
|
408
|
-
command :
|
|
409
|
-
command :
|
|
413
|
+
command :scan, description: 'Scan a file or folder and generate previews', setup: :setup_node_api,
|
|
414
|
+
arguments: [{name: :path, description: 'File or folder from which to start scanning', mandatory: false, default: '/'}]
|
|
415
|
+
command :events, description: 'Process file events and generate previews', setup: :setup_node_api, action: ->(**) { run_event_loop(:events) }
|
|
416
|
+
command :trevents, description: 'Process transfer events and generate previews', setup: :setup_node_api, action: ->(**) { run_event_loop(:trevents) }
|
|
417
|
+
command :check, description: 'Check required tools are installed', action: ->(**) { tools_cleanup { Result::Status.new('Tools validated') } }
|
|
418
|
+
command :show, description: 'Generate and display preview of a source file',
|
|
419
|
+
arguments: [{name: :source_file, description: 'Local file to preview'}]
|
|
410
420
|
command :test, description: 'Test preview generation for a source file',
|
|
411
421
|
arguments: [
|
|
412
|
-
{name: :source_file,
|
|
422
|
+
{name: :source_file, description: 'Local file to preview'},
|
|
413
423
|
{name: :format, description: 'Preview output format', allowed: Aspera::Preview::Generator::PREVIEW_FORMATS, mandatory: false, default: :png}
|
|
414
424
|
]
|
|
415
425
|
|
|
@@ -428,7 +438,7 @@ module Aspera
|
|
|
428
438
|
Log.log.debug { "root: #{node_info['docroot']}" }
|
|
429
439
|
# Default storage url to local file if not provided
|
|
430
440
|
option_root_url = options.get_option(:root_url, mandatory: true)
|
|
431
|
-
option_root_url = UriReader.file_url(@access_key_self['storage']['path']) if option_root_url.eql?(REMOTE_ACCESS) && @access_key_self['storage']
|
|
441
|
+
option_root_url = UriReader.file_url(@access_key_self['storage']['path']) if option_root_url.eql?(REMOTE_ACCESS) && @access_key_self['storage']&.fetch('type').eql?('local')
|
|
432
442
|
@access_remote = !UriReader.file?(option_root_url)
|
|
433
443
|
Log.log.debug { "remote: #{@access_remote}" }
|
|
434
444
|
# TODO: can the `previews` folder parameter be read from Node API ?
|
|
@@ -461,71 +471,49 @@ module Aspera
|
|
|
461
471
|
# --- handlers ---
|
|
462
472
|
|
|
463
473
|
def action_scan(path:, **)
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
cleanup_tmp_folder
|
|
477
|
-
end
|
|
478
|
-
|
|
479
|
-
def action_events(**)
|
|
480
|
-
check_tools_and_mimemagic
|
|
481
|
-
run_event_loop(:events, filter: options.get_option(:filter))
|
|
482
|
-
ensure
|
|
483
|
-
cleanup_tmp_folder
|
|
484
|
-
end
|
|
485
|
-
|
|
486
|
-
def action_trevents(**)
|
|
487
|
-
check_tools_and_mimemagic
|
|
488
|
-
run_event_loop(:trevents, filter: options.get_option(:filter))
|
|
489
|
-
ensure
|
|
490
|
-
cleanup_tmp_folder
|
|
474
|
+
tools_cleanup do
|
|
475
|
+
apifid =
|
|
476
|
+
if (selector = Parser.percent_selector(path))
|
|
477
|
+
Aspera.assert_values(selector[:field], ['id'], type: BadArgument) { 'file id' }
|
|
478
|
+
Api::NodeFileId.new(@api_node, selector[:value].to_s.empty? ? @access_key_self['root_file_id'] : selector[:value])
|
|
479
|
+
else
|
|
480
|
+
@api_node.resolve_api_fid(@access_key_self['root_file_id'], path)
|
|
481
|
+
end
|
|
482
|
+
@filter_block = Base.file_matcher(options.get_option(:filter))
|
|
483
|
+
scan_folder_files(@api_node.read("files/#{apifid.file_id}"))
|
|
484
|
+
Result::Status.new('scan finished')
|
|
485
|
+
end
|
|
491
486
|
end
|
|
492
487
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
Result::Status.new('Tools validated')
|
|
496
|
-
ensure
|
|
497
|
-
cleanup_tmp_folder
|
|
498
|
-
end
|
|
488
|
+
# Status message for generation
|
|
489
|
+
MSG_GEN_TITLE = 'Generated: '
|
|
499
490
|
|
|
500
491
|
def action_test(source_file:, format:, **)
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
cleanup_tmp_folder
|
|
492
|
+
tools_cleanup do
|
|
493
|
+
generated_file_path = preview_filename(format, options.get_option(:base))
|
|
494
|
+
Aspera::Preview::Generator.new(source_file, generated_file_path, @gen_options, @tmp_folder).generate
|
|
495
|
+
Result::Status.new("#{MSG_GEN_TITLE}#{generated_file_path}")
|
|
496
|
+
end
|
|
507
497
|
end
|
|
508
498
|
|
|
509
499
|
def action_show(source_file:, **)
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
formatter.display_status("generated: #{generated_file_path}")
|
|
500
|
+
status = action_test(source_file: source_file, format: :png)
|
|
501
|
+
formatter.display_status(status.data)
|
|
502
|
+
generated_file_path = status.data.delete_prefix(MSG_GEN_TITLE)
|
|
514
503
|
Result::Image.new(UriReader.file_url(generated_file_path))
|
|
515
|
-
ensure
|
|
516
|
-
cleanup_tmp_folder
|
|
517
504
|
end
|
|
518
505
|
|
|
519
506
|
private
|
|
520
507
|
|
|
521
|
-
# Check tools and set
|
|
522
|
-
|
|
523
|
-
|
|
508
|
+
# Check tools and set option flag.
|
|
509
|
+
# Create the temporary folder for the command, and clean it up after.
|
|
510
|
+
def tools_cleanup
|
|
511
|
+
Aspera::Preview::FileTypes.instance.detect_mime = options.get_option(:detect_mime, mandatory: true)
|
|
524
512
|
Aspera::Preview::Utils.check_tools(@option_skip_types)
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
513
|
+
FileUtils.mkdir_p(@tmp_folder)
|
|
514
|
+
Log.log.debug { "tmpdir: #{@tmp_folder}" }
|
|
515
|
+
yield
|
|
516
|
+
ensure
|
|
529
517
|
Log.log.debug { "cleaning up temp folder #{@tmp_folder}" }
|
|
530
518
|
FileUtils.rm_rf(@tmp_folder)
|
|
531
519
|
end
|
|
@@ -533,23 +521,26 @@ module Aspera
|
|
|
533
521
|
# Shared event-loop body for :events and :trevents.
|
|
534
522
|
# @param command [:events, :trevents]
|
|
535
523
|
# @param filter [String, Regexp, Proc, nil] optional filter expression
|
|
536
|
-
def run_event_loop(command
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
iteration_persistency =
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
524
|
+
def run_event_loop(command)
|
|
525
|
+
tools_cleanup do
|
|
526
|
+
filter = options.get_option(:filter)
|
|
527
|
+
@filter_block = Base.file_matcher(filter)
|
|
528
|
+
iteration_persistency = nil
|
|
529
|
+
if options.get_option(:once_only, mandatory: true)
|
|
530
|
+
iteration_persistency = PersistencyActionOnce.new(
|
|
531
|
+
manager: persistency,
|
|
532
|
+
data: [],
|
|
533
|
+
id: IdGenerator.from_list(
|
|
534
|
+
'preview_iteration',
|
|
535
|
+
command.to_s,
|
|
536
|
+
options.get_option(:url, mandatory: true),
|
|
537
|
+
options.get_option(:username, mandatory: true)
|
|
538
|
+
)
|
|
548
539
|
)
|
|
549
|
-
|
|
540
|
+
end
|
|
541
|
+
send(:"process_#{command}", iteration_persistency)
|
|
542
|
+
Result::Status.new("#{command} finished")
|
|
550
543
|
end
|
|
551
|
-
send(:"process_#{command}", iteration_persistency)
|
|
552
|
-
Result::Status.new("#{command} finished")
|
|
553
544
|
end
|
|
554
545
|
end
|
|
555
546
|
end
|
|
@@ -72,7 +72,7 @@ module Aspera
|
|
|
72
72
|
username: options.get_option(:username, mandatory: true),
|
|
73
73
|
password: options.get_option(:password, mandatory: true)
|
|
74
74
|
},
|
|
75
|
-
test_args: '
|
|
75
|
+
test_args: 'browse /'
|
|
76
76
|
}
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -81,7 +81,6 @@ module Aspera
|
|
|
81
81
|
@connection_type = :ssh
|
|
82
82
|
@ascmd_executor = nil
|
|
83
83
|
@server_transfer_spec = nil
|
|
84
|
-
options.parse_options!
|
|
85
84
|
@ssh_opts = options.get_option(:ssh_options).symbolize_keys
|
|
86
85
|
end
|
|
87
86
|
|
|
@@ -160,7 +159,7 @@ module Aspera
|
|
|
160
159
|
end
|
|
161
160
|
|
|
162
161
|
Sync::Operations::DIRECTIONS.each do |dir|
|
|
163
|
-
|
|
162
|
+
define_action_method([:sync, dir]) { |path:, sync_info: {}, **| run_sync_transfer(dir, path: path, sync_info: sync_info) { server_transfer_spec } }
|
|
164
163
|
end
|
|
165
164
|
|
|
166
165
|
# --- DSL ---
|
|
@@ -169,22 +168,18 @@ module Aspera
|
|
|
169
168
|
option :passphrase, description: 'SSH private key passphrase'
|
|
170
169
|
option :ssh_options, description: 'SSH options', allowed: Hash
|
|
171
170
|
|
|
172
|
-
# root_setup runs once before any command argument is consumed, populating
|
|
173
|
-
# @server_transfer_spec and @ascmd_executor so that condition methods work.
|
|
174
|
-
root_setup :setup_server
|
|
175
|
-
|
|
176
171
|
command :health, description: 'Check transfer health'
|
|
177
|
-
command(:upload, description: 'Upload files to server', transfer_paths: :send, action: lambda do
|
|
178
|
-
|
|
179
|
-
Runner.result_transfer(transfer.start(
|
|
172
|
+
command(:upload, description: 'Upload files to server', transfer_paths: :send, action: lambda do |**|
|
|
173
|
+
server_transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(:upload)
|
|
174
|
+
Runner.result_transfer(transfer.start(server_transfer_spec))
|
|
180
175
|
end)
|
|
181
176
|
command(
|
|
182
177
|
:download,
|
|
183
178
|
description: 'Download files from server',
|
|
184
179
|
transfer_paths: :receive,
|
|
185
|
-
action: lambda do
|
|
186
|
-
|
|
187
|
-
Runner.result_transfer(transfer.start(
|
|
180
|
+
action: lambda do |**|
|
|
181
|
+
server_transfer_spec['direction'] = Transfer::Spec.transfer_type_to_direction(:download)
|
|
182
|
+
Runner.result_transfer(transfer.start(server_transfer_spec))
|
|
188
183
|
end
|
|
189
184
|
)
|
|
190
185
|
command :sync, description: 'Synchronize files with server'
|
|
@@ -192,8 +187,7 @@ module Aspera
|
|
|
192
187
|
Sync::Operations::DIRECTIONS.each do |dir|
|
|
193
188
|
command dir, description: "#{dir.capitalize}-sync with server", transfer_paths: :send, arguments: SyncActions::PATH_AND_INFO_ARGS
|
|
194
189
|
end
|
|
195
|
-
|
|
196
|
-
SyncActions.register_sync_admin_commands(self, :admin)
|
|
190
|
+
sync_admin_commands :admin
|
|
197
191
|
end
|
|
198
192
|
|
|
199
193
|
commands_under :health do
|
|
@@ -201,43 +195,76 @@ module Aspera
|
|
|
201
195
|
end
|
|
202
196
|
|
|
203
197
|
# AsCmd operations (available only when SSH/local - not WSS)
|
|
204
|
-
|
|
205
|
-
|
|
198
|
+
# Arguments follow AsCmd::OPS_ARGS: one path per operation, or source and destination
|
|
199
|
+
PATHS_ARGS = [{name: :paths, multiple: true}].freeze
|
|
200
|
+
private_constant :PATHS_ARGS
|
|
206
201
|
|
|
207
|
-
command
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
command :
|
|
202
|
+
command(
|
|
203
|
+
:ls, description: 'List files', condition: :ascmd_available?, aliases: [:browse], arguments: PATHS_ARGS,
|
|
204
|
+
action: lambda do |paths:, **|
|
|
205
|
+
execute_ascmd(:ls, paths) do |result|
|
|
206
|
+
Result::ObjectList.new(result.map(&:stringify_keys), fields: %w[zmode zuid zgid size mtime name])
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
)
|
|
210
|
+
command :rm, description: 'Delete files', condition: :ascmd_available?, aliases: [:delete], arguments: PATHS_ARGS
|
|
211
|
+
command :mv, description: 'Rename/move files: source destination pairs, or sources with --to-folder', condition: :ascmd_available?, aliases: [:rename], arguments: PATHS_ARGS
|
|
212
|
+
command :cp, description: 'Copy files: source destination pairs, or sources with --to-folder', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
213
|
+
command :mkdir, description: 'Create directory', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
214
|
+
command(
|
|
215
|
+
:df, description: 'Show disk usage', condition: :ascmd_available?,
|
|
216
|
+
action: lambda do |**|
|
|
217
|
+
execute_ascmd(:df, []) do |result|
|
|
218
|
+
Result::ObjectList.new(result.map(&:stringify_keys))
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
)
|
|
222
|
+
command :du, description: 'Show file sizes', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
223
|
+
command :md5sum, description: 'Compute MD5 checksums', condition: :ascmd_available?, arguments: PATHS_ARGS
|
|
224
|
+
command :info, description: 'Show server system information', condition: :ascmd_available?,
|
|
225
|
+
action: ->(**) { execute_ascmd(:info, []) { |r| Result::SingleObject.new(r.stringify_keys) } }
|
|
216
226
|
|
|
217
227
|
# Generate ascmd handlers - convention: action_<op>
|
|
218
|
-
%i[rm
|
|
219
|
-
define_action_method([op]) do |
|
|
220
|
-
execute_ascmd(op,
|
|
228
|
+
%i[rm mkdir].each do |op|
|
|
229
|
+
define_action_method([op]) do |paths:, **|
|
|
230
|
+
execute_ascmd(op, paths) { Result::Success.new }
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
%i[mv cp].each do |op|
|
|
235
|
+
define_action_method([op]) do |paths:, **|
|
|
236
|
+
execute_ascmd(op, source_destination_pairs(paths)) { Result::Success.new }
|
|
221
237
|
end
|
|
222
238
|
end
|
|
223
239
|
|
|
224
|
-
%i[du md5sum
|
|
225
|
-
define_action_method([op]) do |
|
|
226
|
-
execute_ascmd(op,
|
|
240
|
+
%i[du md5sum].each do |op|
|
|
241
|
+
define_action_method([op]) do |paths:, **|
|
|
242
|
+
execute_ascmd(op, paths) { |r| Result::SingleObject.new(r.stringify_keys) }
|
|
227
243
|
end
|
|
228
244
|
end
|
|
229
245
|
|
|
230
246
|
# --- conditions ---
|
|
231
247
|
|
|
232
248
|
def ascmd_available?
|
|
233
|
-
|
|
249
|
+
!ascmd_executor.nil?
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# --- server access ---
|
|
253
|
+
|
|
254
|
+
# @return [Hash] base transfer spec, built from CLI options on first use
|
|
255
|
+
def server_transfer_spec
|
|
256
|
+
build_server_access if @server_transfer_spec.nil?
|
|
257
|
+
@server_transfer_spec
|
|
234
258
|
end
|
|
235
259
|
|
|
236
|
-
#
|
|
260
|
+
# @return [LocalExecutor, Ssh, nil] ascmd executor, nil with WSS (ascmd not available)
|
|
261
|
+
def ascmd_executor
|
|
262
|
+
build_server_access if @server_transfer_spec.nil?
|
|
263
|
+
@ascmd_executor
|
|
264
|
+
end
|
|
237
265
|
|
|
238
266
|
# Build the transfer spec and ascmd executor from CLI options.
|
|
239
|
-
|
|
240
|
-
def setup_server(**)
|
|
267
|
+
def build_server_access
|
|
241
268
|
@server_transfer_spec = options_to_base_transfer_spec
|
|
242
269
|
@ascmd_executor = case @connection_type
|
|
243
270
|
when :local then LocalExecutor.new
|
|
@@ -245,14 +272,13 @@ module Aspera
|
|
|
245
272
|
when :ssh then Ssh.new(@server_transfer_spec['remote_host'], @server_transfer_spec['remote_user'], @ssh_opts)
|
|
246
273
|
else Aspera.error_unexpected_value(@connection_type) { 'connection type' }
|
|
247
274
|
end
|
|
248
|
-
{}
|
|
249
275
|
end
|
|
250
276
|
|
|
251
277
|
# --- health ---
|
|
252
278
|
|
|
253
|
-
def action_health_transfer
|
|
279
|
+
def action_health_transfer(**)
|
|
254
280
|
nagios = Nagios.new
|
|
255
|
-
probe_ts =
|
|
281
|
+
probe_ts = server_transfer_spec.merge({
|
|
256
282
|
'direction' => 'send',
|
|
257
283
|
'cookie' => 'aspera.sync', # hide in console
|
|
258
284
|
'resume_policy' => 'none',
|
|
@@ -269,24 +295,23 @@ module Aspera
|
|
|
269
295
|
|
|
270
296
|
# --- ascmd handlers ---
|
|
271
297
|
|
|
272
|
-
|
|
273
|
-
execute_ascmd(:ls, command_arguments) do |result|
|
|
274
|
-
Result::ObjectList.new(result.map(&:stringify_keys), fields: %w[zmode zuid zgid size mtime name])
|
|
275
|
-
end
|
|
276
|
-
end
|
|
298
|
+
private
|
|
277
299
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
300
|
+
# AsCmd `cp` and `mv` take source and destination pairs.
|
|
301
|
+
# With option `to_folder`, paths are only sources, copied or moved into that folder.
|
|
302
|
+
# @param paths [Array<String>] command line arguments
|
|
303
|
+
# @return [Array<String>] flat list of source and destination pairs
|
|
304
|
+
def source_destination_pairs(paths)
|
|
305
|
+
to_folder = options.get_option(:to_folder)
|
|
306
|
+
return paths.flat_map { |source| [source, "#{to_folder.chomp('/')}/#{File.basename(source)}"] } unless to_folder.nil?
|
|
307
|
+
Aspera.assert(paths.length.even?, type: Cli::BadArgument) { 'expecting pairs: <source> <destination> ..., or sources with option to_folder' }
|
|
308
|
+
paths
|
|
282
309
|
end
|
|
283
310
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
def execute_ascmd(op, command_arguments)
|
|
287
|
-
ascmd = AsCmd.new(@ascmd_executor)
|
|
311
|
+
def execute_ascmd(op, arguments)
|
|
312
|
+
ascmd = AsCmd.new(ascmd_executor)
|
|
288
313
|
begin
|
|
289
|
-
result = ascmd.execute_single(op,
|
|
314
|
+
result = ascmd.execute_single(op, arguments)
|
|
290
315
|
yield(result)
|
|
291
316
|
rescue AsCmd::Error => e
|
|
292
317
|
raise Cli::BadArgument, e.extended_message
|