aspera-cli 4.24.0 → 4.24.2
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 +19 -1
 - data/README.md +1264 -941
 - data/bin/ascli +20 -1
 - data/bin/asession +23 -27
 - data/lib/aspera/agent/base.rb +10 -21
 - data/lib/aspera/agent/connect.rb +2 -3
 - data/lib/aspera/agent/desktop.rb +2 -2
 - data/lib/aspera/agent/direct.rb +49 -32
 - data/lib/aspera/agent/factory.rb +31 -0
 - data/lib/aspera/api/aoc.rb +79 -49
 - data/lib/aspera/api/faspex.rb +212 -0
 - data/lib/aspera/api/node.rb +99 -84
 - data/lib/aspera/ascp/installation.rb +22 -21
 - data/lib/aspera/ascp/management.rb +119 -23
 - data/lib/aspera/assert.rb +14 -8
 - data/lib/aspera/cli/extended_value.rb +15 -15
 - data/lib/aspera/cli/formatter.rb +7 -5
 - data/lib/aspera/cli/hints.rb +8 -0
 - data/lib/aspera/cli/info.rb +4 -4
 - data/lib/aspera/cli/main.rb +56 -71
 - data/lib/aspera/cli/manager.rb +7 -4
 - data/lib/aspera/cli/plugins/alee.rb +2 -1
 - data/lib/aspera/cli/plugins/aoc.rb +110 -186
 - data/lib/aspera/cli/plugins/ats.rb +4 -4
 - data/lib/aspera/cli/plugins/base.rb +335 -0
 - data/lib/aspera/cli/plugins/basic_auth.rb +45 -0
 - data/lib/aspera/cli/plugins/config.rb +263 -221
 - data/lib/aspera/cli/plugins/console.rb +15 -15
 - data/lib/aspera/cli/plugins/cos.rb +2 -2
 - data/lib/aspera/cli/plugins/factory.rb +78 -0
 - data/lib/aspera/cli/plugins/faspex.rb +17 -20
 - data/lib/aspera/cli/plugins/faspex5.rb +79 -193
 - data/lib/aspera/cli/plugins/faspio.rb +14 -13
 - data/lib/aspera/cli/plugins/httpgw.rb +13 -12
 - data/lib/aspera/cli/plugins/node.rb +34 -32
 - data/lib/aspera/cli/plugins/oauth.rb +48 -0
 - data/lib/aspera/cli/plugins/orchestrator.rb +15 -13
 - data/lib/aspera/cli/plugins/preview.rb +4 -4
 - data/lib/aspera/cli/plugins/server.rb +15 -13
 - data/lib/aspera/cli/plugins/shares.rb +18 -15
 - data/lib/aspera/cli/sync_actions.rb +1 -1
 - data/lib/aspera/cli/transfer_agent.rb +24 -20
 - data/lib/aspera/cli/transfer_progress.rb +6 -6
 - data/lib/aspera/cli/version.rb +3 -3
 - data/lib/aspera/cli/wizard.rb +74 -65
 - data/lib/aspera/colors.rb +6 -0
 - data/lib/aspera/command_line_builder.rb +45 -50
 - data/lib/aspera/command_line_converter.rb +2 -1
 - data/lib/aspera/coverage.rb +1 -1
 - data/lib/aspera/data_repository.rb +1 -1
 - data/lib/aspera/environment.rb +13 -9
 - data/lib/aspera/faspex_gw.rb +6 -4
 - data/lib/aspera/faspex_postproc.rb +1 -1
 - data/lib/aspera/keychain/macos_security.rb +1 -1
 - data/lib/aspera/log.rb +88 -37
 - data/lib/aspera/nagios.rb +1 -1
 - data/lib/aspera/oauth/base.rb +17 -10
 - data/lib/aspera/oauth/factory.rb +8 -8
 - data/lib/aspera/oauth/web.rb +2 -2
 - data/lib/aspera/products/connect.rb +4 -3
 - data/lib/aspera/products/desktop.rb +1 -4
 - data/lib/aspera/products/other.rb +9 -1
 - data/lib/aspera/products/transferd.rb +0 -1
 - data/lib/aspera/rest.rb +126 -83
 - data/lib/aspera/ssh.rb +3 -3
 - data/lib/aspera/sync/args.schema.yaml +46 -3
 - data/lib/aspera/sync/conf.schema.yaml +130 -94
 - data/lib/aspera/sync/operations.rb +71 -74
 - data/lib/aspera/temp_file_manager.rb +17 -5
 - data/lib/aspera/transfer/error.rb +16 -7
 - data/lib/aspera/transfer/parameters.rb +34 -20
 - data/lib/aspera/transfer/resumer.rb +74 -0
 - data/lib/aspera/transfer/spec.rb +4 -3
 - data/lib/aspera/transfer/spec.schema.yaml +132 -51
 - data/lib/aspera/transfer/spec_doc.rb +41 -35
 - data/lib/aspera/uri_reader.rb +1 -1
 - data/lib/aspera/web_auth.rb +6 -6
 - data.tar.gz.sig +0 -0
 - metadata +9 -7
 - metadata.gz.sig +2 -2
 - data/lib/aspera/cli/basic_auth_plugin.rb +0 -43
 - data/lib/aspera/cli/plugin.rb +0 -333
 - data/lib/aspera/cli/plugin_factory.rb +0 -81
 - data/lib/aspera/resumer.rb +0 -77
 - data/lib/aspera/transfer/error_info.rb +0 -91
 
| 
         @@ -0,0 +1,335 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'aspera/cli/extended_value'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'aspera/assert'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Aspera
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Cli
         
     | 
| 
      
 8 
     | 
    
         
            +
                module Plugins
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # Base class for command plugins
         
     | 
| 
      
 10 
     | 
    
         
            +
                  class Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # Operations without id (create list)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    GLOBAL_OPS = %i[create list].freeze
         
     | 
| 
      
 13 
     | 
    
         
            +
                    # Operations with id (modify delete show)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    INSTANCE_OPS = %i[modify delete show].freeze
         
     | 
| 
      
 15 
     | 
    
         
            +
                    # All standard operations (create list modify delete show)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    ALL_OPS = (GLOBAL_OPS + INSTANCE_OPS).freeze
         
     | 
| 
      
 17 
     | 
    
         
            +
                    # Special query parameter: `max`: max number of items for list command
         
     | 
| 
      
 18 
     | 
    
         
            +
                    MAX_ITEMS = 'max'
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # Special query parameter: `pmax`: max number of pages for list command
         
     | 
| 
      
 20 
     | 
    
         
            +
                    MAX_PAGES = 'pmax'
         
     | 
| 
      
 21 
     | 
    
         
            +
                    # Special identifier format: look for this name to find where supported
         
     | 
| 
      
 22 
     | 
    
         
            +
                    REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                    class << self
         
     | 
| 
      
 25 
     | 
    
         
            +
                      def declare_options(options)
         
     | 
| 
      
 26 
     | 
    
         
            +
                        options.declare(:query, 'Additional filter for for some commands (list/delete)', types: [Hash, Array])
         
     | 
| 
      
 27 
     | 
    
         
            +
                        options.declare(:property, 'Name of property to set (modify operation)')
         
     | 
| 
      
 28 
     | 
    
         
            +
                        options.declare(:bulk, 'Bulk operation (only some)', values: :bool, default: :no)
         
     | 
| 
      
 29 
     | 
    
         
            +
                        options.declare(:bfail, 'Bulk operation error handling', values: :bool, default: :yes)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      end
         
     | 
| 
      
 31 
     | 
    
         
            +
                    end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                    def initialize(context:)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      # Check presence in descendant of mandatory method and constant
         
     | 
| 
      
 35 
     | 
    
         
            +
                      Aspera.assert(respond_to?(:execute_action), type: InternalError){"Missing method 'execute_action' in #{self.class}"}
         
     | 
| 
      
 36 
     | 
    
         
            +
                      Aspera.assert(self.class.constants.include?(:ACTIONS), type: InternalError){"Missing constant 'ACTIONS' in #{self.class}"}
         
     | 
| 
      
 37 
     | 
    
         
            +
                      @context = context
         
     | 
| 
      
 38 
     | 
    
         
            +
                      add_manual_header if @context.man_header
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                    # Global objects
         
     | 
| 
      
 42 
     | 
    
         
            +
                    attr_reader :context
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                    def options; @context.options; end
         
     | 
| 
      
 45 
     | 
    
         
            +
                    def transfer; @context.transfer; end
         
     | 
| 
      
 46 
     | 
    
         
            +
                    def config; @context.config; end
         
     | 
| 
      
 47 
     | 
    
         
            +
                    def formatter; @context.formatter; end
         
     | 
| 
      
 48 
     | 
    
         
            +
                    def persistency; @context.persistency; end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                    def add_manual_header(has_options = true)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      # Manual header for all plugins
         
     | 
| 
      
 52 
     | 
    
         
            +
                      options.parser.separator('')
         
     | 
| 
      
 53 
     | 
    
         
            +
                      options.parser.separator("COMMAND: #{self.class.name.split('::').last.downcase}")
         
     | 
| 
      
 54 
     | 
    
         
            +
                      options.parser.separator("SUBCOMMANDS: #{self.class.const_get(:ACTIONS).map(&:to_s).sort.join(' ')}")
         
     | 
| 
      
 55 
     | 
    
         
            +
                      options.parser.separator('OPTIONS:') if has_options
         
     | 
| 
      
 56 
     | 
    
         
            +
                    end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    # Must be called AFTER the instance action:
         
     | 
| 
      
 59 
     | 
    
         
            +
                    # ... folder browse _call_instance_identifier
         
     | 
| 
      
 60 
     | 
    
         
            +
                    #
         
     | 
| 
      
 61 
     | 
    
         
            +
                    # @param description [String] description of the identifier
         
     | 
| 
      
 62 
     | 
    
         
            +
                    # @param as_option   [Symbol] option name to use if identifier is an option
         
     | 
| 
      
 63 
     | 
    
         
            +
                    # @param block       [Proc] block to search for identifier based on attribute value
         
     | 
| 
      
 64 
     | 
    
         
            +
                    # @return   [String, Array] identifier or list of ids
         
     | 
| 
      
 65 
     | 
    
         
            +
                    def instance_identifier(description: 'identifier', as_option: nil, &block)
         
     | 
| 
      
 66 
     | 
    
         
            +
                      if as_option.nil?
         
     | 
| 
      
 67 
     | 
    
         
            +
                        res_id = options.get_next_argument(description, multiple: options.get_option(:bulk)) if res_id.nil?
         
     | 
| 
      
 68 
     | 
    
         
            +
                      else
         
     | 
| 
      
 69 
     | 
    
         
            +
                        res_id = options.get_option(as_option)
         
     | 
| 
      
 70 
     | 
    
         
            +
                      end
         
     | 
| 
      
 71 
     | 
    
         
            +
                      # Can be an Array
         
     | 
| 
      
 72 
     | 
    
         
            +
                      if res_id.is_a?(String) && (m = res_id.match(REGEX_LOOKUP_ID_BY_FIELD))
         
     | 
| 
      
 73 
     | 
    
         
            +
                        if block
         
     | 
| 
      
 74 
     | 
    
         
            +
                          res_id = yield(m[1], ExtendedValue.instance.evaluate(m[2]))
         
     | 
| 
      
 75 
     | 
    
         
            +
                        else
         
     | 
| 
      
 76 
     | 
    
         
            +
                          raise Cli::BadArgument, "Percent syntax for #{description} not supported in this context"
         
     | 
| 
      
 77 
     | 
    
         
            +
                        end
         
     | 
| 
      
 78 
     | 
    
         
            +
                      end
         
     | 
| 
      
 79 
     | 
    
         
            +
                      return res_id
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                    # For create and delete operations: execute one action or multiple if bulk is yes
         
     | 
| 
      
 83 
     | 
    
         
            +
                    # @param command   [Symbol] operation: :create, :delete, ...
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # @param descr     [String] description of the value
         
     | 
| 
      
 85 
     | 
    
         
            +
                    # @param values    [Object] the value(s), or the type of value to get from user
         
     | 
| 
      
 86 
     | 
    
         
            +
                    # @param id_result [String] key in result hash to use as identifier
         
     | 
| 
      
 87 
     | 
    
         
            +
                    # @param fields    [Array]  fields to display
         
     | 
| 
      
 88 
     | 
    
         
            +
                    # @param &block    [Proc]   block to execute for each value
         
     | 
| 
      
 89 
     | 
    
         
            +
                    def do_bulk_operation(command:, descr: nil, values: Hash, id_result: 'id', fields: :default)
         
     | 
| 
      
 90 
     | 
    
         
            +
                      Aspera.assert(block_given?){'missing block'}
         
     | 
| 
      
 91 
     | 
    
         
            +
                      is_bulk = options.get_option(:bulk)
         
     | 
| 
      
 92 
     | 
    
         
            +
                      case values
         
     | 
| 
      
 93 
     | 
    
         
            +
                      when :identifier
         
     | 
| 
      
 94 
     | 
    
         
            +
                        values = instance_identifier(description: descr)
         
     | 
| 
      
 95 
     | 
    
         
            +
                      when Class
         
     | 
| 
      
 96 
     | 
    
         
            +
                        values = value_create_modify(command: command, description: descr, type: values, bulk: is_bulk)
         
     | 
| 
      
 97 
     | 
    
         
            +
                      end
         
     | 
| 
      
 98 
     | 
    
         
            +
                      # If not bulk, there is a single value
         
     | 
| 
      
 99 
     | 
    
         
            +
                      params = is_bulk ? values : [values]
         
     | 
| 
      
 100 
     | 
    
         
            +
                      Log.log.warn('Empty list given for bulk operation') if params.empty?
         
     | 
| 
      
 101 
     | 
    
         
            +
                      Log.dump(:bulk_operation, params)
         
     | 
| 
      
 102 
     | 
    
         
            +
                      result_list = []
         
     | 
| 
      
 103 
     | 
    
         
            +
                      params.each do |param|
         
     | 
| 
      
 104 
     | 
    
         
            +
                        # Init for delete
         
     | 
| 
      
 105 
     | 
    
         
            +
                        result = {id_result => param}
         
     | 
| 
      
 106 
     | 
    
         
            +
                        begin
         
     | 
| 
      
 107 
     | 
    
         
            +
                          # Execute custom code
         
     | 
| 
      
 108 
     | 
    
         
            +
                          res = yield(param)
         
     | 
| 
      
 109 
     | 
    
         
            +
                          # If block returns a hash, let's use this (create)
         
     | 
| 
      
 110 
     | 
    
         
            +
                          result = res if res.is_a?(Hash)
         
     | 
| 
      
 111 
     | 
    
         
            +
                          # TODO: remove when faspio gw api fixes this
         
     | 
| 
      
 112 
     | 
    
         
            +
                          result = res.first if res.is_a?(Array) && res.first.is_a?(Hash)
         
     | 
| 
      
 113 
     | 
    
         
            +
                          # Create -> created
         
     | 
| 
      
 114 
     | 
    
         
            +
                          result['status'] = "#{command}#{'e' unless command.to_s.end_with?('e')}d".gsub(/yed$/, 'ied')
         
     | 
| 
      
 115 
     | 
    
         
            +
                        rescue StandardError => e
         
     | 
| 
      
 116 
     | 
    
         
            +
                          raise e if options.get_option(:bfail)
         
     | 
| 
      
 117 
     | 
    
         
            +
                          result['status'] = e.to_s
         
     | 
| 
      
 118 
     | 
    
         
            +
                        end
         
     | 
| 
      
 119 
     | 
    
         
            +
                        result_list.push(result)
         
     | 
| 
      
 120 
     | 
    
         
            +
                      end
         
     | 
| 
      
 121 
     | 
    
         
            +
                      display_fields = [id_result, 'status']
         
     | 
| 
      
 122 
     | 
    
         
            +
                      if is_bulk
         
     | 
| 
      
 123 
     | 
    
         
            +
                        return Main.result_object_list(result_list, fields: display_fields)
         
     | 
| 
      
 124 
     | 
    
         
            +
                      else
         
     | 
| 
      
 125 
     | 
    
         
            +
                        display_fields = fields unless fields.eql?(:default)
         
     | 
| 
      
 126 
     | 
    
         
            +
                        return Main.result_single_object(result_list.first, fields: display_fields)
         
     | 
| 
      
 127 
     | 
    
         
            +
                      end
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                    # Operations: Create, Delete, Show, List, Modify
         
     | 
| 
      
 131 
     | 
    
         
            +
                    # @param api            [Rest]    api to use
         
     | 
| 
      
 132 
     | 
    
         
            +
                    # @param entity         [String]  sub path in URL to resource relative to base url
         
     | 
| 
      
 133 
     | 
    
         
            +
                    # @param command        [Symbol]  command to execute: create show list modify delete
         
     | 
| 
      
 134 
     | 
    
         
            +
                    # @param display_fields [Array]   fields to display by default
         
     | 
| 
      
 135 
     | 
    
         
            +
                    # @param items_key      [String]  result is in a sub key of the json
         
     | 
| 
      
 136 
     | 
    
         
            +
                    # @param delete_style   [String]  if set, the delete operation by array in payload
         
     | 
| 
      
 137 
     | 
    
         
            +
                    # @param id_as_arg      [String]  if set, the id is provided as url argument ?<id_as_arg>=<id>
         
     | 
| 
      
 138 
     | 
    
         
            +
                    # @param is_singleton   [Boolean] if true, entity is the full path to the resource
         
     | 
| 
      
 139 
     | 
    
         
            +
                    # @param tclo           [Bool]    if set, :list use paging with total_count, limit, offset
         
     | 
| 
      
 140 
     | 
    
         
            +
                    # @param block          [Proc]    block to search for identifier based on attribute value
         
     | 
| 
      
 141 
     | 
    
         
            +
                    # @return result suitable for CLI result
         
     | 
| 
      
 142 
     | 
    
         
            +
                    def entity_execute(
         
     | 
| 
      
 143 
     | 
    
         
            +
                      api:,
         
     | 
| 
      
 144 
     | 
    
         
            +
                      entity:,
         
     | 
| 
      
 145 
     | 
    
         
            +
                      command: nil,
         
     | 
| 
      
 146 
     | 
    
         
            +
                      display_fields: nil,
         
     | 
| 
      
 147 
     | 
    
         
            +
                      items_key: nil,
         
     | 
| 
      
 148 
     | 
    
         
            +
                      delete_style: nil,
         
     | 
| 
      
 149 
     | 
    
         
            +
                      id_as_arg: false,
         
     | 
| 
      
 150 
     | 
    
         
            +
                      is_singleton: false,
         
     | 
| 
      
 151 
     | 
    
         
            +
                      list_query: nil,
         
     | 
| 
      
 152 
     | 
    
         
            +
                      tclo: false,
         
     | 
| 
      
 153 
     | 
    
         
            +
                      &block
         
     | 
| 
      
 154 
     | 
    
         
            +
                    )
         
     | 
| 
      
 155 
     | 
    
         
            +
                      command = options.get_next_command(ALL_OPS) if command.nil?
         
     | 
| 
      
 156 
     | 
    
         
            +
                      if is_singleton
         
     | 
| 
      
 157 
     | 
    
         
            +
                        one_res_path = entity
         
     | 
| 
      
 158 
     | 
    
         
            +
                      elsif INSTANCE_OPS.include?(command)
         
     | 
| 
      
 159 
     | 
    
         
            +
                        one_res_id = instance_identifier(&block)
         
     | 
| 
      
 160 
     | 
    
         
            +
                        one_res_path = "#{entity}/#{one_res_id}"
         
     | 
| 
      
 161 
     | 
    
         
            +
                        one_res_path = "#{entity}?#{id_as_arg}=#{one_res_id}" if id_as_arg
         
     | 
| 
      
 162 
     | 
    
         
            +
                      end
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
      
 164 
     | 
    
         
            +
                      case command
         
     | 
| 
      
 165 
     | 
    
         
            +
                      when :create
         
     | 
| 
      
 166 
     | 
    
         
            +
                        raise BadArgument, 'cannot create singleton' if is_singleton
         
     | 
| 
      
 167 
     | 
    
         
            +
                        return do_bulk_operation(command: command, descr: 'data', fields: display_fields) do |params|
         
     | 
| 
      
 168 
     | 
    
         
            +
                          api.create(entity, params)
         
     | 
| 
      
 169 
     | 
    
         
            +
                        end
         
     | 
| 
      
 170 
     | 
    
         
            +
                      when :delete
         
     | 
| 
      
 171 
     | 
    
         
            +
                        raise BadArgument, 'cannot delete singleton' if is_singleton
         
     | 
| 
      
 172 
     | 
    
         
            +
                        if !delete_style.nil?
         
     | 
| 
      
 173 
     | 
    
         
            +
                          one_res_id = [one_res_id] unless one_res_id.is_a?(Array)
         
     | 
| 
      
 174 
     | 
    
         
            +
                          Aspera.assert_type(one_res_id, Array, type: Cli::BadArgument)
         
     | 
| 
      
 175 
     | 
    
         
            +
                          api.call(
         
     | 
| 
      
 176 
     | 
    
         
            +
                            operation:    'DELETE',
         
     | 
| 
      
 177 
     | 
    
         
            +
                            subpath:      entity,
         
     | 
| 
      
 178 
     | 
    
         
            +
                            content_type: Rest::MIME_JSON,
         
     | 
| 
      
 179 
     | 
    
         
            +
                            body:         {delete_style => one_res_id},
         
     | 
| 
      
 180 
     | 
    
         
            +
                            headers:      {'Accept' => Rest::MIME_JSON}
         
     | 
| 
      
 181 
     | 
    
         
            +
                          )
         
     | 
| 
      
 182 
     | 
    
         
            +
                          return Main.result_status('deleted')
         
     | 
| 
      
 183 
     | 
    
         
            +
                        end
         
     | 
| 
      
 184 
     | 
    
         
            +
                        return do_bulk_operation(command: command, values: one_res_id) do |one_id|
         
     | 
| 
      
 185 
     | 
    
         
            +
                          api.delete("#{entity}/#{one_id}", query_read_delete)
         
     | 
| 
      
 186 
     | 
    
         
            +
                          {'id' => one_id}
         
     | 
| 
      
 187 
     | 
    
         
            +
                        end
         
     | 
| 
      
 188 
     | 
    
         
            +
                      when :show
         
     | 
| 
      
 189 
     | 
    
         
            +
                        return Main.result_single_object(api.read(one_res_path), fields: display_fields)
         
     | 
| 
      
 190 
     | 
    
         
            +
                      when :list
         
     | 
| 
      
 191 
     | 
    
         
            +
                        if tclo
         
     | 
| 
      
 192 
     | 
    
         
            +
                          data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: query_read_delete(default: list_query))
         
     | 
| 
      
 193 
     | 
    
         
            +
                          return Main.result_object_list(data, total: total, fields: display_fields)
         
     | 
| 
      
 194 
     | 
    
         
            +
                        end
         
     | 
| 
      
 195 
     | 
    
         
            +
                        resp = api.call(operation: 'GET', subpath: entity, headers: {'Accept' => Rest::MIME_JSON}, query: query_read_delete)
         
     | 
| 
      
 196 
     | 
    
         
            +
                        return Main.result_empty if resp[:http].code == '204'
         
     | 
| 
      
 197 
     | 
    
         
            +
                        data = resp[:data]
         
     | 
| 
      
 198 
     | 
    
         
            +
                        # TODO: not generic : which application is this for ?
         
     | 
| 
      
 199 
     | 
    
         
            +
                        if resp[:http]['Content-Type'].start_with?('application/vnd.api+json')
         
     | 
| 
      
 200 
     | 
    
         
            +
                          Log.log.debug('is vnd.api')
         
     | 
| 
      
 201 
     | 
    
         
            +
                          data = data[entity]
         
     | 
| 
      
 202 
     | 
    
         
            +
                        end
         
     | 
| 
      
 203 
     | 
    
         
            +
                        data = data[items_key] if items_key
         
     | 
| 
      
 204 
     | 
    
         
            +
                        case data
         
     | 
| 
      
 205 
     | 
    
         
            +
                        when Hash
         
     | 
| 
      
 206 
     | 
    
         
            +
                          return Main.result_single_object(data, fields: display_fields)
         
     | 
| 
      
 207 
     | 
    
         
            +
                        when Array
         
     | 
| 
      
 208 
     | 
    
         
            +
                          return Main.result_object_list(data, fields: display_fields) if data.empty? || data.first.is_a?(Hash)
         
     | 
| 
      
 209 
     | 
    
         
            +
                          return Main.result_value_list(data)
         
     | 
| 
      
 210 
     | 
    
         
            +
                        else
         
     | 
| 
      
 211 
     | 
    
         
            +
                          raise "An error occurred: unexpected result type for list: #{data.class}"
         
     | 
| 
      
 212 
     | 
    
         
            +
                        end
         
     | 
| 
      
 213 
     | 
    
         
            +
                      when :modify
         
     | 
| 
      
 214 
     | 
    
         
            +
                        parameters = value_create_modify(command: command)
         
     | 
| 
      
 215 
     | 
    
         
            +
                        property = options.get_option(:property)
         
     | 
| 
      
 216 
     | 
    
         
            +
                        parameters = {property => parameters} unless property.nil?
         
     | 
| 
      
 217 
     | 
    
         
            +
                        api.update(one_res_path, parameters)
         
     | 
| 
      
 218 
     | 
    
         
            +
                        return Main.result_status('modified')
         
     | 
| 
      
 219 
     | 
    
         
            +
                      else
         
     | 
| 
      
 220 
     | 
    
         
            +
                        raise "unknown action: #{command}"
         
     | 
| 
      
 221 
     | 
    
         
            +
                      end
         
     | 
| 
      
 222 
     | 
    
         
            +
                    end
         
     | 
| 
      
 223 
     | 
    
         
            +
             
     | 
| 
      
 224 
     | 
    
         
            +
                    # Query parameters in URL suitable for REST: list/GET and delete/DELETE
         
     | 
| 
      
 225 
     | 
    
         
            +
                    def query_read_delete(default: nil)
         
     | 
| 
      
 226 
     | 
    
         
            +
                      query = options.get_option(:query)
         
     | 
| 
      
 227 
     | 
    
         
            +
                      # Dup default, as it could be frozen
         
     | 
| 
      
 228 
     | 
    
         
            +
                      query = default.dup if query.nil?
         
     | 
| 
      
 229 
     | 
    
         
            +
                      Log.log.debug{"query_read_delete=#{query}".bg_red}
         
     | 
| 
      
 230 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 231 
     | 
    
         
            +
                        # Check it is suitable
         
     | 
| 
      
 232 
     | 
    
         
            +
                        URI.encode_www_form(query) unless query.nil?
         
     | 
| 
      
 233 
     | 
    
         
            +
                      rescue StandardError => e
         
     | 
| 
      
 234 
     | 
    
         
            +
                        raise Cli::BadArgument, "Query must be an extended value (Hash, Array) which can be encoded with URI.encode_www_form. Refer to manual. (#{e.message})"
         
     | 
| 
      
 235 
     | 
    
         
            +
                      end
         
     | 
| 
      
 236 
     | 
    
         
            +
                      return query
         
     | 
| 
      
 237 
     | 
    
         
            +
                    end
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
      
 239 
     | 
    
         
            +
                    # Retrieves an extended value from command line, used for creation or modification of entities
         
     | 
| 
      
 240 
     | 
    
         
            +
                    # @param command [Symbol]  command name for error message
         
     | 
| 
      
 241 
     | 
    
         
            +
                    # @param type    [Class]   expected type of value, either a Class, an Array of Class
         
     | 
| 
      
 242 
     | 
    
         
            +
                    # @param bulk    [Boolean] if true, value must be an Array of <type>
         
     | 
| 
      
 243 
     | 
    
         
            +
                    # @param default [Object]  default value if not provided
         
     | 
| 
      
 244 
     | 
    
         
            +
                    def value_create_modify(command:, description: nil, type: Hash, bulk: false, default: nil)
         
     | 
| 
      
 245 
     | 
    
         
            +
                      value = options.get_next_argument(
         
     | 
| 
      
 246 
     | 
    
         
            +
                        "parameters for #{command}#{" (#{description})" unless description.nil?}", mandatory: default.nil?,
         
     | 
| 
      
 247 
     | 
    
         
            +
                        validation: bulk ? Array : type
         
     | 
| 
      
 248 
     | 
    
         
            +
                      )
         
     | 
| 
      
 249 
     | 
    
         
            +
                      value = default if value.nil?
         
     | 
| 
      
 250 
     | 
    
         
            +
                      unless type.nil?
         
     | 
| 
      
 251 
     | 
    
         
            +
                        type = [type] unless type.is_a?(Array)
         
     | 
| 
      
 252 
     | 
    
         
            +
                        Aspera.assert(type.all?(Class)){"check types must be a Class, not #{type.map(&:class).join(',')}"}
         
     | 
| 
      
 253 
     | 
    
         
            +
                        if bulk
         
     | 
| 
      
 254 
     | 
    
         
            +
                          Aspera.assert_type(value, Array, type: Cli::BadArgument)
         
     | 
| 
      
 255 
     | 
    
         
            +
                          value.each do |v|
         
     | 
| 
      
 256 
     | 
    
         
            +
                            Aspera.assert_values(v.class, type, type: Cli::BadArgument)
         
     | 
| 
      
 257 
     | 
    
         
            +
                          end
         
     | 
| 
      
 258 
     | 
    
         
            +
                        else
         
     | 
| 
      
 259 
     | 
    
         
            +
                          Aspera.assert_values(value.class, type, type: Cli::BadArgument)
         
     | 
| 
      
 260 
     | 
    
         
            +
                        end
         
     | 
| 
      
 261 
     | 
    
         
            +
                      end
         
     | 
| 
      
 262 
     | 
    
         
            +
                      return value
         
     | 
| 
      
 263 
     | 
    
         
            +
                    end
         
     | 
| 
      
 264 
     | 
    
         
            +
             
     | 
| 
      
 265 
     | 
    
         
            +
                    # Get a (full or partial) list of all entities of a given type with query: offset/limit
         
     | 
| 
      
 266 
     | 
    
         
            +
                    # @param `api`       [Rest]          the API object
         
     | 
| 
      
 267 
     | 
    
         
            +
                    # @param `entity`    [String,Symbol] the API endpoint of entity to list
         
     | 
| 
      
 268 
     | 
    
         
            +
                    # @param `items_key` [String]        key in the result to get the list of items
         
     | 
| 
      
 269 
     | 
    
         
            +
                    # @param `query`     [Hash,nil]      additional query parameters
         
     | 
| 
      
 270 
     | 
    
         
            +
                    # @return [Array] items, total_count
         
     | 
| 
      
 271 
     | 
    
         
            +
                    def list_entities_limit_offset_total_count(
         
     | 
| 
      
 272 
     | 
    
         
            +
                      api:,
         
     | 
| 
      
 273 
     | 
    
         
            +
                      entity:,
         
     | 
| 
      
 274 
     | 
    
         
            +
                      items_key: nil,
         
     | 
| 
      
 275 
     | 
    
         
            +
                      query: nil
         
     | 
| 
      
 276 
     | 
    
         
            +
                    )
         
     | 
| 
      
 277 
     | 
    
         
            +
                      entity = entity.to_s if entity.is_a?(Symbol)
         
     | 
| 
      
 278 
     | 
    
         
            +
                      items_key = entity.split('/').last if items_key.nil?
         
     | 
| 
      
 279 
     | 
    
         
            +
                      query = {} if query.nil?
         
     | 
| 
      
 280 
     | 
    
         
            +
                      Aspera.assert_type(entity, String)
         
     | 
| 
      
 281 
     | 
    
         
            +
                      Aspera.assert_type(items_key, String)
         
     | 
| 
      
 282 
     | 
    
         
            +
                      Aspera.assert_type(query, Hash)
         
     | 
| 
      
 283 
     | 
    
         
            +
                      Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
         
     | 
| 
      
 284 
     | 
    
         
            +
                      result = []
         
     | 
| 
      
 285 
     | 
    
         
            +
                      offset = 0
         
     | 
| 
      
 286 
     | 
    
         
            +
                      max_items = query.delete(MAX_ITEMS)
         
     | 
| 
      
 287 
     | 
    
         
            +
                      remain_pages = query.delete(MAX_PAGES)
         
     | 
| 
      
 288 
     | 
    
         
            +
                      # Merge default parameters, by default 100 per page
         
     | 
| 
      
 289 
     | 
    
         
            +
                      query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
         
     | 
| 
      
 290 
     | 
    
         
            +
                      total_count = nil
         
     | 
| 
      
 291 
     | 
    
         
            +
                      loop do
         
     | 
| 
      
 292 
     | 
    
         
            +
                        query['offset'] = offset
         
     | 
| 
      
 293 
     | 
    
         
            +
                        page_result = api.read(entity, query)
         
     | 
| 
      
 294 
     | 
    
         
            +
                        Aspera.assert_type(page_result[items_key], Array)
         
     | 
| 
      
 295 
     | 
    
         
            +
                        result.concat(page_result[items_key])
         
     | 
| 
      
 296 
     | 
    
         
            +
                        # Reach the limit set by user ?
         
     | 
| 
      
 297 
     | 
    
         
            +
                        if !max_items.nil? && (result.length >= max_items)
         
     | 
| 
      
 298 
     | 
    
         
            +
                          result = result.slice(0, max_items)
         
     | 
| 
      
 299 
     | 
    
         
            +
                          break
         
     | 
| 
      
 300 
     | 
    
         
            +
                        end
         
     | 
| 
      
 301 
     | 
    
         
            +
                        total_count ||= page_result['total_count']
         
     | 
| 
      
 302 
     | 
    
         
            +
                        break if result.length >= total_count
         
     | 
| 
      
 303 
     | 
    
         
            +
                        remain_pages -= 1 unless remain_pages.nil?
         
     | 
| 
      
 304 
     | 
    
         
            +
                        break if remain_pages == 0
         
     | 
| 
      
 305 
     | 
    
         
            +
                        offset += page_result[items_key].length
         
     | 
| 
      
 306 
     | 
    
         
            +
                        formatter.long_operation_running
         
     | 
| 
      
 307 
     | 
    
         
            +
                      end
         
     | 
| 
      
 308 
     | 
    
         
            +
                      formatter.long_operation_terminated
         
     | 
| 
      
 309 
     | 
    
         
            +
                      return result, total_count
         
     | 
| 
      
 310 
     | 
    
         
            +
                    end
         
     | 
| 
      
 311 
     | 
    
         
            +
             
     | 
| 
      
 312 
     | 
    
         
            +
                    # Lookup an entity id from its name
         
     | 
| 
      
 313 
     | 
    
         
            +
                    # @param entity    [String] the type of entity to lookup, by default it is the path, and it is also the field name in result
         
     | 
| 
      
 314 
     | 
    
         
            +
                    # @param value     [String] the value to lookup
         
     | 
| 
      
 315 
     | 
    
         
            +
                    # @param field     [String] the field to match, by default it is 'name'
         
     | 
| 
      
 316 
     | 
    
         
            +
                    # @param items_key [String] key in the result to get the list of items (override entity)
         
     | 
| 
      
 317 
     | 
    
         
            +
                    # @param query     [Hash]   additional query parameters
         
     | 
| 
      
 318 
     | 
    
         
            +
                    def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
         
     | 
| 
      
 319 
     | 
    
         
            +
                      if query.eql?(:default)
         
     | 
| 
      
 320 
     | 
    
         
            +
                        Aspera.assert(field.eql?('name')){'Default query is on name only'}
         
     | 
| 
      
 321 
     | 
    
         
            +
                        query = {'q'=> value}
         
     | 
| 
      
 322 
     | 
    
         
            +
                      end
         
     | 
| 
      
 323 
     | 
    
         
            +
                      found = list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first.select{ |i| i[field].eql?(value)}
         
     | 
| 
      
 324 
     | 
    
         
            +
                      case found.length
         
     | 
| 
      
 325 
     | 
    
         
            +
                      when 0 then raise "No #{entity} with #{field} = #{value}"
         
     | 
| 
      
 326 
     | 
    
         
            +
                      when 1 then return found.first
         
     | 
| 
      
 327 
     | 
    
         
            +
                      else raise "Found #{found.length} #{entity} with #{field} = #{value}"
         
     | 
| 
      
 328 
     | 
    
         
            +
                      end
         
     | 
| 
      
 329 
     | 
    
         
            +
                    end
         
     | 
| 
      
 330 
     | 
    
         
            +
                    PER_PAGE_DEFAULT = 1000
         
     | 
| 
      
 331 
     | 
    
         
            +
                    private_constant :PER_PAGE_DEFAULT
         
     | 
| 
      
 332 
     | 
    
         
            +
                  end
         
     | 
| 
      
 333 
     | 
    
         
            +
                end
         
     | 
| 
      
 334 
     | 
    
         
            +
              end
         
     | 
| 
      
 335 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'aspera/rest'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'aspera/cli/plugins/base'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Aspera
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Cli
         
     | 
| 
      
 8 
     | 
    
         
            +
                module Plugins
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # base class for applications supporting basic authentication
         
     | 
| 
      
 10 
     | 
    
         
            +
                  class BasicAuth < Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                    class << self
         
     | 
| 
      
 12 
     | 
    
         
            +
                      def declare_options(options)
         
     | 
| 
      
 13 
     | 
    
         
            +
                        options.declare(:url, 'URL of application, e.g. https://app.example.com/aspera/app')
         
     | 
| 
      
 14 
     | 
    
         
            +
                        options.declare(:username, "User's identifier")
         
     | 
| 
      
 15 
     | 
    
         
            +
                        options.declare(:password, "User's password")
         
     | 
| 
      
 16 
     | 
    
         
            +
                        options.parse_options!
         
     | 
| 
      
 17 
     | 
    
         
            +
                      end
         
     | 
| 
      
 18 
     | 
    
         
            +
                    end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    def initialize(context:, basic_options: true)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      super(context: context)
         
     | 
| 
      
 22 
     | 
    
         
            +
                      BasicAuth.declare_options(options) if basic_options
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                    # returns a Rest object with basic auth
         
     | 
| 
      
 26 
     | 
    
         
            +
                    def basic_auth_params(subpath = nil)
         
     | 
| 
      
 27 
     | 
    
         
            +
                      api_url = options.get_option(:url, mandatory: true)
         
     | 
| 
      
 28 
     | 
    
         
            +
                      api_url = "#{api_url}/#{subpath}" unless subpath.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      return {
         
     | 
| 
      
 30 
     | 
    
         
            +
                        base_url: api_url,
         
     | 
| 
      
 31 
     | 
    
         
            +
                        auth:     {
         
     | 
| 
      
 32 
     | 
    
         
            +
                          type:     :basic,
         
     | 
| 
      
 33 
     | 
    
         
            +
                          username: options.get_option(:username, mandatory: true),
         
     | 
| 
      
 34 
     | 
    
         
            +
                          password: options.get_option(:password, mandatory: true)
         
     | 
| 
      
 35 
     | 
    
         
            +
                        }
         
     | 
| 
      
 36 
     | 
    
         
            +
                      }
         
     | 
| 
      
 37 
     | 
    
         
            +
                    end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                    def basic_auth_api(subpath = nil)
         
     | 
| 
      
 40 
     | 
    
         
            +
                      return Rest.new(**basic_auth_params(subpath))
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     |