cloud-templates 0.1.0

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +29 -0
  3. data/.simplecov +6 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +201 -0
  6. data/NOTICE +13 -0
  7. data/README.md +124 -0
  8. data/Rakefile +27 -0
  9. data/cloud-templates.gemspec +27 -0
  10. data/examples/lib/user_directory/artifacts/catalogized.rb +11 -0
  11. data/examples/lib/user_directory/artifacts/group.rb +37 -0
  12. data/examples/lib/user_directory/artifacts/ided.rb +11 -0
  13. data/examples/lib/user_directory/artifacts/organization.rb +17 -0
  14. data/examples/lib/user_directory/artifacts/pathed.rb +22 -0
  15. data/examples/lib/user_directory/artifacts/person.rb +20 -0
  16. data/examples/lib/user_directory/artifacts/team.rb +31 -0
  17. data/examples/lib/user_directory/artifacts/unit.rb +24 -0
  18. data/examples/lib/user_directory/artifacts/user.rb +29 -0
  19. data/examples/lib/user_directory/render/etc/artifact_view.rb +15 -0
  20. data/examples/lib/user_directory/render/etc/composite_view.rb +26 -0
  21. data/examples/lib/user_directory/render/etc/group_view.rb +23 -0
  22. data/examples/lib/user_directory/render/etc/person_view.rb +19 -0
  23. data/examples/lib/user_directory/render/etc/registry.rb +33 -0
  24. data/examples/lib/user_directory/render/etc/user_view.rb +35 -0
  25. data/examples/lib/user_directory/render/etc.rb +3 -0
  26. data/examples/lib/user_directory/render/ldap/artifact_view.rb +27 -0
  27. data/examples/lib/user_directory/render/ldap/composite_view.rb +32 -0
  28. data/examples/lib/user_directory/render/ldap/group_view.rb +28 -0
  29. data/examples/lib/user_directory/render/ldap/organization_view.rb +26 -0
  30. data/examples/lib/user_directory/render/ldap/person_view.rb +39 -0
  31. data/examples/lib/user_directory/render/ldap/registry.rb +16 -0
  32. data/examples/lib/user_directory/render/ldap/unit_view.rb +26 -0
  33. data/examples/lib/user_directory/render/ldap/user_view.rb +39 -0
  34. data/examples/lib/user_directory/render/ldap.rb +3 -0
  35. data/examples/lib/user_directory/utils.rb +18 -0
  36. data/examples/lib/user_directory.rb +23 -0
  37. data/examples/lib_path.rb +2 -0
  38. data/examples/spec/spec_helper.rb +1 -0
  39. data/examples/spec/user_directory_spec.rb +568 -0
  40. data/lib/aws/templates/artifact.rb +140 -0
  41. data/lib/aws/templates/composite.rb +178 -0
  42. data/lib/aws/templates/exceptions.rb +221 -0
  43. data/lib/aws/templates/render/registry.rb +60 -0
  44. data/lib/aws/templates/render/utils/base_type_views.rb +131 -0
  45. data/lib/aws/templates/render/view.rb +127 -0
  46. data/lib/aws/templates/render.rb +72 -0
  47. data/lib/aws/templates/utils/artifact_storage.rb +141 -0
  48. data/lib/aws/templates/utils/contextualized/filters.rb +437 -0
  49. data/lib/aws/templates/utils/contextualized/hash.rb +13 -0
  50. data/lib/aws/templates/utils/contextualized/nil.rb +13 -0
  51. data/lib/aws/templates/utils/contextualized/proc.rb +13 -0
  52. data/lib/aws/templates/utils/contextualized.rb +113 -0
  53. data/lib/aws/templates/utils/default.rb +185 -0
  54. data/lib/aws/templates/utils/dependency/enumerable.rb +13 -0
  55. data/lib/aws/templates/utils/dependency/object.rb +46 -0
  56. data/lib/aws/templates/utils/dependency.rb +121 -0
  57. data/lib/aws/templates/utils/dependent.rb +28 -0
  58. data/lib/aws/templates/utils/inheritable.rb +52 -0
  59. data/lib/aws/templates/utils/late_bound.rb +89 -0
  60. data/lib/aws/templates/utils/memoized.rb +27 -0
  61. data/lib/aws/templates/utils/named.rb +19 -0
  62. data/lib/aws/templates/utils/options.rb +279 -0
  63. data/lib/aws/templates/utils/parametrized/constraints.rb +423 -0
  64. data/lib/aws/templates/utils/parametrized/getters.rb +293 -0
  65. data/lib/aws/templates/utils/parametrized/guarded.rb +32 -0
  66. data/lib/aws/templates/utils/parametrized/mapper.rb +73 -0
  67. data/lib/aws/templates/utils/parametrized/nested.rb +72 -0
  68. data/lib/aws/templates/utils/parametrized/transformations.rb +660 -0
  69. data/lib/aws/templates/utils/parametrized.rb +240 -0
  70. data/lib/aws/templates/utils.rb +219 -0
  71. data/lib/aws/templates.rb +16 -0
  72. data/spec/aws/templates/artifact_spec.rb +161 -0
  73. data/spec/aws/templates/composite_spec.rb +361 -0
  74. data/spec/aws/templates/render/utils/base_type_views_spec.rb +104 -0
  75. data/spec/aws/templates/render_spec.rb +62 -0
  76. data/spec/aws/templates/utils/as_named_spec.rb +31 -0
  77. data/spec/aws/templates/utils/contextualized/filters_spec.rb +108 -0
  78. data/spec/aws/templates/utils/contextualized_spec.rb +115 -0
  79. data/spec/aws/templates/utils/late_bound_spec.rb +52 -0
  80. data/spec/aws/templates/utils/options_spec.rb +67 -0
  81. data/spec/aws/templates/utils/parametrized/constraint_spec.rb +175 -0
  82. data/spec/aws/templates/utils/parametrized/getters_spec.rb +139 -0
  83. data/spec/aws/templates/utils/parametrized/transformation_spec.rb +314 -0
  84. data/spec/aws/templates/utils/parametrized_spec.rb +241 -0
  85. data/spec/spec_helper.rb +6 -0
  86. metadata +244 -0
@@ -0,0 +1,185 @@
1
+ require 'aws/templates/utils'
2
+ require 'aws/templates/utils/options'
3
+ require 'aws/templates/utils/inheritable'
4
+
5
+ module Aws
6
+ module Templates
7
+ module Utils
8
+ ##
9
+ # Default mixin.
10
+ #
11
+ # It implements class instance-based definitions of so-called
12
+ # defaults. Defaults are input hash alterations and transformations
13
+ # which are defined per-class basis and applied according to class
14
+ # hierarchy when invoked. The target mixing entity should be either
15
+ # Module or Class. In the former case it's possible to model set of
16
+ # object which have common traits organized as an arbitrary graph
17
+ # with many-to-many relationship.
18
+ module Default
19
+ include Inheritable
20
+
21
+ ##
22
+ # Hash wrapper
23
+ #
24
+ # The hash wrapper does intermediate calculations of nested lambdas in the specified
25
+ # context as they are encountered
26
+ class Definition
27
+ ##
28
+ # Defined hash keys
29
+ def keys
30
+ @hash.keys
31
+ end
32
+
33
+ ##
34
+ # Transform to hash
35
+ def to_hash
36
+ _recurse_into(@hash)
37
+ end
38
+
39
+ def dependency?
40
+ true
41
+ end
42
+
43
+ def dependencies
44
+ to_hash.dependencies
45
+ end
46
+
47
+ ##
48
+ # Index operator
49
+ #
50
+ # Performs intermediate transformation of value if needed (if value is a lambda) and
51
+ # returns it wrapping into Definition instance with the same context if needed
52
+ # (if value is a map)
53
+ def [](k)
54
+ result = _process_value(@hash[k])
55
+ result.respond_to?(:to_hash) ? self.class.new(result, @context) : result
56
+ end
57
+
58
+ ##
59
+ # Check if the key is present in the hash
60
+ def include?(k)
61
+ @hash.include?(k)
62
+ end
63
+
64
+ ##
65
+ # Create wrapper object
66
+ #
67
+ # Creates wrapper object with attached hash and context to evaluate lambdas in
68
+ def initialize(hsh, ctx)
69
+ @hash = hsh
70
+ @context = ctx
71
+ end
72
+
73
+ private
74
+
75
+ def _process_value(value)
76
+ if value.respond_to?(:to_hash)
77
+ value
78
+ elsif value.respond_to?(:to_proc)
79
+ @context.instance_eval(&value)
80
+ else
81
+ value
82
+ end
83
+ end
84
+
85
+ def _recurse_into(value)
86
+ value.each_with_object({}) do |(k, v), memo|
87
+ processed = _process_value(v)
88
+ processed = _recurse_into(processed.to_hash) if Utils.hashable?(processed)
89
+ memo[k] = processed
90
+ end
91
+ end
92
+ end
93
+
94
+ instance_scope do
95
+ ##
96
+ # Apply specified defaults to options
97
+ #
98
+ # It's a mixin method which depends on presence of options accessor
99
+ # methods in the consuming class. The options property should contain
100
+ # an object implementing to_hash method. The method is mutating for
101
+ # options. The algorithm is to walk down the hierarchy of the
102
+ # class and collect and merge all defaults from its ancestors
103
+ # prioritizing the ones made later in the class hierarchy. The method
104
+ # is working correctly with both parent classes and all Default
105
+ # mixins used in between.
106
+ def process_options(params = nil)
107
+ # iterating through all ancestors with defaults
108
+ ancestors_with_defaults.reverse_each do |mod|
109
+ # ... through all defaults of particular ancestor
110
+ mod.defaults.each do |defaults_definition|
111
+ # merge the default definition with options
112
+ options.merge!(Definition.new(defaults_definition, self))
113
+ end
114
+ end
115
+
116
+ # re-inforce caller-specified overrides
117
+ options.merge!(params) if params
118
+ end
119
+
120
+ private
121
+
122
+ def ancestors_with_defaults
123
+ self
124
+ .class
125
+ .ancestors
126
+ .select do |mod|
127
+ (mod != Default) && mod.ancestors.include?(Default)
128
+ end
129
+ end
130
+ end
131
+
132
+ ##
133
+ # Class-level mixins
134
+ #
135
+ # It's a DSL extension to declaratively define defaults
136
+ class_scope do
137
+ ##
138
+ # Defaults for the input hash
139
+ #
140
+ # Class-level accessor of a hash which will be merged into input
141
+ # parameters hash. The hash can't be changed directly or set to
142
+ # another value. Only incremental changes are allowed with
143
+ # default method which is a part of the framework DSL. The method
144
+ # returns only defaults for the current class without
145
+ # consideration of the class hierarchy.
146
+ def defaults
147
+ @defaults ||= []
148
+ end
149
+
150
+ ##
151
+ # Put an default/calculation for the input hash
152
+ #
153
+ # The class method is the main knob which is used to build
154
+ # hierarchical hash mutation pipeline using language-provided
155
+ # features such as class inheritance and introspection. You can
156
+ # specify either hash (or an object which has :to_hash method) or
157
+ # a lambda/Proc as a parameter.
158
+ #
159
+ # If you specify a hash then it will be merged with the current
160
+ # value of default where the hash passed will take preference
161
+ # during the merge.
162
+ #
163
+ # If you specify a lambda it will be added to calculations stack
164
+ #
165
+ # If the parameter you passed is neither a hash nor callable or
166
+ # no parameters are passed at all, ArgumentError will be thrown.
167
+ def default(defaults_hash = nil)
168
+ raise_defaults_is_nil unless defaults_hash
169
+ raise_default_type_mismatch(defaults_hash) unless defaults_hash.respond_to?(:to_hash)
170
+
171
+ defaults << defaults_hash.to_hash
172
+ end
173
+
174
+ def raise_defaults_is_nil
175
+ raise ArgumentError.new('Map should be specified')
176
+ end
177
+
178
+ def raise_default_type_mismatch(defaults_hash)
179
+ raise ArgumentError.new("#{defaults_hash.inspect} is not a hash")
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,13 @@
1
+ require 'set'
2
+
3
+ ##
4
+ # Dependencies methods definitions for all collections
5
+ module Enumerable
6
+ def dependencies
7
+ find_all(&:dependency?).inject(Set.new) { |acc, elem| acc.merge(elem.dependencies) }
8
+ end
9
+
10
+ def dependency?
11
+ true
12
+ end
13
+ end
@@ -0,0 +1,46 @@
1
+ require 'set'
2
+ require 'aws/templates/utils/dependency'
3
+
4
+ ##
5
+ # Dependency method stubs
6
+ #
7
+ # To avoid checking classes directly to filter out dependencies and non-dependencies, we're
8
+ # monkey-patching Object class with stubs for Dependency class.
9
+ #
10
+ # TODO: Devise a better approach and remove the extension
11
+ class Object
12
+ EMPTY_SET = Set.new.freeze
13
+
14
+ # By default an object is not a dependency
15
+ def dependency?
16
+ false
17
+ end
18
+
19
+ # It returns self
20
+ def object
21
+ self
22
+ end
23
+
24
+ ##
25
+ # Object root
26
+ #
27
+ # It is used to gracefully process dependencies
28
+ def root; end
29
+
30
+ alias not_a_dependency object
31
+
32
+ # it returns a set containing a single dependency on itself
33
+ def dependencies
34
+ EMPTY_SET
35
+ end
36
+
37
+ # mark the object as dependency
38
+ def as_a_dependency
39
+ Aws::Templates::Utils::Dependency.new(object)
40
+ end
41
+
42
+ # mark the object as dependency of itself
43
+ def as_a_self_dependency
44
+ as_a_dependency.to_self
45
+ end
46
+ end
@@ -0,0 +1,121 @@
1
+ require 'set'
2
+ require 'aws/templates/utils/dependency/object'
3
+ require 'aws/templates/utils/dependency/enumerable'
4
+
5
+ module Aws
6
+ module Templates
7
+ module Utils
8
+ ##
9
+ # Dependency marker proxy
10
+ #
11
+ # Used internally in the framework to mark an object as potential dependency. There are other
12
+ # alternatives for doing the same like singleton class and reference object. There are a few
13
+ # advantages of the approach taken:
14
+ # * Dependency can be used whereever original object is expected
15
+ # * Dependecy can be applied case-by-case basis whereas singleton is attached to the object
16
+ # itself
17
+ class Dependency < BasicObject
18
+ ##
19
+ # Equality
20
+ #
21
+ # Two Dependency objects are equal if it's the same object or if they are pointing to the
22
+ # same target.
23
+ def eql?(other)
24
+ equal?(other) || ((self.class == other.class) && (object == other.object))
25
+ end
26
+
27
+ ##
28
+ # Alias for #eql?
29
+ def ==(other)
30
+ eql?(other)
31
+ end
32
+
33
+ # Non-equality
34
+ def !=(other)
35
+ !eql?(other)
36
+ end
37
+
38
+ # BasicObject is so basic that this part is missing too
39
+ def class
40
+ Dependency
41
+ end
42
+
43
+ # It's a dependency
44
+ def dependency?
45
+ true
46
+ end
47
+
48
+ # mark the object as dependency
49
+ def as_dependency
50
+ self
51
+ end
52
+
53
+ attr_reader :object
54
+ attr_reader :dependencies
55
+
56
+ ##
57
+ # Redirect every method call to the proxied object if the object supports it
58
+ def method_missing(name, *args, &block)
59
+ object.respond_to?(name) ? object.send(name, *args, &block) : super
60
+ end
61
+
62
+ ##
63
+ # It supports every method proxied object supports
64
+ def respond_to_missing?(name, include_private = false)
65
+ object.respond_to?(name, include_private) || super(name, include_private)
66
+ end
67
+
68
+ ##
69
+ # Add dependency
70
+ #
71
+ # Add a link to the target to the current Dependency object
72
+ def to(target)
73
+ if target.dependency?
74
+ dependencies.merge(target.dependencies)
75
+ else
76
+ dependencies << target
77
+ end
78
+
79
+ self
80
+ end
81
+
82
+ ##
83
+ # Link the value to the source
84
+ #
85
+ # Links source or result of calculation of the block to the target object of the dependency.
86
+ # The mecahanism is a middle ground between extreme case of indefinite recursive dependency
87
+ # propagation and no propagation at all
88
+ #
89
+ # some_artifact.as_a_dependency.with { some_attribute }
90
+ # # => Dependency(@object = <some_attribute value>, <link to some_artifact>)
91
+ def with(source = nil, &source_calculation_block)
92
+ value = if source_calculation_block.nil?
93
+ source
94
+ else
95
+ object.instance_exec(value, &source_calculation_block)
96
+ end
97
+
98
+ value.as_a_dependency.to(self)
99
+ end
100
+
101
+ ##
102
+ # Set dependency to the target
103
+ def to_self
104
+ to(object)
105
+ end
106
+
107
+ ##
108
+ # Initialize the proxy
109
+ def initialize(source_object)
110
+ @object = source_object.object
111
+
112
+ @dependencies = if source_object.dependency?
113
+ source_object.dependencies.dup
114
+ else
115
+ ::Set.new
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,28 @@
1
+ require 'aws/templates/utils/dependency/object'
2
+ require 'set'
3
+
4
+ module Aws
5
+ module Templates
6
+ module Utils
7
+ ##
8
+ # Dependency node mixin
9
+ #
10
+ # Introduces methods needed to track dependencies of an object. The object needs to implement
11
+ # options method and root method.
12
+ module Dependent
13
+ ##
14
+ # Introduce dependencies manually
15
+ #
16
+ # Dependencies are calculated from "options" recursive structure by traversal and location
17
+ # of all dependencies automatically. If some dependency is not logical/parametrical but
18
+ # purely chronological, it can be introduced into the dependency list with this method.
19
+ def depends_on(*depends)
20
+ new_dependencies = depends.dependencies
21
+ new_dependencies.select! { |obj| obj.root == root } unless root.nil?
22
+ dependencies.merge(new_dependencies)
23
+ self
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,52 @@
1
+ module Aws
2
+ module Templates
3
+ module Utils
4
+ ##
5
+ # Introduces class method inheritance through module hierarchy
6
+ module Inheritable
7
+ ##
8
+ # Core class-level mixins
9
+ #
10
+ # Contains core logic of the inheritance feature. This module is used as extention
11
+ # in every including module/class to expose appropriate module-level primitives for
12
+ # handling inheritance of class-scope methods.
13
+ module ClassMethods
14
+ DEFAULT_MODULE = Module.new.freeze
15
+
16
+ ##
17
+ # To add class methods also while including the module
18
+ def included(base)
19
+ super(base)
20
+ base.extend(Inheritable::ClassMethods)
21
+ base._merge_class_scope(class_scope || DEFAULT_MODULE)
22
+ end
23
+
24
+ def instance_scope(&blk)
25
+ module_eval(&blk)
26
+ end
27
+
28
+ def class_scope(&blk)
29
+ if blk
30
+ @class_scope.module_eval(&blk)
31
+ extend @class_scope
32
+ end
33
+
34
+ @class_scope
35
+ end
36
+
37
+ def _merge_class_scope(mod)
38
+ if @class_scope.nil?
39
+ @class_scope = mod.dup
40
+ else
41
+ @class_scope.include(mod)
42
+ end
43
+
44
+ extend @class_scope
45
+ end
46
+ end
47
+
48
+ extend ClassMethods
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,89 @@
1
+ require 'aws/templates/utils/inheritable'
2
+
3
+ module Aws
4
+ module Templates
5
+ module Utils
6
+ ##
7
+ # Late binding utilities
8
+ #
9
+ # Late binding is a technique of referencing values which don't exist at the template
10
+ # calculation stage. Examples could be Process ID, SQL record calculated ID or AWS object
11
+ # ARN you're creating through a script or CFN template.
12
+ #
13
+ # The module provides DSL for creating late binding points known as References.
14
+ module LateBound
15
+ include Inheritable
16
+
17
+ ##
18
+ # Reference
19
+ #
20
+ # Reference is a special placeholder object designed to point to the object reference was
21
+ # created for with optional path and args attached.
22
+ #
23
+ # References are used when the final value of an object property is unknown at the template
24
+ # calculation stage and can be extracted only when the final rendered view is submitted to
25
+ # the target system (late binding)
26
+ class Reference
27
+ attr_reader :instance
28
+ attr_reader :path
29
+ attr_reader :arguments
30
+
31
+ FAILURE_MESSAGES = {
32
+ to_s: 'string',
33
+ to_i: 'integer',
34
+ to_f: 'float',
35
+ to_a: 'array',
36
+ to_h: 'hash',
37
+ to_str: 'string',
38
+ to_int: 'integer',
39
+ to_ary: 'array',
40
+ to_hash: 'hash',
41
+ to_proc: 'proc'
42
+ }.freeze
43
+
44
+ FAILURE_MESSAGES.each_pair do |method_name, type_name|
45
+ define_method(method_name) do
46
+ raise(
47
+ "Reference can't be transformed to #{type_name} or paricipate in any operations" \
48
+ 'References are placeholders for values which don\'t exist at the template ' \
49
+ 'calculation stage (late binding)'
50
+ )
51
+ end
52
+ end
53
+
54
+ def initialize(target_instance, target_path = nil, args = nil)
55
+ @instance = target_instance
56
+ @path = target_path
57
+ @arguments = args
58
+ end
59
+ end
60
+
61
+ instance_scope do
62
+ ##
63
+ # Create reference
64
+ #
65
+ # Create and return Reference object attached to the current instance with specified path
66
+ # and arguments
67
+ def reference(path = nil, *args)
68
+ Reference.new(self, path, args)
69
+ end
70
+ end
71
+
72
+ ##
73
+ # Class-level DSL
74
+ class_scope do
75
+ ##
76
+ # Wrap reference for postponed instantiation
77
+ #
78
+ # References are instance-level objects so they can be attached only to an instance, not
79
+ # to a class. So, to be able to do that in "default" section in an artifact, for instance,
80
+ # you need to specify a proc/lambda object for the option. This method makes the wrappin
81
+ # unnecessary.
82
+ def reference(path = nil, *args)
83
+ -> { reference(path, *args) }
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,27 @@
1
+ module Aws
2
+ module Templates
3
+ module Utils
4
+ ##
5
+ # Simple memoization facility
6
+ module Memoized
7
+ # Cancel all memoizations
8
+ def dirty!
9
+ @memoized = nil
10
+ self
11
+ end
12
+
13
+ ##
14
+ # Memoize block result
15
+ #
16
+ # Return memoized value with the ID. If slot is empty - call the block
17
+ def memoize(id)
18
+ memoized[id] ||= yield
19
+ end
20
+
21
+ def memoized
22
+ @memoized ||= {}
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require 'aws/templates/utils/parametrized'
2
+ require 'aws/templates/utils/parametrized/constraints'
3
+
4
+ module Aws
5
+ module Templates
6
+ module Utils
7
+ ##
8
+ # Named parametrized object mixin
9
+ #
10
+ # Provides a simple utility to define artifacts/objects which have
11
+ # "name" parameter which should be present as :name in the input hash
12
+ module AsNamed
13
+ include Parametrized
14
+
15
+ parameter :name, description: 'Name of the object', constraint: not_nil
16
+ end
17
+ end
18
+ end
19
+ end