etna 0.1.15 → 0.1.21

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 +4 -0
  60. data/lib/helpers.rb +90 -0
  61. metadata +70 -5
@@ -0,0 +1,10 @@
1
+ require_relative './workflows/crud_workflow'
2
+ require_relative './workflows/file_linking_workflow'
3
+ require_relative './workflows/ensure_containing_record_workflow'
4
+ require_relative './workflows/model_synchronization_workflow'
5
+ require_relative './workflows/file_attributes_blank_workflow'
6
+ require_relative './workflows/record_synchronization_workflow'
7
+ require_relative './workflows/update_attributes_from_csv_workflow'
8
+ require_relative './workflows/create_project_workflow'
9
+ require_relative './workflows/add_project_models_workflow'
10
+ require_relative './workflows/attribute_actions_from_json_workflow'
@@ -0,0 +1,67 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'ostruct'
4
+ require_relative './model_synchronization_workflow'
5
+ require_relative '../../janus/models'
6
+ require_relative './json_validators'
7
+ require_relative './json_converters'
8
+
9
+ module Etna
10
+ module Clients
11
+ class Magma
12
+ class AddProjectModelsWorkflow < Struct.new(:magma_client, keyword_init: true)
13
+ def plan_synchronization(changeset, project, target_model = 'project')
14
+ apply_matrix_constants(changeset)
15
+ workflow = ModelSynchronizationWorkflow.new(target_client: magma_client,
16
+ source_models: changeset.models, renames: changeset.renames,
17
+ target_project: project, plan_only: true)
18
+ workflow.ensure_model_tree(target_model)
19
+ workflow
20
+ end
21
+
22
+ def apply_matrix_constants(changeset)
23
+ changeset.models.model_keys.each do |model_name|
24
+ model = changeset.models.model(model_name)
25
+ attributes = model.template.attributes
26
+ attributes.attribute_keys.each do |attribute_name|
27
+ attribute = attributes.attribute(attribute_name)
28
+ next unless (validation = attribute&.validation)
29
+ next unless (value = validation['value'])
30
+
31
+ if validation['type'] == 'Array' && value&.first.start_with?(Etna::Clients::Magma::ModelsCsv::COPY_OPTIONS_SENTINEL)
32
+ digest = value&.first.slice((Etna::Clients::Magma::ModelsCsv::COPY_OPTIONS_SENTINEL.length)..-1)
33
+ attribute.validation = { 'type' => 'Array', 'value' => changeset.matrix_constants[digest] }
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def prepare_changeset_from_csv(filename: nil, io: nil, &err_block)
40
+ importer = ModelsCsv::Importer.new
41
+ changeset = importer.prepare_changeset(filename: filename, input_io: io, &err_block)
42
+ self.class.validate_changeset(changeset, &err_block)
43
+ changeset
44
+ end
45
+
46
+ def self.validate_changeset(changeset, &err_block)
47
+ changeset.models.model_keys.each do |model_name|
48
+ validator = AddModelValidator.new(changeset.models, model_name)
49
+ validator.validate
50
+ validator.errors.each(&err_block)
51
+ end
52
+
53
+ validator = RenamesValidator.new(changeset.models, changeset.renames)
54
+ validator.validate
55
+ validator.errors.each(&err_block)
56
+ end
57
+
58
+ def write_models_template_csv(project_name, target_model = 'project', filename: nil, io: nil)
59
+ models = magma_client.retrieve(RetrievalRequest.new(project_name: project_name, model_name: 'all')).models
60
+ descendants = models.to_directed_graph.descendants(target_model)
61
+ exporter = ModelsCsv::Exporter.new
62
+ exporter.write_models(models, [target_model] + descendants.keys, filename: filename, output_io: io)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,62 @@
1
+ # Updates, renames, or adds attributes to a model, from a JSON file.
2
+ # This workflow:
3
+ # 1) Validates the attributes JSON file.
4
+ # 2) Validates that the model(s) and any link attribute models exist in Magma.
5
+ # 3) Executes each of the updates. This will have to be direct Magma client calls,
6
+ # since the model_synch workflow only does Adds and does not currently
7
+ # handle other attribute actions.
8
+
9
+ require 'json'
10
+ require 'ostruct'
11
+ require_relative './json_validators'
12
+ require_relative './json_converters'
13
+
14
+ module Etna
15
+ module Clients
16
+ class Magma
17
+ # Note! These workflows are not perfectly atomic, nor perfectly synchronized due to nature of the backend.
18
+ # These primitives are best effort locally synchronized, but cannot defend the backend or simultaneous
19
+ # system updates.
20
+ class AttributeActionsFromJsonWorkflow < Struct.new(:magma_client, :project_name, :filepath, keyword_init: true)
21
+ attr_reader :actions
22
+ def initialize(**params)
23
+ super({}.update(params))
24
+
25
+ actions_json = JSON.parse(File.read(filepath))
26
+
27
+ converter = Etna::Clients::Magma::AttributeActionsConverter.new(actions_json)
28
+ @actions = converter.convert
29
+
30
+ validator = Etna::Clients::Magma::AttributeActionsValidator.new(
31
+ actions,
32
+ project_models)
33
+ validator.validate
34
+
35
+ raise "Attributes JSON has errors:\n * #{format_errors(validator.errors)}" unless validator.valid?
36
+ end
37
+
38
+ def format_errors(errors)
39
+ errors.map { |e| e.gsub("\n", "\n\t") }.join("\n * ")
40
+ end
41
+
42
+ def project_models
43
+ @project_models ||= magma_client.retrieve(Etna::Clients::Magma::RetrievalRequest.new(
44
+ project_name: project_name,
45
+ model_name: 'all')).models
46
+ end
47
+
48
+ def execute_actions
49
+ magma_client.update_model(Etna::Clients::Magma::UpdateModelRequest.new(
50
+ project_name: project_name,
51
+ actions: actions))
52
+ end
53
+
54
+ def run!
55
+ puts "Executing the attribute actions against Magma."
56
+ execute_actions
57
+ puts "All complete!"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,123 @@
1
+ # Base workflow for setting up a project by a super user.
2
+ # 1) Creates the project in janus
3
+ # 2) Adds administrator(s) to Janus
4
+ # 3) Refreshes the user's token with the new privileges.
5
+ # 4) Creates the project in Magma.
6
+
7
+ require 'base64'
8
+ require 'json'
9
+ require 'ostruct'
10
+ require_relative './model_synchronization_workflow'
11
+ require_relative '../../janus/models'
12
+ require_relative './json_validators'
13
+ require_relative './json_converters'
14
+
15
+ module Etna
16
+ module Clients
17
+ class Magma
18
+ class CreateProjectWorkflow < Struct.new(:magma_client, :janus_client, :project_name, :project_name_full, keyword_init: true)
19
+ def create_janus_project!
20
+ janus_client.add_project(Etna::Clients::Janus::AddProjectRequest.new(
21
+ project_name: project_name,
22
+ project_name_full: project_name_full
23
+ ))
24
+ end
25
+
26
+ def add_janus_user(email, name, role)
27
+ janus_client.add_user(Etna::Clients::Janus::AddUserRequest.new(
28
+ project_name: project_name,
29
+ email: email,
30
+ name: name,
31
+ role: role,
32
+ ))
33
+ end
34
+
35
+ def promote_to_administrator(email)
36
+ janus_client.update_permission(Etna::Clients::Janus::UpdatePermissionRequest.new(
37
+ project_name: project_name,
38
+ # email: user['email'],
39
+ email: email,
40
+ role: 'administrator',
41
+ ))
42
+ end
43
+
44
+ def refreshed_token
45
+ janus_client.refresh_token(Etna::Clients::Janus::RefreshTokenRequest.new).token
46
+ end
47
+
48
+ def update_magma_client_token!
49
+ self.magma_client = Etna::Clients::Magma.new(
50
+ host: self.magma_client.host,
51
+ token: refreshed_token,
52
+ ignore_ssl: self.magma_client.ignore_ssl)
53
+ end
54
+
55
+ def create_magma_project!
56
+ magma_client.update_model(Etna::Clients::Magma::UpdateModelRequest.new(
57
+ project_name: project_name,
58
+ actions: [Etna::Clients::Magma::AddProjectAction.new]))
59
+ end
60
+
61
+ def create_magma_project_record!
62
+ magma_client.update(Etna::Clients::Magma::UpdateRequest.new(
63
+ project_name: project_name,
64
+ revisions: {
65
+ 'project' => { project_name => { name: project_name } },
66
+ }))
67
+ end
68
+
69
+ def setup_janus_project!
70
+ puts "Creating Janus project."
71
+ create_janus_project!
72
+ puts "Done! Adding you as an administrator on the project."
73
+ add_janus_user(user['email'], "#{user['first']} #{user['last']}", 'editor')
74
+ promote_to_administrator(user['email'])
75
+ update_magma_client_token!
76
+
77
+ puts "Done with setting up the project in Janus!"
78
+ end
79
+
80
+ def setup_magma_project!
81
+ puts "Creating the project in Magma."
82
+ create_magma_project!
83
+ puts "Done! Adding your new project record."
84
+ create_magma_project_record!
85
+ end
86
+
87
+ def create!
88
+ setup_janus_project!
89
+ setup_magma_project!
90
+
91
+ while true
92
+ puts "Add more users? Y/n"
93
+ break unless STDIN.gets.chomp == 'Y'
94
+ puts "User name?"
95
+ name = STDIN.gets.chomp
96
+ puts "Email?"
97
+ email = STDIN.gets.chomp
98
+ puts "Role? (editor/viewer/administrator)"
99
+ role = STDIN.gets.chomp
100
+ puts "Adding #{name} (#{email}) as a #{role}."
101
+ puts "Confirm? Y/n"
102
+ break unless STDIN.gets.chomp == 'Y'
103
+
104
+ if role == 'administrator'
105
+ add_janus_user(email, name, 'editor')
106
+ promote_to_administrator(email)
107
+ else
108
+ add_janus_user(email, name, role)
109
+ end
110
+ end
111
+
112
+ puts "All complete!"
113
+ puts "You need to visit Janus to refresh your token."
114
+ puts "You can now log into any app to manage your data."
115
+ end
116
+
117
+ def user
118
+ @user ||= JSON.parse(Base64.urlsafe_decode64(magma_client.token.split('.')[1]))
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,85 @@
1
+ require 'ostruct'
2
+
3
+ module Etna
4
+ module Clients
5
+ class Magma
6
+ class MagmaCrudWorkflow < Struct.new(:magma_client, :project_name, :read_only, :logger, :batch_size, :cache_policy, keyword_init: true)
7
+ attr_reader :recorded_updates
8
+
9
+ def initialize(args)
10
+ super(**{logger: Etna::Logger.new('/dev/stdout', 999999, 1024 * 1024), batch_size: 30, cache_policy: :update_on_write}.update(args))
11
+ end
12
+
13
+ def lookup_record(model_name, record_id)
14
+ if (cached = (@cache ||= {}).dig(model_name, record_id))
15
+ return cached
16
+ end
17
+
18
+ result = magma_client.retrieve(RetrievalRequest.new(project_name: project_name, record_names: [record_id], model_name: model_name))\
19
+ .models.model(model_name).documents.document(record_id)
20
+
21
+ if cache_policy
22
+ ((@cache ||= {})[model_name] ||= {})[record_id = result]
23
+ end
24
+
25
+ result
26
+ end
27
+
28
+ def page_records(model_name, request = Etna::Clients::Magma::RetrievalRequest.new(
29
+ project_name: project_name,
30
+ model_name: model_name,
31
+ record_names: 'all',
32
+ page_size: 20,
33
+ page: 1,
34
+ ))
35
+
36
+ documents = Documents.new({})
37
+ last_page = nil
38
+ while last_page.nil? || last_page.raw.length >= 20
39
+ last_page = magma_client.retrieve(request).models.model(model_name).documents
40
+ documents += last_page unless block_given?
41
+ yield last_page if block_given?
42
+ request.page += 1
43
+ end
44
+
45
+ documents
46
+ end
47
+
48
+ # Todo: Introduce associative concatenation operations for response objects and return
49
+ # one response that munges the batched responses together.
50
+ def update_records(method: :update)
51
+ @recorded_updates ||= UpdateRequest.new(project_name: project_name)
52
+
53
+ request = UpdateRequest.new(project_name: project_name)
54
+ yield request
55
+
56
+ case cache_policy
57
+ when :update_on_write
58
+ @cache ||= {}
59
+ request.revisions.each do |model_name, revisions|
60
+ model_revisions = @cache[model_name] ||= {}
61
+
62
+ revisions.each do |record_name, revision|
63
+ if model_revisions.include? record_name
64
+ model_revisions[record_name].update(revision)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ revisions = request.revisions
71
+
72
+ responses = []
73
+ revisions.to_a.each_slice(batch_size) do |batch|
74
+ request.revisions = batch.to_h
75
+ magma_client.send(method, request) unless read_only
76
+ responses << @recorded_updates.revisions.update(request.revisions)
77
+ end
78
+
79
+ responses
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,44 @@
1
+ require 'ostruct'
2
+ require_relative './crud_workflow'
3
+
4
+ module Etna
5
+ module Clients
6
+ class Magma
7
+ class EnsureContainingRecordWorkflow < Struct.new(:magma_crud, :models, keyword_init: true)
8
+ def self.from_remote_models(magma_crud:, **opts)
9
+ opts[:models] = magma_crud.magma_client.retrieve(RetrievalRequest.new(project_name: magma_crud.project_name, model_name: 'all')).models
10
+ opts[:magma_crud] = magma_crud
11
+ self.class.new(**opts)
12
+ end
13
+
14
+ def magma_client
15
+ magma_crud.magma_client
16
+ end
17
+
18
+ def ensure_record(model_name, record_identifiers)
19
+ raise "Could not find containing model #{model_name} defined." unless (model = models.model(model_name))
20
+
21
+ raise "Identifiers #{record_identifiers} do not contain #{model_name}" unless (id = record_identifiers[model_name])
22
+ record = magma_crud.lookup_record(model_name, id)
23
+
24
+ if record.nil?
25
+ attrs = { model.template.identifier => id }
26
+
27
+ parent_attr = model.template.attributes.all.select { |a| a.attribute_type == AttributeType::PARENT }.first
28
+ unless parent_attr.nil?
29
+ parent_attribute_name = parent_attr.attribute_name
30
+ parent_identifier = ensure_record(model.template.parent, record_identifiers)
31
+ attrs.update({parent_attribute_name => parent_identifier})
32
+ end
33
+
34
+ magma_crud.update_records do |update_request|
35
+ update_request.update_revision(model_name, id, attrs)
36
+ end
37
+ end
38
+
39
+ id
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,68 @@
1
+ require 'ostruct'
2
+ require_relative './crud_workflow'
3
+
4
+ module Etna
5
+ module Clients
6
+ class Magma
7
+ class FileAttributesBlankWorkflow < Struct.new(:magma_crud, :model_names, :project_name, keyword_init: true)
8
+
9
+ def initialize(opts)
10
+ super(**{}.update(opts))
11
+ end
12
+
13
+ def magma_client
14
+ magma_crud.magma_client
15
+ end
16
+
17
+ def find_file_and_image_attributes
18
+ {}.tap do |all_matches|
19
+ model_names.each do |model_name|
20
+ attribute_names = models.model(model_name).template.attributes.all.select {|a|
21
+ a.attribute_type == Etna::Clients::Magma::AttributeType::FILE ||
22
+ a.attribute_type == Etna::Clients::Magma::AttributeType::IMAGE}.map { |a|
23
+ a.attribute_name}
24
+ all_matches[model_name] = attribute_names
25
+ end
26
+ end
27
+ end
28
+
29
+ def each_revision
30
+ find_file_and_image_attributes.each do |model_name, attribute_names|
31
+ # Query all records in this model
32
+ request = Etna::Clients::Magma::RetrievalRequest.new(project_name: project_name)
33
+ request.model_name = model_name
34
+ request.attribute_names = attribute_names
35
+ request.record_names = 'all'
36
+ documents = magma_client.retrieve(request).models.model(model_name).documents
37
+
38
+ documents.document_keys.each do |record_name|
39
+ document = documents.document(record_name)
40
+ attribute_names.each do |attribute_name|
41
+ yield [model_name, record_name, attribute_name] if !document[attribute_name]
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def set_file_attrs_blank
48
+ magma_crud.update_records do |update_request|
49
+ each_revision do |model_name, record_name, attribute_name|
50
+ update_request.update_revision(model_name, record_name, revision_for(attribute_name))
51
+ end
52
+ end
53
+ end
54
+
55
+ def revision_for(attribute_name)
56
+ {attribute_name => {path: "::blank"}}
57
+ end
58
+
59
+ def models
60
+ @models ||= begin
61
+ magma_client.retrieve(RetrievalRequest.new(project_name: self.project_name, model_name: 'all')).models
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+