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
 
| 
         @@ -18,26 +18,26 @@ module Aspera 
     | 
|
| 
       18 
18 
     | 
    
         
             
              module Sync
         
     | 
| 
       19 
19 
     | 
    
         
             
                # builds command line arg for async and execute it
         
     | 
| 
       20 
20 
     | 
    
         
             
                module Operations
         
     | 
| 
       21 
     | 
    
         
            -
                  #  
     | 
| 
      
 21 
     | 
    
         
            +
                  # Sync direction
         
     | 
| 
       22 
22 
     | 
    
         
             
                  DIRECTIONS = %i[push pull bidi].freeze
         
     | 
| 
       23 
     | 
    
         
            -
                  #  
     | 
| 
       24 
     | 
    
         
            -
                  DEFAULT_DIRECTION =  
     | 
| 
      
 23 
     | 
    
         
            +
                  # Default direction for sync
         
     | 
| 
      
 24 
     | 
    
         
            +
                  DEFAULT_DIRECTION = DIRECTIONS.first
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
26 
     | 
    
         
             
                  class << self
         
     | 
| 
       27 
27 
     | 
    
         
             
                    # Set `remote_dir` in sync parameters based on transfer spec
         
     | 
| 
       28 
     | 
    
         
            -
                    # @param params 
     | 
| 
       29 
     | 
    
         
            -
                    # @param remote_dir_key [String]  
     | 
| 
       30 
     | 
    
         
            -
                    # @param transfer_spec 
     | 
| 
       31 
     | 
    
         
            -
                    def update_remote_dir( 
     | 
| 
      
 28 
     | 
    
         
            +
                    # @param params         [Hash]   Sync parameters, in `conf` or `args` format.
         
     | 
| 
      
 29 
     | 
    
         
            +
                    # @param remote_dir_key [String] Key to update in above hash
         
     | 
| 
      
 30 
     | 
    
         
            +
                    # @param transfer_spec  [Hash]   Transfer spec
         
     | 
| 
      
 31 
     | 
    
         
            +
                    def update_remote_dir(params, remote_dir_key, transfer_spec)
         
     | 
| 
       32 
32 
     | 
    
         
             
                      if transfer_spec.dig(*%w[tags aspera node file_id])
         
     | 
| 
       33 
33 
     | 
    
         
             
                        # in AoC, use gen4
         
     | 
| 
       34 
     | 
    
         
            -
                         
     | 
| 
      
 34 
     | 
    
         
            +
                        params[remote_dir_key] = '/'
         
     | 
| 
       35 
35 
     | 
    
         
             
                      elsif transfer_spec['cookie']&.start_with?('aspera.shares2')
         
     | 
| 
       36 
36 
     | 
    
         
             
                        # TODO : something more generic, independent of Shares
         
     | 
| 
       37 
37 
     | 
    
         
             
                        # in Shares, the actual folder on remote end is not always the same as the name of the share
         
     | 
| 
       38 
38 
     | 
    
         
             
                        remote_key = transfer_spec['direction'].eql?('send') ? 'destination' : 'source'
         
     | 
| 
       39 
39 
     | 
    
         
             
                        actual_remote = transfer_spec['paths']&.first&.[](remote_key)
         
     | 
| 
       40 
     | 
    
         
            -
                         
     | 
| 
      
 40 
     | 
    
         
            +
                        params[remote_dir_key] = actual_remote if actual_remote
         
     | 
| 
       41 
41 
     | 
    
         
             
                      end
         
     | 
| 
       42 
42 
     | 
    
         
             
                      nil
         
     | 
| 
       43 
43 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -70,34 +70,36 @@ module Aspera 
     | 
|
| 
       70 
70 
     | 
    
         
             
                    end
         
     | 
| 
       71 
71 
     | 
    
         | 
| 
       72 
72 
     | 
    
         
             
                    # Get symbol of sync direction, defaulting to :push
         
     | 
| 
       73 
     | 
    
         
            -
                    # @param params [Hash]  
     | 
| 
      
 73 
     | 
    
         
            +
                    # @param params [Hash] Sync parameters, old or new format
         
     | 
| 
       74 
74 
     | 
    
         
             
                    # @return [Symbol] direction symbol, one of :push, :pull, :bidi
         
     | 
| 
       75 
75 
     | 
    
         
             
                    def direction_sym(params)
         
     | 
| 
       76 
76 
     | 
    
         
             
                      (params['direction'] || DEFAULT_DIRECTION).to_sym
         
     | 
| 
       77 
77 
     | 
    
         
             
                    end
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
79 
     | 
    
         
             
                    # Start the sync process
         
     | 
| 
       80 
     | 
    
         
            -
                    # @param  
     | 
| 
      
 80 
     | 
    
         
            +
                    # @param params [Hash] Sync parameters, old or new format
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # @param opt_ts [Hash] Optional transfer spec
         
     | 
| 
       81 
82 
     | 
    
         
             
                    # @param &block [nil, Proc] block to generate transfer spec, takes: direction (one of DIRECTIONS), local_dir, remote_dir
         
     | 
| 
       82 
     | 
    
         
            -
                    def start( 
     | 
| 
       83 
     | 
    
         
            -
                      Log.dump(:sync_params_initial,  
     | 
| 
       84 
     | 
    
         
            -
                      Aspera.assert_type( 
     | 
| 
      
 83 
     | 
    
         
            +
                    def start(params, opt_ts = nil)
         
     | 
| 
      
 84 
     | 
    
         
            +
                      Log.dump(:sync_params_initial, params)
         
     | 
| 
      
 85 
     | 
    
         
            +
                      Aspera.assert_type(params, Hash)
         
     | 
| 
      
 86 
     | 
    
         
            +
                      Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
         
     | 
| 
       85 
87 
     | 
    
         
             
                      env_args = {
         
     | 
| 
       86 
88 
     | 
    
         
             
                        args: [],
         
     | 
| 
       87 
89 
     | 
    
         
             
                        env:  {}
         
     | 
| 
       88 
90 
     | 
    
         
             
                      }
         
     | 
| 
       89 
     | 
    
         
            -
                      if  
     | 
| 
      
 91 
     | 
    
         
            +
                      if params.key?('local')
         
     | 
| 
       90 
92 
     | 
    
         
             
                        # "conf" format
         
     | 
| 
       91 
     | 
    
         
            -
                        Aspera.assert_type( 
     | 
| 
       92 
     | 
    
         
            -
                        remote =  
     | 
| 
      
 93 
     | 
    
         
            +
                        Aspera.assert_type(params['local'], Hash){'local'}
         
     | 
| 
      
 94 
     | 
    
         
            +
                        remote = params['remote']
         
     | 
| 
       93 
95 
     | 
    
         
             
                        Aspera.assert_type(remote, Hash){'remote'}
         
     | 
| 
       94 
96 
     | 
    
         
             
                        Aspera.assert_type(remote['path'], String){'remote path'}
         
     | 
| 
       95 
97 
     | 
    
         
             
                        # get transfer spec if possible, and feed back to new structure
         
     | 
| 
       96 
98 
     | 
    
         
             
                        if block_given?
         
     | 
| 
       97 
     | 
    
         
            -
                          transfer_spec = yield(direction_sym( 
     | 
| 
      
 99 
     | 
    
         
            +
                          transfer_spec = yield(direction_sym(params), params['local']['path'], remote['path'])
         
     | 
| 
       98 
100 
     | 
    
         
             
                          Log.dump(:auth_ts, transfer_spec)
         
     | 
| 
       99 
101 
     | 
    
         
             
                          transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
         
     | 
| 
       100 
     | 
    
         
            -
                          tspec_to_sync_info(transfer_spec,  
     | 
| 
      
 102 
     | 
    
         
            +
                          tspec_to_sync_info(transfer_spec, params, CONF_SCHEMA)
         
     | 
| 
       101 
103 
     | 
    
         
             
                          update_remote_dir(remote, 'path', transfer_spec)
         
     | 
| 
       102 
104 
     | 
    
         
             
                        end
         
     | 
| 
       103 
105 
     | 
    
         
             
                        remote['connect_mode'] ||= transfer_spec['wss_enabled'] ? 'ws' : 'ssh'
         
     | 
| 
         @@ -107,44 +109,42 @@ module Aspera 
     | 
|
| 
       107 
109 
     | 
    
         
             
                          remote['private_key_paths'].concat(add_certificates)
         
     | 
| 
       108 
110 
     | 
    
         
             
                        end
         
     | 
| 
       109 
111 
     | 
    
         
             
                        # '--exclusive-mgmt-port=12345', '--arg-err-path=-',
         
     | 
| 
       110 
     | 
    
         
            -
                        env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate( 
     | 
| 
       111 
     | 
    
         
            -
                        Log.dump(:sync_conf,  
     | 
| 
      
 112 
     | 
    
         
            +
                        env_args[:args] = ["--conf64=#{Base64.strict_encode64(JSON.generate(params))}"]
         
     | 
| 
      
 113 
     | 
    
         
            +
                        Log.dump(:sync_conf, params)
         
     | 
| 
       112 
114 
     | 
    
         
             
                        agent = Agent::Direct.new
         
     | 
| 
       113 
115 
     | 
    
         
             
                        agent.start_and_monitor_process(session: {}, name: :async, **env_args)
         
     | 
| 
       114 
     | 
    
         
            -
                       
     | 
| 
      
 116 
     | 
    
         
            +
                      else
         
     | 
| 
       115 
117 
     | 
    
         
             
                        # "args" format
         
     | 
| 
       116 
118 
     | 
    
         
             
                        raise StandardError, "Only 'sessions', and optionally 'instance' keys are allowed" unless
         
     | 
| 
       117 
     | 
    
         
            -
                           
     | 
| 
       118 
     | 
    
         
            -
                        Aspera.assert_type( 
     | 
| 
       119 
     | 
    
         
            -
                        Aspera.assert_type( 
     | 
| 
      
 119 
     | 
    
         
            +
                          params.keys.push('instance').uniq.sort.eql?(CMDLINE_PARAMS_KEYS)
         
     | 
| 
      
 120 
     | 
    
         
            +
                        Aspera.assert_type(params['sessions'], Array)
         
     | 
| 
      
 121 
     | 
    
         
            +
                        Aspera.assert_type(params['sessions'].first, Hash)
         
     | 
| 
       120 
122 
     | 
    
         
             
                        if block_given?
         
     | 
| 
       121 
     | 
    
         
            -
                           
     | 
| 
      
 123 
     | 
    
         
            +
                          params['sessions'].each do |session|
         
     | 
| 
       122 
124 
     | 
    
         
             
                            Aspera.assert_type(session['local_dir'], String){'local_dir'}
         
     | 
| 
       123 
125 
     | 
    
         
             
                            Aspera.assert_type(session['remote_dir'], String){'remote_dir'}
         
     | 
| 
       124 
126 
     | 
    
         
             
                            transfer_spec = yield(direction_sym(session), session['local_dir'], session['remote_dir'])
         
     | 
| 
       125 
127 
     | 
    
         
             
                            Log.dump(:auth_ts, transfer_spec)
         
     | 
| 
       126 
128 
     | 
    
         
             
                            transfer_spec.deep_merge!(opt_ts) unless opt_ts.nil?
         
     | 
| 
       127 
     | 
    
         
            -
                            tspec_to_sync_info(transfer_spec, session,  
     | 
| 
      
 129 
     | 
    
         
            +
                            tspec_to_sync_info(transfer_spec, session, ARGS_SESSION_SCHEMA)
         
     | 
| 
       128 
130 
     | 
    
         
             
                            session['private_key_paths'] = Ascp::Installation.instance.aspera_token_ssh_key_paths(:rsa) if transfer_spec.key?('token')
         
     | 
| 
       129 
131 
     | 
    
         
             
                            update_remote_dir(session, 'remote_dir', transfer_spec)
         
     | 
| 
       130 
132 
     | 
    
         
             
                          end
         
     | 
| 
       131 
133 
     | 
    
         
             
                        end
         
     | 
| 
       132 
     | 
    
         
            -
                        if  
     | 
| 
       133 
     | 
    
         
            -
                          Aspera.assert_type( 
     | 
| 
       134 
     | 
    
         
            -
                          instance_builder = CommandLineBuilder.new( 
     | 
| 
      
 134 
     | 
    
         
            +
                        if params.key?('instance')
         
     | 
| 
      
 135 
     | 
    
         
            +
                          Aspera.assert_type(params['instance'], Hash)
         
     | 
| 
      
 136 
     | 
    
         
            +
                          instance_builder = CommandLineBuilder.new(params['instance'], ARGS_INSTANCE_SCHEMA, CommandLineConverter)
         
     | 
| 
       135 
137 
     | 
    
         
             
                          instance_builder.process_params
         
     | 
| 
       136 
138 
     | 
    
         
             
                          instance_builder.add_env_args(env_args)
         
     | 
| 
       137 
139 
     | 
    
         
             
                        end
         
     | 
| 
       138 
     | 
    
         
            -
                         
     | 
| 
      
 140 
     | 
    
         
            +
                        params['sessions'].each do |session_params|
         
     | 
| 
       139 
141 
     | 
    
         
             
                          Aspera.assert_type(session_params, Hash)
         
     | 
| 
       140 
142 
     | 
    
         
             
                          Aspera.assert(session_params.key?('name')){'session must contain at least: name'}
         
     | 
| 
       141 
     | 
    
         
            -
                          session_builder = CommandLineBuilder.new(session_params,  
     | 
| 
      
 143 
     | 
    
         
            +
                          session_builder = CommandLineBuilder.new(session_params, ARGS_SESSION_SCHEMA, CommandLineConverter)
         
     | 
| 
       142 
144 
     | 
    
         
             
                          session_builder.process_params
         
     | 
| 
       143 
145 
     | 
    
         
             
                          session_builder.add_env_args(env_args)
         
     | 
| 
       144 
146 
     | 
    
         
             
                        end
         
     | 
| 
       145 
147 
     | 
    
         
             
                        Environment.secure_execute(exec: Ascp::Installation.instance.path(:async), **env_args)
         
     | 
| 
       146 
     | 
    
         
            -
                      else
         
     | 
| 
       147 
     | 
    
         
            -
                        raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
         
     | 
| 
       148 
148 
     | 
    
         
             
                      end
         
     | 
| 
       149 
149 
     | 
    
         
             
                      return
         
     | 
| 
       150 
150 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -168,23 +168,24 @@ module Aspera 
     | 
|
| 
       168 
168 
     | 
    
         
             
                    end
         
     | 
| 
       169 
169 
     | 
    
         | 
| 
       170 
170 
     | 
    
         
             
                    # Run `asyncadmin` to get status of sync session
         
     | 
| 
       171 
     | 
    
         
            -
                    # @param  
     | 
| 
      
 171 
     | 
    
         
            +
                    # @param params [Hash] sync parameters in conf or args format
         
     | 
| 
       172 
172 
     | 
    
         
             
                    # @return [Hash] parsed output of asyncadmin
         
     | 
| 
       173 
     | 
    
         
            -
                    def admin_status( 
     | 
| 
      
 173 
     | 
    
         
            +
                    def admin_status(params)
         
     | 
| 
      
 174 
     | 
    
         
            +
                      Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
         
     | 
| 
       174 
175 
     | 
    
         
             
                      arguments = ['--quiet']
         
     | 
| 
       175 
     | 
    
         
            -
                      if  
     | 
| 
      
 176 
     | 
    
         
            +
                      if params.key?('local')
         
     | 
| 
       176 
177 
     | 
    
         
             
                        # "conf" format
         
     | 
| 
       177 
     | 
    
         
            -
                        arguments.push("--name=#{ 
     | 
| 
       178 
     | 
    
         
            -
                        if  
     | 
| 
       179 
     | 
    
         
            -
                          arguments.push("--local-db-dir=#{ 
     | 
| 
       180 
     | 
    
         
            -
                        elsif  
     | 
| 
       181 
     | 
    
         
            -
                          arguments.push("--local-dir=#{ 
     | 
| 
      
 178 
     | 
    
         
            +
                        arguments.push("--name=#{params['name']}")
         
     | 
| 
      
 179 
     | 
    
         
            +
                        if params.key?('local_db_dir')
         
     | 
| 
      
 180 
     | 
    
         
            +
                          arguments.push("--local-db-dir=#{params['local_db_dir']}")
         
     | 
| 
      
 181 
     | 
    
         
            +
                        elsif params.dig('local', 'path')
         
     | 
| 
      
 182 
     | 
    
         
            +
                          arguments.push("--local-dir=#{params.dig('local', 'path')}")
         
     | 
| 
       182 
183 
     | 
    
         
             
                        else
         
     | 
| 
       183 
184 
     | 
    
         
             
                          raise Error, 'Missing either local_db_dir or local.path'
         
     | 
| 
       184 
185 
     | 
    
         
             
                        end
         
     | 
| 
       185 
     | 
    
         
            -
                       
     | 
| 
      
 186 
     | 
    
         
            +
                      else
         
     | 
| 
       186 
187 
     | 
    
         
             
                        # "args" format
         
     | 
| 
       187 
     | 
    
         
            -
                        session =  
     | 
| 
      
 188 
     | 
    
         
            +
                        session = params['sessions'].first
         
     | 
| 
       188 
189 
     | 
    
         
             
                        arguments.push("--name=#{session['name']}")
         
     | 
| 
       189 
190 
     | 
    
         
             
                        if session.key?('local_db_dir')
         
     | 
| 
       190 
191 
     | 
    
         
             
                          arguments.push("--local-db-dir=#{session['local_db_dir']}")
         
     | 
| 
         @@ -193,30 +194,28 @@ module Aspera 
     | 
|
| 
       193 
194 
     | 
    
         
             
                        else
         
     | 
| 
       194 
195 
     | 
    
         
             
                          raise Error, 'Missing either local_db_dir or local_dir'
         
     | 
| 
       195 
196 
     | 
    
         
             
                        end
         
     | 
| 
       196 
     | 
    
         
            -
                      else
         
     | 
| 
       197 
     | 
    
         
            -
                        raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
         
     | 
| 
       198 
197 
     | 
    
         
             
                      end
         
     | 
| 
       199 
198 
     | 
    
         
             
                      stdout = Environment.secure_capture(exec: ASYNC_ADMIN_EXECUTABLE, args: arguments)
         
     | 
| 
       200 
199 
     | 
    
         
             
                      return parse_status(stdout)
         
     | 
| 
       201 
200 
     | 
    
         
             
                    end
         
     | 
| 
       202 
201 
     | 
    
         | 
| 
       203 
     | 
    
         
            -
                    # Find the local database folder based on  
     | 
| 
       204 
     | 
    
         
            -
                    # @param  
     | 
| 
       205 
     | 
    
         
            -
                    # @param exception [Bool] Raise exception in case of problem, else return nil
         
     | 
| 
      
 202 
     | 
    
         
            +
                    # Find the local database folder based on params
         
     | 
| 
      
 203 
     | 
    
         
            +
                    # @param params [Hash] sync parameters in conf or args format
         
     | 
| 
       206 
204 
     | 
    
         
             
                    # @return [String, nil] path to "local DB dir", i.e. folder that contains folders that contain snap.db
         
     | 
| 
       207 
     | 
    
         
            -
                    def local_db_folder( 
     | 
| 
       208 
     | 
    
         
            -
                       
     | 
| 
      
 205 
     | 
    
         
            +
                    def local_db_folder(params)
         
     | 
| 
      
 206 
     | 
    
         
            +
                      Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
         
     | 
| 
      
 207 
     | 
    
         
            +
                      if params.key?('local')
         
     | 
| 
       209 
208 
     | 
    
         
             
                        # "conf" format
         
     | 
| 
       210 
     | 
    
         
            -
                        if  
     | 
| 
       211 
     | 
    
         
            -
                          return  
     | 
| 
       212 
     | 
    
         
            -
                        elsif (local_path =  
     | 
| 
      
 209 
     | 
    
         
            +
                        if params.key?('local_db_dir')
         
     | 
| 
      
 210 
     | 
    
         
            +
                          return params['local_db_dir']
         
     | 
| 
      
 211 
     | 
    
         
            +
                        elsif (local_path = params.dig('local', 'path'))
         
     | 
| 
       213 
212 
     | 
    
         
             
                          return local_path
         
     | 
| 
       214 
213 
     | 
    
         
             
                        elsif exception
         
     | 
| 
       215 
214 
     | 
    
         
             
                          raise Error, 'Missing either local_db_dir or local.path'
         
     | 
| 
       216 
215 
     | 
    
         
             
                        end
         
     | 
| 
       217 
     | 
    
         
            -
                       
     | 
| 
      
 216 
     | 
    
         
            +
                      else
         
     | 
| 
       218 
217 
     | 
    
         
             
                        # "args" format
         
     | 
| 
       219 
     | 
    
         
            -
                        session =  
     | 
| 
      
 218 
     | 
    
         
            +
                        session = params['sessions'].first
         
     | 
| 
       220 
219 
     | 
    
         
             
                        if session.key?('local_db_dir')
         
     | 
| 
       221 
220 
     | 
    
         
             
                          return session['local_db_dir']
         
     | 
| 
       222 
221 
     | 
    
         
             
                        elsif session.key?('local_dir')
         
     | 
| 
         @@ -224,26 +223,23 @@ module Aspera 
     | 
|
| 
       224 
223 
     | 
    
         
             
                        elsif exception
         
     | 
| 
       225 
224 
     | 
    
         
             
                          raise Error, 'Missing either local_db_dir or local_dir'
         
     | 
| 
       226 
225 
     | 
    
         
             
                        end
         
     | 
| 
       227 
     | 
    
         
            -
                      elsif exception
         
     | 
| 
       228 
     | 
    
         
            -
                        raise Error, 'At least one of `local` or `sessions` must be present in async parameters'
         
     | 
| 
       229 
226 
     | 
    
         
             
                      end
         
     | 
| 
       230 
227 
     | 
    
         
             
                      nil
         
     | 
| 
       231 
228 
     | 
    
         
             
                    end
         
     | 
| 
       232 
229 
     | 
    
         | 
| 
       233 
     | 
    
         
            -
                    def session_name( 
     | 
| 
       234 
     | 
    
         
            -
                       
     | 
| 
      
 230 
     | 
    
         
            +
                    def session_name(params)
         
     | 
| 
      
 231 
     | 
    
         
            +
                      Aspera.assert(PARAM_KEYS.any?{ |k| params.key?(k)}, type: Error){'At least one of `local` or `sessions` must be present in async parameters'}
         
     | 
| 
      
 232 
     | 
    
         
            +
                      if params.key?('local')
         
     | 
| 
       235 
233 
     | 
    
         
             
                        # "conf" format
         
     | 
| 
       236 
     | 
    
         
            -
                        return  
     | 
| 
       237 
     | 
    
         
            -
                      elsif sync_params.key?('sessions')
         
     | 
| 
       238 
     | 
    
         
            -
                        # "args" format
         
     | 
| 
       239 
     | 
    
         
            -
                        return sync_params['sessions'].first['name']
         
     | 
| 
      
 234 
     | 
    
         
            +
                        return params['name']
         
     | 
| 
       240 
235 
     | 
    
         
             
                      else
         
     | 
| 
       241 
     | 
    
         
            -
                         
     | 
| 
      
 236 
     | 
    
         
            +
                        # "args" format
         
     | 
| 
      
 237 
     | 
    
         
            +
                        return params['sessions'].first['name']
         
     | 
| 
       242 
238 
     | 
    
         
             
                      end
         
     | 
| 
       243 
239 
     | 
    
         
             
                    end
         
     | 
| 
       244 
240 
     | 
    
         | 
| 
       245 
     | 
    
         
            -
                    def session_db_file( 
     | 
| 
       246 
     | 
    
         
            -
                      db_file = File.join(local_db_folder( 
     | 
| 
      
 241 
     | 
    
         
            +
                    def session_db_file(params)
         
     | 
| 
      
 242 
     | 
    
         
            +
                      db_file = File.join(local_db_folder(params), PRIVATE_FOLDER, session_name(params), ASYNC_DB)
         
     | 
| 
       247 
243 
     | 
    
         
             
                      Aspera.assert(File.exist?(db_file)){"Database file #{db_file} does not exist"}
         
     | 
| 
       248 
244 
     | 
    
         
             
                      db_file
         
     | 
| 
       249 
245 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -281,19 +277,20 @@ module Aspera 
     | 
|
| 
       281 
277 
     | 
    
         
             
                  end
         
     | 
| 
       282 
278 
     | 
    
         
             
                  # Private stuff:
         
     | 
| 
       283 
279 
     | 
    
         
             
                  # Read JSON schema and mapping to command line options
         
     | 
| 
       284 
     | 
    
         
            -
                   
     | 
| 
       285 
     | 
    
         
            -
                   
     | 
| 
       286 
     | 
    
         
            -
                   
     | 
| 
      
 280 
     | 
    
         
            +
                  ARGS_INSTANCE_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'args')
         
     | 
| 
      
 281 
     | 
    
         
            +
                  ARGS_SESSION_SCHEMA = ARGS_INSTANCE_SCHEMA['properties']['sessions']['items']
         
     | 
| 
      
 282 
     | 
    
         
            +
                  ARGS_INSTANCE_SCHEMA['properties'].delete('sessions')
         
     | 
| 
       287 
283 
     | 
    
         
             
                  CONF_SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'conf')
         
     | 
| 
       288 
     | 
    
         
            -
                  CommandLineBuilder. 
     | 
| 
       289 
     | 
    
         
            -
                  CommandLineBuilder. 
     | 
| 
       290 
     | 
    
         
            -
                  CommandLineBuilder. 
     | 
| 
      
 284 
     | 
    
         
            +
                  CommandLineBuilder.validate_schema(ARGS_INSTANCE_SCHEMA)
         
     | 
| 
      
 285 
     | 
    
         
            +
                  CommandLineBuilder.validate_schema(ARGS_SESSION_SCHEMA)
         
     | 
| 
      
 286 
     | 
    
         
            +
                  CommandLineBuilder.validate_schema(CONF_SCHEMA)
         
     | 
| 
       291 
287 
     | 
    
         
             
                  CMDLINE_PARAMS_KEYS = %w[instance sessions].freeze
         
     | 
| 
       292 
288 
     | 
    
         
             
                  ASYNC_ADMIN_EXECUTABLE = 'asyncadmin'
         
     | 
| 
       293 
289 
     | 
    
         
             
                  PRIVATE_FOLDER = '.private-asp'
         
     | 
| 
       294 
290 
     | 
    
         
             
                  ASYNC_DB = 'snap.db'
         
     | 
| 
      
 291 
     | 
    
         
            +
                  PARAM_KEYS = %w[local sessions].freeze
         
     | 
| 
       295 
292 
     | 
    
         | 
| 
       296 
     | 
    
         
            -
                  private_constant : 
     | 
| 
      
 293 
     | 
    
         
            +
                  private_constant :ARGS_INSTANCE_SCHEMA, :ARGS_SESSION_SCHEMA, :CMDLINE_PARAMS_KEYS, :ASYNC_ADMIN_EXECUTABLE, :PRIVATE_FOLDER, :ASYNC_DB, :PARAM_KEYS
         
     | 
| 
       297 
294 
     | 
    
         
             
                end
         
     | 
| 
       298 
295 
     | 
    
         
             
              end
         
     | 
| 
       299 
296 
     | 
    
         
             
            end
         
     | 
| 
         @@ -17,17 +17,29 @@ module Aspera 
     | 
|
| 
       17 
17 
     | 
    
         
             
                private_constant :SEC_IN_DAY, :FILE_LIST_AGE_MAX_SEC
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                attr_accessor :cleanup_on_exit
         
     | 
| 
      
 20 
     | 
    
         
            +
                attr_reader :global_temp
         
     | 
| 
       20 
21 
     | 
    
         | 
| 
       21 
22 
     | 
    
         
             
                def initialize
         
     | 
| 
       22 
23 
     | 
    
         
             
                  @created_files = []
         
     | 
| 
       23 
24 
     | 
    
         
             
                  @cleanup_on_exit = true
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @global_temp = Etc.systmpdir
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def global_temp=(value)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @global_temp = case value
         
     | 
| 
      
 30 
     | 
    
         
            +
                  when '@env' then Dir.tmpdir
         
     | 
| 
      
 31 
     | 
    
         
            +
                  when '@sys' then Etc.systmpdir
         
     | 
| 
      
 32 
     | 
    
         
            +
                  else value
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
       24 
34 
     | 
    
         
             
                end
         
     | 
| 
       25 
35 
     | 
    
         | 
| 
       26 
36 
     | 
    
         
             
                def delete_file(filepath)
         
     | 
| 
       27 
37 
     | 
    
         
             
                  File.delete(filepath) if @cleanup_on_exit
         
     | 
| 
      
 38 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 39 
     | 
    
         
            +
                  Log.log.warn{"Problem deleting file: #{filepath}: #{e.message}"}
         
     | 
| 
       28 
40 
     | 
    
         
             
                end
         
     | 
| 
       29 
41 
     | 
    
         | 
| 
       30 
     | 
    
         
            -
                #  
     | 
| 
      
 42 
     | 
    
         
            +
                # Call this on process exit
         
     | 
| 
       31 
43 
     | 
    
         
             
                def cleanup
         
     | 
| 
       32 
44 
     | 
    
         
             
                  @created_files.each do |filepath|
         
     | 
| 
       33 
45 
     | 
    
         
             
                    delete_file(filepath) if File.file?(filepath)
         
     | 
| 
         @@ -35,7 +47,7 @@ module Aspera 
     | 
|
| 
       35 
47 
     | 
    
         
             
                  @created_files = []
         
     | 
| 
       36 
48 
     | 
    
         
             
                end
         
     | 
| 
       37 
49 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
                #  
     | 
| 
      
 50 
     | 
    
         
            +
                # Ensure that provided folder exists, or create it, generate a unique filename
         
     | 
| 
       39 
51 
     | 
    
         
             
                # @return path to that unique file
         
     | 
| 
       40 
52 
     | 
    
         
             
                def new_file_path_in_folder(temp_folder, prefix: nil, suffix: nil)
         
     | 
| 
       41 
53 
     | 
    
         
             
                  FileUtils.mkdir_p(temp_folder)
         
     | 
| 
         @@ -44,7 +56,7 @@ module Aspera 
     | 
|
| 
       44 
56 
     | 
    
         
             
                  new_file
         
     | 
| 
       45 
57 
     | 
    
         
             
                end
         
     | 
| 
       46 
58 
     | 
    
         | 
| 
       47 
     | 
    
         
            -
                #  
     | 
| 
      
 59 
     | 
    
         
            +
                # Same as above but in global temp folder, with user's name
         
     | 
| 
       48 
60 
     | 
    
         
             
                def new_file_path_global(prefix = nil, suffix: nil)
         
     | 
| 
       49 
61 
     | 
    
         
             
                  username =
         
     | 
| 
       50 
62 
     | 
    
         
             
                    begin
         
     | 
| 
         @@ -53,11 +65,11 @@ module Aspera 
     | 
|
| 
       53 
65 
     | 
    
         
             
                      'unknown_user'
         
     | 
| 
       54 
66 
     | 
    
         
             
                    end
         
     | 
| 
       55 
67 
     | 
    
         
             
                  prefix = [prefix, username].compact.join('-')
         
     | 
| 
       56 
     | 
    
         
            -
                  new_file_path_in_folder( 
     | 
| 
      
 68 
     | 
    
         
            +
                  new_file_path_in_folder(@global_temp, prefix: prefix, suffix: suffix)
         
     | 
| 
       57 
69 
     | 
    
         
             
                end
         
     | 
| 
       58 
70 
     | 
    
         | 
| 
      
 71 
     | 
    
         
            +
                # Garbage collect undeleted files
         
     | 
| 
       59 
72 
     | 
    
         
             
                def cleanup_expired(temp_folder)
         
     | 
| 
       60 
     | 
    
         
            -
                  # garbage collect undeleted files
         
     | 
| 
       61 
73 
     | 
    
         
             
                  Dir.entries(temp_folder).each do |name|
         
     | 
| 
       62 
74 
     | 
    
         
             
                    file_path = File.join(temp_folder, name)
         
     | 
| 
       63 
75 
     | 
    
         
             
                    age_sec = (Time.now - File.stat(file_path).mtime).to_i
         
     | 
| 
         @@ -1,24 +1,33 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            require 'aspera/ 
     | 
| 
      
 3 
     | 
    
         
            +
            require 'aspera/ascp/management'
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
            module Aspera
         
     | 
| 
       6 
6 
     | 
    
         
             
              module Transfer
         
     | 
| 
       7 
     | 
    
         
            -
                #  
     | 
| 
      
 7 
     | 
    
         
            +
                # Error raised if transfer fails
         
     | 
| 
       8 
8 
     | 
    
         
             
                class Error < StandardError
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # Error code like on management port
         
     | 
| 
       9 
10 
     | 
    
         
             
                  attr_reader :err_code
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                   
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
      
 12 
     | 
    
         
            +
                  # @param description [String] `Description` on management port
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # @param code [Integer] `Description` on management port, use zero if unknown
         
     | 
| 
      
 14 
     | 
    
         
            +
                  def initialize(description, code: nil)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    super(description)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @err_code = code.to_i
         
     | 
| 
       14 
17 
     | 
    
         
             
                  end
         
     | 
| 
       15 
18 
     | 
    
         | 
| 
      
 19 
     | 
    
         
            +
                  # @return [Hash] Information on that error
         
     | 
| 
       16 
20 
     | 
    
         
             
                  def info
         
     | 
| 
       17 
     | 
    
         
            -
                    r =  
     | 
| 
      
 21 
     | 
    
         
            +
                    r = Ascp::Management::ERRORS[@err_code] || Ascp::Management::ERRORS[0]
         
     | 
| 
       18 
22 
     | 
    
         
             
                    return r.merge({i: @err_code})
         
     | 
| 
       19 
23 
     | 
    
         
             
                  end
         
     | 
| 
       20 
24 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                   
     | 
| 
      
 25 
     | 
    
         
            +
                  # Is that transfer error retryable ?
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @param message [String, nil] Optional actual message on management port
         
     | 
| 
      
 27 
     | 
    
         
            +
                  def retryable?
         
     | 
| 
      
 28 
     | 
    
         
            +
                    return false if @err_code.eql?(14) && message.eql?('Target address not available')
         
     | 
| 
      
 29 
     | 
    
         
            +
                    info[:r]
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
       22 
31 
     | 
    
         
             
                end
         
     | 
| 
       23 
32 
     | 
    
         
             
              end
         
     | 
| 
       24 
33 
     | 
    
         
             
            end
         
     | 
| 
         @@ -18,11 +18,8 @@ require 'openssl' 
     | 
|
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
            module Aspera
         
     | 
| 
       20 
20 
     | 
    
         
             
              module Transfer
         
     | 
| 
       21 
     | 
    
         
            -
                #  
     | 
| 
      
 21 
     | 
    
         
            +
                # Translate transfer specification to `ascp` parameter list
         
     | 
| 
       22 
22 
     | 
    
         
             
                class Parameters
         
     | 
| 
       23 
     | 
    
         
            -
                  # `ascp` options to provide a file list
         
     | 
| 
       24 
     | 
    
         
            -
                  FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
         
     | 
| 
       25 
     | 
    
         
            -
                  private_constant :FILE_LIST_OPTIONS
         
     | 
| 
       26 
23 
     | 
    
         
             
                  HTTP_FALLBACK_ACTIVATION_VALUES = ['1', 1, true, 'force'].freeze
         
     | 
| 
       27 
24 
     | 
    
         | 
| 
       28 
25 
     | 
    
         
             
                  class << self
         
     | 
| 
         @@ -30,7 +27,6 @@ module Aspera 
     | 
|
| 
       30 
27 
     | 
    
         
             
                    def file_list_folder=(value)
         
     | 
| 
       31 
28 
     | 
    
         
             
                      @file_list_folder = value
         
     | 
| 
       32 
29 
     | 
    
         
             
                      return if @file_list_folder.nil?
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
30 
     | 
    
         
             
                      FileUtils.mkdir_p(@file_list_folder)
         
     | 
| 
       35 
31 
     | 
    
         
             
                      TempFileManager.instance.cleanup_expired(@file_list_folder)
         
     | 
| 
       36 
32 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -42,14 +38,19 @@ module Aspera 
     | 
|
| 
       42 
38 
     | 
    
         
             
                      @file_list_folder ||= TempFileManager.instance.new_file_path_global('asession_filelists')
         
     | 
| 
       43 
39 
     | 
    
         
             
                    end
         
     | 
| 
       44 
40 
     | 
    
         | 
| 
       45 
     | 
    
         
            -
                    #  
     | 
| 
      
 41 
     | 
    
         
            +
                    # File list is provided directly with ascp arguments
         
     | 
| 
       46 
42 
     | 
    
         
             
                    # @columns ascp_args [Array,NilClass] ascp arguments
         
     | 
| 
       47 
43 
     | 
    
         
             
                    def ascp_args_file_list?(ascp_args)
         
     | 
| 
       48 
44 
     | 
    
         
             
                      ascp_args&.any?{ |i| FILE_LIST_OPTIONS.include?(i)}
         
     | 
| 
       49 
45 
     | 
    
         
             
                    end
         
     | 
| 
       50 
46 
     | 
    
         
             
                  end
         
     | 
| 
       51 
47 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
                  # @ 
     | 
| 
      
 48 
     | 
    
         
            +
                  # @param job_spec        [Hash]   Transfer spec
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # @param ascp_args       [Array]  Other ascp args
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # @param quiet           [Bool]   Remove ascp output
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @param trusted_certs   [Array]  Trusted certificates
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # @param client_ssh_key  [Symbol] :rsa or :dsa
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @param check_ignore_cb [Proc]   Callback
         
     | 
| 
       53 
54 
     | 
    
         
             
                  def initialize(
         
     | 
| 
       54 
55 
     | 
    
         
             
                    job_spec,
         
     | 
| 
       55 
56 
     | 
    
         
             
                    ascp_args:       nil,
         
     | 
| 
         @@ -60,17 +61,17 @@ module Aspera 
     | 
|
| 
       60 
61 
     | 
    
         
             
                    check_ignore_cb: nil
         
     | 
| 
       61 
62 
     | 
    
         
             
                  )
         
     | 
| 
       62 
63 
     | 
    
         
             
                    @job_spec = job_spec
         
     | 
| 
      
 64 
     | 
    
         
            +
                    Aspera.assert_type(@job_spec, Hash)
         
     | 
| 
       63 
65 
     | 
    
         
             
                    @ascp_args = ascp_args.nil? ? [] : ascp_args
         
     | 
| 
      
 66 
     | 
    
         
            +
                    Aspera.assert_type(@ascp_args, Array){'ascp_args'}
         
     | 
| 
      
 67 
     | 
    
         
            +
                    Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
         
     | 
| 
       64 
68 
     | 
    
         
             
                    @wss = wss
         
     | 
| 
       65 
69 
     | 
    
         
             
                    @quiet = quiet
         
     | 
| 
       66 
70 
     | 
    
         
             
                    @trusted_certs = trusted_certs.nil? ? [] : trusted_certs
         
     | 
| 
       67 
     | 
    
         
            -
                    @client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
         
     | 
| 
       68 
     | 
    
         
            -
                    @check_ignore_cb = check_ignore_cb
         
     | 
| 
       69 
     | 
    
         
            -
                    Aspera.assert_type(@job_spec, Hash)
         
     | 
| 
       70 
     | 
    
         
            -
                    Aspera.assert_type(@ascp_args, Array){'ascp_args'}
         
     | 
| 
       71 
     | 
    
         
            -
                    Aspera.assert(@ascp_args.all?(String)){'all ascp arguments must be String'}
         
     | 
| 
       72 
71 
     | 
    
         
             
                    Aspera.assert_type(@trusted_certs, Array){'trusted_certs'}
         
     | 
| 
      
 72 
     | 
    
         
            +
                    @client_ssh_key = client_ssh_key.nil? ? :rsa : client_ssh_key.to_sym
         
     | 
| 
       73 
73 
     | 
    
         
             
                    Aspera.assert_values(@client_ssh_key, Ascp::Installation::CLIENT_SSH_KEY_OPTIONS)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    @check_ignore_cb = check_ignore_cb
         
     | 
| 
       74 
75 
     | 
    
         
             
                    @builder = CommandLineBuilder.new(@job_spec, Spec::SCHEMA, CommandLineConverter)
         
     | 
| 
       75 
76 
     | 
    
         
             
                  end
         
     | 
| 
       76 
77 
     | 
    
         | 
| 
         @@ -109,7 +110,7 @@ module Aspera 
     | 
|
| 
       109 
110 
     | 
    
         
             
                        Log.log.debug{"#{file_list_option}=\n#{File.read(file_list_file)}".red}
         
     | 
| 
       110 
111 
     | 
    
         
             
                      end
         
     | 
| 
       111 
112 
     | 
    
         
             
                    end
         
     | 
| 
       112 
     | 
    
         
            -
                    @builder.add_command_line_options( 
     | 
| 
      
 113 
     | 
    
         
            +
                    @builder.add_command_line_options("#{file_list_option}=#{file_list_file}") unless file_list_option.nil?
         
     | 
| 
       113 
114 
     | 
    
         
             
                  end
         
     | 
| 
       114 
115 
     | 
    
         | 
| 
       115 
116 
     | 
    
         
             
                  # @return the list of certificates (option `-i`) to use when token/ssh or wss are used
         
     | 
| 
         @@ -118,7 +119,7 @@ module Aspera 
     | 
|
| 
       118 
119 
     | 
    
         
             
                    # use web socket secure for session ?
         
     | 
| 
       119 
120 
     | 
    
         
             
                    if @builder.read_param('wss_enabled') && (@wss || !@job_spec.key?('fasp_port'))
         
     | 
| 
       120 
121 
     | 
    
         
             
                      # by default use web socket session if available, unless removed by user
         
     | 
| 
       121 
     | 
    
         
            -
                      @builder.add_command_line_options( 
     | 
| 
      
 122 
     | 
    
         
            +
                      @builder.add_command_line_options('--ws-connect')
         
     | 
| 
       122 
123 
     | 
    
         
             
                      # TODO: option to give order ssh,ws (legacy http is implied by ssh)
         
     | 
| 
       123 
124 
     | 
    
         
             
                      # This will need to be cleaned up in aspera core
         
     | 
| 
       124 
125 
     | 
    
         
             
                      @job_spec['ssh_port'] = @builder.read_param('wss_port')
         
     | 
| 
         @@ -148,7 +149,7 @@ module Aspera 
     | 
|
| 
       148 
149 
     | 
    
         
             
                    return certificates_to_use
         
     | 
| 
       149 
150 
     | 
    
         
             
                  end
         
     | 
| 
       150 
151 
     | 
    
         | 
| 
       151 
     | 
    
         
            -
                  #  
     | 
| 
      
 152 
     | 
    
         
            +
                  # Translate transfer spec to env vars and command line arguments for `ascp`
         
     | 
| 
       152 
153 
     | 
    
         
             
                  def ascp_args
         
     | 
| 
       153 
154 
     | 
    
         
             
                    env_args = {
         
     | 
| 
       154 
155 
     | 
    
         
             
                      args: [],
         
     | 
| 
         @@ -156,18 +157,27 @@ module Aspera 
     | 
|
| 
       156 
157 
     | 
    
         
             
                      name: :ascp
         
     | 
| 
       157 
158 
     | 
    
         
             
                    }
         
     | 
| 
       158 
159 
     | 
    
         | 
| 
       159 
     | 
    
         
            -
                    #  
     | 
| 
      
 160 
     | 
    
         
            +
                    # Special cases
         
     | 
| 
       160 
161 
     | 
    
         
             
                    @job_spec.delete('source_root') if @job_spec.key?('source_root') && @job_spec['source_root'].empty?
         
     | 
| 
       161 
162 
     | 
    
         | 
| 
       162 
     | 
    
         
            -
                    #  
     | 
| 
      
 163 
     | 
    
         
            +
                    # Notify multi-session was already used, anyway it was deleted by agent direct
         
     | 
| 
       163 
164 
     | 
    
         
             
                    Aspera.assert(!@builder.read_param('multi_session'))
         
     | 
| 
       164 
165 
     | 
    
         | 
| 
       165 
     | 
    
         
            -
                    #  
     | 
| 
      
 166 
     | 
    
         
            +
                    # Add ssh or wss certificates
         
     | 
| 
       166 
167 
     | 
    
         
             
                    # (reverse, to keep order, as we unshift)
         
     | 
| 
       167 
168 
     | 
    
         
             
                    remote_certificates&.reverse_each do |cert|
         
     | 
| 
       168 
169 
     | 
    
         
             
                      env_args[:args].unshift('-i', cert)
         
     | 
| 
       169 
170 
     | 
    
         
             
                    end
         
     | 
| 
       170 
171 
     | 
    
         | 
| 
      
 172 
     | 
    
         
            +
                    case (delete_source = @builder.read_param('delete_source'))
         
     | 
| 
      
 173 
     | 
    
         
            +
                    when true
         
     | 
| 
      
 174 
     | 
    
         
            +
                      DELETE_EQUIV.each{ |i| @job_spec[i] = true}
         
     | 
| 
      
 175 
     | 
    
         
            +
                    when false
         
     | 
| 
      
 176 
     | 
    
         
            +
                      DELETE_EQUIV.each{ |i| @job_spec.delete(i)}
         
     | 
| 
      
 177 
     | 
    
         
            +
                    when nil
         
     | 
| 
      
 178 
     | 
    
         
            +
                    else Aspera.error_unexpected_value(delete_source){'delete_source'}
         
     | 
| 
      
 179 
     | 
    
         
            +
                    end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
       171 
181 
     | 
    
         
             
                    # process parameters as specified in table
         
     | 
| 
       172 
182 
     | 
    
         
             
                    @builder.process_params
         
     | 
| 
       173 
183 
     | 
    
         | 
| 
         @@ -180,7 +190,7 @@ module Aspera 
     | 
|
| 
       180 
190 
     | 
    
         
             
                      base64_destination = true
         
     | 
| 
       181 
191 
     | 
    
         
             
                    end
         
     | 
| 
       182 
192 
     | 
    
         
             
                    # destination will be base64 encoded, put this before source path arguments
         
     | 
| 
       183 
     | 
    
         
            -
                    @builder.add_command_line_options( 
     | 
| 
      
 193 
     | 
    
         
            +
                    @builder.add_command_line_options('--dest64') if base64_destination
         
     | 
| 
       184 
194 
     | 
    
         
             
                    # optional arguments, at the end to override previous ones (to allow override)
         
     | 
| 
       185 
195 
     | 
    
         
             
                    @builder.add_command_line_options(@ascp_args)
         
     | 
| 
       186 
196 
     | 
    
         
             
                    # get list of source files to transfer and build arg for ascp
         
     | 
| 
         @@ -190,7 +200,7 @@ module Aspera 
     | 
|
| 
       190 
200 
     | 
    
         
             
                    # ascp4 does not support base64 encoding of destination
         
     | 
| 
       191 
201 
     | 
    
         
             
                    destination_folder = Base64.strict_encode64(destination_folder) if base64_destination
         
     | 
| 
       192 
202 
     | 
    
         
             
                    # destination MUST be last command line argument to ascp
         
     | 
| 
       193 
     | 
    
         
            -
                    @builder.add_command_line_options( 
     | 
| 
      
 203 
     | 
    
         
            +
                    @builder.add_command_line_options(destination_folder)
         
     | 
| 
       194 
204 
     | 
    
         
             
                    @builder.add_env_args(env_args)
         
     | 
| 
       195 
205 
     | 
    
         
             
                    env_args[:args].unshift('-q') if @quiet
         
     | 
| 
       196 
206 
     | 
    
         
             
                    # add fallback cert and key as arguments if needed
         
     | 
| 
         @@ -203,6 +213,10 @@ module Aspera 
     | 
|
| 
       203 
213 
     | 
    
         
             
                    Log.log.debug{"ascp args: #{env_args}"}
         
     | 
| 
       204 
214 
     | 
    
         
             
                    return env_args
         
     | 
| 
       205 
215 
     | 
    
         
             
                  end
         
     | 
| 
      
 216 
     | 
    
         
            +
                  DELETE_EQUIV = %w[remove_after_transfer remove_empty_directories remove_empty_source_directory]
         
     | 
| 
      
 217 
     | 
    
         
            +
                  # `ascp` options to provide a file list
         
     | 
| 
      
 218 
     | 
    
         
            +
                  FILE_LIST_OPTIONS = ['--file-list', '--file-pair-list'].freeze
         
     | 
| 
      
 219 
     | 
    
         
            +
                  private_constant :DELETE_EQUIV, :FILE_LIST_OPTIONS
         
     | 
| 
       206 
220 
     | 
    
         
             
                end
         
     | 
| 
       207 
221 
     | 
    
         
             
              end
         
     | 
| 
       208 
222 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,74 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'singleton'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'aspera/log'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'aspera/assert'
         
     | 
| 
      
 6 
     | 
    
         
            +
            require 'aspera/transfer/error'
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Aspera
         
     | 
| 
      
 9 
     | 
    
         
            +
              module Transfer
         
     | 
| 
      
 10 
     | 
    
         
            +
                # Implements a simple resume policy
         
     | 
| 
      
 11 
     | 
    
         
            +
                class Resumer
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # @param iter_max      [Integer] Maximum number of executions
         
     | 
| 
      
 13 
     | 
    
         
            +
                  # @param sleep_initial [Integer] Initial wait to re-execute
         
     | 
| 
      
 14 
     | 
    
         
            +
                  # @param sleep_factor  [Integer] Multiplier
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # @param sleep_max.    [Integer] Max iterations
         
     | 
| 
      
 16 
     | 
    
         
            +
                  def initialize(
         
     | 
| 
      
 17 
     | 
    
         
            +
                    iter_max: 7,
         
     | 
| 
      
 18 
     | 
    
         
            +
                    sleep_initial: 2,
         
     | 
| 
      
 19 
     | 
    
         
            +
                    sleep_factor:  2,
         
     | 
| 
      
 20 
     | 
    
         
            +
                    sleep_max:     60
         
     | 
| 
      
 21 
     | 
    
         
            +
                  )
         
     | 
| 
      
 22 
     | 
    
         
            +
                    Aspera.assert_type(iter_max, Integer){k}
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @iter_max = iter_max
         
     | 
| 
      
 24 
     | 
    
         
            +
                    Aspera.assert_type(sleep_initial, Integer){k}
         
     | 
| 
      
 25 
     | 
    
         
            +
                    @sleep_initial = sleep_initial
         
     | 
| 
      
 26 
     | 
    
         
            +
                    Aspera.assert_type(sleep_factor, Integer){k}
         
     | 
| 
      
 27 
     | 
    
         
            +
                    @sleep_factor = sleep_factor
         
     | 
| 
      
 28 
     | 
    
         
            +
                    Aspera.assert_type(sleep_max, Integer){k}
         
     | 
| 
      
 29 
     | 
    
         
            +
                    @sleep_max = sleep_max
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  # Calls block a number of times (resumes) until success or limit reached
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # This is re-entrant, one resumer can handle multiple transfers in //
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # @param block [Proc]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  def execute_with_resume
         
     | 
| 
      
 37 
     | 
    
         
            +
                    Aspera.assert(block_given?)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    # maximum of retry
         
     | 
| 
      
 39 
     | 
    
         
            +
                    remaining_resumes = @iter_max
         
     | 
| 
      
 40 
     | 
    
         
            +
                    sleep_seconds = @sleep_initial
         
     | 
| 
      
 41 
     | 
    
         
            +
                    Log.log.debug{"retries=#{remaining_resumes}"}
         
     | 
| 
      
 42 
     | 
    
         
            +
                    # try to send the file until ascp is successful
         
     | 
| 
      
 43 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 44 
     | 
    
         
            +
                      Log.log.debug('Transfer session starting')
         
     | 
| 
      
 45 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 46 
     | 
    
         
            +
                        # Call provided block: execute transfer
         
     | 
| 
      
 47 
     | 
    
         
            +
                        yield
         
     | 
| 
      
 48 
     | 
    
         
            +
                        # Exit retry loop if success
         
     | 
| 
      
 49 
     | 
    
         
            +
                        break
         
     | 
| 
      
 50 
     | 
    
         
            +
                      rescue Error => e
         
     | 
| 
      
 51 
     | 
    
         
            +
                        Log.log.warn{"A transfer error occurred during transfer: #{e.message}"}
         
     | 
| 
      
 52 
     | 
    
         
            +
                        Log.log.debug{"Retryable ? #{e.retryable?}"}
         
     | 
| 
      
 53 
     | 
    
         
            +
                        # do not retry non-retryable
         
     | 
| 
      
 54 
     | 
    
         
            +
                        raise unless e.retryable?
         
     | 
| 
      
 55 
     | 
    
         
            +
                        # exit if we exceed the max number of retry
         
     | 
| 
      
 56 
     | 
    
         
            +
                        raise Error, "Maximum number of retry reached: #{@iter_max}" if remaining_resumes <= 0
         
     | 
| 
      
 57 
     | 
    
         
            +
                      end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                      # take this retry in account
         
     | 
| 
      
 60 
     | 
    
         
            +
                      remaining_resumes -= 1
         
     | 
| 
      
 61 
     | 
    
         
            +
                      Log.log.warn{"Resuming in #{sleep_seconds} seconds (retry left:#{remaining_resumes})"}
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                      # wait a bit before retrying, maybe network condition will be better
         
     | 
| 
      
 64 
     | 
    
         
            +
                      sleep(sleep_seconds)
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                      # increase retry period
         
     | 
| 
      
 67 
     | 
    
         
            +
                      sleep_seconds *= @sleep_factor
         
     | 
| 
      
 68 
     | 
    
         
            +
                      # cap value
         
     | 
| 
      
 69 
     | 
    
         
            +
                      sleep_seconds = @sleep_max if sleep_seconds > @sleep_max
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/aspera/transfer/spec.rb
    CHANGED
    
    | 
         @@ -5,7 +5,8 @@ require 'aspera/assert' 
     | 
|
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module Aspera
         
     | 
| 
       7 
7 
     | 
    
         
             
              module Transfer
         
     | 
| 
       8 
     | 
    
         
            -
                #  
     | 
| 
      
 8 
     | 
    
         
            +
                # Parameters for Transfer Spec
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Parameters are generated from JSON Schema.
         
     | 
| 
       9 
10 
     | 
    
         
             
                class Spec
         
     | 
| 
       10 
11 
     | 
    
         
             
                  # default transfer username for access key based transfers
         
     | 
| 
       11 
12 
     | 
    
         
             
                  ACCESS_KEY_TRANSFER_USER = 'xfer'
         
     | 
| 
         @@ -44,12 +45,12 @@ module Aspera 
     | 
|
| 
       44 
45 
     | 
    
         
             
                    end
         
     | 
| 
       45 
46 
     | 
    
         | 
| 
       46 
47 
     | 
    
         
             
                    def fix_transferd_resume_policy(transfer_spec)
         
     | 
| 
       47 
     | 
    
         
            -
                      # Fix  
     | 
| 
      
 48 
     | 
    
         
            +
                      # Fix discrepancy in transfer spec
         
     | 
| 
       48 
49 
     | 
    
         
             
                      transfer_spec['resume_policy'] = POLICY_FIX[transfer_spec['resume_policy']] if transfer_spec.key?('resume_policy')
         
     | 
| 
       49 
50 
     | 
    
         
             
                    end
         
     | 
| 
       50 
51 
     | 
    
         
             
                  end
         
     | 
| 
       51 
52 
     | 
    
         
             
                  SCHEMA = CommandLineBuilder.read_schema(__FILE__, 'spec')
         
     | 
| 
       52 
     | 
    
         
            -
                  CommandLineBuilder. 
     | 
| 
      
 53 
     | 
    
         
            +
                  CommandLineBuilder.validate_schema(SCHEMA, ascp: true)
         
     | 
| 
       53 
54 
     | 
    
         
             
                  # define constants for enums of parameters: <parameter>_<enum>, e.g. CIPHER_AES_128, DIRECTION_SEND, ...
         
     | 
| 
       54 
55 
     | 
    
         
             
                  SCHEMA['properties'].each do |name, description|
         
     | 
| 
       55 
56 
     | 
    
         
             
                    next unless description['enum'].is_a?(Array)
         
     |