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.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/CHANGELOG.md +5 -0
- data/README.md +368 -3
- data/examples/block-helper.rb +17 -0
- data/examples/import-base.rb +19 -0
- data/examples/import-fragments-base.rb +38 -0
- data/examples/import-fragments-importer.rb +18 -0
- data/examples/import-importer.rb +13 -0
- data/examples/module-helper.rb +19 -0
- data/lib/cumuliform.rb +1 -93
- data/lib/cumuliform/dsl.rb +32 -0
- data/lib/cumuliform/dsl/fragments.rb +67 -0
- data/lib/cumuliform/dsl/functions.rb +256 -0
- data/lib/cumuliform/dsl/helpers.rb +70 -0
- data/lib/cumuliform/dsl/import.rb +34 -0
- data/lib/cumuliform/error.rb +19 -0
- data/lib/cumuliform/output.rb +10 -1
- data/lib/cumuliform/rake_task.rb +6 -0
- data/lib/cumuliform/runner.rb +12 -0
- data/lib/cumuliform/{import.rb → section.rb} +1 -25
- data/lib/cumuliform/sections.rb +52 -0
- data/lib/cumuliform/template.rb +36 -0
- data/lib/cumuliform/version.rb +1 -1
- metadata +17 -6
- data/lib/cumuliform/fragments.rb +0 -63
- data/lib/cumuliform/functions.rb +0 -220
@@ -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
|
data/lib/cumuliform/error.rb
CHANGED
@@ -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
|
data/lib/cumuliform/output.rb
CHANGED
@@ -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
|
data/lib/cumuliform/rake_task.rb
CHANGED
@@ -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
|
data/lib/cumuliform/runner.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/cumuliform/version.rb
CHANGED
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.
|
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:
|
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.
|
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
|
data/lib/cumuliform/fragments.rb
DELETED
@@ -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
|