cumuliform 0.5.1 → 0.5.2

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.
@@ -0,0 +1,70 @@
1
+ require_relative '../error'
2
+
3
+ module Cumuliform
4
+ module DSL
5
+ # Contains DSL methods for including modules containing simple helper methods
6
+ #
7
+ # Helper methods are isolated from the template object, so they can't
8
+ # call other DSL methods. They're really intended for wrapping code that
9
+ # is used in several places but requires complex setup, for example a
10
+ # third-party API that issues tokens you need to include in your template
11
+ # in some way.
12
+ module Helpers
13
+ # Add helper methods to the template.
14
+ #
15
+ # @overload helpers(mod, ...)
16
+ # Include one or more helper modules
17
+ # @param mod [Module] Module containing helper methods to include
18
+ # @overload helpers(&block)
19
+ # @param block [lambda] Block containing helper method definitins to include
20
+ def helpers(*mods, &block)
21
+ if block_given?
22
+ mods << Module.new(&block)
23
+ end
24
+ mods.each do |mod|
25
+ helpers_class.class_eval {
26
+ include mod
27
+ }
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ # @api private
34
+ def has_helper?(meth)
35
+ helpers_instance.respond_to?(meth)
36
+ end
37
+
38
+ # @api private
39
+ def template_for_helper(meth)
40
+ return self if has_helper?(meth)
41
+ imports.reverse.find { |template|
42
+ template.template_for_helper(meth)
43
+ }
44
+ end
45
+
46
+ # @api private
47
+ def send_helper(meth, *args)
48
+ helpers_instance.send(meth, *args)
49
+ end
50
+
51
+ private
52
+
53
+ def helpers_class
54
+ @helpers_class ||= Class.new
55
+ end
56
+
57
+ def helpers_instance
58
+ @helpers_instance ||= helpers_class.new
59
+ end
60
+
61
+ def method_missing(meth, *args)
62
+ if template = template_for_helper(meth)
63
+ template.send_helper(meth, *args)
64
+ else
65
+ super
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../error'
2
+
3
+ module Cumuliform
4
+ module DSL
5
+ # DSL methods for importing other templates
6
+ module Import
7
+ # Import another Cumuliform::Template into this one
8
+ #
9
+ # @param template [Template] the template to import
10
+ def import(template)
11
+ imports << template
12
+ end
13
+
14
+ # @api private
15
+ def has_logical_id?(logical_id)
16
+ (imports.reverse).inject(logical_ids.include?(logical_id)) { |found, template|
17
+ found || template.has_logical_id?(logical_id)
18
+ }
19
+ end
20
+
21
+ # @api private
22
+ def verify_logical_id!(logical_id)
23
+ raise Error::NoSuchLogicalId, logical_id unless has_logical_id?(logical_id)
24
+ true
25
+ end
26
+
27
+ private
28
+
29
+ def imports
30
+ @imports ||= []
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,44 +1,63 @@
1
1
  module Cumuliform
2
2
  module Error
3
+ # The base error class for id-related errors
3
4
  class IDError < StandardError
5
+ # The logical ID this error refers to
4
6
  attr_reader :id
5
7
 
8
+ # Initialize a new IDError for the given ID
9
+ #
10
+ # @param id [String] CloudFormation logical ID
6
11
  def initialize(id)
7
12
  @id = id
8
13
  end
9
14
  end
10
15
 
16
+ # raised when a logical ID already exists
11
17
  class DuplicateLogicalID < IDError
18
+ # returns human-readable error message
12
19
  def to_s
13
20
  "Existing item with logical ID '#{id}'"
14
21
  end
15
22
  end
16
23
 
24
+ # raised when a lookup for a logical ID fails
17
25
  class NoSuchLogicalId < IDError
26
+ # returns human-readable error message
18
27
  def to_s
19
28
  "No such logical ID '#{id}'"
20
29
  end
21
30
  end
22
31
 
32
+ # raised when a Fragment with the same ID has already been defined
23
33
  class FragmentAlreadyDefined < IDError
34
+ # returns human-readable error message
24
35
  def to_s
25
36
  "Fragment '#{id}' already defined"
26
37
  end
27
38
  end
28
39
 
40
+ # raised when a Fragment lookup fails
29
41
  class FragmentNotFound < IDError
42
+ # returns human-readable error message
30
43
  def to_s
31
44
  "No fragment with name '#{id}'"
32
45
  end
33
46
  end
34
47
 
48
+ # raised when an item (e.g. Resource or Parameter) to be output contains
49
+ # nothing
35
50
  class EmptyItem < IDError
51
+ # returns human-readable error message
36
52
  def to_s
37
53
  "Empty item '#{id}'"
38
54
  end
39
55
  end
40
56
 
57
+ # raised when the template has no Resources defined at all (CloudFormation
58
+ # requires at least one resource)
41
59
  class NoResourcesDefined < StandardError
60
+ # returns human-readable error message
42
61
  def to_s
43
62
  "No resources defined"
44
63
  end
@@ -2,10 +2,15 @@
2
2
  # DSLize every last thing. Simple is the watch word
3
3
 
4
4
  require 'json'
5
- require 'set'
6
5
 
7
6
  module Cumuliform
7
+ # Manages converting the Cumuliform::Template into a CloudFormation JSON
8
+ # string
8
9
  module Output
10
+ # Processes the template and returns a hash representing the CloudFormation
11
+ # template
12
+ #
13
+ # @return [Hash] Hash representing the CloudFormation template
9
14
  def to_hash
10
15
  output = {}
11
16
  SECTIONS.each do |section_name, _|
@@ -17,6 +22,10 @@ module Cumuliform
17
22
  output
18
23
  end
19
24
 
25
+ # Generates the JSON representation of the template from the Hash
26
+ # representation provided by #to_hash
27
+ #
28
+ # @return [String] JSON representation of the CloudFormation template
20
29
  def to_json
21
30
  JSON.pretty_generate(to_hash)
22
31
  end
@@ -1,9 +1,13 @@
1
1
  require 'cumuliform/runner'
2
2
 
3
3
  module Cumuliform
4
+ # Creates a Rake rule task for converting a Cumuliform template file into a
5
+ # CloudFormation JSON file
4
6
  module RakeTask
5
7
  extend self
6
8
 
9
+ # Rake task lib for generating Cumuliform processing rule
10
+ # @api private
7
11
  class TaskLib < Rake::TaskLib
8
12
  include ::Rake::DSL if defined?(::Rake::DSL)
9
13
 
@@ -16,6 +20,8 @@ module Cumuliform
16
20
  end
17
21
  end
18
22
 
23
+ # Define a new Rake rule task to process Cumuliform templates into
24
+ # CloudFormation JSON
19
25
  def rule(*args)
20
26
  TaskLib.new.define_rule(args)
21
27
  end
@@ -2,7 +2,14 @@ require 'cumuliform'
2
2
  require 'pathname'
3
3
 
4
4
  module Cumuliform
5
+ # The Runner class reads a template, execute it and write the generated JSON
6
+ # to a file
5
7
  class Runner
8
+ # Processes an IO object which will, when read, return a Cumuliform
9
+ # template file.
10
+ #
11
+ # @param io [IO] The IO-like object containing the template
12
+ # @return [Template] the parsed Cumuliform::Template object
6
13
  def self.process_io(io)
7
14
  mod = Module.new
8
15
  path = io.respond_to?(:path) ? io.path : nil
@@ -10,6 +17,11 @@ module Cumuliform
10
17
  template = mod.class_eval(*args)
11
18
  end
12
19
 
20
+ # Reads the template file at <tt>input_path</tt>, parses and executes it to generate CloudFormation JSON which is written to <tt>output_path</tt>
21
+ #
22
+ # @param input_path [String] The path to the input Cumuliform template file
23
+ # @param output_path [String] The path to the output CloudFormation JSON template file
24
+ # @return [void]
13
25
  def self.process(input_path, output_path)
14
26
  input_path = Pathname.new(input_path)
15
27
  output_path = Pathname.new(output_path)
@@ -1,29 +1,5 @@
1
- require_relative 'error'
2
-
3
1
  module Cumuliform
4
- module Import
5
- def import(template)
6
- imports << template
7
- end
8
-
9
- def has_logical_id?(logical_id)
10
- (imports.reverse).inject(logical_ids.include?(logical_id)) { |found, template|
11
- found || template.has_logical_id?(logical_id)
12
- }
13
- end
14
-
15
- def verify_logical_id!(logical_id)
16
- raise Error::NoSuchLogicalId, logical_id unless has_logical_id?(logical_id)
17
- true
18
- end
19
-
20
- private
21
-
22
- def imports
23
- @imports ||= []
24
- end
25
- end
26
-
2
+ # @api private
27
3
  class Section
28
4
  attr_reader :name, :imports, :items
29
5
 
@@ -0,0 +1,52 @@
1
+ require_relative 'section'
2
+
3
+ module Cumuliform
4
+ SECTIONS = {
5
+ "Parameters" => :parameter,
6
+ "Mappings" => :mapping,
7
+ "Conditions" => :condition,
8
+ "Resources" => :resource,
9
+ "Outputs" => :output
10
+ }
11
+
12
+ # @api private
13
+ module Sections
14
+ def initialize
15
+ SECTIONS.each do |section_name, _|
16
+ instance_variable_set(:"@#{section_name}", Section.new(section_name, imports))
17
+ end
18
+ end
19
+
20
+ def get_section(name)
21
+ instance_variable_get(:"@#{name}")
22
+ end
23
+
24
+ SECTIONS.each do |section_name, method_name|
25
+ error_class = Class.new(Error::IDError) do
26
+ def to_s
27
+ "No logical ID '#{id}' in section"
28
+ end
29
+ end
30
+ Error.const_set("NoSuchLogicalIdIn#{section_name}", error_class)
31
+
32
+ define_method method_name, ->(logical_id, &block) {
33
+ add_to_section(section_name, logical_id, block)
34
+ }
35
+
36
+ define_method :"verify_#{method_name}_logical_id!", ->(logical_id) {
37
+ raise error_class, logical_id unless get_section(section_name).member?(logical_id)
38
+ true
39
+ }
40
+ end
41
+
42
+ private
43
+
44
+ def add_to_section(section_name, logical_id, block)
45
+ if has_local_logical_id?(logical_id)
46
+ raise Error::DuplicateLogicalID, logical_id
47
+ end
48
+ logical_ids << logical_id
49
+ get_section(section_name)[logical_id] = block
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,36 @@
1
+ # Simple AWS CloudFormation. Doesn't try to run the commands, doesn't try to
2
+ # DSLize every last thing. Simple is the watch word
3
+
4
+ require 'set'
5
+ require_relative 'error'
6
+ require_relative 'sections'
7
+ require_relative 'output'
8
+
9
+ module Cumuliform
10
+ AWS_PSEUDO_PARAMS = %w{
11
+ AWS::AccountId AWS::NotificationARNs AWS::NoValue
12
+ AWS::Region AWS::StackId AWS::StackName
13
+ }
14
+
15
+ # Represents a single CloudFormation template
16
+ class Template
17
+ include Output
18
+ include Sections
19
+
20
+ # @api private
21
+ def define(&block)
22
+ instance_exec(&block)
23
+ self
24
+ end
25
+
26
+ private
27
+
28
+ def logical_ids
29
+ @logical_ids ||= Set.new(AWS_PSEUDO_PARAMS)
30
+ end
31
+
32
+ def has_local_logical_id?(logical_id)
33
+ logical_ids.include?(logical_id)
34
+ end
35
+ end
36
+ end
@@ -1,3 +1,3 @@
1
1
  module Cumuliform
2
- VERSION = "0.5.1"
2
+ VERSION = "0.5.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cumuliform
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Patterson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-12-03 00:00:00.000000000 Z
11
+ date: 2016-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -90,20 +90,31 @@ files:
90
90
  - bin/setup
91
91
  - cumuliform.gemspec
92
92
  - examples/Rakefile
93
+ - examples/block-helper.rb
93
94
  - examples/condition_functions.rb
94
95
  - examples/fragments.rb
96
+ - examples/import-base.rb
97
+ - examples/import-fragments-base.rb
98
+ - examples/import-fragments-importer.rb
99
+ - examples/import-importer.rb
95
100
  - examples/intrinsic_functions.rb
101
+ - examples/module-helper.rb
96
102
  - examples/simplest.rb
97
103
  - examples/top-level-declarations.rb
98
104
  - exe/cumuliform
99
105
  - lib/cumuliform.rb
106
+ - lib/cumuliform/dsl.rb
107
+ - lib/cumuliform/dsl/fragments.rb
108
+ - lib/cumuliform/dsl/functions.rb
109
+ - lib/cumuliform/dsl/helpers.rb
110
+ - lib/cumuliform/dsl/import.rb
100
111
  - lib/cumuliform/error.rb
101
- - lib/cumuliform/fragments.rb
102
- - lib/cumuliform/functions.rb
103
- - lib/cumuliform/import.rb
104
112
  - lib/cumuliform/output.rb
105
113
  - lib/cumuliform/rake_task.rb
106
114
  - lib/cumuliform/runner.rb
115
+ - lib/cumuliform/section.rb
116
+ - lib/cumuliform/sections.rb
117
+ - lib/cumuliform/template.rb
107
118
  - lib/cumuliform/version.rb
108
119
  homepage: https://www.github.com/tape-tv/cumuliform
109
120
  licenses:
@@ -125,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
136
  version: '0'
126
137
  requirements: []
127
138
  rubyforge_project:
128
- rubygems_version: 2.4.5
139
+ rubygems_version: 2.5.1
129
140
  signing_key:
130
141
  specification_version: 4
131
142
  summary: DSL library for generating AWS CloudFormation templates
@@ -1,63 +0,0 @@
1
- require_relative 'error'
2
-
3
- module Cumuliform
4
- module Fragments
5
- # Define a fragment for later use.
6
- #
7
- # Essentially stores a block under the name given for later use.
8
- #
9
- # @param name [Symbol] name of the fragment to define
10
- # @yieldparam opts [Hash] will yield the options hash passed to
11
- # <tt>#fragment()</tt> when called
12
- # @raise [Error::FragmentAlreadyDefined] if the <tt>name</tt> is not unique
13
- # in this template
14
- def def_fragment(name, &block)
15
- if fragments.has_key?(name)
16
- raise Error::FragmentAlreadyDefined, name
17
- end
18
- fragments[name] = block
19
- end
20
-
21
- # Use an already-defined fragment
22
- #
23
- # Retrieves the block stored under <tt>name</tt> and calls it, passing any options.
24
- #
25
- # @param name [Symbol] The name of the fragment to use
26
- # @param opts [Hash] Options to be passed to the fragment
27
- # @return [Object<JSON-serialisable>] the return value of the called block
28
- def fragment(name, *args, &block)
29
- if block_given?
30
- warn "fragment definition form (with block) is deprecated. Use #def_fragment instead"
31
- def_fragment(name, *args, &block)
32
- else
33
- use_fragment(name, *args)
34
- end
35
- end
36
-
37
- # @api private
38
- def find_fragment(name)
39
- local_fragment = fragments[name]
40
- imports.reverse.reduce(local_fragment) { |fragment, import|
41
- fragment || import.find_fragment(name)
42
- }
43
- end
44
-
45
- private
46
-
47
- def use_fragment(name, opts = {})
48
- unless has_fragment?(name)
49
- raise Error::FragmentNotFound, name
50
- end
51
- instance_exec(opts, &find_fragment(name))
52
- end
53
-
54
-
55
- def fragments
56
- @fragments ||= {}
57
- end
58
-
59
- def has_fragment?(name)
60
- !find_fragment(name).nil?
61
- end
62
- end
63
- end