etna 0.1.20 → 0.1.25
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/etna.completion +26926 -2
- data/lib/commands.rb +82 -3
- data/lib/etna.rb +1 -0
- data/lib/etna/client.rb +15 -36
- data/lib/etna/clients/base_client.rb +39 -0
- data/lib/etna/clients/janus/client.rb +2 -13
- data/lib/etna/clients/janus/models.rb +2 -1
- data/lib/etna/clients/magma/client.rb +2 -17
- data/lib/etna/clients/magma/models.rb +7 -3
- data/lib/etna/clients/magma/workflows.rb +2 -0
- data/lib/etna/clients/magma/workflows/crud_workflow.rb +10 -4
- data/lib/etna/clients/magma/workflows/materialize_magma_record_files_workflow.rb +135 -0
- data/lib/etna/clients/magma/workflows/walk_model_tree_workflow.rb +101 -0
- data/lib/etna/clients/metis/client.rb +22 -9
- data/lib/etna/clients/metis/models.rb +3 -2
- data/lib/etna/clients/polyphemus/client.rb +18 -13
- data/lib/etna/clients/polyphemus/models.rb +26 -1
- data/lib/etna/csvs.rb +2 -1
- data/lib/etna/filesystem.rb +93 -0
- data/lib/etna/spec/vcr.rb +7 -0
- data/lib/etna/test_auth.rb +7 -3
- data/lib/helpers.rb +9 -3
- metadata +6 -2
data/lib/commands.rb
CHANGED
@@ -54,7 +54,7 @@ class EtnaApp
|
|
54
54
|
boolean_flags << '--ignore-ssl'
|
55
55
|
|
56
56
|
def execute(host, ignore_ssl: false)
|
57
|
-
polyphemus_client
|
57
|
+
polyphemus_client = Etna::Clients::Polyphemus.new(
|
58
58
|
host: host,
|
59
59
|
token: token(ignore_environment: true),
|
60
60
|
ignore_ssl: ignore_ssl)
|
@@ -62,7 +62,7 @@ class EtnaApp
|
|
62
62
|
polyphemus_client: polyphemus_client,
|
63
63
|
config_file: EtnaApp.config_file_path)
|
64
64
|
config = workflow.update_configuration_file(ignore_ssl: ignore_ssl)
|
65
|
-
logger
|
65
|
+
logger&.info("Updated #{config.environment} configuration from #{host}.")
|
66
66
|
end
|
67
67
|
|
68
68
|
def setup(config)
|
@@ -226,7 +226,7 @@ class EtnaApp
|
|
226
226
|
|
227
227
|
@last_load = File.stat(file).mtime
|
228
228
|
@changeset = File.open(file, 'r') do |f|
|
229
|
-
workflow.prepare_changeset_from_csv(f) do |err|
|
229
|
+
workflow.prepare_changeset_from_csv(io: f) do |err|
|
230
230
|
@errors << err
|
231
231
|
end
|
232
232
|
end
|
@@ -299,6 +299,85 @@ class EtnaApp
|
|
299
299
|
workflow.write_csv_io(filename: file)
|
300
300
|
end
|
301
301
|
end
|
302
|
+
|
303
|
+
class LoadTableFromCsv < Etna::Command
|
304
|
+
include WithEtnaClients
|
305
|
+
|
306
|
+
boolean_flags << '--execute'
|
307
|
+
|
308
|
+
def execute(project_name, model_name, file_path, execute: false)
|
309
|
+
request = Etna::Clients::Magma::RetrievalRequest.new(project_name: project_name)
|
310
|
+
request.model_name = model_name
|
311
|
+
request.attribute_names = 'all'
|
312
|
+
request.record_names = 'all'
|
313
|
+
model = magma_client.retrieve(request).models.model(model_name)
|
314
|
+
model_parent_name = model.template.attributes.all.select do |attribute|
|
315
|
+
attribute.attribute_type == Etna::Clients::Magma::AttributeType::PARENT
|
316
|
+
end.first.name
|
317
|
+
|
318
|
+
other_attribute_names = model.template.attributes.all.reject do |attribute|
|
319
|
+
attribute.attribute_type == Etna::Clients::Magma::AttributeType::PARENT
|
320
|
+
end.map do |attribute|
|
321
|
+
attribute.name
|
322
|
+
end
|
323
|
+
|
324
|
+
# NOTE: This does not call ensure_parent currently because of MVIR1 consent--
|
325
|
+
# if the timepoint doesn't exist, the patient may be no study? (one example, at least)
|
326
|
+
update_request = Etna::Clients::Magma::UpdateRequest.new(project_name: project_name)
|
327
|
+
|
328
|
+
data = CSV.parse(File.read(file_path), headers: true)
|
329
|
+
|
330
|
+
data.by_row.each do |row|
|
331
|
+
revision = {}
|
332
|
+
other_attribute_names.each do |attribute_name|
|
333
|
+
revision[attribute_name] = row[attribute_name] unless row[attribute_name].nil?
|
334
|
+
end
|
335
|
+
update_request.append_table(model_parent_name, row[model_parent_name], model_name, revision)
|
336
|
+
end
|
337
|
+
|
338
|
+
puts update_request
|
339
|
+
|
340
|
+
if execute
|
341
|
+
magma_client.update_json(update_request)
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
class LoadFromRedcap < Etna::Command
|
348
|
+
include WithEtnaClients
|
349
|
+
include WithLogger
|
350
|
+
include StrongConfirmation
|
351
|
+
|
352
|
+
boolean_flags << '--commit'
|
353
|
+
string_flags << '--models'
|
354
|
+
|
355
|
+
def execute(project_name, redcap_tokens, models: "all", commit: false)
|
356
|
+
raise "Must provide at least one REDCap token (comma-separated)." unless redcap_tokens.split(',').length > 0
|
357
|
+
|
358
|
+
puts "NOTE: This is a **preview** of what the data loading will look like. Use the --commit flag to load records into Magma." unless commit
|
359
|
+
|
360
|
+
polyphemus_client.job(Etna::Clients::Polyphemus::RedcapJobRequest.new(
|
361
|
+
model_names: "all" == models ? "all" : models.split(','),
|
362
|
+
redcap_tokens: redcap_tokens.split(','),
|
363
|
+
project_name: project_name,
|
364
|
+
commit: commit
|
365
|
+
)) do |response|
|
366
|
+
response.read_body do |chunk|
|
367
|
+
puts clean_sne_message(chunk)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def clean_sne_message(chunk)
|
373
|
+
chunk.split("\n").reject do |c|
|
374
|
+
c.start_with?("retry:") || c.start_with?("event:")
|
375
|
+
end.map do |c|
|
376
|
+
c.gsub("data:", "").strip
|
377
|
+
end.reject do |c|
|
378
|
+
c.empty?
|
379
|
+
end
|
380
|
+
end
|
302
381
|
end
|
303
382
|
end
|
304
383
|
end
|
data/lib/etna.rb
CHANGED
data/lib/etna/client.rb
CHANGED
@@ -1,14 +1,12 @@
|
|
1
|
-
require 'net/http/persistent'
|
2
1
|
require 'net/http/post/multipart'
|
3
2
|
require 'singleton'
|
4
3
|
require 'rack/utils'
|
5
4
|
|
6
5
|
module Etna
|
7
6
|
class Client
|
8
|
-
def initialize(host, token, routes_available: true,
|
7
|
+
def initialize(host, token, routes_available: true, ignore_ssl: false)
|
9
8
|
@host = host.sub(%r!/$!, '')
|
10
9
|
@token = token
|
11
|
-
@persistent = persistent
|
12
10
|
@ignore_ssl = ignore_ssl
|
13
11
|
|
14
12
|
if routes_available
|
@@ -38,6 +36,10 @@ module Etna
|
|
38
36
|
query_request(Net::HTTP::Get, endpoint, params, &block)
|
39
37
|
end
|
40
38
|
|
39
|
+
def head(endpoint, params = {}, &block)
|
40
|
+
query_request(Net::HTTP::Head, endpoint, params, &block)
|
41
|
+
end
|
42
|
+
|
41
43
|
def options(endpoint, params = {}, &block)
|
42
44
|
query_request(Net::HTTP::Options, endpoint, params, &block)
|
43
45
|
end
|
@@ -79,16 +81,6 @@ module Etna
|
|
79
81
|
end
|
80
82
|
end
|
81
83
|
|
82
|
-
def persistent_connection
|
83
|
-
@http ||= begin
|
84
|
-
http = Net::HTTP::Persistent.new
|
85
|
-
http.read_timeout = 3600
|
86
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @ignore_ssl
|
87
|
-
http
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
84
|
def body_request(type, endpoint, params = {}, &block)
|
93
85
|
uri = request_uri(endpoint)
|
94
86
|
req = type.new(uri.request_uri, request_params)
|
@@ -141,36 +133,23 @@ module Etna
|
|
141
133
|
|
142
134
|
def request(uri, data)
|
143
135
|
if block_given?
|
144
|
-
|
145
|
-
|
136
|
+
verify_mode = @ignore_ssl ?
|
137
|
+
OpenSSL::SSL::VERIFY_NONE :
|
138
|
+
OpenSSL::SSL::VERIFY_PEER
|
139
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode) do |http|
|
140
|
+
http.request(data) do |response|
|
146
141
|
status_check!(response)
|
147
142
|
yield response
|
148
143
|
end
|
149
|
-
else
|
150
|
-
verify_mode = @ignore_ssl ?
|
151
|
-
OpenSSL::SSL::VERIFY_NONE :
|
152
|
-
OpenSSL::SSL::VERIFY_PEER
|
153
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode) do |http|
|
154
|
-
http.request(data) do |response|
|
155
|
-
status_check!(response)
|
156
|
-
yield response
|
157
|
-
end
|
158
|
-
end
|
159
144
|
end
|
160
145
|
else
|
161
|
-
|
162
|
-
|
146
|
+
verify_mode = @ignore_ssl ?
|
147
|
+
OpenSSL::SSL::VERIFY_NONE :
|
148
|
+
OpenSSL::SSL::VERIFY_PEER
|
149
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode) do |http|
|
150
|
+
response = http.request(data)
|
163
151
|
status_check!(response)
|
164
152
|
return response
|
165
|
-
else
|
166
|
-
verify_mode = @ignore_ssl ?
|
167
|
-
OpenSSL::SSL::VERIFY_NONE :
|
168
|
-
OpenSSL::SSL::VERIFY_PEER
|
169
|
-
Net::HTTP.start(uri.host, uri.port, use_ssl: true, verify_mode: verify_mode) do |http|
|
170
|
-
response = http.request(data)
|
171
|
-
status_check!(response)
|
172
|
-
return response
|
173
|
-
end
|
174
153
|
end
|
175
154
|
end
|
176
155
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'json'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Etna
|
6
|
+
module Clients
|
7
|
+
class BaseClient
|
8
|
+
attr_reader :host, :token, :ignore_ssl
|
9
|
+
def initialize(host:, token:, ignore_ssl: false)
|
10
|
+
raise "#{self.class.name} client configuration is missing host." unless host
|
11
|
+
raise "#{self.class.name} client configuration is missing token." unless token
|
12
|
+
|
13
|
+
@token = token
|
14
|
+
raise "Your token is expired." if token_expired?
|
15
|
+
|
16
|
+
@etna_client = ::Etna::Client.new(
|
17
|
+
host,
|
18
|
+
token,
|
19
|
+
routes_available: false,
|
20
|
+
ignore_ssl: ignore_ssl)
|
21
|
+
@host = host
|
22
|
+
@ignore_ssl = ignore_ssl
|
23
|
+
end
|
24
|
+
|
25
|
+
def token_expired?
|
26
|
+
# Has the token already expired?
|
27
|
+
token_will_expire?(0)
|
28
|
+
end
|
29
|
+
|
30
|
+
def token_will_expire?(offset=3000)
|
31
|
+
# offset in seconds
|
32
|
+
# Will the user's token expire in the given amount of time?
|
33
|
+
epoch_seconds = JSON.parse(Base64.urlsafe_decode64(token.split('.')[1]))["exp"]
|
34
|
+
expiration = DateTime.strptime(epoch_seconds.to_s, "%s")
|
35
|
+
expiration <= DateTime.now.new_offset + offset
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,23 +1,12 @@
|
|
1
|
-
require 'net/http/persistent'
|
2
1
|
require 'net/http/post/multipart'
|
3
2
|
require 'singleton'
|
4
3
|
require_relative '../../client'
|
5
4
|
require_relative './models'
|
5
|
+
require_relative '../base_client'
|
6
6
|
|
7
7
|
module Etna
|
8
8
|
module Clients
|
9
|
-
class Janus
|
10
|
-
def initialize(host:, token:, persistent: true, ignore_ssl: false)
|
11
|
-
raise 'Janus client configuration is missing host.' unless host
|
12
|
-
raise 'Janus client configuration is missing token.' unless token
|
13
|
-
@etna_client = ::Etna::Client.new(
|
14
|
-
host,
|
15
|
-
token,
|
16
|
-
routes_available: false,
|
17
|
-
persistent: persistent,
|
18
|
-
ignore_ssl: ignore_ssl)
|
19
|
-
end
|
20
|
-
|
9
|
+
class Janus < Etna::Clients::BaseClient
|
21
10
|
def get_project(get_project_request = GetProjectRequest.new)
|
22
11
|
html = nil
|
23
12
|
@etna_client.get(
|
@@ -1,11 +1,12 @@
|
|
1
1
|
require 'ostruct'
|
2
2
|
require_relative '../../json_serializable_struct'
|
3
|
+
require_relative '../base_client'
|
3
4
|
|
4
5
|
# TODO: In the near future, I'd like to transition to specifying apis via SWAGGER and generating model stubs from the
|
5
6
|
# common definitions. For nowe I've written them out by hand here.
|
6
7
|
module Etna
|
7
8
|
module Clients
|
8
|
-
class Janus
|
9
|
+
class Janus < Etna::Clients::BaseClient
|
9
10
|
class GetProjectRequest < Struct.new(:project_name, keyword_init: true)
|
10
11
|
include JsonSerializableStruct
|
11
12
|
|
@@ -1,27 +1,12 @@
|
|
1
|
-
require 'net/http/persistent'
|
2
1
|
require 'net/http/post/multipart'
|
3
2
|
require 'singleton'
|
3
|
+
require_relative '../base_client'
|
4
4
|
require_relative '../../client'
|
5
5
|
require_relative './models'
|
6
6
|
|
7
7
|
module Etna
|
8
8
|
module Clients
|
9
|
-
class Magma
|
10
|
-
attr_reader :host, :token, :ignore_ssl
|
11
|
-
def initialize(host:, token:, persistent: true, ignore_ssl: false)
|
12
|
-
raise 'Magma client configuration is missing host.' unless host
|
13
|
-
raise 'Magma client configuration is missing token.' unless token
|
14
|
-
@etna_client = ::Etna::Client.new(
|
15
|
-
host,
|
16
|
-
token,
|
17
|
-
routes_available: false,
|
18
|
-
persistent: persistent,
|
19
|
-
ignore_ssl: ignore_ssl)
|
20
|
-
@host = host
|
21
|
-
@token = token
|
22
|
-
@ignore_ssl = ignore_ssl
|
23
|
-
end
|
24
|
-
|
9
|
+
class Magma < Etna::Clients::BaseClient
|
25
10
|
# This endpoint returns models and records by name:
|
26
11
|
# e.g. params:
|
27
12
|
# {
|
@@ -3,12 +3,13 @@ require_relative '../../json_serializable_struct'
|
|
3
3
|
require_relative '../../multipart_serializable_nested_hash'
|
4
4
|
require_relative '../../directed_graph'
|
5
5
|
require_relative '../enum'
|
6
|
+
require_relative '../base_client'
|
6
7
|
|
7
8
|
# TODO: In the near future, I'd like to transition to specifying apis via SWAGGER and generating model stubs from the
|
8
9
|
# common definitions. For nowe I've written them out by hand here.
|
9
10
|
module Etna
|
10
11
|
module Clients
|
11
|
-
class Magma
|
12
|
+
class Magma < Etna::Clients::BaseClient
|
12
13
|
class RetrievalRequest < Struct.new(:model_name, :attribute_names, :record_names, :project_name, :page, :page_size, :order, :filter, keyword_init: true)
|
13
14
|
include JsonSerializableStruct
|
14
15
|
|
@@ -316,8 +317,11 @@ module Etna
|
|
316
317
|
end
|
317
318
|
|
318
319
|
def document(document_key)
|
319
|
-
|
320
|
-
|
320
|
+
if document_key.is_a?(String)
|
321
|
+
raw[document_key]
|
322
|
+
else
|
323
|
+
raw[document_key&.to_s]
|
324
|
+
end
|
321
325
|
end
|
322
326
|
end
|
323
327
|
|
@@ -8,3 +8,5 @@ require_relative './workflows/update_attributes_from_csv_workflow'
|
|
8
8
|
require_relative './workflows/create_project_workflow'
|
9
9
|
require_relative './workflows/add_project_models_workflow'
|
10
10
|
require_relative './workflows/attribute_actions_from_json_workflow'
|
11
|
+
require_relative './workflows/materialize_magma_record_files_workflow'
|
12
|
+
require_relative './workflows/walk_model_tree_workflow'
|
@@ -31,13 +31,19 @@ module Etna
|
|
31
31
|
record_names: 'all',
|
32
32
|
page_size: 20,
|
33
33
|
page: 1,
|
34
|
-
))
|
34
|
+
), &block)
|
35
35
|
|
36
36
|
documents = Documents.new({})
|
37
37
|
last_page = nil
|
38
|
-
while last_page.nil? || last_page.raw.length
|
39
|
-
|
40
|
-
|
38
|
+
while last_page.nil? || last_page.models.model_keys.map { |k| last_page.models.model(k).documents.raw.length }.sum > 0
|
39
|
+
begin
|
40
|
+
last_page = magma_client.retrieve(request)
|
41
|
+
rescue Etna::Error => e
|
42
|
+
raise e unless e.message.include?('not found')
|
43
|
+
break
|
44
|
+
end
|
45
|
+
|
46
|
+
documents += last_page.models.model(model_name).documents unless block_given?
|
41
47
|
yield last_page if block_given?
|
42
48
|
request.page += 1
|
43
49
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'digest'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
module Etna
|
7
|
+
module Clients
|
8
|
+
class Magma
|
9
|
+
class MaterializeDataWorkflow < Struct.new(
|
10
|
+
:metis_client, :magma_client, :project_name,
|
11
|
+
:model_name, :model_filters, :model_attributes_mask,
|
12
|
+
:filesystem, :logger, :stub_files,
|
13
|
+
keyword_init: true)
|
14
|
+
|
15
|
+
def initialize(**kwds)
|
16
|
+
super(**({filesystem: Etna::Filesystem.new}.update(kwds)))
|
17
|
+
end
|
18
|
+
|
19
|
+
def magma_crud
|
20
|
+
@magma_crud ||= Etna::Clients::Magma::MagmaCrudWorkflow.new(magma_client: magma_client, project_name: project_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def model_walker
|
24
|
+
@model_walker ||= WalkModelTreeWorkflow.new(magma_crud: magma_crud, logger: logger)
|
25
|
+
end
|
26
|
+
|
27
|
+
def with_materialized_dir(&block)
|
28
|
+
tmp_dir = filesystem.tmpdir
|
29
|
+
|
30
|
+
begin
|
31
|
+
model_walker.walk_from(
|
32
|
+
model_name,
|
33
|
+
model_attributes_mask: model_attributes_mask,
|
34
|
+
model_filters: model_filters,
|
35
|
+
) do |template, document|
|
36
|
+
logger&.info("Materializing #{template.name}##{document[template.identifier]}")
|
37
|
+
materialize_record(tmp_dir, template, document)
|
38
|
+
end
|
39
|
+
|
40
|
+
yield tmp_dir
|
41
|
+
ensure
|
42
|
+
filesystem.rm_rf(tmp_dir)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def each_root_record
|
47
|
+
request = RetrievalRequest.new(project_name: project_name, model_name: model_name, record_names: "all",
|
48
|
+
filter: filter, page_size: 100, page: 1)
|
49
|
+
magma_crud.page_records(model_name, request) do |response|
|
50
|
+
model = response.models.model(model_name)
|
51
|
+
template = model.template
|
52
|
+
model.documents.document_keys.each do |key|
|
53
|
+
yield template, model.documents.document(key)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_file(template, record, &block)
|
59
|
+
results = []
|
60
|
+
|
61
|
+
template.attributes.all.each do |attribute|
|
62
|
+
if attribute.attribute_type == AttributeType::FILE_COLLECTION
|
63
|
+
record[attribute.name]&.each_with_index do |file, i|
|
64
|
+
results << [attribute, file, i]
|
65
|
+
end
|
66
|
+
elsif attribute.attribute_type == AttributeType::FILE
|
67
|
+
results << [attribute, record[attribute.name], 0]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
results.each do |attr, file, idx|
|
72
|
+
next if file.nil?
|
73
|
+
next unless file.is_a?(Hash)
|
74
|
+
next unless file['url']
|
75
|
+
yield attr.name, file['url'], (file['original_filename'] || File.basename(file['path'])), idx
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def materialize_record(dest_dir, template, record)
|
80
|
+
record_to_serialize = record.dup
|
81
|
+
metadata_path = metadata_file_name(record_name: record[template.identifier], record_model_name: template.name)
|
82
|
+
|
83
|
+
each_file(template, record) do |attr_name, url, filename, idx|
|
84
|
+
metadata = metis_client.file_metadata(url)
|
85
|
+
etag = metadata[:etag]
|
86
|
+
size = metadata[:size]
|
87
|
+
|
88
|
+
if idx == 0
|
89
|
+
record_to_serialize[attr_name] = []
|
90
|
+
end
|
91
|
+
|
92
|
+
dest_file = bin_file_name(etag: etag)
|
93
|
+
record_to_serialize[attr_name] << { file: dest_file, original_filename: filename }
|
94
|
+
|
95
|
+
# Already materialized, continue
|
96
|
+
if filesystem.exist?(dest_file)
|
97
|
+
next
|
98
|
+
end
|
99
|
+
|
100
|
+
logger&.info("materializing file #{filename} (#{size} bytes)")
|
101
|
+
filesystem.mkdir_p(File.dirname(File.join(dest_dir, dest_file)))
|
102
|
+
|
103
|
+
filesystem.with_writeable(File.join(dest_dir, dest_file), "w") do |io|
|
104
|
+
if stub_files
|
105
|
+
io.write("(stub) #{filename}: #{size} bytes")
|
106
|
+
else
|
107
|
+
metis_client.download_file(url) do |chunk|
|
108
|
+
if Random.rand < 0.1
|
109
|
+
logger&.info("Writing #{chunk.length} bytes into #{dest_file}")
|
110
|
+
end
|
111
|
+
|
112
|
+
io.write(chunk)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
dest_file = File.join(dest_dir, metadata_path)
|
119
|
+
filesystem.mkdir_p(File.dirname(dest_file))
|
120
|
+
filesystem.with_writeable(dest_file, "w") do |io|
|
121
|
+
io.write(record_to_serialize.to_json)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def metadata_file_name(record_name:, record_model_name:)
|
126
|
+
"#{record_model_name}/#{record_name.gsub(/\s/, '_')}.json"
|
127
|
+
end
|
128
|
+
|
129
|
+
def bin_file_name(etag:)
|
130
|
+
"bin/#{etag}"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|