cts-mpx-aci 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +64 -0
  3. data/Gemfile +30 -0
  4. data/Gemfile.lock +178 -0
  5. data/Guardfile +40 -0
  6. data/LICENSE +201 -0
  7. data/README.md +203 -0
  8. data/Rakefile +6 -0
  9. data/Rules +53 -0
  10. data/bin/console +7 -0
  11. data/bin/setup +8 -0
  12. data/content/CHANGELOG.md +1 -0
  13. data/content/EXAMPLES.md +1 -0
  14. data/content/README.md +1 -0
  15. data/content/assets/bootstrap.min.css +12 -0
  16. data/content/assets/images/cts-logo-wide.svg +121 -0
  17. data/content/assets/images/cts-logo.svg +119 -0
  18. data/content/assets/syntax.css +210 -0
  19. data/content/coverage +1 -0
  20. data/content/doc +1 -0
  21. data/content/specifications.html +1 -0
  22. data/content/stylesheet.css +101 -0
  23. data/cts-mpx-aci.gemspec +23 -0
  24. data/data/stencils/account_record.json +431 -0
  25. data/data/stencils/media_custom_fields.json +37 -0
  26. data/data/stencils/servers.json +31 -0
  27. data/data/stencils/task_templates.json +17 -0
  28. data/data/stencils/test.json +13 -0
  29. data/examples/collect.md +21 -0
  30. data/examples/complete_basic.md +95 -0
  31. data/examples/deploy.md +25 -0
  32. data/examples/image.md +41 -0
  33. data/examples/pre_post_block.md +101 -0
  34. data/layouts/default.html +52 -0
  35. data/lib/cts/mpx/aci/extensions/cts/mpx/entries.rb +29 -0
  36. data/lib/cts/mpx/aci/extensions/cts/mpx/entry.rb +130 -0
  37. data/lib/cts/mpx/aci/extensions/services/data/entry.rb +136 -0
  38. data/lib/cts/mpx/aci/stencil.rb +148 -0
  39. data/lib/cts/mpx/aci/tasks/collect.rb +91 -0
  40. data/lib/cts/mpx/aci/tasks/deploy.rb +117 -0
  41. data/lib/cts/mpx/aci/tasks/image.rb +161 -0
  42. data/lib/cts/mpx/aci/transformations.rb +144 -0
  43. data/lib/cts/mpx/aci/validators.rb +114 -0
  44. data/lib/cts/mpx/aci/version.rb +7 -0
  45. data/lib/cts/mpx/aci.rb +76 -0
  46. data/nanoc.yaml +22 -0
  47. metadata +158 -0
@@ -0,0 +1,29 @@
1
+ module Cts
2
+ module Mpx
3
+ # extensions to the Cts::Mpx::Collection
4
+ class Entries
5
+ # All entries in the collections.
6
+ #
7
+ # @return [hash] of entries with filepath as key and md5 hash as value
8
+ def files
9
+ hash = {}
10
+ entries.map { |e| hash.store "#{e.directory}/#{e.filename}", e.hash }
11
+ hash
12
+ end
13
+
14
+ # transform every entry
15
+ def transform(user)
16
+ each { |entry| entry.transform user }
17
+ nil
18
+ end
19
+
20
+ # untransform every entry
21
+ #
22
+ # @param [String] target_account to transform to
23
+ def untransform(user, target_account)
24
+ each { |entry| entry.untransform(user, target_account) }
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,130 @@
1
+ module Cts
2
+ module Mpx
3
+ # extensions to the Cts::Mpx::Entry
4
+ class Entry
5
+ # Collection of dependencies required to deploy this entry
6
+ #
7
+ # @return [Array] unordered list of dependencies
8
+ def dependencies
9
+ dependencies = []
10
+ Cts::Mpx::Aci::Transformations.traverse_for(to_h[:entry], :transform) do |_k, v|
11
+ next if v.start_with? "http://access.auth.theplatform.com/data/Account"
12
+ next if v == "urn:cts:aci:target-account"
13
+
14
+ dependencies.push v
15
+ end
16
+ dependencies.uniq
17
+ end
18
+
19
+ # return the difference between two entries
20
+ def diff(other_entry)
21
+ raise ArgumentError, 'an entry must be supplied' unless other_entry.class == self.class
22
+ raise ArgumentError, 'both entries must have the same service' unless service == other_entry.service
23
+ raise ArgumentError, 'both entries must have the same endpoint' unless endpoint == other_entry.endpoint
24
+
25
+ Diffy::Diff.new(to_s, other_entry.to_s).to_s
26
+ end
27
+
28
+ # @return [String] computed filename of the entry.
29
+ def filename
30
+ raise 'Entry must include a guid field' unless fields.collection.map(&:name).include? 'guid'
31
+
32
+ "#{fields['guid']}.json"
33
+ end
34
+
35
+ # @return [String] computed path of the entry.
36
+ def directory
37
+ "#{service}/#{endpoint}"
38
+ end
39
+
40
+ def exists_by?(user, query)
41
+ raise ArgumentError, "user must be signed in" unless user&.token
42
+ raise ArgumentError, "query must be a type of Query" unless query.is_a? Query
43
+
44
+ query.run(user: user)
45
+ return true if query.page.entries&.count&.positive?
46
+
47
+ false
48
+ end
49
+
50
+ # @return [String] complete filepath for an entry
51
+ def filepath
52
+ "#{directory}/#{filename}"
53
+ end
54
+
55
+ # @return [String] MD5 hash, based on formatted JSON
56
+ def hash
57
+ Digest::MD5.hexdigest to_s
58
+ end
59
+
60
+ # true if any reference is found in the entry, false otherwise.
61
+ #
62
+ # @return [Boolean]
63
+ def includes_reference?
64
+ included = false
65
+ Cts::Mpx::Aci::Transformations.traverse_for(to_h[:entry], :transform) do |_k, v|
66
+ included = true
67
+ v
68
+ end
69
+ included
70
+ end
71
+
72
+ # true if any transformed reference is found in the entry, false otherwise.
73
+ #
74
+ # @return [Boolean]
75
+ def includes_transformed_reference?
76
+ ref = false
77
+
78
+ Cts::Mpx::Aci::Transformations.traverse_for(to_h[:entry], :untransform) do |_k, v|
79
+ ref = true
80
+ v
81
+ end
82
+ ref
83
+ end
84
+
85
+ # transform all references in this entry
86
+ # @raise [RuntimeError] if guid field is not included
87
+ # @raise [RuntimeError] if ownerId field is not included
88
+ # @raise [RuntimeError] if user is not set
89
+ def transform(user)
90
+ raise_entry_exceptions!(user)
91
+ hash = to_h[:entry]
92
+ output = Cts::Mpx::Aci::Transformations.traverse_for(hash, :transform) do |_k, v|
93
+ if Cts::Mpx::Aci::Validators.field_reference? v
94
+ Cts::Mpx::Aci::Transformations.transform_field_reference field_reference: v, user: user
95
+ else
96
+ Cts::Mpx::Aci::Transformations.transform_reference reference: v, user: user, original_account: fields['ownerId']
97
+ end
98
+ end
99
+ @fields = Fields.create_from_data(data: output, xmlns: to_h[:namespace])
100
+ self
101
+ end
102
+
103
+ # untransform all transformed references in this entry
104
+ # @raise [RuntimeError] if guid field is not included
105
+ # @raise [RuntimeError] if ownerId field is not included
106
+ # @raise [RuntimeError] if user is not set
107
+ def untransform(user, target_account)
108
+ raise_entry_exceptions!(user)
109
+ hash = to_h[:entry]
110
+ output = Cts::Mpx::Aci::Transformations.traverse_for(hash, :untransform) do |_k, v|
111
+ if Cts::Mpx::Aci::Validators.transformed_field_reference? v
112
+ Cts::Mpx::Aci::Transformations.untransform_field_reference transformed_reference: v, user: user
113
+ else
114
+ Cts::Mpx::Aci::Transformations.untransform_reference transformed_reference: v, user: user, target_account: target_account
115
+ end
116
+ end
117
+ @fields = Fields.create_from_data(data: output, xmlns: to_h[:namespace])
118
+ self
119
+ end
120
+
121
+ private
122
+
123
+ def raise_entry_exceptions!(user)
124
+ raise 'Entry must include an guid field' unless fields.to_h.key?('guid')
125
+ raise 'Entry must include an ownerId field' unless fields.to_h.key?('ownerId')
126
+ raise 'Entry must have user set' unless user
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,136 @@
1
+ module Theplatform
2
+ module Services
3
+ module Data
4
+ # extensions to the Theplatform::Services::Data::Entry
5
+ class Entry
6
+ # Collection of dependencies required to deploy this entry
7
+ #
8
+ # @return [Array] unordered list of dependencies
9
+ def dependencies
10
+ dependencies = []
11
+ Cts::Mpx::Aci::Transformations.traverse_for(to_hash['entry'], :transform) do |_k, v|
12
+ next if v.start_with? "http://access.auth.theplatform.com/data/Account"
13
+ next if v == "urn:cts:aci:target-account"
14
+ dependencies.push v
15
+ v
16
+ end
17
+ dependencies.uniq
18
+ end
19
+
20
+ # return the difference between two entri
21
+
22
+ # true if any reference is found in the entry, false otherwise.
23
+ #
24
+ # @return [Boolean]
25
+ def includes_reference?
26
+ output = false
27
+ Cts::Mpx::Aci::Transformations.traverse_for(to_hash['entry'], :transform) do |_k, v|
28
+ output = true
29
+ v
30
+ end
31
+ output
32
+ end
33
+
34
+ # true if any transformed reference is found in the entry, false otherwise.
35
+ #
36
+ # @return [Boolean]
37
+ def includes_transformed_reference?
38
+ ref = false
39
+ Cts::Mpx::Aci::Transformations.traverse_for(to_hash['entry'], :untransform) do |_k, v|
40
+ ref = true
41
+ v
42
+ end
43
+ ref
44
+ end
45
+
46
+ # @return [String] computed filename of the entry.
47
+ def filename
48
+ raise 'Entry must include an guid field' unless fields.include? 'guid'
49
+ "#{entry['guid']}.json"
50
+ end
51
+
52
+ # @return [String] computed path of the entry.
53
+ def directory
54
+ "#{service}/#{endpoint}"
55
+ end
56
+
57
+ # @return [String] complete filepath for an entry
58
+ def filepath
59
+ "#{directory}/#{filename}"
60
+ end
61
+
62
+ # @return [String] MD5 hash, based on formatted JSON
63
+ def hash
64
+ Digest::MD5.hexdigest to_s
65
+ end
66
+
67
+ # return the difference between two entries
68
+ def diff(other_entry)
69
+ raise ArgumentError, 'an entry must be supplied' unless other_entry.class == self.class
70
+ raise ArgumentError, 'both entries must have the same service' unless service == other_entry.service
71
+ raise ArgumentError, 'both entries must have the same endpoint' unless endpoint == other_entry.endpoint
72
+ Diffy::Diff.new(to_s, other_entry.to_s).to_s
73
+ end
74
+
75
+ # transform all references in this entry
76
+ # @raise [RuntimeError] if guid field is not included
77
+ # @raise [RuntimeError] if ownerId field is not included
78
+ # @raise [RuntimeError] if user is not set
79
+ def transform
80
+ raise_entry_exceptions!
81
+ hash = to_hash['entry']
82
+ output = Cts::Mpx::Aci::Transformations.traverse_for(hash, :transform) do |_k, v|
83
+ if Cts::Mpx::Aci::Validators.field_reference? v
84
+ Cts::Mpx::Aci::Transformations.transform_field_reference field_reference: v, user: user
85
+ else
86
+ Cts::Mpx::Aci::Transformations.transform_reference reference: v, user: user, original_account: ownerId
87
+ end
88
+ end
89
+ load_from_hash "$xmlns" => namespace, 'entry' => { "id" => id }.merge(output)
90
+ end
91
+
92
+ # untransform all transformed references in this entry
93
+ # @raise [RuntimeError] if guid field is not included
94
+ # @raise [RuntimeError] if ownerId field is not included
95
+ # @raise [RuntimeError] if user is not set
96
+ def untransform(target_account)
97
+ raise_entry_exceptions!
98
+ hash = to_hash['entry']
99
+ output = Cts::Mpx::Aci::Transformations.traverse_for(hash, :untransform) do |_k, v|
100
+ if Cts::Mpx::Aci::Validators.transformed_field_reference? v
101
+ Cts::Mpx::Aci::Transformations.untransform_field_reference transformed_reference: v, user: user
102
+ else
103
+ Cts::Mpx::Aci::Transformations.untransform_reference transformed_reference: v, user: user, target_account: target_account
104
+ end
105
+ end
106
+ load_from_hash "$xmlns" => namespace, 'entry' => { "id" => id }.merge(output)
107
+ end
108
+
109
+ def endpoint
110
+ @endpoint.tr '/', ''
111
+ end
112
+
113
+ # :nocov:
114
+ # addresses a bug in the SDK. in the load from hash, the result['entries']. Adding '.first' is the fix so only an entry is returned, not an array.
115
+ def save_to_service
116
+ if existing_entry?
117
+ service_module.send "put_#{endpoint.split(/(?=[A-Z])/).join('_').downcase}", user, [to_hash(include_read_only: false)["entry"]], namespace: namespace
118
+ else
119
+ result = service_module.send "post_#{endpoint.split(/(?=[A-Z])/).join('_').downcase}", user, [to_hash(include_read_only: false)["entry"]], namespace: namespace
120
+ load_from_hash("entry" => result['entries'].first) if result
121
+ end
122
+ true
123
+ end
124
+ # :nocov:
125
+
126
+ private
127
+
128
+ def raise_entry_exceptions!
129
+ raise 'Entry must include an guid field' unless fields.include? 'guid'
130
+ raise 'Entry must include an ownerId field' unless fields.include? 'ownerId'
131
+ raise 'Entry must have user set' unless user
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,148 @@
1
+ module Cts
2
+ module Mpx
3
+ module Aci
4
+ # Container for a selection of queries to run on an account.
5
+ # @!attribute [rw] name
6
+ # @return [String] Name of the stencil
7
+ # @!attribute [rw] original_url
8
+ # @return [String] If applicable, the original location of the asset.
9
+ # @!attribute [rw] schema
10
+ # @return [String] Schema of this stencil
11
+ # @!attribute [rw] queries
12
+ # @return [String] Queries to perform in the collect object
13
+ class Stencil
14
+ include Creatable
15
+
16
+ attribute name: 'name', kind_of: String
17
+ attribute name: 'original_url', kind_of: String
18
+ attribute name: 'schema', kind_of: String
19
+ attribute name: 'queries', kind_of: Array
20
+
21
+ # find a stencil in the loaded stencils by name. when used with no parameter will show all available stencils.
22
+ #
23
+ # @param key [String] name of the stencil
24
+ # @return [Stencil] referenced stencil
25
+ def self.[](key = nil)
26
+ Stencil.class_variable_set(:@@available_stencils, {}) unless Stencil.class_variable_defined? :@@available_stencils
27
+ return @@available_stencils[key] if key
28
+
29
+ @@available_stencils
30
+ end
31
+
32
+ # Smart load a stencil. This will attemp to parse it as json, then load it as a url, and finally try to load a file.
33
+ #
34
+ # @param string [String] string to attempt to smart load.
35
+ # @return [Stencil] loaded stencil
36
+ def self.load(string)
37
+ begin
38
+ data = load_string Stencil.parse_json(string)
39
+ rescue ArgumentError
40
+ return load_url(string) if string.start_with? 'http'
41
+
42
+ return load_file string
43
+ end
44
+
45
+ load_string(string) if data
46
+ end
47
+
48
+ # Attempt to open a local file for reading, loading in the JSON if it is able.
49
+ #
50
+ # @param file [String] file of the file to load.
51
+ # @raise [RuntimeError] If file could not be found
52
+ # @raise [RuntimeError] If file is empty.
53
+ # @return [Stencil] loaded stencil
54
+ def self.load_file(file)
55
+ raise "Could not find file #{file}." unless File.exist? file
56
+
57
+ content = File.read(file)
58
+ raise RuntimeError if content.empty?
59
+
60
+ stencil = load_string(content)
61
+ stencil.original_url = file
62
+ stencil
63
+ end
64
+
65
+ # Attempt to open a local file for reading, loading in the JSON if it is able.
66
+ #
67
+ # @param string [String] string to be parsed and loaded.
68
+ # @raise [ArgumentError] if the parsed queries is not an array
69
+ # @return [Stencil] loaded stencil
70
+ def self.load_string(string)
71
+ data = Stencil.parse_json(string)
72
+
73
+ stencil = Stencil.new
74
+ stencil.name = data['name']
75
+ raise ArgumentError, "queries is not a kind of Array" unless data['queries'].is_a? Array
76
+
77
+ stencil.queries = data['queries']
78
+
79
+ @@available_stencils[stencil.name] = stencil
80
+ stencil
81
+ end
82
+
83
+ # Attempt to open a local file for reading, loading in the JSON if it is able.
84
+ #
85
+ # @param url [String] url to be fetched and passed to load_string
86
+ # @raise [ArgumentError] if the url is not valid
87
+ # @return [Stencil] loaded stencil
88
+ def self.load_url(url)
89
+ begin
90
+ URI.parse url
91
+ rescue URI::InvalidURIError
92
+ raise ArgumentError, "#{url} is not a url"
93
+ end
94
+
95
+ load_string(Excon.get(url).body)
96
+ end
97
+
98
+ # Attempt to open a local file for reading, loading in the JSON if it is able.
99
+ #
100
+ # @param string [String] string is parsed and returned as a hash
101
+ # @raise [ArgumentError] if the arugment is not a type of a string
102
+ # @raise [ArgumentError] if the string could not be parsed
103
+ # @return [Hash] hash of the data from the json
104
+ def self.parse_json(string)
105
+ raise ArgumentError, "#{string.class} is not a kind of String" unless string&.is_a?(String)
106
+
107
+ begin
108
+ data = Oj.load(string)
109
+ rescue Oj::ParseError
110
+ raise ArgumentError, "could not be parsed"
111
+ end
112
+
113
+ data
114
+ end
115
+
116
+ def initialize
117
+ Stencil.class_variable_set(:@@available_stencils, {}) unless Stencil.class_variable_defined? :@@available_stencils
118
+ @name = ""
119
+ @original_url = ""
120
+ @queries = []
121
+ @schema = 1
122
+ end
123
+
124
+ # Attempt to open a local file for reading, loading in the JSON if it is able.
125
+ #
126
+ # @param account_id [String] account_id account_id to collect from
127
+ # @param user [String] user to use to make the collection.
128
+ # @raise [ArgumentError] if the arugment is not a type of a string
129
+ # @raise [ArgumentError] if no queries are provided
130
+ # @return [Collect] a collect object created from the json and parameters passed in.
131
+ def to_collect(account_id: nil, user: nil)
132
+ raise ArgumentError, 'queries must contain entries' unless queries.any?
133
+
134
+ collect = Tasks::Collect.new
135
+ collect.account_id = account_id if account_id
136
+ collect.user = user if user
137
+
138
+ queries.each do |q|
139
+ query = Query.create service: q[:service], endpoint: q[:endpoint]
140
+ collect.queries.push query
141
+ end
142
+
143
+ collect
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,91 @@
1
+ module Cts
2
+ module Mpx
3
+ module Aci
4
+ module Tasks
5
+ # Collect class for gathering data from MPX.
6
+ class Collect
7
+ include Creatable
8
+
9
+ attribute name: 'account_id', kind_of: String
10
+ attribute name: 'user', kind_of: User
11
+ attribute name: 'queries', kind_of: Array
12
+ attribute name: 'entries', kind_of: Entries
13
+
14
+ # Executes the array of queries one by one. Collecting the result into the collections attribute.
15
+ # @raise [RuntimeError] when queries is not set correctly
16
+ # @raise [ArgumentError] when the user attribute is not set
17
+ # @raise [ArgumentError] when the account_id attribute is not set
18
+ # @raise [ArgumentError] when a query does not have service set
19
+ # @raise [ArgumentError] when a query does not have endpoint set
20
+ def collect
21
+ raise "empty queries array" if @queries&.empty?
22
+ raise 'must set the queries attribute.' unless queries
23
+
24
+ @entries = Entries.new
25
+
26
+ queries.each do |config|
27
+ query = run_query(config)
28
+
29
+ if query&.entries.count.positive?
30
+ log_collected_zero_entries config
31
+ next
32
+ end
33
+
34
+ @entries += query.page.to_mpx_entries
35
+ @entries.each do |entry|
36
+ # if we collect the read only fields, it just messes everything up.
37
+ # best to just not collect them.
38
+ service_read_only_fields.each { |field| entry.fields.remove field }
39
+
40
+ log_collected entry
41
+ end
42
+ end
43
+ end
44
+
45
+ def initialize
46
+ @account_id = ""
47
+ @user = nil
48
+ @entries = Entries.new
49
+ @queries = []
50
+ end
51
+
52
+ def queries=(new_queries)
53
+ raise ArgumentError unless new_queries.is_a? Array
54
+ raise ArgumentError if new_queries.map { |e| e.is_a? Hash }.include? false
55
+
56
+ @queries = new_queries
57
+ end
58
+
59
+ private
60
+
61
+ def service_read_only_fields
62
+ ["updated", "added", "addedByUserId", "updatedByUserId", "version"]
63
+ end
64
+
65
+ def logger
66
+ Aci.logger
67
+ end
68
+
69
+ def log_collected(entry)
70
+ logger.info "collected: id: #{entry.fields['id']} guid: #{entry.fields['guid']}"
71
+ end
72
+
73
+ def log_collected_zero_entries(config)
74
+ logger.info "collected zero results for #{config['service']}/#{config['endpoint']}"
75
+ end
76
+
77
+ def run_query(config)
78
+ raise ArgumentError, 'must set the user attribute.' unless user
79
+ raise ArgumentError, 'must set the account_id attribute.' unless account_id
80
+
81
+ raise ArgumentError, "#{config} does not have service set" if config["service"].empty?
82
+ raise ArgumentError, "#{config} does not have endpoint set" if config["endpoint"].empty?
83
+
84
+ query = Query.create config
85
+ query.run user: user
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end