aspera-cli 4.12.0 → 4.14.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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +45 -5
  4. data/CONTRIBUTING.md +113 -22
  5. data/README.md +1289 -754
  6. data/bin/ascli +3 -3
  7. data/examples/dascli +1 -1
  8. data/examples/rubyc +24 -0
  9. data/lib/aspera/aoc.rb +63 -74
  10. data/lib/aspera/ascmd.rb +5 -3
  11. data/lib/aspera/cli/basic_auth_plugin.rb +6 -6
  12. data/lib/aspera/cli/extended_value.rb +24 -37
  13. data/lib/aspera/cli/formatter.rb +23 -25
  14. data/lib/aspera/cli/info.rb +2 -4
  15. data/lib/aspera/cli/main.rb +27 -27
  16. data/lib/aspera/cli/manager.rb +143 -120
  17. data/lib/aspera/cli/plugin.rb +88 -43
  18. data/lib/aspera/cli/plugins/alee.rb +2 -2
  19. data/lib/aspera/cli/plugins/aoc.rb +235 -104
  20. data/lib/aspera/cli/plugins/ats.rb +16 -18
  21. data/lib/aspera/cli/plugins/bss.rb +3 -3
  22. data/lib/aspera/cli/plugins/config.rb +190 -373
  23. data/lib/aspera/cli/plugins/console.rb +4 -6
  24. data/lib/aspera/cli/plugins/cos.rb +12 -13
  25. data/lib/aspera/cli/plugins/faspex.rb +21 -21
  26. data/lib/aspera/cli/plugins/faspex5.rb +399 -150
  27. data/lib/aspera/cli/plugins/node.rb +260 -174
  28. data/lib/aspera/cli/plugins/orchestrator.rb +15 -18
  29. data/lib/aspera/cli/plugins/preview.rb +40 -62
  30. data/lib/aspera/cli/plugins/server.rb +33 -16
  31. data/lib/aspera/cli/plugins/shares.rb +24 -33
  32. data/lib/aspera/cli/plugins/sync.rb +6 -6
  33. data/lib/aspera/cli/transfer_agent.rb +47 -30
  34. data/lib/aspera/cli/version.rb +2 -1
  35. data/lib/aspera/colors.rb +9 -7
  36. data/lib/aspera/command_line_builder.rb +2 -1
  37. data/lib/aspera/cos_node.rb +1 -1
  38. data/lib/aspera/data/6 +0 -0
  39. data/lib/aspera/environment.rb +7 -3
  40. data/lib/aspera/fasp/agent_connect.rb +6 -1
  41. data/lib/aspera/fasp/agent_direct.rb +17 -17
  42. data/lib/aspera/fasp/agent_httpgw.rb +138 -60
  43. data/lib/aspera/fasp/agent_node.rb +14 -4
  44. data/lib/aspera/fasp/agent_trsdk.rb +2 -0
  45. data/lib/aspera/fasp/error_info.rb +2 -0
  46. data/lib/aspera/fasp/installation.rb +19 -19
  47. data/lib/aspera/fasp/parameters.rb +29 -20
  48. data/lib/aspera/fasp/parameters.yaml +5 -2
  49. data/lib/aspera/fasp/resume_policy.rb +3 -3
  50. data/lib/aspera/fasp/transfer_spec.rb +8 -5
  51. data/lib/aspera/fasp/uri.rb +23 -21
  52. data/lib/aspera/faspex_gw.rb +1 -0
  53. data/lib/aspera/faspex_postproc.rb +3 -3
  54. data/lib/aspera/hash_ext.rb +12 -2
  55. data/lib/aspera/keychain/macos_security.rb +13 -13
  56. data/lib/aspera/log.rb +1 -0
  57. data/lib/aspera/node.rb +73 -84
  58. data/lib/aspera/oauth.rb +4 -3
  59. data/lib/aspera/persistency_action_once.rb +1 -1
  60. data/lib/aspera/preview/file_types.rb +8 -6
  61. data/lib/aspera/preview/generator.rb +23 -11
  62. data/lib/aspera/preview/options.rb +3 -2
  63. data/lib/aspera/preview/terminal.rb +80 -0
  64. data/lib/aspera/preview/utils.rb +11 -11
  65. data/lib/aspera/proxy_auto_config.js +2 -2
  66. data/lib/aspera/rest.rb +42 -4
  67. data/lib/aspera/rest_call_error.rb +3 -1
  68. data/lib/aspera/secret_hider.rb +10 -5
  69. data/lib/aspera/ssh.rb +1 -1
  70. data/lib/aspera/sync.rb +41 -33
  71. data/lib/aspera/web_server_simple.rb +22 -18
  72. data.tar.gz.sig +0 -0
  73. metadata +40 -48
  74. metadata.gz.sig +0 -0
  75. data/docs/test_env.conf +0 -179
  76. data/examples/aoc.rb +0 -30
  77. data/examples/faspex4.rb +0 -94
  78. data/examples/node.rb +0 -96
  79. data/examples/server.rb +0 -93
  80. data/lib/aspera/data/7 +0 -0
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'aspera/cli/extended_value'
4
+
3
5
  module Aspera
4
6
  module Cli
5
7
  # base class for plugins modules
@@ -8,13 +10,16 @@ module Aspera
8
10
  GLOBAL_OPS = %i[create list].freeze
9
11
  # operations with id
10
12
  INSTANCE_OPS = %i[modify delete show].freeze
13
+ # all standard operations
11
14
  ALL_OPS = [GLOBAL_OPS, INSTANCE_OPS].flatten.freeze
12
- # max number of items for list command
15
+ # special query parameter: max number of items for list command
13
16
  MAX_ITEMS = 'max'
14
- # max number of pages for list command
17
+ # special query parameter: max number of pages for list command
15
18
  MAX_PAGES = 'pmax'
16
19
  # used when all resources are selected
17
20
  VAL_ALL = 'ALL'
21
+ # special identifier format: look for this name to find where supported
22
+ REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/.freeze
18
23
 
19
24
  # global for inherited classes
20
25
  @@options_created = false # rubocop:disable Style/ClassVars
@@ -24,46 +29,54 @@ module Aspera
24
29
  # env.each_key {|k| raise "wrong agent key #{k}" unless AGENTS.include?(k)}
25
30
  @agents = env
26
31
  # check presence in descendant of mandatory method and constant
27
- raise StandardError, "missing method 'execute_action' in #{self.class}" unless respond_to?(:execute_action)
32
+ raise StandardError, "Missing method 'execute_action' in #{self.class}" unless respond_to?(:execute_action)
28
33
  raise StandardError, 'ACTIONS shall be redefined by subclass' unless self.class.constants.include?(:ACTIONS)
29
34
  options.parser.separator('')
30
35
  options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
31
36
  options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
32
37
  options.parser.separator('OPTIONS:')
33
38
  return if @@options_created
34
- options.add_opt_simple(:query, 'additional filter for API calls (extended value) (some commands)')
35
- options.add_opt_simple(:value, 'extended value for create, update, list filter')
36
- options.add_opt_simple(:property, 'name of property to set')
37
- options.add_opt_simple(:id, "resource identifier (#{INSTANCE_OPS.join(',')})")
38
- options.add_opt_boolean(:bulk, 'Bulk operation (only some)')
39
- options.add_opt_boolean(:bfail, 'Bulk operation error handling')
40
- options.set_option(:bulk, :no)
41
- options.set_option(:bfail, :yes)
39
+ options.declare(:query, 'Additional filter for for some commands (list/delete)', types: Hash)
40
+ options.declare(
41
+ :value, 'Value for create, update, list filter', types: Hash,
42
+ deprecation: 'Use positional value for create/modify or option: query for list/delete')
43
+ options.declare(:property, 'Name of property to set (modify operation)')
44
+ options.declare(:id, 'Resource identifier', deprecation: "Use identifier after verb (#{INSTANCE_OPS.join(',')})")
45
+ options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
46
+ options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
42
47
  options.parse_options!
43
48
  @@options_created = true # rubocop:disable Style/ClassVars
44
49
  end
45
50
 
46
- # must be called AFTER the instance action
47
- def instance_identifier
51
+ # must be called AFTER the instance action, ... folder browse <call instance_identifier>
52
+ # @param description [String] description of the identifier
53
+ # @param block [Proc] block to search for identifier based on attribute value
54
+ # @return [String] identifier
55
+ def instance_identifier(description: 'identifier', &block)
48
56
  res_id = options.get_option(:id)
49
- res_id = options.get_next_argument('identifier') if res_id.nil?
57
+ res_id = options.get_next_argument(description) if res_id.nil?
58
+ # cab be an Array
59
+ if res_id.is_a?(String) && (m = res_id.match(REGEX_LOOKUP_ID_BY_FIELD))
60
+ if block
61
+ res_id = yield(m[1], ExtendedValue.instance.evaluate(m[2]))
62
+ else
63
+ raise CliBadArgument, "Percent syntax for #{description} not supported in this context"
64
+ end
65
+ end
50
66
  return res_id
51
67
  end
52
68
 
53
- # TODO
54
- # def get_next_id_command(instance_ops: INSTANCE_OPS,global_ops: GLOBAL_OPS)
55
- # return get_next_argument('command',expected: command_list)
56
- # end
57
-
58
69
  # For create and delete operations: execute one actin or multiple if bulk is yes
59
- # @param params either single id or hash, or array for bulk
70
+ # @param single_or_array [Object] single hash, or array of hash for bulk
60
71
  # @param success_msg deleted or created
72
+ # @param id_result [String] key in result hash to use as identifier
73
+ # @param fields [Array] fields to display
61
74
  def do_bulk_operation(single_or_array, success_msg, id_result: 'id', fields: :default)
62
75
  raise 'programming error: missing block' unless block_given?
63
76
  params = options.get_option(:bulk) ? single_or_array : [single_or_array]
64
77
  raise 'expecting Array for bulk operation' unless params.is_a?(Array)
65
78
  Log.log.warn('Empty list given for bulk operation') if params.empty?
66
- Log.dump(:bulk_create, params)
79
+ Log.dump(:bulk_operation, params)
67
80
  result_list = []
68
81
  params.each do |param|
69
82
  # init for delete
@@ -96,11 +109,14 @@ module Aspera
96
109
  # @param id_default [String] default identifier to use for existing entity commands (show, modify)
97
110
  # @param item_list_key [String] result is in a sub key of the json
98
111
  # @param id_as_arg [String] if set, the id is provided as url argument ?<id_as_arg>=<id>
112
+ # @param is_singleton [Boolean] if true, res_class_path is the full path to the resource
99
113
  # @return result suitable for CLI result
100
- def entity_command(command, rest_api, res_class_path, display_fields: nil, id_default: nil, item_list_key: false, id_as_arg: false)
101
- if INSTANCE_OPS.include?(command)
114
+ def entity_command(command, rest_api, res_class_path, display_fields: nil, id_default: nil, item_list_key: false, id_as_arg: false, is_singleton: false, &block)
115
+ if is_singleton
116
+ one_res_path = res_class_path
117
+ elsif INSTANCE_OPS.include?(command)
102
118
  begin
103
- one_res_id = instance_identifier
119
+ one_res_id = instance_identifier(&block)
104
120
  rescue StandardError => e
105
121
  raise e if id_default.nil?
106
122
  one_res_id = id_default
@@ -108,29 +124,24 @@ module Aspera
108
124
  one_res_path = "#{res_class_path}/#{one_res_id}"
109
125
  one_res_path = "#{res_class_path}?#{id_as_arg}=#{one_res_id}" if id_as_arg
110
126
  end
111
- # parameters mandatory for create/modify
112
- if %i[create modify].include?(command)
113
- parameters = options.get_option(:value, is_type: :mandatory)
114
- end
115
- # parameters optional for list
116
- if %i[list delete].include?(command)
117
- parameters = options.get_option(:value)
118
- end
127
+
119
128
  case command
120
129
  when :create
121
- return do_bulk_operation(parameters, 'created', fields: display_fields) do |params|
130
+ raise 'cannot create singleton' if is_singleton
131
+ return do_bulk_operation(value_create_modify(command: command, type: :bulk_hash), 'created', fields: display_fields) do |params|
122
132
  raise 'expecting Hash' unless params.is_a?(Hash)
123
133
  rest_api.create(res_class_path, params)[:data]
124
134
  end
125
135
  when :delete
136
+ raise 'cannot delete singleton' if is_singleton
126
137
  return do_bulk_operation(one_res_id, 'deleted') do |one_id|
127
- rest_api.delete("#{res_class_path}/#{one_id}", parameters)
138
+ rest_api.delete("#{res_class_path}/#{one_id}", old_query_read_delete)
128
139
  {'id' => one_id}
129
140
  end
130
141
  when :show
131
142
  return {type: :single_object, data: rest_api.read(one_res_path)[:data], fields: display_fields}
132
143
  when :list
133
- resp = rest_api.read(res_class_path, parameters)
144
+ resp = rest_api.read(res_class_path, old_query_read_delete)
134
145
  data = resp[:data]
135
146
  # TODO: not generic : which application is this for ?
136
147
  if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
@@ -140,11 +151,7 @@ module Aspera
140
151
  if item_list_key
141
152
  item_list = data[item_list_key]
142
153
  total_count = data['total_count']
143
- if !total_count.nil?
144
- count_msg = "Items: #{item_list.length}/#{total_count}"
145
- count_msg = count_msg.bg_red unless item_list.length.eql?(total_count.to_i)
146
- formatter.display_status(count_msg)
147
- end
154
+ formatter.display_item_count(item_list.length, total_count) unless total_count.nil?
148
155
  data = item_list
149
156
  end
150
157
  case data
@@ -157,6 +164,7 @@ module Aspera
157
164
  raise "An error occurred: unexpected result type for list: #{data.class}"
158
165
  end
159
166
  when :modify
167
+ parameters = value_create_modify(command: command, type: Hash)
160
168
  property = options.get_option(:property)
161
169
  parameters = {property => parameters} unless property.nil?
162
170
  rest_api.update(one_res_path, parameters)
@@ -173,8 +181,8 @@ module Aspera
173
181
  return entity_command(command, rest_api, res_class_path, **opts)
174
182
  end
175
183
 
176
- # query for list operation
177
- def option_url_query(default)
184
+ # query parameters in URL suitable for REST list/GET and delete/DELETE
185
+ def query_read_delete(default: nil)
178
186
  query = options.get_option(:query)
179
187
  # dup default, as it could be frozen
180
188
  query = default.dup if query.nil?
@@ -183,11 +191,48 @@ module Aspera
183
191
  # check it is suitable
184
192
  URI.encode_www_form(query) unless query.nil?
185
193
  rescue StandardError => e
186
- raise CliBadArgument, "query must be an extended value which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
194
+ raise CliBadArgument, "Query must be an extended value which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
187
195
  end
188
196
  return query
189
197
  end
190
198
 
199
+ # TODO: when deprecation of `value` is completed: remove this method, replace with query_read_delete
200
+ def old_query_read_delete
201
+ query = options.get_option(:value) # legacy, deprecated, remove, one day...
202
+ query = query_read_delete if query.nil?
203
+ return query
204
+ end
205
+
206
+ # TODO: when deprecation of `value` is completed: remove this method, replace with options.get_option(:query)
207
+ def value_or_query(mandatory: false, allowed_types: nil)
208
+ value = options.get_option(:value, mandatory: false, allowed_types: allowed_types)
209
+ value = options.get_option(:query, mandatory: mandatory, allowed_types: allowed_types) if value.nil?
210
+ return value
211
+ end
212
+
213
+ # Retrieves an extended value from command line, used for creation or modification of entities
214
+ # @param default [Object] default value if not provided
215
+ # @param command [String] command name for error message
216
+ # @param type [Class] expected type of value
217
+ # TODO: when deprecation of `value` is completed: remove line with :value
218
+ def value_create_modify(default: nil, command: 'command', type: nil)
219
+ value = options.get_option(:value)
220
+ value = options.get_next_argument("parameters for #{command}", mandatory: default.nil?) if value.nil?
221
+ value = default if value.nil?
222
+ if type.nil?
223
+ # nothing to do
224
+ elsif type.is_a?(Class)
225
+ raise CliBadArgument, "Value must be a #{type}" unless value.is_a?(type)
226
+ elsif type.is_a?(Array)
227
+ raise CliBadArgument, "Value must be one of #{type.join(', ')}" unless type.any?{|t| value.is_a?(t)}
228
+ elsif type.eql?(:bulk_hash)
229
+ raise CliBadArgument, 'Value must be a Hash or Array of Hash' unless value.is_a?(Hash) || (value.is_a?(Array) && value.all?(Hash))
230
+ else
231
+ raise "Internal error: #{type}"
232
+ end
233
+ return value
234
+ end
235
+
191
236
  # shortcuts helpers for plugin environment
192
237
  %i[options transfer config formatter persistency].each do |name|
193
238
  define_method(name){@agents[name]}
@@ -13,8 +13,8 @@ module Aspera
13
13
  command = options.get_next_command(ACTIONS)
14
14
  case command
15
15
  when :entitlement
16
- entitlement_id = options.get_option(:username, is_type: :mandatory)
17
- customer_id = options.get_option(:password, is_type: :mandatory)
16
+ entitlement_id = options.get_option(:username, mandatory: true)
17
+ customer_id = options.get_option(:password, mandatory: true)
18
18
  api_metering = AoC.metering_api(entitlement_id, customer_id)
19
19
  return {type: :single_object, data: api_metering.read('entitlement')[:data]}
20
20
  end