etna 0.1.14 → 0.1.20

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/bin/etna +18 -0
  3. data/etna.completion +1001 -0
  4. data/etna_app.completion +133 -0
  5. data/ext/completions/extconf.rb +20 -0
  6. data/lib/commands.rb +395 -0
  7. data/lib/etna.rb +7 -0
  8. data/lib/etna/application.rb +46 -22
  9. data/lib/etna/client.rb +82 -48
  10. data/lib/etna/clients.rb +4 -0
  11. data/lib/etna/clients/enum.rb +9 -0
  12. data/lib/etna/clients/janus.rb +2 -0
  13. data/lib/etna/clients/janus/client.rb +73 -0
  14. data/lib/etna/clients/janus/models.rb +78 -0
  15. data/lib/etna/clients/magma.rb +4 -0
  16. data/lib/etna/clients/magma/client.rb +80 -0
  17. data/lib/etna/clients/magma/formatting.rb +1 -0
  18. data/lib/etna/clients/magma/formatting/models_csv.rb +354 -0
  19. data/lib/etna/clients/magma/models.rb +630 -0
  20. data/lib/etna/clients/magma/workflows.rb +10 -0
  21. data/lib/etna/clients/magma/workflows/add_project_models_workflow.rb +67 -0
  22. data/lib/etna/clients/magma/workflows/attribute_actions_from_json_workflow.rb +62 -0
  23. data/lib/etna/clients/magma/workflows/create_project_workflow.rb +123 -0
  24. data/lib/etna/clients/magma/workflows/crud_workflow.rb +85 -0
  25. data/lib/etna/clients/magma/workflows/ensure_containing_record_workflow.rb +44 -0
  26. data/lib/etna/clients/magma/workflows/file_attributes_blank_workflow.rb +68 -0
  27. data/lib/etna/clients/magma/workflows/file_linking_workflow.rb +115 -0
  28. data/lib/etna/clients/magma/workflows/json_converters.rb +81 -0
  29. data/lib/etna/clients/magma/workflows/json_validators.rb +452 -0
  30. data/lib/etna/clients/magma/workflows/model_synchronization_workflow.rb +306 -0
  31. data/lib/etna/clients/magma/workflows/record_synchronization_workflow.rb +63 -0
  32. data/lib/etna/clients/magma/workflows/update_attributes_from_csv_workflow.rb +246 -0
  33. data/lib/etna/clients/metis.rb +3 -0
  34. data/lib/etna/clients/metis/client.rb +239 -0
  35. data/lib/etna/clients/metis/models.rb +313 -0
  36. data/lib/etna/clients/metis/workflows.rb +2 -0
  37. data/lib/etna/clients/metis/workflows/metis_download_workflow.rb +37 -0
  38. data/lib/etna/clients/metis/workflows/metis_upload_workflow.rb +137 -0
  39. data/lib/etna/clients/polyphemus.rb +3 -0
  40. data/lib/etna/clients/polyphemus/client.rb +33 -0
  41. data/lib/etna/clients/polyphemus/models.rb +68 -0
  42. data/lib/etna/clients/polyphemus/workflows.rb +1 -0
  43. data/lib/etna/clients/polyphemus/workflows/set_configuration_workflow.rb +47 -0
  44. data/lib/etna/command.rb +243 -5
  45. data/lib/etna/controller.rb +4 -0
  46. data/lib/etna/csvs.rb +159 -0
  47. data/lib/etna/directed_graph.rb +56 -0
  48. data/lib/etna/environment_scoped.rb +19 -0
  49. data/lib/etna/errors.rb +6 -0
  50. data/lib/etna/generate_autocompletion_script.rb +131 -0
  51. data/lib/etna/json_serializable_struct.rb +37 -0
  52. data/lib/etna/logger.rb +24 -2
  53. data/lib/etna/multipart_serializable_nested_hash.rb +50 -0
  54. data/lib/etna/route.rb +1 -1
  55. data/lib/etna/server.rb +3 -0
  56. data/lib/etna/spec/vcr.rb +99 -0
  57. data/lib/etna/templates/attribute_actions_template.json +43 -0
  58. data/lib/etna/test_auth.rb +3 -1
  59. data/lib/etna/user.rb +11 -1
  60. data/lib/helpers.rb +90 -0
  61. metadata +70 -5
@@ -0,0 +1,3 @@
1
+ require_relative './metis/client'
2
+ require_relative './metis/models'
3
+ require_relative './metis/workflows'
@@ -0,0 +1,239 @@
1
+ require 'net/http/persistent'
2
+ require 'net/http/post/multipart'
3
+ require 'singleton'
4
+ require 'cgi'
5
+ require 'json'
6
+ require_relative '../../client'
7
+ require_relative './models'
8
+
9
+ module Etna
10
+ module Clients
11
+ class Metis
12
+ attr_reader :token
13
+ def initialize(host:, token:, persistent: true, ignore_ssl: false)
14
+ raise 'Metis client configuration is missing host.' unless host
15
+ raise 'Metis client configuration is missing token.' unless token
16
+ @etna_client = ::Etna::Client.new(
17
+ host,
18
+ token,
19
+ persistent: persistent,
20
+ ignore_ssl: ignore_ssl)
21
+
22
+ @token = token
23
+ end
24
+
25
+ def list_all_folders(list_all_folders_request = ListFoldersRequest.new)
26
+ FoldersResponse.new(
27
+ @etna_client.folder_list_all_folders(list_all_folders_request.to_h))
28
+ end
29
+
30
+ def list_folder(list_folder_request = ListFolderRequest.new)
31
+ FoldersAndFilesResponse.new(
32
+ @etna_client.folder_list(list_folder_request.to_h))
33
+ end
34
+
35
+ def ensure_parent_folder_exists(project_name:, bucket_name:, path:)
36
+ create_folder_request = CreateFolderRequest.new(
37
+ project_name: project_name,
38
+ bucket_name: bucket_name,
39
+ folder_path: parent_folder_path(path)
40
+ )
41
+ create_folder(create_folder_request) if !folder_exists?(create_folder_request)
42
+ end
43
+
44
+ def rename_folder(rename_folder_request)
45
+ ensure_parent_folder_exists(
46
+ project_name: rename_folder_request.project_name,
47
+ bucket_name: rename_folder_request.new_bucket_name,
48
+ path: rename_folder_request.new_folder_path
49
+ ) if rename_folder_request.create_parent
50
+
51
+ FoldersResponse.new(
52
+ @etna_client.folder_rename(rename_folder_request.to_h))
53
+ end
54
+
55
+ def rename_file(rename_file_request)
56
+ ensure_parent_folder_exists(
57
+ project_name: rename_file_request.project_name,
58
+ bucket_name: rename_file_request.new_bucket_name,
59
+ path: rename_file_request.new_file_path # ensure_parent_folder_exists() parses this for the parent path
60
+ ) if rename_file_request.create_parent
61
+
62
+ FilesResponse.new(
63
+ @etna_client.file_rename(rename_file_request.to_h))
64
+ end
65
+
66
+ def create_folder(create_folder_request)
67
+ FoldersResponse.new(
68
+ @etna_client.folder_create(create_folder_request.to_h))
69
+ end
70
+
71
+ def delete_folder(delete_folder_request)
72
+ FoldersResponse.new(
73
+ @etna_client.folder_remove(delete_folder_request.to_h))
74
+ end
75
+
76
+ def find(find_request)
77
+ FoldersAndFilesResponse.new(
78
+ @etna_client.bucket_find(find_request.to_h))
79
+ end
80
+
81
+ def download_file(file_or_url = File.new, &block)
82
+ if file_or_url.instance_of?(File)
83
+ download_path = file_or_url.download_path
84
+ else
85
+ download_path = file_or_url.sub(%r!^https://[^/]*?/!, '/')
86
+ end
87
+
88
+ @etna_client.get(download_path) do |response|
89
+ response.read_body(&block)
90
+ end
91
+ end
92
+
93
+ def upload_start(upload_start_request = UploadStartRequest.new)
94
+ json = nil
95
+ @etna_client.post(upload_start_request.upload_path, upload_start_request) do |res|
96
+ json = JSON.parse(res.body)
97
+ end
98
+
99
+ UploadResponse.new(json)
100
+ end
101
+
102
+ def authorize_upload(authorize_upload_request = AuthorizeUploadRequest.new)
103
+ json = nil
104
+ @etna_client.post("/authorize/upload", authorize_upload_request) do |res|
105
+ json = JSON.parse(res.body)
106
+ end
107
+
108
+ UploadResponse.new(json)
109
+ end
110
+
111
+ def upload_blob(upload_blob_request = UploadBlobRequest.new)
112
+ json = nil
113
+ @etna_client.multipart_post(upload_blob_request.upload_path, upload_blob_request.encode_multipart_content) do |res|
114
+ json = JSON.parse(res.body)
115
+ end
116
+
117
+ UploadResponse.new(json)
118
+ end
119
+
120
+ def copy_files(copy_files_request)
121
+ FilesResponse.new(
122
+ @etna_client.file_bulk_copy(copy_files_request.to_h))
123
+ end
124
+
125
+ def folder_exists?(create_folder_request)
126
+ # NOTE: this doesn't test if the folder_path itself exists
127
+ # This can be confusing for root folders, because
128
+ # they have no parents, so you don't need
129
+ # to create anything.
130
+ return true if create_folder_request.folder_path.empty? # root folder
131
+
132
+ # returns 422 if the folder_path does not exist
133
+ begin
134
+ list_folder(
135
+ Etna::Clients::Metis::ListFolderRequest.new(
136
+ project_name: create_folder_request.project_name,
137
+ bucket_name: create_folder_request.bucket_name,
138
+ folder_path: create_folder_request.folder_path
139
+ ))
140
+ rescue Etna::Error => e
141
+ return false if e.status == 422
142
+ raise
143
+ end
144
+ return true
145
+ end
146
+
147
+ def folders(project_name:, bucket_name:)
148
+ @folders ||= Hash.new { |h, key|
149
+ h[key] = list_all_folders(
150
+ Etna::Clients::Metis::ListFoldersRequest.new(
151
+ project_name: project_name,
152
+ bucket_name: key
153
+ )).folders.all
154
+ }
155
+
156
+ @folders[bucket_name]
157
+ end
158
+
159
+ def rename_folders_by_regex(project_name:, source_bucket:, source_folders:, dest_bucket:, regex:)
160
+ found_folders = source_folders.select { |folder|
161
+ folder.folder_path =~ regex
162
+ }
163
+
164
+ return if found_folders.length == 0
165
+
166
+ found_folders.each { |folder|
167
+ # If the destination folder already exists, we need to copy the files
168
+ # over to it and delete the source folder.
169
+ create_folder_request = CreateFolderRequest.new(
170
+ project_name: project_name,
171
+ bucket_name: dest_bucket,
172
+ folder_path: folder.folder_path
173
+ )
174
+
175
+ if folder_exists?(create_folder_request)
176
+ recursively_rename_folder(
177
+ project_name: project_name,
178
+ source_bucket: source_bucket,
179
+ dest_bucket: dest_bucket,
180
+ folder: folder
181
+ )
182
+ else
183
+ rename_folder(Etna::Clients::Metis::RenameFolderRequest.new(
184
+ bucket_name: source_bucket,
185
+ project_name: project_name,
186
+ folder_path: folder.folder_path,
187
+ new_bucket_name: dest_bucket,
188
+ new_folder_path: folder.folder_path,
189
+ create_parent: true)
190
+ )
191
+ end
192
+ }
193
+ end
194
+
195
+ def recursively_rename_folder(project_name:, source_bucket:, dest_bucket:, folder:)
196
+ folder_contents = list_folder(
197
+ Etna::Clients::Metis::ListFolderRequest.new(
198
+ project_name: project_name,
199
+ bucket_name: source_bucket,
200
+ folder_path: folder.folder_path
201
+ ))
202
+
203
+ folder_contents.folders.all.each do |sub_folder|
204
+ recursively_rename_folder(
205
+ project_name: project_name,
206
+ source_bucket: source_bucket,
207
+ dest_bucket: dest_bucket,
208
+ folder: sub_folder
209
+ )
210
+ end
211
+
212
+ folder_contents.files.all.each do |file|
213
+ rename_file(Etna::Clients::Metis::RenameFileRequest.new(
214
+ bucket_name: source_bucket,
215
+ project_name: project_name,
216
+ file_path: file.file_path,
217
+ new_bucket_name: dest_bucket,
218
+ new_file_path: file.file_path,
219
+ create_parent: true)
220
+ )
221
+ end
222
+
223
+ # Now delete the source folder
224
+ delete_folder(
225
+ Etna::Clients::Metis::DeleteFolderRequest.new(
226
+ project_name: project_name,
227
+ bucket_name: source_bucket,
228
+ folder_path: folder.folder_path
229
+ ))
230
+ end
231
+
232
+ private
233
+
234
+ def parent_folder_path(folder_path)
235
+ folder_path.split('/')[0..-2].join('/')
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,313 @@
1
+ require_relative '../../json_serializable_struct'
2
+ require 'ostruct'
3
+
4
+ module Etna
5
+ module Clients
6
+ class Metis
7
+ class ListFoldersRequest < Struct.new(:project_name, :bucket_name, :offset, :limit, keyword_init: true)
8
+ include JsonSerializableStruct
9
+
10
+ def initialize(**params)
11
+ super({}.update(params))
12
+ end
13
+
14
+ def to_h
15
+ # The :project_name comes in from Polyphemus as a symbol value,
16
+ # we need to make sure it's a string because it's going
17
+ # in the URL.
18
+ super().compact.transform_values(&:to_s)
19
+ end
20
+ end
21
+
22
+ class RenameFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, :new_bucket_name, :new_folder_path, :create_parent, keyword_init: true)
23
+ include JsonSerializableStruct
24
+
25
+ def initialize(**params)
26
+ super({create_parent: false}.update(params))
27
+ end
28
+
29
+ def to_h
30
+ # The :project_name comes in from Polyphemus as a symbol value,
31
+ # we need to make sure it's a string because it's going
32
+ # in the URL.
33
+ super().compact.transform_values(&:to_s)
34
+ end
35
+ end
36
+
37
+ class RenameFileRequest < Struct.new(:project_name, :bucket_name, :file_path, :new_bucket_name, :new_file_path, :create_parent, keyword_init: true)
38
+ include JsonSerializableStruct
39
+
40
+ def initialize(**params)
41
+ super({create_parent: false}.update(params))
42
+ end
43
+
44
+ def to_h
45
+ # The :project_name comes in from Polyphemus as a symbol value,
46
+ # we need to make sure it's a string because it's going
47
+ # in the URL.
48
+ super().compact.transform_values(&:to_s)
49
+ end
50
+ end
51
+
52
+ class ListFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
53
+ include JsonSerializableStruct
54
+
55
+ def initialize(**params)
56
+ super({}.update(params))
57
+ end
58
+
59
+ def to_h
60
+ # The :project_name comes in from Polyphemus as a symbol value,
61
+ # we need to make sure it's a string because it's going
62
+ # in the URL.
63
+ super().compact.transform_values(&:to_s)
64
+ end
65
+ end
66
+
67
+ class CreateFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
68
+ include JsonSerializableStruct
69
+
70
+ def initialize(**params)
71
+ super({}.update(params))
72
+ end
73
+
74
+ def to_h
75
+ # The :project_name comes in from Polyphemus as a symbol value,
76
+ # we need to make sure it's a string because it's going
77
+ # in the URL.
78
+ super().compact.transform_values(&:to_s)
79
+ end
80
+ end
81
+
82
+ class DeleteFolderRequest < Struct.new(:project_name, :bucket_name, :folder_path, keyword_init: true)
83
+ include JsonSerializableStruct
84
+
85
+ def initialize(**params)
86
+ super({}.update(params))
87
+ end
88
+
89
+ def to_h
90
+ # The :project_name comes in from Polyphemus as a symbol value,
91
+ # we need to make sure it's a string because it's going
92
+ # in the URL.
93
+ super().compact.transform_values(&:to_s)
94
+ end
95
+ end
96
+
97
+ class FindRequest < Struct.new(:project_name, :bucket_name, :limit, :offset, :params, keyword_init: true)
98
+ include JsonSerializableStruct
99
+
100
+ def initialize(**args)
101
+ super({params: []}.update(args))
102
+ end
103
+
104
+ def add_param(param)
105
+ params << param
106
+ end
107
+
108
+ def to_h
109
+ # The nested :params values don't get converted correctly with transform_values, so it's
110
+ # easier to do from a JSON string
111
+ JSON.parse(to_json, :symbolize_names => true)
112
+ end
113
+ end
114
+
115
+ class FindParam < Struct.new(:attribute, :predicate, :value, :type, keyword_init: true)
116
+ include JsonSerializableStruct
117
+ def initialize(**args)
118
+ super({}.update(args))
119
+ end
120
+ end
121
+
122
+ class CopyFilesRequest < Struct.new(:project_name, :revisions, keyword_init: true)
123
+ include JsonSerializableStruct
124
+
125
+ def initialize(**args)
126
+ super({revisions: []}.update(args))
127
+ end
128
+
129
+ def add_revision(revision)
130
+ revisions << revision
131
+ end
132
+
133
+ def to_h
134
+ # The nested :revisions values don't get converted correctly with transform_values, so it's
135
+ # easier to do from a JSON string
136
+ JSON.parse(to_json, :symbolize_names => true)
137
+ end
138
+ end
139
+
140
+ class CopyRevision < Struct.new(:source, :dest, keyword_init: true)
141
+ include JsonSerializableStruct
142
+ def initialize(**args)
143
+ super({}.update(args))
144
+ end
145
+ end
146
+
147
+ class FoldersResponse
148
+ attr_reader :raw
149
+
150
+ def initialize(raw = {})
151
+ @raw = raw
152
+ end
153
+
154
+ def folders
155
+ Folders.new(raw[:folders])
156
+ end
157
+ end
158
+
159
+ class FilesResponse
160
+ attr_reader :raw
161
+
162
+ def initialize(raw = {})
163
+ @raw = raw
164
+ end
165
+
166
+ def files
167
+ Files.new(raw[:files])
168
+ end
169
+ end
170
+
171
+ class FoldersAndFilesResponse < FoldersResponse
172
+ def files
173
+ Files.new(raw[:files])
174
+ end
175
+ end
176
+
177
+ class Files
178
+ attr_reader :raw
179
+
180
+ def initialize(raw = {})
181
+ @raw = raw
182
+ end
183
+
184
+ def all
185
+ raw.map { |file| File.new(file) }
186
+ end
187
+ end
188
+
189
+ class Folders
190
+ attr_reader :raw
191
+
192
+ def initialize(raw = {})
193
+ @raw = raw
194
+ end
195
+
196
+ def all
197
+ raw.map { |folder| Folder.new(folder) }
198
+ end
199
+ end
200
+
201
+ class File
202
+ attr_reader :raw
203
+
204
+ def initialize(raw = {})
205
+ @raw = raw
206
+ end
207
+
208
+ def file_path
209
+ raw[:file_path]
210
+ end
211
+
212
+ def project_name
213
+ raw[:project_name]
214
+ end
215
+
216
+ def bucket_name
217
+ raw[:bucket_name]
218
+ end
219
+
220
+ def download_path
221
+ raw[:download_url].nil? ?
222
+ "/#{project_name}/download/#{bucket_name}/#{file_path}" :
223
+ raw[:download_url].sub(%r!^https://[^/]*?/!, '/')
224
+ end
225
+
226
+ def download_url
227
+ raw[:download_url] || ''
228
+ end
229
+
230
+ def file_name
231
+ raw[:file_name]
232
+ end
233
+
234
+ def updated_at
235
+ time = raw[:updated_at]
236
+ time.nil? ? nil : Time.parse(time)
237
+ end
238
+
239
+ def size
240
+ raw[:size]
241
+ end
242
+ end
243
+
244
+ class Folder
245
+ attr_reader :raw
246
+
247
+ def initialize(raw = {})
248
+ @raw = raw
249
+ end
250
+
251
+ def folder_path
252
+ raw[:folder_path]
253
+ end
254
+
255
+ def bucket_name
256
+ raw[:bucket_name]
257
+ end
258
+ end
259
+
260
+ class AuthorizeUploadRequest < Struct.new(:project_name, :bucket_name, :file_path, keyword_init: true)
261
+ include JsonSerializableStruct
262
+ end
263
+
264
+ class UploadStartRequest < Struct.new(:file_size, :action, :metis_uid, :next_blob_size, :upload_path, :next_blob_hash, :reset, keyword_init: true)
265
+ include JsonSerializableStruct
266
+
267
+ def initialize(args)
268
+ super({ action: UploadAction::START }.update(args))
269
+ end
270
+ end
271
+
272
+ class UploadBlobRequest < Struct.new(:file_size, :action, :metis_uid, :blob_data, :upload_path, :next_blob_size, :next_blob_hash, :current_byte_position, keyword_init: true)
273
+ include MultipartSerializableNestedHash
274
+
275
+ def initialize(args)
276
+ super({ action: UploadAction::BLOB }.update(args))
277
+ end
278
+
279
+ def encode_multipart_content(base_key = '')
280
+ self.class.encode_multipart_content(to_h, base_key)
281
+ end
282
+ end
283
+
284
+ class UploadResponse
285
+ attr_reader :raw
286
+ def initialize(raw = {})
287
+ @raw = raw
288
+ end
289
+
290
+ def current_byte_position
291
+ raw['current_byte_position'].to_i
292
+ end
293
+
294
+ def url
295
+ raw['url'] || ''
296
+ end
297
+
298
+ def next_blob_size
299
+ raw['next_blob_size'].to_i
300
+ end
301
+
302
+ def upload_path
303
+ url.sub(%r!^https://[^/]*?/!, '/')
304
+ end
305
+ end
306
+
307
+ class UploadAction < String
308
+ START = UploadAction.new("start")
309
+ BLOB = UploadAction.new("blob")
310
+ end
311
+ end
312
+ end
313
+ end