cantemo-portal-agent 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/cantemo/portal/agent/cli/commands/watch_folders-working.rb +2 -2
- data/lib/cantemo/portal/agent/cli/commands/watch_folders.rb +23 -7
- data/lib/cantemo/portal/agent/version.rb +1 -1
- data/lib/envoi/mam/agent.rb +5 -2
- data/lib/envoi/mam/agent/transfer_client/aspera.rb +39 -12
- data/lib/envoi/mam/cantemo/agent.rb +106 -22
- data/lib/envoi/mam/cantemo/agent/watch_folder_handler-working.rb +5 -2
- data/lib/envoi/mam/cantemo/agent/watch_folder_manager.rb +426 -0
- data/lib/envoi/mam/vidispine/agent.rb +1 -1
- data/lib/envoi/watch_folder_utility/watch_folder/handler/listen.rb +152 -23
- metadata +3 -3
- data/lib/envoi/mam/cantemo/agent/watch_folder_handler.rb +0 -176
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1a098feb7ef559fc883d25d942ee7f501599684
|
4
|
+
data.tar.gz: 2b90e43a0ccdd4ad7e2283b8ddb09495a15998e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 853bd438e02b10f756fb8524671c2bfa3884bf6d8fe4cfde3b189117d8681deb5f230b290ca28df524b1ce7e9ede16d07ce13044a38ac97d1ac611beea1c57f6
|
7
|
+
data.tar.gz: '078cc907795490344fa612380e21fc4f240ab6edbbf4118d7d2063ea438796564e908352c525106e865cd215d3d011e5efc7763b78d990793fb503ad2269a855'
|
@@ -226,10 +226,10 @@ class Watcher
|
|
226
226
|
w.run
|
227
227
|
end
|
228
228
|
|
229
|
-
def self.run_as_daemon(args)
|
229
|
+
def self.run_as_daemon(args, options = { })
|
230
230
|
# ARGV.unshift 'run' unless %w(start stop restart run zap killall status).include? ARGV.first
|
231
231
|
require 'daemons'
|
232
|
-
Daemons.run_proc('cantemo-portal-agent-watch-folders') { self.run(args) }
|
232
|
+
Daemons.run_proc('cantemo-portal-agent-watch-folders', options) { self.run(args) }
|
233
233
|
end
|
234
234
|
|
235
235
|
end
|
@@ -10,7 +10,7 @@ require 'pp'
|
|
10
10
|
require 'envoi/mam/agent/cli'
|
11
11
|
require 'envoi/mam/cantemo/agent'
|
12
12
|
# require 'envoi/aspera/watch_service/watch_folder'
|
13
|
-
require 'envoi/mam/cantemo/agent/
|
13
|
+
require 'envoi/mam/cantemo/agent/watch_folder_manager'
|
14
14
|
|
15
15
|
Envoi::Mam::Agent::CLI::CONFIG_FILE_PATHS.clear
|
16
16
|
Envoi::Mam::Agent::CLI::CONFIG_FILE_PATHS.concat [
|
@@ -32,7 +32,7 @@ ARGV << 'run' if ARGV.empty?
|
|
32
32
|
:config_file_path => default_config_file_paths,
|
33
33
|
:dry_run => false,
|
34
34
|
:operation => :upload,
|
35
|
-
:preserve_path =>
|
35
|
+
:preserve_path => false,
|
36
36
|
:transfer_type => '',
|
37
37
|
}
|
38
38
|
def args; @args end
|
@@ -49,15 +49,31 @@ op.parse!
|
|
49
49
|
config_file_path = args[:config_file_path]
|
50
50
|
args[:config_file_path].map! { |v| File.expand_path(v) } if config_file_path.is_a?(Array)
|
51
51
|
|
52
|
-
|
52
|
+
command = ARGV.first.dup
|
53
53
|
|
54
|
-
|
55
|
-
|
54
|
+
control_command_present = command && begin
|
55
|
+
command.downcase!
|
56
|
+
%w(start stop restart run zap killall status).include?(command)
|
56
57
|
end
|
57
58
|
|
59
|
+
if command
|
60
|
+
case command
|
61
|
+
when 'install'
|
62
|
+
puts
|
63
|
+
when 'uninstall'
|
64
|
+
puts
|
65
|
+
else
|
66
|
+
puts
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
daemonize = (control_command_present && %(start restart).include?(command)) || args[:daemonize]
|
72
|
+
|
58
73
|
# puts "#{__FILE__}:#{__LINE__ } #{args}"
|
59
74
|
# next_command = ARGV.shift
|
60
75
|
# puts "COMMAND: #{next_command}"
|
61
76
|
|
62
|
-
class Watcher < Envoi::Mam::Cantemo::Agent::
|
63
|
-
daemonize ? Watcher.run_as_daemon(args) : Watcher.run(args)
|
77
|
+
class Watcher < Envoi::Mam::Cantemo::Agent::WatchFolderManager; end
|
78
|
+
# daemonize ? Watcher.run_as_daemon(args, { force: true }) : Watcher.run(args)
|
79
|
+
daemonize ? Watcher.run_as_daemon(args) : Watcher.run(args)
|
data/lib/envoi/mam/agent.rb
CHANGED
@@ -48,10 +48,13 @@ module Envoi
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
# @param [String] command
|
52
|
+
# @param [Boolean] dry_run
|
53
|
+
# @return [Hash]
|
51
54
|
def shell_execute(command, dry_run = @dry_run)
|
52
55
|
if dry_run
|
53
56
|
logger.debug { "Skipping Execution of Command: '#{command}' " }
|
54
|
-
return
|
57
|
+
return { }
|
55
58
|
end
|
56
59
|
logger.debug { "Executing Command: '#{command}'" }
|
57
60
|
|
@@ -74,7 +77,7 @@ module Envoi
|
|
74
77
|
success = thread.value == 0 ? true : false
|
75
78
|
end
|
76
79
|
|
77
|
-
success
|
80
|
+
{ success: success }
|
78
81
|
end
|
79
82
|
|
80
83
|
def self.load_from_config_file(args)
|
@@ -105,6 +105,14 @@ module Envoi
|
|
105
105
|
end
|
106
106
|
|
107
107
|
aspera_token = config['aspera_transfer_token'] || config['aspera_token'] || config['token'] || config['transfer_token']
|
108
|
+
if aspera_username && aspera_password && (aspera_token.nil? || aspera_token.empty?)
|
109
|
+
_token_string = %(Basic #{["#{aspera_username}:#{aspera_password}"]
|
110
|
+
.pack('m')
|
111
|
+
.delete("\r\n")})
|
112
|
+
aspera_token = _token_string
|
113
|
+
aspera_password = nil
|
114
|
+
aspera_username = 'xfer'
|
115
|
+
end
|
108
116
|
|
109
117
|
# @ascp_path = config['ascp_path'] || default_ascp_path
|
110
118
|
# @ascp_path = File.expand_path(@ascp_path)
|
@@ -112,11 +120,12 @@ module Envoi
|
|
112
120
|
|
113
121
|
env_vars = { }
|
114
122
|
env_vars['ASPERA_SCP_PASS'] = aspera_password if aspera_password
|
123
|
+
# env_vars['ASPERA_SCP_TOKEN'] = aspera_token if aspera_token
|
115
124
|
|
116
125
|
ascp_args = config['ascp_args'] || default_ascp_args || agent.default_ascp_args
|
117
126
|
|
118
|
-
tags = config['tags']
|
119
|
-
aspera_tags = tags['aspera']
|
127
|
+
tags = config['tags'] ||= { }
|
128
|
+
aspera_tags = tags['aspera'] ||= { }
|
120
129
|
aspera_tags['xfer_id'] ||= SecureRandom.uuid
|
121
130
|
|
122
131
|
cmdline_args = [
|
@@ -127,7 +136,9 @@ module Envoi
|
|
127
136
|
]
|
128
137
|
cmdline_args.concat [ '-P', aspera_ssh_port ] if aspera_ssh_port
|
129
138
|
cmdline_args.concat ascp_args.is_a?(Array) ? ascp_args : ascp_args.split(' ') if ascp_args && !ascp_args.empty?
|
130
|
-
|
139
|
+
if aspera_token && !aspera_token.empty?
|
140
|
+
cmdline_args.concat ['-W', aspera_token, '-i', 'asperaweb_id_dsa.openssh' ]
|
141
|
+
end
|
131
142
|
cmdline_args.concat [ source_path.gsub('"', '\"'), target_path.gsub('"', '\"') ]
|
132
143
|
|
133
144
|
{ env: env_vars, args: cmdline_args, ascp_version: :ascp }
|
@@ -150,7 +161,12 @@ module Envoi
|
|
150
161
|
end
|
151
162
|
|
152
163
|
aspera_token = config['aspera_transfer_token'] || config['aspera_token'] || config['token'] || config['transfer_token']
|
153
|
-
|
164
|
+
if aspera_username && aspera_password && (aspera_token.nil? || aspera_token.empty?)
|
165
|
+
_token_string = %(Basic #{["#{aspera_username}:#{aspera_password}"]
|
166
|
+
.pack('m')
|
167
|
+
.delete("\r\n")})
|
168
|
+
aspera_username = 'xfer'
|
169
|
+
end
|
154
170
|
@ascp_path = config['ascp_path'] || default_ascp_path
|
155
171
|
@ascp_path = File.expand_path(@ascp_path)
|
156
172
|
|
@@ -169,14 +185,16 @@ module Envoi
|
|
169
185
|
command << %("#{aspera_ascp_path}" --mode=#{mode} --host="#{aspera_host_address}" --user="#{aspera_username}")
|
170
186
|
command << %( -P #{aspera_ssh_port}) if aspera_ssh_port
|
171
187
|
command << %(--tags64 #{Base64.strict_encode64(JSON.generate(tags))}) if tags && !tags.empty?
|
172
|
-
|
188
|
+
if ascp_args && !ascp_args.empty?
|
189
|
+
command << (ascp_args.is_a?(Array)) ? ascp_args.join(' ') : ascp_args
|
190
|
+
end
|
173
191
|
command << %( -W "#{aspera_token}") if aspera_token && !aspera_token.empty?
|
174
192
|
command << %( "#{source_path.gsub('"', '\"')}" "#{target_path.gsub('"', '\"')}")
|
175
193
|
end
|
176
194
|
|
177
195
|
def download(config, path, destination_path = DEFAULT_DESTINATION_PATH)
|
178
196
|
aspera_base_path = config['base_path'] || ''
|
179
|
-
source_path = File.join(aspera_base_path, path)
|
197
|
+
source_path = aspera_base_path.empty? ? path : File.join(aspera_base_path, path)
|
180
198
|
|
181
199
|
mode = 'recv'
|
182
200
|
transfer(config, mode, source_path, destination_path)
|
@@ -192,7 +210,7 @@ module Envoi
|
|
192
210
|
end
|
193
211
|
|
194
212
|
def transfer(config, mode, source_path, destination_path)
|
195
|
-
if
|
213
|
+
if true
|
196
214
|
transfer_using_asperala(config, mode, source_path, destination_path)
|
197
215
|
else
|
198
216
|
transfer_using_shell_execute(config, mode, source_path, destination_path)
|
@@ -201,21 +219,30 @@ module Envoi
|
|
201
219
|
|
202
220
|
def transfer_using_asperala(config, mode, source_path, destination_path)
|
203
221
|
args_out = build_asperala_transfer_args(config, mode, source_path, destination_path)
|
204
|
-
fasp
|
205
|
-
Asperalm::Log.instance.level = :debug
|
206
|
-
fasp.start_transfer_with_args_env(args_out, {})
|
222
|
+
@fasp ||= Asperalm::Fasp::Local.instance
|
223
|
+
Asperalm::Log.instance.level = :debug #logger.level
|
224
|
+
@fasp.start_transfer_with_args_env(args_out, {})
|
225
|
+
{ success: true }
|
207
226
|
end
|
208
227
|
|
209
228
|
def transfer_using_shell_execute(config, mode, source_path, destination_path)
|
210
229
|
command = build_ascp_command(config, mode, source_path, destination_path)
|
211
230
|
|
212
231
|
unless ascp_path_exists?
|
213
|
-
|
214
|
-
|
232
|
+
msg = "ASCP not found. '#{ascp_path}'"
|
233
|
+
warn msg
|
234
|
+
return { message: msg, success: false }
|
215
235
|
end
|
236
|
+
|
216
237
|
agent.shell_execute(command)
|
217
238
|
end
|
218
239
|
|
240
|
+
def shutdown(graceful = true)
|
241
|
+
if @fasp && @fasp.respond_to?(:shutdown)
|
242
|
+
@fasp.shutdown(graceful)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
219
246
|
# AsperaTransferClient
|
220
247
|
end
|
221
248
|
|
@@ -13,15 +13,18 @@ module Envoi
|
|
13
13
|
|
14
14
|
DEFAULT_SHAPE_TAG = 'original'
|
15
15
|
DEFAULT_DESTINATION_PATH = '.'
|
16
|
+
DEFAULT_PRESERVE_FILE_PATH = true
|
16
17
|
|
17
18
|
attr_accessor :default_aspera_ascp_args,
|
18
19
|
:default_aspera_ascp_path,
|
19
|
-
:default_vidispine_shape_tag
|
20
|
+
:default_vidispine_shape_tag,
|
21
|
+
:default_preserve_file_path
|
20
22
|
|
21
23
|
def after_initialize
|
22
24
|
args = initial_args
|
23
|
-
@default_aspera_ascp_path
|
24
|
-
@default_aspera_args = args
|
25
|
+
@default_aspera_ascp_path = args[:default_aspera_ascp_path]
|
26
|
+
@default_aspera_args = args.fetch(:default_ascp_args, Envoi::Mam::Agent::TransferClient::Aspera::DEFAULT_ASCP_ARGS)
|
27
|
+
@default_preserve_file_path = args.fetch(:default_preserve_file_path, DEFAULT_PRESERVE_FILE_PATH)
|
25
28
|
end
|
26
29
|
|
27
30
|
def dry_run?; @dry_run end
|
@@ -35,8 +38,8 @@ module Envoi
|
|
35
38
|
@api_client = args[:api_client] || begin
|
36
39
|
|
37
40
|
api_host = api_config['host']
|
38
|
-
api_host_use_ssl = api_config['use_ssl']
|
39
41
|
api_port = api_config['port']
|
42
|
+
api_host_use_ssl = api_config['ssl']
|
40
43
|
api_username = api_config['username']
|
41
44
|
api_password = api_config['password']
|
42
45
|
api_auth_token = api_config['api_auth_token']
|
@@ -54,13 +57,17 @@ module Envoi
|
|
54
57
|
api_username ||= _api_username
|
55
58
|
api_password ||= _api_password
|
56
59
|
end
|
60
|
+
api_host_use_ssl = api_uri.scheme == 'https' if api_host_use_ssl.nil?
|
57
61
|
api_uri_query = api_uri.query
|
58
62
|
api_default_query_data ||= Hash[api_uri_query.split('&').map { |kp| kp.split('=') }]
|
59
63
|
api_base_path ||= api_uri.path
|
60
64
|
end
|
61
65
|
|
62
66
|
api_port ||= (api_host_use_ssl ? 443 : 80)
|
63
|
-
api_base_path ||= '
|
67
|
+
api_base_path ||= '/'
|
68
|
+
|
69
|
+
api_endpoint_prefix = 'VSAPI'
|
70
|
+
api_noauth_endpoint_prefix = 'APInoauth'
|
64
71
|
|
65
72
|
client_args = { }
|
66
73
|
client_args[:http_host_address] = api_host if api_host
|
@@ -70,6 +77,8 @@ module Envoi
|
|
70
77
|
client_args[:password] = api_password if api_password
|
71
78
|
client_args[:default_base_path] = api_base_path if api_base_path
|
72
79
|
client_args[:default_query_data] = api_default_query_data if api_default_query_data
|
80
|
+
client_args[:api_endpoint_prefix] = api_endpoint_prefix
|
81
|
+
client_args[:api_noauth_endpoint_prefix] = api_noauth_endpoint_prefix
|
73
82
|
|
74
83
|
if api_auth_token
|
75
84
|
# Cantemo Portal supports an auth token for authentication, replace basic auth with auth-token
|
@@ -78,10 +87,16 @@ module Envoi
|
|
78
87
|
end
|
79
88
|
|
80
89
|
_client = ::Vidispine::API::Utilities.new(client_args)
|
81
|
-
_client
|
82
|
-
end
|
83
90
|
|
91
|
+
begin
|
92
|
+
_client.version
|
93
|
+
rescue => e
|
94
|
+
e.message = "Error connecting to Portal: #{e.message}"
|
95
|
+
raise e
|
96
|
+
end
|
84
97
|
|
98
|
+
_client
|
99
|
+
end
|
85
100
|
|
86
101
|
@default_vidispine_shape_tag = args[:default_shape_tag] || api_config['default_shape_tag'] || api_config['shape_tag'] || DEFAULT_SHAPE_TAG
|
87
102
|
|
@@ -133,7 +148,7 @@ module Envoi
|
|
133
148
|
end
|
134
149
|
|
135
150
|
logger.info { "Transferring File Path: '#{file_path}'" }
|
136
|
-
preserve_path = args.fetch(:preserve_path, file_storage_config.fetch('preserve_path',
|
151
|
+
preserve_path = args.fetch(:preserve_path, file_storage_config.fetch('preserve_path', default_preserve_file_path))
|
137
152
|
|
138
153
|
destination_path = args[:destination_path] || file_storage_config['destination_path'] || DEFAULT_DESTINATION_PATH
|
139
154
|
relative_path = preserve_path ? File.dirname(file_path) : nil
|
@@ -161,6 +176,8 @@ module Envoi
|
|
161
176
|
end
|
162
177
|
|
163
178
|
def upload(args = { })
|
179
|
+
_response = { }
|
180
|
+
|
164
181
|
file_path = args[:file_path]
|
165
182
|
raise ArgumentError, "Path not found: '#{file_path}'" unless File.exists?(file_path)
|
166
183
|
|
@@ -183,7 +200,7 @@ module Envoi
|
|
183
200
|
|
184
201
|
should_import_file = args.fetch(:import_file, vidispine_storage_config.fetch('import', true))
|
185
202
|
|
186
|
-
should_preserve_path = args.fetch(:preserve_path, vidispine_storage_config.fetch('preserve_path',
|
203
|
+
should_preserve_path = args.fetch(:preserve_path, vidispine_storage_config.fetch('preserve_path', default_preserve_file_path))
|
187
204
|
|
188
205
|
destination_path = args[:destination_path] || vidispine_storage_config['destination_path'] || '/'
|
189
206
|
relative_path = should_preserve_path ? File.dirname(file_path) : nil
|
@@ -194,7 +211,7 @@ module Envoi
|
|
194
211
|
|
195
212
|
|
196
213
|
# upload file
|
197
|
-
|
214
|
+
|
198
215
|
transfer_response = begin
|
199
216
|
response = nil
|
200
217
|
aspera_config = vidispine_storage_config['aspera']
|
@@ -204,7 +221,7 @@ module Envoi
|
|
204
221
|
response = client.upload(aspera_config, file_path, target_path)
|
205
222
|
end
|
206
223
|
rescue => e
|
207
|
-
logger.error { "Aspera Transfer Failed. '#{e.message}'" }
|
224
|
+
logger.error { "Aspera Transfer Failed. '#{e.message}'\n#{e.backtrace.first}" }
|
208
225
|
end
|
209
226
|
|
210
227
|
s3_config = vidispine_storage_config['s3']
|
@@ -222,28 +239,95 @@ module Envoi
|
|
222
239
|
|
223
240
|
response
|
224
241
|
end
|
225
|
-
|
226
|
-
|
242
|
+
transfer_response = { success: transfer_response } if transfer_response == true || transfer_response == false
|
243
|
+
|
244
|
+
logger.debug { "Transfer Response: #{transfer_response}" }
|
245
|
+
_response[:transfer_response] = transfer_response
|
227
246
|
|
228
|
-
|
247
|
+
if transfer_response.nil?
|
248
|
+
logger.warn { "No supported TransferClient configuration#{transfer_type && !transfer_type.empty? ? " for transfer type '#{transfer_type}' " : ''}found in storage configuration." }
|
249
|
+
_response[:success] = false
|
250
|
+
return _response
|
251
|
+
end
|
252
|
+
|
253
|
+
unless transfer_response[:success]
|
254
|
+
logger.error { "Error transferring file." }
|
255
|
+
_response[:success] = false
|
256
|
+
return _response
|
257
|
+
end
|
229
258
|
|
230
|
-
|
259
|
+
unless should_import_file
|
260
|
+
_response[:success] = transfer_response[:success]
|
261
|
+
return _response
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
### IMPORT - START
|
266
|
+
import_file_args = args.dup
|
267
|
+
import_file_args[:response_object] = _response
|
268
|
+
import_file_args[:target_path] = target_path
|
269
|
+
import_file(import_file_args)
|
270
|
+
_response[:success] = true unless _response[:success] === false
|
271
|
+
### IMPORT - END
|
272
|
+
|
273
|
+
_response
|
274
|
+
rescue => e
|
275
|
+
logger.error { "Exception: #{e.message}" }
|
276
|
+
_response[:exception] = e
|
277
|
+
return _response
|
278
|
+
end
|
279
|
+
|
280
|
+
def import_file(args = { })
|
281
|
+
_response = args[:response_object] || { }
|
282
|
+
|
283
|
+
file_path = args[:file_path]
|
284
|
+
storage_id = args[:storage_id]
|
285
|
+
|
286
|
+
target_path = args[:target_path]
|
231
287
|
|
232
288
|
item_id = args[:item_id]
|
233
289
|
shape_tag = args[:shape_tag] || default_vidispine_shape_tag
|
234
290
|
|
235
291
|
# attach file to item as shape
|
236
292
|
path_on_storage = File.join(target_path, File.basename(file_path))
|
237
|
-
|
238
|
-
|
293
|
+
path_on_storage = path_on_storage[1..-1] if path_on_storage.start_with?('/')
|
294
|
+
# file_create_response = api_client.storage_file_create(storage_id: storage_id,
|
295
|
+
# path: path_on_storage, state: 'CLOSED')
|
296
|
+
file_create_response = api_client.storage_file_get_or_create(storage_id, path_on_storage, { :extended_response => true })
|
297
|
+
_response[:file_create_response] = file_create_response
|
298
|
+
file = file_create_response[:file]
|
299
|
+
|
300
|
+
file_id = file['id']
|
301
|
+
|
302
|
+
unless file_id
|
303
|
+
_file = file.dup
|
304
|
+
_file.keep_if { |k,v| v }
|
305
|
+
logger.error { "Failed to create file. #{_file}" }
|
306
|
+
_response[:success] = false
|
307
|
+
return _response
|
308
|
+
end
|
309
|
+
|
310
|
+
item = (file['item'] || []).first
|
311
|
+
|
312
|
+
if item
|
313
|
+
shape = (item['shape'] || []).first
|
314
|
+
msg = "File already exist and is associated to item #{item['id']} as shape #{shape['id']}."
|
315
|
+
logger.warn { "#{msg} #{file}" }
|
316
|
+
_response[:error] = { :message => msg }
|
317
|
+
_response[:success] = false
|
318
|
+
return _response
|
319
|
+
end
|
239
320
|
|
240
321
|
if item_id
|
241
|
-
item_shape_import_response = api_client.item_shape_import
|
322
|
+
item_shape_import_response = api_client.item_shape_import(item_id: item_id,
|
323
|
+
tag: shape_tag, fileId: file_id)
|
324
|
+
|
325
|
+
|
242
326
|
else
|
243
|
-
item_shape_import_response = api_client.item_add_using_file_path
|
244
|
-
|
245
|
-
|
246
|
-
|
327
|
+
item_shape_import_response = api_client.item_add_using_file_path(storage_id: storage_id,
|
328
|
+
file_path: path_on_storage,
|
329
|
+
fileId: file_id,
|
330
|
+
storage_path_map: { '/' => storage_id })
|
247
331
|
end
|
248
332
|
_response[:import_response] = item_shape_import_response
|
249
333
|
|