aspera-cli 4.26.2 → 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 +310 -21
- data/CONTRIBUTING.md +99 -14
- data/TODO.md +50 -0
- data/bin/ascli +3 -3
- data/docs/README.md +10977 -0
- data/docs/test-mcp-with-ai.md +202 -0
- data/lib/aspera/agent/base.rb +29 -7
- data/lib/aspera/agent/connect.rb +58 -21
- data/lib/aspera/agent/desktop.rb +53 -18
- data/lib/aspera/agent/direct.rb +71 -40
- data/lib/aspera/agent/factory.rb +3 -3
- data/lib/aspera/agent/httpgw.rb +55 -14
- data/lib/aspera/agent/node.rb +34 -2
- data/lib/aspera/agent/transferd.rb +48 -12
- data/lib/aspera/api/alee.rb +1 -1
- data/lib/aspera/api/aoc.rb +42 -44
- data/lib/aspera/api/cos_node.rb +10 -9
- data/lib/aspera/api/faspex.rb +16 -15
- data/lib/aspera/api/httpgw.rb +23 -23
- data/lib/aspera/api/node.rb +51 -70
- 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 +32 -30
- data/lib/aspera/ascp/installation.rb +75 -69
- data/lib/aspera/ascp/management.rb +7 -8
- data/lib/aspera/assert.rb +12 -13
- data/lib/aspera/cli/ascp_actions.rb +155 -0
- data/lib/aspera/cli/async_transfer_store.rb +125 -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 +141 -0
- data/lib/aspera/cli/context.rb +19 -6
- data/lib/aspera/cli/error.rb +16 -0
- data/lib/aspera/cli/extended_value.rb +27 -26
- data/lib/aspera/cli/formatter.rb +95 -59
- data/lib/aspera/cli/gem_checker.rb +65 -0
- data/lib/aspera/cli/hints.rb +3 -3
- data/lib/aspera/cli/http.rb +74 -30
- data/lib/aspera/cli/info.rb +2 -0
- data/lib/aspera/cli/mailer.rb +97 -0
- data/lib/aspera/cli/mcp_tool.rb +225 -0
- data/lib/aspera/cli/option_declarator.rb +77 -0
- data/lib/aspera/cli/options.schema.yaml +634 -7
- data/lib/aspera/cli/parser.rb +1217 -0
- data/lib/aspera/cli/plugins/alee.rb +20 -22
- data/lib/aspera/cli/plugins/aoc.rb +1178 -881
- data/lib/aspera/cli/plugins/ats.rb +205 -162
- data/lib/aspera/cli/plugins/base.rb +703 -175
- data/lib/aspera/cli/plugins/basic_auth.rb +7 -9
- data/lib/aspera/cli/plugins/config.rb +434 -757
- data/lib/aspera/cli/plugins/console.rb +107 -65
- data/lib/aspera/cli/plugins/cos.rb +46 -33
- data/lib/aspera/cli/plugins/factory.rb +6 -6
- data/lib/aspera/cli/plugins/faspex5.rb +627 -388
- data/lib/aspera/cli/plugins/faspio.rb +54 -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 +988 -840
- data/lib/aspera/cli/plugins/oauth.rb +7 -10
- data/lib/aspera/cli/plugins/orchestrator.rb +112 -135
- data/lib/aspera/cli/plugins/preview.rb +195 -157
- data/lib/aspera/cli/plugins/server.rb +141 -91
- data/lib/aspera/cli/plugins/shares.rb +343 -110
- data/lib/aspera/cli/preset_actions.rb +159 -0
- data/lib/aspera/cli/preset_manager.rb +82 -39
- data/lib/aspera/cli/result.rb +78 -26
- data/lib/aspera/cli/runner.rb +323 -153
- 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 +89 -64
- data/lib/aspera/cli/terminal_formatter.rb +3 -3
- data/lib/aspera/cli/transfer_actions.rb +90 -0
- data/lib/aspera/cli/transfer_agent.rb +129 -64
- data/lib/aspera/cli/transfer_progress.rb +9 -9
- data/lib/aspera/cli/vault_manager.rb +79 -0
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/cli/wizard.rb +28 -30
- data/lib/aspera/colors.rb +3 -3
- data/lib/aspera/command_line_builder.rb +34 -34
- data/lib/aspera/command_line_converter.rb +1 -1
- data/lib/aspera/coverage.rb +1 -2
- data/lib/aspera/data_repository.rb +2 -1
- data/lib/aspera/dot_container.rb +12 -12
- data/lib/aspera/environment.rb +30 -23
- data/lib/aspera/exec_spec.rb +13 -0
- data/lib/aspera/faspex_gw.rb +5 -5
- data/lib/aspera/faspex_postproc.rb +16 -10
- data/lib/aspera/graphql.rb +37 -0
- data/lib/aspera/hash_ext.rb +8 -2
- data/lib/aspera/json_rpc/client.rb +62 -0
- data/lib/aspera/json_rpc/version.rb +7 -0
- data/lib/aspera/keychain/base.rb +10 -3
- data/lib/aspera/keychain/encrypted_hash.rb +92 -17
- data/lib/aspera/keychain/factory.rb +20 -8
- 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 +82 -0
- data/lib/aspera/log.rb +29 -15
- data/lib/aspera/markdown.rb +90 -7
- data/lib/aspera/nagios.rb +8 -8
- data/lib/aspera/node_simulator.rb +35 -27
- data/lib/aspera/oauth/base.rb +9 -10
- data/lib/aspera/oauth/boot.rb +5 -5
- data/lib/aspera/oauth/factory.rb +12 -10
- data/lib/aspera/oauth/jwt.rb +9 -11
- data/lib/aspera/oauth/web.rb +6 -6
- data/lib/aspera/persistency_action_once.rb +12 -12
- data/lib/aspera/persistency_folder.rb +18 -11
- data/lib/aspera/preview/file_types.rb +5 -5
- data/lib/aspera/preview/generator.rb +52 -49
- data/lib/aspera/preview/options.rb +3 -2
- data/lib/aspera/preview/terminal.rb +5 -5
- data/lib/aspera/preview/utils.rb +66 -19
- data/lib/aspera/products/connect.rb +2 -2
- data/lib/aspera/products/desktop.rb +1 -1
- data/lib/aspera/products/other.rb +3 -3
- data/lib/aspera/products/transferd.rb +4 -4
- data/lib/aspera/proxy_auto_config.rb +15 -14
- data/lib/aspera/rest.rb +159 -151
- data/lib/aspera/rest_error_analyzer.rb +6 -6
- data/lib/aspera/rest_errors_aspera.rb +0 -10
- data/lib/aspera/rest_list.rb +11 -10
- 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 on Cloud API-0.2.6-enhanced.yaml +1853 -510
- data/lib/aspera/schema/IBM_Aspera_Shares.yaml +4653 -0
- data/lib/aspera/schema/documentation.rb +65 -28
- data/lib/aspera/schema/reader.rb +136 -13
- data/lib/aspera/schema/registry.rb +60 -8
- data/lib/aspera/secret_hider.rb +12 -7
- data/lib/aspera/ssh.rb +64 -31
- data/lib/aspera/ssl.rb +5 -5
- data/lib/aspera/sync/conf.schema.yaml +2 -2
- data/lib/aspera/sync/database.rb +2 -2
- data/lib/aspera/sync/operations.rb +22 -25
- data/lib/aspera/temp_file_manager.rb +25 -5
- data/lib/aspera/timer_limiter.rb +1 -1
- data/lib/aspera/transfer/faux_file.rb +24 -11
- data/lib/aspera/transfer/parameters.rb +36 -34
- data/lib/aspera/transfer/result.rb +74 -0
- data/lib/aspera/transfer/resumer.rb +10 -10
- data/lib/aspera/transfer/spec.rb +18 -1
- data/lib/aspera/transfer/spec.schema.yaml +12 -3
- data/lib/aspera/uri_reader.rb +52 -15
- data/lib/aspera/web_auth.rb +8 -7
- data/lib/aspera/web_server_simple.rb +15 -12
- data/lib/aspera/yaml.rb +5 -4
- data.tar.gz.sig +0 -0
- metadata +34 -6
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/manager.rb +0 -766
- data/lib/aspera/cli/plugins/faspex.rb +0 -539
- data/lib/aspera/json_rpc.rb +0 -52
- data/lib/aspera/transfer/uri.rb +0 -56
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/log'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
module Aspera
|
|
8
|
+
module Cli
|
|
9
|
+
# Persist the state of asynchronous transfers on the local file system.
|
|
10
|
+
#
|
|
11
|
+
# Each entry is a JSON document identified by a `job_id` (UUID generated by ascli).
|
|
12
|
+
# The underlying storage uses PersistencyFolder with the category prefix
|
|
13
|
+
# +CATEGORY+ so all async-transfer files are grouped together and can be
|
|
14
|
+
# garbage-collected independently.
|
|
15
|
+
#
|
|
16
|
+
# Schema of a stored entry:
|
|
17
|
+
# job_id [String] UUID generated by ascli (= the store key)
|
|
18
|
+
# agent_type [String] 'desktop' | 'node' | 'connect' | 'transferd' | 'direct'
|
|
19
|
+
# transfer_id [String] Opaque ID returned by the agent's start_transfer
|
|
20
|
+
# agent_params [Hash] Agent-specific connection parameters for re-querying
|
|
21
|
+
# status [String] 'running' | 'completed' | 'failed' | 'cancelled'
|
|
22
|
+
# bytes_transferred [Integer] Last known bytes transferred (0 if unknown)
|
|
23
|
+
# started_at [String] ISO-8601 timestamp
|
|
24
|
+
# ended_at [String, nil] ISO-8601 timestamp once finished
|
|
25
|
+
# error [String, nil] Error message when status == 'failed'
|
|
26
|
+
class AsyncTransferStore
|
|
27
|
+
CATEGORY = 'async_transfer_'
|
|
28
|
+
private_constant :CATEGORY
|
|
29
|
+
|
|
30
|
+
# Class-level registry of in-process agent references, keyed by job_id.
|
|
31
|
+
# Shared across all AsyncTransferStore instances within the same Ruby process,
|
|
32
|
+
# so that a reference registered in one Runner call is visible in the next
|
|
33
|
+
# (e.g. MCP server mode where each tool call creates a fresh Runner/Context).
|
|
34
|
+
# Never persisted to disk.
|
|
35
|
+
@agent_refs = {}
|
|
36
|
+
class << self
|
|
37
|
+
attr_reader :agent_refs
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @param persistency [PersistencyFolder]
|
|
41
|
+
def initialize(persistency)
|
|
42
|
+
@persistency = persistency
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Register an in-process agent reference for a job.
|
|
46
|
+
# Called by TransferAgent immediately after start_transfer (async mode).
|
|
47
|
+
# The reference is only available while the same Ruby process is alive.
|
|
48
|
+
# @param job_id [String] the ascli-generated UUID
|
|
49
|
+
# @param agent_ref [Object] live agent instance (responds to sessions_by_job)
|
|
50
|
+
def register_agent_ref(job_id, agent_ref)
|
|
51
|
+
self.class.agent_refs[job_id] = agent_ref
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Retrieve a previously registered in-process agent reference.
|
|
55
|
+
# @param job_id [String]
|
|
56
|
+
# @return [Object, nil] the live agent instance, or nil if not registered / process restarted
|
|
57
|
+
def agent_ref(job_id)
|
|
58
|
+
self.class.agent_refs[job_id]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Persist (create or update) an async transfer entry.
|
|
62
|
+
# Keys whose name starts with '_' are in-process references (e.g. '_agent_ref')
|
|
63
|
+
# and are intentionally excluded from the JSON serialization — at every nesting level.
|
|
64
|
+
# @param job_id [String] the ascli-generated UUID
|
|
65
|
+
# @param data [Hash] fields to store (will be JSON-serialised)
|
|
66
|
+
def write(job_id, data)
|
|
67
|
+
Aspera.assert_type(job_id, String) { 'job_id' }
|
|
68
|
+
Aspera.assert_type(data, Hash) { 'data' }
|
|
69
|
+
@persistency.put(store_key(job_id), JSON.generate(strip_internal_keys(data)))
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Read one entry.
|
|
74
|
+
# @param job_id [String]
|
|
75
|
+
# @return [Hash, nil] the stored data, or nil if not found
|
|
76
|
+
def read(job_id)
|
|
77
|
+
Aspera.assert_type(job_id, String) { 'job_id' }
|
|
78
|
+
raw = @persistency.get(store_key(job_id))
|
|
79
|
+
return if raw.nil?
|
|
80
|
+
JSON.parse(raw)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# List all async transfer entries.
|
|
84
|
+
# @return [Array<Hash>] each entry includes the +job_id+ field
|
|
85
|
+
def list
|
|
86
|
+
@persistency.current_items(CATEGORY).map do |key, raw|
|
|
87
|
+
data = JSON.parse(raw)
|
|
88
|
+
data['job_id'] ||= key.sub(CATEGORY, '')
|
|
89
|
+
data
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Delete one entry.
|
|
94
|
+
# @param job_id [String]
|
|
95
|
+
def delete(job_id)
|
|
96
|
+
Aspera.assert_type(job_id, String) { 'job_id' }
|
|
97
|
+
@persistency.delete(store_key(job_id))
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Map a job_id to the PersistencyFolder key (includes category prefix).
|
|
104
|
+
def store_key(job_id)
|
|
105
|
+
"#{CATEGORY}#{job_id}"
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Recursively remove keys whose name starts with '_' from a Hash.
|
|
109
|
+
# Such keys hold in-process Ruby object references (e.g. '_agent_ref') that
|
|
110
|
+
# cannot be serialized to JSON and must never reach the file system.
|
|
111
|
+
def strip_internal_keys(value)
|
|
112
|
+
case value
|
|
113
|
+
when Hash
|
|
114
|
+
value.each_with_object({}) do |(k, v), h|
|
|
115
|
+
h[k] = strip_internal_keys(v) unless k.to_s.start_with?('_')
|
|
116
|
+
end
|
|
117
|
+
when Array
|
|
118
|
+
value.map { |v| strip_internal_keys(v) }
|
|
119
|
+
else
|
|
120
|
+
value
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/cli/plugins/factory'
|
|
4
|
+
require 'aspera/cli/extended_value'
|
|
5
|
+
require 'aspera/cli/special_values'
|
|
6
|
+
require 'aspera/cli/info'
|
|
7
|
+
require 'aspera/cli/transfer_progress'
|
|
8
|
+
require 'aspera/cli/preset_manager'
|
|
9
|
+
require 'aspera/cli/http'
|
|
10
|
+
require 'aspera/ascp/installation'
|
|
11
|
+
require 'aspera/products/transferd'
|
|
12
|
+
require 'aspera/transfer/parameters'
|
|
13
|
+
require 'aspera/proxy_auto_config'
|
|
14
|
+
require 'aspera/environment'
|
|
15
|
+
require 'aspera/persistency_folder'
|
|
16
|
+
require 'aspera/rest'
|
|
17
|
+
require 'aspera/log'
|
|
18
|
+
require 'aspera/assert'
|
|
19
|
+
require 'aspera/oauth'
|
|
20
|
+
require 'aspera/ssl'
|
|
21
|
+
require 'aspera/schema/registry'
|
|
22
|
+
|
|
23
|
+
module Aspera
|
|
24
|
+
module Cli
|
|
25
|
+
# Performs one-time bootstrap of all shared services into Context.
|
|
26
|
+
# Called by Runner before instantiating Plugins::Config, so that Config
|
|
27
|
+
# acts only as a CLI plugin (option declaration + command handlers).
|
|
28
|
+
#
|
|
29
|
+
# Responsibilities:
|
|
30
|
+
# - resolve context.main_folder (option :home)
|
|
31
|
+
# - populate context.persistency, context.presets, context.http_config, context.progress_bar
|
|
32
|
+
# - register plugin lookup folders
|
|
33
|
+
# - register @preset / @vault extended-value handlers
|
|
34
|
+
# - configure global singletons: RestParameters, OAuth::Factory, SSL, Transfer::Parameters,
|
|
35
|
+
# RestErrorAnalyzer
|
|
36
|
+
# - set up the PAC proxy executor (option :fpac)
|
|
37
|
+
class Bootstrapper
|
|
38
|
+
# Folder name inside $HOME for all Aspera tool data (~/.aspera)
|
|
39
|
+
ASPERA_HOME_FOLDER_NAME = '.aspera'
|
|
40
|
+
# Default name for the YAML config file
|
|
41
|
+
DEFAULT_CONFIG_FILENAME = 'config.yaml'
|
|
42
|
+
# Sub-folder for user-installed plugins
|
|
43
|
+
ASPERA_PLUGINS_FOLDERNAME = 'plugins'
|
|
44
|
+
# Sub-folder for persistency data
|
|
45
|
+
PERSISTENCY_FOLDER = 'persist_store'
|
|
46
|
+
# Sub-folder for file lists used by transfers
|
|
47
|
+
FILE_LIST_FOLDER_NAME = 'filelists'
|
|
48
|
+
# Log file for REST call exceptions
|
|
49
|
+
REST_EXCEPTIONS_LOG_FILENAME = 'rest_exceptions.log'
|
|
50
|
+
# Extended-value prefix that resolves a named preset
|
|
51
|
+
EXTEND_PRESET = :preset
|
|
52
|
+
# Extended-value prefix that resolves a vault secret
|
|
53
|
+
EXTEND_VAULT = :vault
|
|
54
|
+
# Default preset name for the global config section
|
|
55
|
+
CONF_GLOBAL_SYM = :config
|
|
56
|
+
|
|
57
|
+
private_constant :ASPERA_HOME_FOLDER_NAME,
|
|
58
|
+
:DEFAULT_CONFIG_FILENAME,
|
|
59
|
+
:ASPERA_PLUGINS_FOLDERNAME,
|
|
60
|
+
:PERSISTENCY_FOLDER,
|
|
61
|
+
:FILE_LIST_FOLDER_NAME,
|
|
62
|
+
:REST_EXCEPTIONS_LOG_FILENAME,
|
|
63
|
+
:EXTEND_PRESET,
|
|
64
|
+
:EXTEND_VAULT,
|
|
65
|
+
:CONF_GLOBAL_SYM
|
|
66
|
+
|
|
67
|
+
# @param context [Context] the shared context to populate
|
|
68
|
+
def initialize(context)
|
|
69
|
+
Aspera.assert_type(context, Context) { 'context' }
|
|
70
|
+
@context = context
|
|
71
|
+
@pac_exec = nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Run the full bootstrap sequence.
|
|
75
|
+
# Must be called after context.options and context.formatter are set.
|
|
76
|
+
# On return, context.persistency, context.presets, context.http_config and
|
|
77
|
+
# context.progress_bar are populated and all global singletons are configured.
|
|
78
|
+
#
|
|
79
|
+
# @param gem_plugins_folder [String] folder of built-in plugins (from Plugins::Config)
|
|
80
|
+
# @param vault_value_cb [Proc] block(name): secret value (from VaultManager)
|
|
81
|
+
def run(gem_plugins_folder:, vault_value_cb:)
|
|
82
|
+
setup_main_folder
|
|
83
|
+
setup_persistency_and_plugin_folders(gem_plugins_folder)
|
|
84
|
+
setup_config_file
|
|
85
|
+
setup_extended_value_handlers(vault_value_cb)
|
|
86
|
+
setup_progress_bar
|
|
87
|
+
setup_pac_executor
|
|
88
|
+
setup_rest_and_transfer_runtime
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Public accessor used as option handler for :config_file
|
|
92
|
+
attr_accessor :config_file_option
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
# Declare + parse :home option: sets context.main_folder
|
|
97
|
+
def setup_main_folder
|
|
98
|
+
@context.options.declare(
|
|
99
|
+
:home, description: 'Home folder for tool',
|
|
100
|
+
handler: {o: @context, m: :main_folder},
|
|
101
|
+
default: default_app_main_folder(app_name: Info::CMD_NAME)
|
|
102
|
+
)
|
|
103
|
+
@context.options.parse_options!
|
|
104
|
+
Log.log.debug { "#{Info::CMD_NAME} folder: #{@context.main_folder}" }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# context.persistency + plugin lookup folders
|
|
108
|
+
def setup_persistency_and_plugin_folders(gem_plugins_folder)
|
|
109
|
+
@context.persistency = PersistencyFolder.new(File.join(@context.main_folder, PERSISTENCY_FOLDER))
|
|
110
|
+
Plugins::Factory.instance.add_lookup_folder(gem_plugins_folder)
|
|
111
|
+
Plugins::Factory.instance.add_lookup_folder(File.join(@context.main_folder, ASPERA_PLUGINS_FOLDERNAME))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Declare + parse :config_file option: sets context.presets + context.http_config
|
|
115
|
+
def setup_config_file
|
|
116
|
+
@context.options.declare(
|
|
117
|
+
:config_file, description: 'Path to YAML file with preset configuration',
|
|
118
|
+
handler: {o: self, m: :config_file_option},
|
|
119
|
+
default: File.join(@context.main_folder, DEFAULT_CONFIG_FILENAME)
|
|
120
|
+
)
|
|
121
|
+
@context.options.parse_options!
|
|
122
|
+
@context.presets = PresetManager.new(config_file: @config_file_option)
|
|
123
|
+
@context.http_config = Http.new
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Register @preset and @vault extended-value handlers + global config default preset
|
|
127
|
+
def setup_extended_value_handlers(vault_value_cb)
|
|
128
|
+
@context.options.declare(:secret, description: 'Secret for access keys')
|
|
129
|
+
@context.options.declare(:vault, schema: Schema::Registry::VAULT_OPTIONS)
|
|
130
|
+
@context.options.declare(:vault_password, description: 'Vault password')
|
|
131
|
+
# Register @preset and @vault handlers BEFORE parse_options! so that
|
|
132
|
+
# values like --secret=@preset:name are correctly evaluated at parse time.
|
|
133
|
+
ExtendedValue.instance.on(EXTEND_PRESET) { |v| @context.presets.by_name(v) }
|
|
134
|
+
ExtendedValue.instance.on(EXTEND_VAULT, &vault_value_cb)
|
|
135
|
+
@context.options.parse_options!
|
|
136
|
+
# Load global config default preset (equivalent of add_plugin_default_preset(:config))
|
|
137
|
+
default_config_name = @context.presets.plugin_default_name(CONF_GLOBAL_SYM)
|
|
138
|
+
unless default_config_name.nil?
|
|
139
|
+
Log.log.debug { "add_plugin_default_preset:#{CONF_GLOBAL_SYM}:#{default_config_name}" }
|
|
140
|
+
@context.options.add_option_preset(@context.presets.by_name(default_config_name), 'default_plugin', override: false)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Declare + parse :progress_bar: sets context.progress_bar
|
|
145
|
+
def setup_progress_bar
|
|
146
|
+
@context.options.declare(:progress_bar, description: 'Display progress bar', allowed: Type::BOOLEAN, default: Environment.terminal?)
|
|
147
|
+
@context.options.parse_options!
|
|
148
|
+
@context.progress_bar = TransferProgress.new if @context.options.get_option(:progress_bar)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Declare + parse :fpac / :proxy_credentials: sets up PAC executor
|
|
152
|
+
def setup_pac_executor
|
|
153
|
+
@context.options.declare(:fpac, description: 'Proxy auto configuration script')
|
|
154
|
+
@context.options.declare(:proxy_credentials, description: 'HTTP proxy credentials for fpac: user, password', allowed: [Array, NilClass])
|
|
155
|
+
@context.options.parse_options!
|
|
156
|
+
pac_script = @context.options.get_option(:fpac)
|
|
157
|
+
return unless pac_script
|
|
158
|
+
|
|
159
|
+
@context.pac_executor = ProxyAutoConfig.new(pac_script).register_uri_generic
|
|
160
|
+
proxy_user_pass = @context.options.get_option(:proxy_credentials)
|
|
161
|
+
if proxy_user_pass
|
|
162
|
+
Aspera.assert(proxy_user_pass.length.eql?(2), type: Cli::BadArgument) { "proxy_credentials shall have two elements (#{proxy_user_pass.length})" }
|
|
163
|
+
@context.pac_executor.proxy_user = proxy_user_pass[0]
|
|
164
|
+
@context.pac_executor.proxy_pass = proxy_user_pass[1]
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Configure global singletons: RestParameters, SSL, Transfer, RestErrorAnalyzer.
|
|
169
|
+
# OAuth persist_mgr is NOT set here: it depends on :cache_tokens which is parsed later
|
|
170
|
+
# by Config#initialize. Runner sets it after Config.new.
|
|
171
|
+
def setup_rest_and_transfer_runtime
|
|
172
|
+
RestParameters.instance.user_agent = Info::CMD_NAME
|
|
173
|
+
RestParameters.instance.progress_bar = @context.progress_bar
|
|
174
|
+
RestParameters.instance.session_cb = ->(http_session) { @context.http_config.update_session(http_session) }
|
|
175
|
+
RestParameters.instance.spinner_cb = ->(title = nil, action: :spin) { @context.formatter.long_operation(title, action: action) }
|
|
176
|
+
OAuth::Web.additional_info = "#{Info::CMD_NAME} v#{Cli::VERSION}"
|
|
177
|
+
Transfer::Parameters.file_list_folder = File.join(@context.main_folder, FILE_LIST_FOLDER_NAME)
|
|
178
|
+
RestErrorAnalyzer.instance.log_file = File.join(@context.main_folder, REST_EXCEPTIONS_LOG_FILENAME)
|
|
179
|
+
RestErrorsAspera.register_handlers
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @return [String] ~/.aspera
|
|
183
|
+
def module_family_folder
|
|
184
|
+
user_home_folder = Dir.home
|
|
185
|
+
Aspera.assert(Dir.exist?(user_home_folder), type: Cli::Error) { "Home folder does not exist: #{user_home_folder}. Check your user environment." }
|
|
186
|
+
File.join(user_home_folder, ASPERA_HOME_FOLDER_NAME)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# @return [String] ~/.aspera/<app_name>
|
|
190
|
+
def default_app_main_folder(app_name:)
|
|
191
|
+
Aspera.assert_type(app_name, String)
|
|
192
|
+
Aspera.assert(!app_name.empty?, 'app_name must not be empty')
|
|
193
|
+
File.join(module_family_folder, app_name)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/cli/command_spec'
|
|
4
|
+
|
|
5
|
+
module Aspera
|
|
6
|
+
module Cli
|
|
7
|
+
# Stores CommandSpec objects indexed by their full path (Array<Symbol>).
|
|
8
|
+
# Each plugin class gets its own instance (not shared across the inheritance chain).
|
|
9
|
+
#
|
|
10
|
+
# Public API:
|
|
11
|
+
# register(spec) - store a CommandSpec; raises on duplicate full_path
|
|
12
|
+
# register_option(spec) - store an OptionSpec by name
|
|
13
|
+
# option_specs - Hash{Symbol => OptionSpec} of all registered options
|
|
14
|
+
# [](path) - retrieve a CommandSpec by full path
|
|
15
|
+
# children_of(path) - Hash{Symbol => CommandSpec} of direct children (O(1))
|
|
16
|
+
# all_paths - Array of all registered full paths
|
|
17
|
+
# any? - true if at least one spec has been registered
|
|
18
|
+
# validate! - cross-spec consistency checks; raises on violation
|
|
19
|
+
class CommandRegistry
|
|
20
|
+
# @param path [Array<Symbol>] full path to look up
|
|
21
|
+
# @return [CommandSpec, nil]
|
|
22
|
+
def [](path)
|
|
23
|
+
@specs[Array(path)]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Register a CommandSpec. Raises if the full_path is already registered.
|
|
27
|
+
# Also updates the children index so children_of remains O(1).
|
|
28
|
+
# @param spec [CommandSpec]
|
|
29
|
+
# @raise [ArgumentError] on duplicate full path
|
|
30
|
+
# @return [CommandSpec] the registered spec
|
|
31
|
+
def register(spec)
|
|
32
|
+
path = spec.full_path
|
|
33
|
+
raise ArgumentError, "Duplicate command path: #{path.inspect}" if @specs.key?(path)
|
|
34
|
+
@specs[path] = spec
|
|
35
|
+
# Index: parent_path -> { child_id -> spec }
|
|
36
|
+
parent = path[0..-2] # [] for root-level commands
|
|
37
|
+
(@children_index[parent] ||= {})[spec.id] = spec
|
|
38
|
+
spec
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns a Hash mapping each child id to its CommandSpec for all direct
|
|
42
|
+
# children of `path`. Empty hash if no children are registered.
|
|
43
|
+
# O(1) lookup via the children index built in register().
|
|
44
|
+
# @param path [Array<Symbol>] parent path ([] for root-level commands)
|
|
45
|
+
# @return [Hash{Symbol => CommandSpec}]
|
|
46
|
+
def children_of(path)
|
|
47
|
+
@children_index[Array(path)] || {}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Array<Array<Symbol>>] all registered full paths
|
|
51
|
+
def all_paths
|
|
52
|
+
@specs.keys
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Register an OptionSpec. Raises if the option name is already registered.
|
|
56
|
+
# @param spec [OptionSpec]
|
|
57
|
+
# @raise [ArgumentError] on duplicate option name
|
|
58
|
+
# @return [OptionSpec] the registered spec
|
|
59
|
+
def register_option(spec)
|
|
60
|
+
raise ArgumentError, "Duplicate option: #{spec.name.inspect}" if @option_specs.key?(spec.name)
|
|
61
|
+
@option_specs[spec.name] = spec
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Hash{Symbol => OptionSpec}] all registered option specs
|
|
65
|
+
def option_specs
|
|
66
|
+
@option_specs.dup
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Boolean] true if at least one spec is registered
|
|
70
|
+
def any?
|
|
71
|
+
!@specs.empty?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Boolean] true if no specs have been registered
|
|
75
|
+
def none?
|
|
76
|
+
@specs.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Cross-spec consistency checks.
|
|
80
|
+
# @param plugin_class [Class, nil] when given, also verify that implicit action methods exist
|
|
81
|
+
# @raise [ArgumentError] on any violation
|
|
82
|
+
# @return [self]
|
|
83
|
+
def validate!(plugin_class: nil)
|
|
84
|
+
# Rule: every non-root parent path that appears in the children index must have
|
|
85
|
+
# a registered CommandSpec. A missing parent means commands_under(:x) was used
|
|
86
|
+
# without a matching command :x declaration.
|
|
87
|
+
@children_index.each_key do |parent_path|
|
|
88
|
+
next if parent_path.empty? # root is never a CommandSpec
|
|
89
|
+
unless @specs.key?(parent_path)
|
|
90
|
+
raise ArgumentError,
|
|
91
|
+
"commands_under(#{parent_path.map(&:inspect).join(', ')}) used but #{parent_path.last.inspect} has no command declaration"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@specs.each_value do |spec|
|
|
96
|
+
path = spec.full_path
|
|
97
|
+
|
|
98
|
+
# Rule: delegates_to must point to a known path when present (non-empty array or symbol)
|
|
99
|
+
if spec.delegates_to
|
|
100
|
+
dt_path =
|
|
101
|
+
case spec.delegates_to
|
|
102
|
+
when Symbol then [spec.delegates_to]
|
|
103
|
+
when Array then spec.delegates_to
|
|
104
|
+
end
|
|
105
|
+
# An empty array [] means re-enter the root - always valid
|
|
106
|
+
unless dt_path.empty? || @specs.key?(dt_path)
|
|
107
|
+
raise ArgumentError,
|
|
108
|
+
"#{path.inspect}: delegates_to #{dt_path.inspect} points to unknown path"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Rule: delegate_instance requires delegates_to
|
|
113
|
+
if spec.delegate_instance && spec.delegates_to.nil?
|
|
114
|
+
raise ArgumentError,
|
|
115
|
+
"#{path.inspect}: delegate_instance requires delegates_to to be set"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Rule: leaf commands with no explicit action must have a matching instance method
|
|
119
|
+
next if spec.action # explicit action: skip
|
|
120
|
+
next if @children_index[path]&.any? # intermediate node: skip
|
|
121
|
+
next if spec.delegates_to # delegated: skip
|
|
122
|
+
next unless plugin_class
|
|
123
|
+
implicit_method = CommandSpec.action_method(path)
|
|
124
|
+
unless plugin_class.method_defined?(implicit_method) || plugin_class.private_method_defined?(implicit_method)
|
|
125
|
+
raise ArgumentError,
|
|
126
|
+
"#{path.inspect}: no action: and no method #{implicit_method} on #{plugin_class}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
self
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def initialize
|
|
135
|
+
# Keyed by Array<Symbol> full path
|
|
136
|
+
@specs = {}
|
|
137
|
+
# Keyed by Symbol option name
|
|
138
|
+
@option_specs = {}
|
|
139
|
+
# Children index: parent Array<Symbol> -> Hash{child_id Symbol => CommandSpec}
|
|
140
|
+
# Built incrementally in register(); enables O(1) children_of lookups.
|
|
141
|
+
@children_index = {}
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/schema/registry'
|
|
4
|
+
|
|
5
|
+
module Aspera
|
|
6
|
+
module Cli
|
|
7
|
+
# Declares a positional argument consumed by a command.
|
|
8
|
+
# Declaration order in a command's `arguments:` array defines parsing order.
|
|
9
|
+
# Mandatory arguments must come before optional ones.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute name [Symbol] Name used in help and error messages
|
|
12
|
+
# @!attribute description [String] User-facing description
|
|
13
|
+
# @!attribute type [Class, Array<Class>, :identifier] Validated type; :identifier triggers instance_identifier
|
|
14
|
+
# @!attribute mandatory [Boolean] Default true; optional args must come after all mandatory ones
|
|
15
|
+
# @!attribute multiple [Boolean, String] true: consume all remaining; String: consume until named marker
|
|
16
|
+
# @!attribute default [Object, nil] Default value when mandatory: false and no argument provided
|
|
17
|
+
# @!attribute schema [String, nil] JSON schema name for validation and --help introspection
|
|
18
|
+
# @!attribute bulk [Boolean] When true, wraps read+loop for bulk mode (Array if --bulk yes)
|
|
19
|
+
# @!attribute lookup [Symbol, Proc, nil] Percent-selector resolver (only used when type: :identifier).
|
|
20
|
+
# Symbol → resolved via send(lookup, field, value, **ctx).
|
|
21
|
+
# Proc/lambda → called via instance_exec(field, value, **ctx, &lookup).
|
|
22
|
+
# Style: use Symbol for named methods; ->(){} for 1-liners; lambda do…end for 2–3 statements.
|
|
23
|
+
# @!attribute allowed [Array<Symbol>, nil] Allowed Symbol values; when set, type is forced to Symbol and accept_list is applied
|
|
24
|
+
# @!attribute interactive [Boolean] When true, sets ask_missing_mandatory before resolving so interactive prompting is triggered when no CLI args are provided
|
|
25
|
+
ArgumentSpec = Struct.new(
|
|
26
|
+
:name,
|
|
27
|
+
:description,
|
|
28
|
+
:type,
|
|
29
|
+
:mandatory,
|
|
30
|
+
:multiple,
|
|
31
|
+
:default,
|
|
32
|
+
:schema,
|
|
33
|
+
:bulk,
|
|
34
|
+
:lookup,
|
|
35
|
+
:allowed,
|
|
36
|
+
:interactive,
|
|
37
|
+
keyword_init: true
|
|
38
|
+
) do
|
|
39
|
+
def initialize(**kwargs)
|
|
40
|
+
kwargs[:mandatory] = true if kwargs[:mandatory].nil?
|
|
41
|
+
kwargs[:multiple] = false if kwargs[:multiple].nil?
|
|
42
|
+
kwargs[:bulk] = false if kwargs[:bulk].nil?
|
|
43
|
+
kwargs[:interactive] = false if kwargs[:interactive].nil?
|
|
44
|
+
super
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Declares an option referenced by name from command declarations.
|
|
49
|
+
# Mirrors the existing `options.declare` call but associates the option with
|
|
50
|
+
# the command(s) that use it.
|
|
51
|
+
#
|
|
52
|
+
# @!attribute name [Symbol] Option name (same symbol used in options.declare)
|
|
53
|
+
# @!attribute description [String, nil] User-facing description; nil derives it from schema:
|
|
54
|
+
# @!attribute allowed [Array, nil] Allowed values (forwarded to options.declare)
|
|
55
|
+
# @!attribute default [Object, nil] Default value
|
|
56
|
+
# @!attribute short [String, nil] Single-character short form (e.g. 'x')
|
|
57
|
+
# @!attribute handler [Symbol, Hash, nil]
|
|
58
|
+
# - Symbol: resolved to {o: <plugin instance>, m: <symbol>} at runtime (Category B)
|
|
59
|
+
# - Hash: {o: <object>, m: <method>} used as-is (Category A: singletons / class constants)
|
|
60
|
+
# - nil: option stores its value locally (no delegation)
|
|
61
|
+
# @!attribute deprecation [String, nil] Forwarded to options.declare as deprecation:
|
|
62
|
+
# @!attribute schema [String, nil] JSON schema name; also derives description when nil
|
|
63
|
+
OptionSpec = Struct.new(
|
|
64
|
+
:name,
|
|
65
|
+
:description,
|
|
66
|
+
:allowed,
|
|
67
|
+
:default,
|
|
68
|
+
:short,
|
|
69
|
+
:handler, # kept as-is: this is the option accessor delegation, not a command action
|
|
70
|
+
:deprecation,
|
|
71
|
+
:schema,
|
|
72
|
+
keyword_init: true
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Declares a single command node in the flat registry.
|
|
76
|
+
#
|
|
77
|
+
# @!attribute id [Symbol] Unique identifier within its parent's namespace
|
|
78
|
+
# @!attribute parent [Symbol, Array<Symbol>, nil] Full path to parent; nil for root commands
|
|
79
|
+
# @!attribute description [String] User-facing help text
|
|
80
|
+
# @!attribute options [Array<Symbol>] Option names consumed by this command
|
|
81
|
+
# @!attribute arguments [Array<ArgumentSpec>] Positional arguments, in order.
|
|
82
|
+
# The first ArgumentSpec with type: :identifier is treated as the instance
|
|
83
|
+
# identifier for intermediate nodes (consumed in Phase A) and leaf nodes.
|
|
84
|
+
# @!attribute action [Symbol, Proc, nil] Instance method (Symbol) or inline block (Proc) called when this is a leaf command
|
|
85
|
+
# @!attribute setup [Symbol, nil] Instance method called before dispatching to children; returns Hash merged into ctx
|
|
86
|
+
# @!attribute delegates_to [Symbol, Array<Symbol>, nil] Re-enter the command tree at this path
|
|
87
|
+
# @!attribute delegate_instance [Symbol, nil] Instance method returning a different plugin object
|
|
88
|
+
# @!attribute aliases [Array<Symbol>, nil] Alternative names accepted for this command (each resolves to this command's id)
|
|
89
|
+
# @!attribute transfer_paths [:send, :receive, nil] File-list resolution delegated to TransferAgent; mutually exclusive with arguments
|
|
90
|
+
# @!attribute condition [Symbol, nil] Instance method returning Boolean; if false command is hidden from dispatch
|
|
91
|
+
# @!attribute query_schema [String, nil] Schema path for --query help; when set, the runner hints `--query=help`
|
|
92
|
+
CommandSpec = Struct.new(
|
|
93
|
+
:id,
|
|
94
|
+
:parent,
|
|
95
|
+
:description,
|
|
96
|
+
:options,
|
|
97
|
+
:arguments,
|
|
98
|
+
:action,
|
|
99
|
+
:setup,
|
|
100
|
+
:delegates_to,
|
|
101
|
+
:delegate_instance,
|
|
102
|
+
:aliases,
|
|
103
|
+
:transfer_paths,
|
|
104
|
+
:condition,
|
|
105
|
+
:query_schema,
|
|
106
|
+
keyword_init: true
|
|
107
|
+
) do
|
|
108
|
+
def initialize(**kwargs)
|
|
109
|
+
# Coerce each element of arguments: from Hash to ArgumentSpec if needed
|
|
110
|
+
if kwargs[:arguments]
|
|
111
|
+
kwargs[:arguments] = kwargs[:arguments].map do |a|
|
|
112
|
+
a.is_a?(Hash) ? ArgumentSpec.new(**a) : a
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
super
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class << self
|
|
119
|
+
# Derive the implicit action method name from a path array.
|
|
120
|
+
# e.g. [:admin, :user, :list] -> :action_admin_user_list
|
|
121
|
+
# @param path [Array<Symbol>]
|
|
122
|
+
# @return [Symbol]
|
|
123
|
+
def action_method(path)
|
|
124
|
+
:"action_#{path.join('_')}"
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Compute the full path as Array<Symbol> from parent + id.
|
|
129
|
+
# @return [Array<Symbol>]
|
|
130
|
+
def full_path
|
|
131
|
+
Array(parent) + [id]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Derive the implicit action method name from the full path.
|
|
135
|
+
# @return [Symbol]
|
|
136
|
+
def action_method_name
|
|
137
|
+
self.class.action_method(full_path)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
data/lib/aspera/cli/context.rb
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'aspera/assert'
|
|
4
|
+
require 'aspera/cli/mailer'
|
|
5
|
+
require 'aspera/cli/secret_finder'
|
|
4
6
|
|
|
5
7
|
module Aspera
|
|
6
8
|
module Cli
|
|
7
9
|
# Global objects shared with plugins
|
|
8
10
|
class Context
|
|
9
11
|
# @type [Array<Symbol>]
|
|
10
|
-
|
|
12
|
+
# Members that must be non-nil after bootstrap (validated in #validate)
|
|
13
|
+
MEMBERS = %i[options transfer config formatter persistency man_header presets http_config main_folder mailer secret_finder].freeze
|
|
11
14
|
# @!attribute [rw] options
|
|
12
|
-
# @return [
|
|
15
|
+
# @return [Parser] the command line options manager
|
|
13
16
|
# @!attribute [rw] transfer
|
|
14
17
|
# @return [TransferAgent] the transfer agent, used by transfer plugins
|
|
15
18
|
# @!attribute [rw] config
|
|
@@ -24,21 +27,31 @@ module Aspera
|
|
|
24
27
|
# @return [PresetManager] manages the YAML config file and preset resolution
|
|
25
28
|
# @!attribute [rw] http_config
|
|
26
29
|
# @return [Http] manages HTTP/S and TLS runtime options
|
|
30
|
+
# @!attribute [rw] mailer
|
|
31
|
+
# @return [Mailer] sends emails (SMTP) via ERB templates
|
|
32
|
+
# @!attribute [rw] secret_finder
|
|
33
|
+
# @return [SecretFinder] resolves secrets for url+username pairs
|
|
27
34
|
attr_accessor(*MEMBERS)
|
|
35
|
+
# Optional: nil when progress bar is disabled
|
|
36
|
+
attr_accessor :progress_bar
|
|
37
|
+
# Optional: nil when no PAC script is configured
|
|
38
|
+
attr_accessor :pac_executor
|
|
28
39
|
|
|
29
40
|
# Initialize all members to nil, so that they are defined and can be validated later
|
|
30
41
|
# @return [nil]
|
|
31
42
|
def initialize
|
|
32
|
-
MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
|
|
43
|
+
MEMBERS.each { |i| instance_variable_set(:"@#{i}", nil) }
|
|
44
|
+
@progress_bar = nil
|
|
45
|
+
@pac_executor = nil
|
|
33
46
|
end
|
|
34
47
|
|
|
35
|
-
# Validate that all members are
|
|
48
|
+
# Validate that all mandatory members are non-nil (detect bootstrap bugs)
|
|
36
49
|
# @raise [Aspera::AssertionError] if any member is not set
|
|
37
50
|
# @return [nil]
|
|
38
51
|
def validate
|
|
39
52
|
MEMBERS.each do |i|
|
|
40
|
-
Aspera.assert(instance_variable_defined?(:"@#{i}")){"context member @#{i} is not defined"}
|
|
41
|
-
Aspera.assert(!instance_variable_get(:"@#{i}").nil?){"context member @#{i} is nil"}
|
|
53
|
+
Aspera.assert(instance_variable_defined?(:"@#{i}")) { "context member @#{i} is not defined" }
|
|
54
|
+
Aspera.assert(!instance_variable_get(:"@#{i}").nil?) { "context member @#{i} is nil" }
|
|
42
55
|
end
|
|
43
56
|
end
|
|
44
57
|
|