cumuliform 0.5.1 → 0.5.2

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