aspera-cli 4.27.3 → 4.27.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +118 -0
  4. data/CONTRIBUTING.md +5 -2
  5. data/bin/ascli +1 -0
  6. data/docs/README.md +224 -42
  7. data/lib/aspera/agent/base.rb +7 -2
  8. data/lib/aspera/agent/connect.rb +0 -4
  9. data/lib/aspera/agent/desktop.rb +0 -4
  10. data/lib/aspera/agent/direct.rb +49 -21
  11. data/lib/aspera/agent/node.rb +6 -5
  12. data/lib/aspera/agent/transferd.rb +2 -2
  13. data/lib/aspera/api/faspex.rb +2 -0
  14. data/lib/aspera/api/httpgw.rb +1 -2
  15. data/lib/aspera/api/node.rb +3 -3
  16. data/lib/aspera/ascp/installation.rb +1 -1
  17. data/lib/aspera/cli/async_transfer_store.rb +10 -9
  18. data/lib/aspera/cli/bootstrapper.rb +3 -1
  19. data/lib/aspera/cli/command_registry.rb +69 -7
  20. data/lib/aspera/cli/command_spec.rb +1 -1
  21. data/lib/aspera/cli/extended_value.rb +4 -3
  22. data/lib/aspera/cli/formatter.rb +10 -8
  23. data/lib/aspera/cli/http.rb +8 -20
  24. data/lib/aspera/cli/option_types.rb +3 -1
  25. data/lib/aspera/cli/option_value.rb +16 -19
  26. data/lib/aspera/cli/options.schema.yaml +86 -10
  27. data/lib/aspera/cli/parser.rb +30 -18
  28. data/lib/aspera/cli/plugins/aoc.rb +133 -156
  29. data/lib/aspera/cli/plugins/ats.rb +1 -7
  30. data/lib/aspera/cli/plugins/base.rb +40 -34
  31. data/lib/aspera/cli/plugins/config.rb +24 -11
  32. data/lib/aspera/cli/plugins/console.rb +1 -1
  33. data/lib/aspera/cli/plugins/faspex5.rb +30 -11
  34. data/lib/aspera/cli/plugins/faspio.rb +1 -1
  35. data/lib/aspera/cli/plugins/node.rb +26 -19
  36. data/lib/aspera/cli/plugins/orchestrator.rb +73 -44
  37. data/lib/aspera/cli/plugins/preview.rb +21 -19
  38. data/lib/aspera/cli/plugins/server.rb +3 -4
  39. data/lib/aspera/cli/plugins/shares.rb +21 -24
  40. data/lib/aspera/cli/preset_actions.rb +28 -18
  41. data/lib/aspera/cli/preset_manager.rb +34 -19
  42. data/lib/aspera/cli/prompt.rb +2 -1
  43. data/lib/aspera/cli/result.rb +33 -22
  44. data/lib/aspera/cli/runner.rb +1 -5
  45. data/lib/aspera/cli/sync_actions.rb +17 -16
  46. data/lib/aspera/cli/transfer_actions.rb +14 -3
  47. data/lib/aspera/cli/transfer_agent.rb +6 -4
  48. data/lib/aspera/cli/transfer_progress.rb +290 -55
  49. data/lib/aspera/cli/version.rb +1 -1
  50. data/lib/aspera/cli/wizard.rb +1 -1
  51. data/lib/aspera/coverage.rb +0 -1
  52. data/lib/aspera/environment.rb +29 -5
  53. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  54. data/lib/aspera/keychain/factory.rb +2 -1
  55. data/lib/aspera/log.rb +25 -2
  56. data/lib/aspera/node_emulator.rb +759 -0
  57. data/lib/aspera/oauth/base.rb +2 -1
  58. data/lib/aspera/oauth/factory.rb +6 -3
  59. data/lib/aspera/oauth/json_credentials.rb +34 -0
  60. data/lib/aspera/oauth/jwt.rb +3 -4
  61. data/lib/aspera/oauth.rb +1 -0
  62. data/lib/aspera/persistency_folder.rb +1 -3
  63. data/lib/aspera/preview/generator.rb +4 -1
  64. data/lib/aspera/preview/options.schema.yaml +119 -0
  65. data/lib/aspera/rest/aspera_errors.rb +12 -0
  66. data/lib/aspera/rest/client.rb +28 -19
  67. data/lib/aspera/rest/list.rb +14 -8
  68. data/lib/aspera/rest/parameters.rb +2 -2
  69. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -20
  70. data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +1784 -0
  71. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +1230 -137
  72. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +14 -13
  73. data/lib/aspera/schema/reader.rb +12 -18
  74. data/lib/aspera/schema/registry.rb +6 -1
  75. data.tar.gz.sig +0 -0
  76. metadata +5 -3
  77. metadata.gz.sig +0 -0
  78. data/lib/aspera/node_simulator.rb +0 -345
  79. data/lib/aspera/preview/options.rb +0 -45
@@ -83,7 +83,7 @@ module Aspera
83
83
  # @return [String] The bearer token
84
84
  def token(cache: true, refresh: false)
85
85
  # Get token info from cache (or nil), decoded with date and expiration status
86
- token_info = Factory.instance.get_token_info(@token_cache_id) if cache
86
+ token_info = Factory.instance.get_token_info(@token_cache_id, token_field: @token_field) if cache
87
87
  token_data = nil
88
88
  unless token_info.nil?
89
89
  token_data = token_info[:data]
@@ -130,6 +130,7 @@ module Aspera
130
130
  json_data = create_token.body
131
131
  token_data = JSON.parse(json_data)
132
132
  Factory.instance.persist_mgr.put(@token_cache_id, json_data)
133
+ Factory.instance.get_token_info(@token_cache_id, token_field: @token_field)
133
134
  end
134
135
  Aspera.assert(token_data.key?(@token_field)) { "API error: No such field in answer: #{@token_field}" } unless token_data.nil?
135
136
  # Ok we shall have a token here
@@ -121,7 +121,8 @@ module Aspera
121
121
  data.each.map do |k, v|
122
122
  info = {id: k}
123
123
  begin; info.merge!(JSON.parse(v)); rescue StandardError; nil; end
124
- d = decode_token(info.delete(TOKEN_FIELD))
124
+ token_val = info.delete(TOKEN_FIELD) || info.delete('token')
125
+ d = decode_token(token_val)
125
126
  info.merge(d) if d
126
127
  info
127
128
  end
@@ -129,13 +130,15 @@ module Aspera
129
130
 
130
131
  # Get token information from cache
131
132
  # @param id [String] identifier of token
133
+ # @param token_field [String, nil] Field containing the token, or nil to auto-detect
132
134
  # @return [Hash] token internal information , including Date object for `expiration_date`
133
- def get_token_info(id)
135
+ def get_token_info(id, token_field: nil)
134
136
  token_raw_string = persist_mgr.get(id)
135
137
  return if token_raw_string.nil?
136
138
  token_data = JSON.parse(token_raw_string)
137
139
  Aspera.assert_type(token_data, Hash)
138
- decoded_token = decode_token(token_data[TOKEN_FIELD])
140
+ token_value = token_data[token_field] || token_data[TOKEN_FIELD] || token_data['token']
141
+ decoded_token = decode_token(token_value)
139
142
  info = {data: token_data}
140
143
  if decoded_token.is_a?(Hash)
141
144
  info[:decoded] = decoded_token
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/oauth/base'
4
+ require 'digest'
5
+
6
+ module Aspera
7
+ module OAuth
8
+ # Token creator using JSON credentials (e.g., username/password or apikey) sent to a login endpoint
9
+ class JsonCredentials < Base
10
+ # @param json [Hash] Body parameters to send as JSON (e.g. {username:, password:} or {apikey:})
11
+ # @param generic_params [Hash] Generic parameters for OAuth::Base
12
+ def initialize(
13
+ json:,
14
+ **generic_params
15
+ )
16
+ cache_id = json[:username] || Digest::SHA256.hexdigest(json[:apikey] || json.to_s)[0..23]
17
+ super(**generic_params, cache_ids: [cache_id])
18
+ @body = json
19
+ end
20
+
21
+ def create_token
22
+ api.call(
23
+ operation: 'POST',
24
+ subpath: path_token,
25
+ content_type: Mime::JSON,
26
+ body: @body,
27
+ headers: {'Accept' => Mime::JSON},
28
+ ret: :resp
29
+ )
30
+ end
31
+ end
32
+ Factory.instance.register_token_creator(JsonCredentials)
33
+ end
34
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'aspera/oauth/base'
4
+ require 'aspera/environment'
4
5
  require 'aspera/assert'
5
6
  require 'securerandom'
6
7
  require 'openssl'
@@ -17,10 +18,8 @@ module Aspera
17
18
  class << self
18
19
  def generate_rsa_private_key(path:, length: DEFAULT_PRIV_KEY_LENGTH)
19
20
  priv_key = OpenSSL::PKey::RSA.new(length)
20
- File.write(path, priv_key.to_s)
21
- File.write("#{path}.pub", priv_key.public_key.to_s)
22
- Environment.restrict_file_access(path)
23
- Environment.restrict_file_access("#{path}.pub")
21
+ Environment.write_file_restricted(path, force: true) { priv_key.to_s }
22
+ Environment.write_file_restricted("#{path}.pub", force: true) { priv_key.public_key.to_s }
24
23
  nil
25
24
  end
26
25
  end
data/lib/aspera/oauth.rb CHANGED
@@ -6,3 +6,4 @@ require 'aspera/oauth/generic'
6
6
  require 'aspera/oauth/jwt'
7
7
  require 'aspera/oauth/web'
8
8
  require 'aspera/oauth/url_json'
9
+ require 'aspera/oauth/json_credentials'
@@ -44,9 +44,7 @@ module Aspera
44
44
  Aspera.assert_type(value, String)
45
45
  persist_filepath = id_to_filepath(object_id)
46
46
  Log.log.debug { "persistency saving: #{persist_filepath}" }
47
- FileUtils.rm_f(persist_filepath)
48
- File.write(persist_filepath, value)
49
- Environment.restrict_file_access(persist_filepath)
47
+ Environment.write_file_restricted(persist_filepath, force: true) { value }
50
48
  @cache[object_id] = value
51
49
  nil
52
50
  end
@@ -4,9 +4,9 @@
4
4
  # spellchecker:ignore soffice pauseframes libx264 trunc bufsize muxer apng libmp3lame maxrate posterize movflags faststart
5
5
  # spellchecker:ignore palettegen paletteuse pointsize bordercolor repage lanczos unoconv optipng reencode conv transframes
6
6
 
7
- require 'aspera/preview/options'
8
7
  require 'aspera/preview/utils'
9
8
  require 'aspera/preview/file_types'
9
+ require 'aspera/schema/registry'
10
10
  require 'aspera/log'
11
11
  require 'aspera/assert'
12
12
 
@@ -20,6 +20,9 @@ module Aspera
20
20
  # List of valid ffmpeg option keys for reencode configuration.
21
21
  FFMPEG_OPTIONS_LIST = %w[in out].freeze
22
22
 
23
+ # Generator options, members defined in `options.schema.yaml`.
24
+ Options = Struct.new(*Schema::Registry.instance.reader(Schema::Registry::PREVIEW_OPTIONS).current['properties'].keys.map(&:to_sym))
25
+
23
26
  # CLI needs to know conversion type to know if need skip it.
24
27
  # One of CONVERSION_TYPES.
25
28
  attr_reader :conversion_type, :destination
@@ -0,0 +1,119 @@
1
+ $schema: https://json-schema.org/draft/2020-12/schema
2
+ $id: https://github.com/IBM/aspera-cli/tree/main/lib/aspera/preview/options.schema.yaml
3
+ title: Preview generator options
4
+ description: >-
5
+ Options of the preview generator: tools and parameters used to generate PNG thumbnails and MP4 video previews.
6
+ $comment: >-
7
+ Used to declare the options of the `preview` plugin: values of `enum` properties are `Symbol`.
8
+ Scaling uses `ffmpeg` scale filter arguments, see: https://trac.ffmpeg.org/wiki/Scaling
9
+ (`iw`/`ih`: input width/height, `-x`: keep aspect ratio, with a value multiple of `x`).
10
+ type: object
11
+ additionalProperties: false
12
+ properties:
13
+ office_conversion:
14
+ description: 'PNG thumbnail of office document: conversion tool.'
15
+ type: string
16
+ enum: [soffice, unoconv]
17
+ default: soffice
18
+ max_size:
19
+ description: 'Maximum size (in bytes) of a preview file: a warning is logged if exceeded.'
20
+ type: integer
21
+ minimum: 1
22
+ default: 16777216
23
+ thumb_vid_scale:
24
+ description: 'PNG thumbnail of video: frame size, as `ffmpeg` scale filter argument.'
25
+ type: string
26
+ default: '-1:min(ih,100)'
27
+ thumb_vid_fraction:
28
+ description: 'PNG thumbnail of video (`fixed`): position of the snapshot, as a fraction of the video duration.'
29
+ type: number
30
+ minimum: 0
31
+ maximum: 1
32
+ default: 0.1
33
+ thumb_img_size:
34
+ description: 'PNG thumbnail of image, PDF, office document or text: size (in pixels).'
35
+ type: integer
36
+ minimum: 1
37
+ default: 800
38
+ thumb_text_font:
39
+ description: 'PNG thumbnail of text: font name, as listed by `magick identify -list font`.'
40
+ type: string
41
+ default: Courier
42
+ video_conversion:
43
+ description: >-
44
+ MP4 video preview: generation method.
45
+ `reencode`: re-encode the beginning of the video,
46
+ `blend`: key frames with transitions,
47
+ `clips`: concatenation of short clips.
48
+ type: string
49
+ enum: [reencode, blend, clips]
50
+ default: reencode
51
+ video_png_conv:
52
+ description: >-
53
+ PNG thumbnail of video: generation method.
54
+ `fixed`: single frame,
55
+ `animated`: animated PNG.
56
+ type: string
57
+ enum: [fixed, animated]
58
+ default: fixed
59
+ video_scale:
60
+ description: 'MP4 video preview: frame size, as `ffmpeg` scale filter argument.'
61
+ type: string
62
+ default: 'min(iw,360):-2'
63
+ video_start_sec:
64
+ description: 'Video: start offset (in seconds) of MP4 video preview and of `animated` PNG thumbnail.'
65
+ type: integer
66
+ minimum: 0
67
+ default: 10
68
+ video_codec:
69
+ description: >-
70
+ MP4 video preview (`reencode`): `ffmpeg` video codec, e.g. `libx264`, `h264_videotoolbox`, `h264_nvenc`.
71
+ Default: first available H.264 encoder.
72
+ type: string
73
+ reencode_ffmpeg:
74
+ description: >-
75
+ MP4 video preview (`reencode`): `ffmpeg` options replacing the default ones.
76
+ type: object
77
+ additionalProperties: false
78
+ properties:
79
+ in:
80
+ description: Input options.
81
+ type: array
82
+ items:
83
+ type: [string, number]
84
+ out:
85
+ description: Output options.
86
+ type: array
87
+ items:
88
+ type: [string, number]
89
+ default: {}
90
+ blend_keyframes:
91
+ description: 'MP4 video preview (`blend`): number of key frames.'
92
+ type: integer
93
+ minimum: 1
94
+ default: 30
95
+ blend_pauseframes:
96
+ description: 'MP4 video preview (`blend`): number of pause frames (repetitions of each key frame).'
97
+ type: integer
98
+ minimum: 0
99
+ default: 3
100
+ blend_transframes:
101
+ description: 'MP4 video preview (`blend`): number of transition frames between key frames.'
102
+ type: integer
103
+ minimum: 0
104
+ default: 5
105
+ blend_fps:
106
+ description: 'MP4 video preview (`blend`): frames per second.'
107
+ type: integer
108
+ minimum: 1
109
+ default: 15
110
+ clips_count:
111
+ description: 'MP4 video preview (`clips`): number of clips.'
112
+ type: integer
113
+ minimum: 1
114
+ default: 5
115
+ clips_length:
116
+ description: 'Video: length (in seconds) of each clip of MP4 video preview (`clips`), and of `animated` PNG thumbnail.'
117
+ type: integer
118
+ minimum: 1
119
+ default: 5
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'aspera/rest/error_analyzer'
4
4
  require 'aspera/log'
5
+ require 'xmlsimple'
5
6
 
6
7
  module Aspera
7
8
  module Rest
@@ -45,6 +46,17 @@ module Aspera
45
46
  ErrorAnalyzer.instance.add_handler('Orchestrator') do |type, context|
46
47
  next if context[:response].code.start_with?('2')
47
48
  data = context[:data]
49
+ if data.is_a?(String) && data.start_with?('<?xml')
50
+ parsed = XmlSimple.xml_in(data, {'ForceArray' => false}) rescue nil
51
+ if parsed.is_a?(Hash) && parsed['content'].is_a?(String)
52
+ ErrorAnalyzer.add_error(context, type, parsed['content'].gsub(/\A"|"\z/, ''))
53
+ parsed.each do |k, v|
54
+ next if k.eql?('content')
55
+ ErrorAnalyzer.add_error(context, "#{type}(sub)", "#{k}: #{v}") if [String, Integer].include?(v.class)
56
+ end
57
+ end
58
+ next
59
+ end
48
60
  next unless data.is_a?(Hash)
49
61
  work_order = data['work_order']
50
62
  next unless work_order.is_a?(Hash)
@@ -401,31 +401,39 @@ module Aspera
401
401
  Log.log.debug { "saving to: #{target_file}" }
402
402
  written_size = 0
403
403
  session_id = SecureRandom.uuid.freeze
404
- Parameters.instance.progress_bar&.event(:session_start, session_id: session_id)
405
- Parameters.instance.progress_bar&.event(:session_size, session_id: session_id, info: total_size) if total_size
404
+ progress = Parameters.instance.progress_bar
405
+ progress&.event(:session_start, session_id: session_id)
406
+ progress&.event(:session_size, session_id: session_id, info: total_size) if total_size
406
407
  limiter = TimerLimiter.new(0.5)
407
- if target_file.respond_to?(:write)
408
- # IO object: stream directly into it
409
- response.read_body do |fragment|
410
- target_file.write(fragment)
411
- written_size += fragment.length
412
- Parameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
413
- end
414
- else
415
- # file path: download to partial name first, then rename atomically
416
- target_file_tmp = "#{target_file}#{Parameters.instance.download_partial_suffix}"
417
- FileUtils.mkdir_p(File.dirname(target_file_tmp))
418
- File.open(target_file_tmp, 'wb') do |file|
408
+ success = false
409
+ begin
410
+ if target_file.respond_to?(:write)
411
+ # IO object: stream directly into it
419
412
  response.read_body do |fragment|
420
- file.write(fragment)
413
+ target_file.write(fragment)
421
414
  written_size += fragment.length
422
- Parameters.instance.progress_bar&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
415
+ progress&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
416
+ end
417
+ else
418
+ # file path: download to partial name first, then rename atomically
419
+ target_file_tmp = "#{target_file}#{Parameters.instance.download_partial_suffix}"
420
+ FileUtils.mkdir_p(File.dirname(target_file_tmp))
421
+ File.open(target_file_tmp, 'wb') do |file|
422
+ response.read_body do |fragment|
423
+ file.write(fragment)
424
+ written_size += fragment.length
425
+ progress&.event(:transfer, session_id: session_id, info: written_size) if limiter.trigger?
426
+ end
423
427
  end
428
+ File.rename(target_file_tmp, target_file)
424
429
  end
425
- File.rename(target_file_tmp, target_file)
430
+ success = true
431
+ ensure
432
+ # Last progress may have been skipped by limiter
433
+ progress&.event(:transfer, session_id: session_id, info: written_size)
434
+ progress&.event(:session_end, session_id: session_id)
435
+ progress&.event(:end, info: success)
426
436
  end
427
- Parameters.instance.progress_bar&.event(:session_end, session_id: session_id)
428
- Parameters.instance.progress_bar&.event(:end)
429
437
  true
430
438
  end
431
439
 
@@ -439,6 +447,7 @@ module Aspera
439
447
  # TODO: Remove next 2 lines when bug in async node api is fixed. (Aspera/core/issues/4490)
440
448
  node_api_bug = result_data&.index('}HTTP/1.1 400 Bad Request') if result_data.is_a?(String)
441
449
  result_data = result_data[0..node_api_bug] if node_api_bug
450
+ result_mime = 'application/xml' if result_data.is_a?(String) && result_data.start_with?('<?xml')
442
451
  result_data = JSON.parse(result_data) if Mime.json?(result_mime) && !result_data.nil? && !result_data.empty?
443
452
  Log.dump(:result_data, result_data)
444
453
  result_data
@@ -70,9 +70,11 @@ module Aspera
70
70
  Log.dump(:query, query)
71
71
  result = []
72
72
  offset = 0
73
- max_items = query.delete(MAX_ITEMS)
74
- remain_pages = query.delete(MAX_PAGES)
75
- # Merge default parameters, by default 100 per page
73
+ # Special parameters are not sent to the API (the caller's query is not modified)
74
+ query = query.dup
75
+ max_items = query.delete(MAX_ITEMS)&.to_i
76
+ remain_pages = query.delete(MAX_PAGES)&.to_i
77
+ # Merge default parameters
76
78
  query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
77
79
  total_count = nil
78
80
  call_args = {
@@ -84,18 +86,22 @@ module Aspera
84
86
  loop do
85
87
  query['offset'] = offset
86
88
  page_result = call(**call_args)
87
- Aspera.assert_type(page_result[items_key], Array)
88
- result.concat(page_result[items_key])
89
+ page_items = page_result[items_key]
90
+ Aspera.assert_type(page_items, Array)
91
+ result.concat(page_items)
89
92
  # Reach the limit set by user ?
90
93
  if !max_items.nil? && (result.length >= max_items)
91
94
  result = result.slice(0, max_items)
92
95
  break
93
96
  end
94
97
  total_count ||= page_result['total_count']
95
- break if result.length >= total_count
98
+ # An empty page ends the list, even if the total count is not reached (e.g. items deleted meanwhile)
99
+ break if page_items.empty?
100
+ # Without total count, a partial page is the last one
101
+ break if total_count.nil? ? page_items.length < query['limit'].to_i : result.length >= total_count
96
102
  remain_pages -= 1 unless remain_pages.nil?
97
- break if remain_pages == 0
98
- offset += page_result[items_key].length
103
+ break if !remain_pages.nil? && remain_pages <= 0
104
+ offset += page_items.length
99
105
  Parameters.instance.spinner_cb.call("#{result.length} / #{total_count || '?'}")
100
106
  end
101
107
  Parameters.instance.spinner_cb.call(action: :success)
@@ -32,7 +32,7 @@ module Aspera
32
32
  attr_accessor :session_cb
33
33
  # @return [Object, nil] Progress bar, receives `event` calls during download
34
34
  attr_accessor :progress_bar
35
- # @return [Proc, nil] Called with `(title = nil, action: :spin)` to display progress of long operations
35
+ # @return [Proc] Called with `(title = nil, action: :spin)` to display progress of long operations (default: no display)
36
36
  attr_accessor :spinner_cb
37
37
 
38
38
  private
@@ -48,7 +48,7 @@ module Aspera
48
48
  @retry_sleep = 4
49
49
  @session_cb = nil
50
50
  @progress_bar = nil
51
- @spinner_cb = nil
51
+ @spinner_cb = ->(_title = nil, action: :spin) {}
52
52
  end
53
53
  end
54
54
  end
@@ -3840,12 +3840,6 @@ paths:
3840
3840
  required: false
3841
3841
  schema:
3842
3842
  type: boolean
3843
- - name: get_package_download_link
3844
- in: query
3845
- description: Specifies whether to return the package download context to download the package as an external user.
3846
- required: false
3847
- schema:
3848
- type: boolean
3849
3843
  responses:
3850
3844
  '200':
3851
3845
  description: ' - OK - Successful response'
@@ -30208,13 +30202,6 @@ components:
30208
30202
  - eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
30209
30203
  maxLength: 255
30210
30204
  minLength: 1
30211
- pkg_download_context:
30212
- description: The package download context value used for navigating to the package download view for external users. Only included in responses when `get_package_download_link` is set to `true` and the package in question was not sent to a shared inbox or workgroup.
30213
- type: string
30214
- examples:
30215
- - eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
30216
- maxLength: 255
30217
- minLength: 1
30218
30205
  PutPackage:
30219
30206
  description: Update a package
30220
30207
  type: object
@@ -32779,13 +32766,6 @@ components:
32779
32766
  - eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
32780
32767
  maxLength: 255
32781
32768
  minLength: 1
32782
- pkg_download_context:
32783
- description: The package download context value used for navigating to the package download view for external users. Only included in responses when `get_package_download_link` is set to `true` and the package in question was not sent to a shared inbox or workgroup.
32784
- type: string
32785
- examples:
32786
- - eyJyZXNvdXJjZSI6InBhY2thZ2VzIiwidHlwZSI6ImV4dGVybmFsX3ZpZXdfcGFja2FnZSIsImlkIjoiMzAiLCJwYXNzY29kZSI6IjA2MzBkZDIzLTA3YTAtNGExYi04N2IzLWM3YjZkMGViNjE5NiIsInBhY2thZ2VfaWQiOiIzMCJ9
32787
- maxLength: 255
32788
- minLength: 1
32789
32769
  relays_count:
32790
32770
  description: Package relays count
32791
32771
  type: object