cloud-templates 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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