cfer 0.1.1
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 +15 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/.yardopts +1 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +221 -0
- data/Rakefile +41 -0
- data/bin/cfer +13 -0
- data/bin/cfer-dbg +25 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/cfer-demo.gif +0 -0
- data/cfer.gemspec +37 -0
- data/examples/instance.rb +84 -0
- data/examples/vpc.rb +84 -0
- data/lib/cfer.rb +236 -0
- data/lib/cfer/block.rb +33 -0
- data/lib/cfer/cfn/aws.rb +29 -0
- data/lib/cfer/cfn/client.rb +164 -0
- data/lib/cfer/cli.rb +138 -0
- data/lib/cfer/core/client.rb +15 -0
- data/lib/cfer/core/fn.rb +55 -0
- data/lib/cfer/core/resource.rb +44 -0
- data/lib/cfer/core/stack.rb +170 -0
- data/lib/cfer/util/error.rb +31 -0
- data/lib/cfer/version.rb +3 -0
- data/lib/cferext/aws/ec2/instance.rb +12 -0
- data/lib/cferext/provisioning.rb +6 -0
- metadata +271 -0
data/cfer.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cfer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "cfer"
|
8
|
+
spec.version = Cfer::VERSION
|
9
|
+
spec.authors = ["Sean Edwards"]
|
10
|
+
spec.email = ["stedwards87+git@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Toolkit for automating infrastructure using AWS CloudFormation}
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = "https://github.com/seanedwards/cfer"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'bin'
|
19
|
+
spec.executables = 'cfer'
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'docile'
|
23
|
+
spec.add_runtime_dependency 'thor'
|
24
|
+
spec.add_runtime_dependency 'activesupport'
|
25
|
+
spec.add_runtime_dependency 'aws-sdk'
|
26
|
+
spec.add_runtime_dependency 'aws-sdk-resources'
|
27
|
+
spec.add_runtime_dependency 'preconditions'
|
28
|
+
spec.add_runtime_dependency 'semantic'
|
29
|
+
spec.add_runtime_dependency 'rainbow'
|
30
|
+
spec.add_runtime_dependency 'highline'
|
31
|
+
spec.add_runtime_dependency 'rugged'
|
32
|
+
spec.add_runtime_dependency 'table_print'
|
33
|
+
spec.add_runtime_dependency "rake"
|
34
|
+
|
35
|
+
spec.add_development_dependency "bundler"
|
36
|
+
spec.add_development_dependency "yard"
|
37
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
description 'Example stack template for a small EC2 instance'
|
2
|
+
|
3
|
+
# NOTE: This template depends on vpc.rb
|
4
|
+
|
5
|
+
|
6
|
+
# By not specifying a default value, a parameter becomes required.
|
7
|
+
# Specify this parameter by adding `--parameters KeyName:<ec2-keyname>` to your CLI options.
|
8
|
+
parameter :KeyName
|
9
|
+
|
10
|
+
# We define some more parameters the same way we did in the VPC template.
|
11
|
+
# Cfer will interpret the default value by looking up the stack output named `vpcid`
|
12
|
+
# on the stack named `vpc`.
|
13
|
+
#
|
14
|
+
# If you created the VPC stack with a different name, you can overwrite these default values
|
15
|
+
# by adding `VpcId:@<vpc-stack-name>.vpcid SubnetId:@<vpc-stack-name>.subnetid1`
|
16
|
+
# to your `--parameters` option
|
17
|
+
parameter :VpcId, default: '@vpc.vpcid'
|
18
|
+
parameter :SubnetId, default: '@vpc.subnetid1'
|
19
|
+
|
20
|
+
# This is the Ubuntu 14.04 LTS HVM AMI provided by Amazon.
|
21
|
+
parameter :ImageId, default: 'ami-d05e75b8'
|
22
|
+
parameter :InstanceType, default: 't2.medium'
|
23
|
+
|
24
|
+
# Define a security group to be applied to an instance.
|
25
|
+
# This one will allow SSH access from anywhere, and no other inbound traffic.
|
26
|
+
resource :instancesg, "AWS::EC2::SecurityGroup" do
|
27
|
+
group_description 'Wide-open SSH'
|
28
|
+
vpc_id Fn::ref(:VpcId)
|
29
|
+
|
30
|
+
# Parameter values can be Ruby arrays and hashes. These will be transformed to JSON.
|
31
|
+
# You could write your own functions to make stuff like this easier, too.
|
32
|
+
security_group_ingress [
|
33
|
+
{
|
34
|
+
CidrIp: '0.0.0.0/0',
|
35
|
+
IpProtocol: 'tcp',
|
36
|
+
FromPort: 22,
|
37
|
+
ToPort: 22
|
38
|
+
}
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
# We can define extension objects, which extend the basic JSON-building
|
43
|
+
# functionality of Cfer. Cfer provides a few of these, but you're free
|
44
|
+
# to define your own by creating a class that matches the name of an
|
45
|
+
# CloudFormation resource type, inheriting from `Cfer::AWS::Resource`
|
46
|
+
# inside the `CferExt` module:
|
47
|
+
module CferExt::AWS::EC2
|
48
|
+
# This class adds methods to resources with the type `AWS::EC2::Instance`
|
49
|
+
# Remember, this class could go in your own gem to be shared between your templates
|
50
|
+
# in a way that works with the rest of your infrastructure.
|
51
|
+
class Instance < Cfer::Cfn::Resource
|
52
|
+
def boot_script(data)
|
53
|
+
# This function simply wraps a bash script in the little bit of extra
|
54
|
+
# sugar (hashbang + base64 encoding) that EC2 requires for userdata boot scripts.
|
55
|
+
# See the AWS docs here: http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html
|
56
|
+
script = <<-EOS.strip_heredoc
|
57
|
+
#!/bin/bash
|
58
|
+
#{data}
|
59
|
+
EOS
|
60
|
+
|
61
|
+
user_data Base64.encode64(script)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
resource :instance, "AWS::EC2::Instance" do
|
67
|
+
# Using the extension defined above, we can have the instance write a simple
|
68
|
+
# file to show that it's working. When you converge this template, there
|
69
|
+
# should be a `welcome.txt` file sitting in the `ubuntu` user's home directory.
|
70
|
+
boot_script "echo 'Welcome to Cfer!' > /home/ubuntu/welcome.txt"
|
71
|
+
|
72
|
+
image_id Fn::ref(:ImageId)
|
73
|
+
instance_type Fn::ref(:InstanceType)
|
74
|
+
key_name Fn::ref(:KeyName)
|
75
|
+
|
76
|
+
network_interfaces [ {
|
77
|
+
AssociatePublicIpAddress: "true",
|
78
|
+
DeviceIndex: "0",
|
79
|
+
GroupSet: [ Fn::ref(:instancesg) ],
|
80
|
+
SubnetId: Fn::ref(:SubnetId)
|
81
|
+
} ]
|
82
|
+
end
|
83
|
+
|
84
|
+
output :instance, Fn::ref(:instance)
|
data/examples/vpc.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
description 'Stack template for a simple example VPC'
|
2
|
+
|
3
|
+
# This template creates the following resources for a basic beginning AWS VPC setup:
|
4
|
+
#
|
5
|
+
# 1) A VPC
|
6
|
+
# 2) A route table to control network routing
|
7
|
+
# 3) An Internet gateway to route traffic to the public internet
|
8
|
+
# 4) 3 subnets, one in each of the account's first 3 availability zones
|
9
|
+
# 5) A default network route to the IGW
|
10
|
+
# 6) Associated plumbing resources to link it all together
|
11
|
+
|
12
|
+
# Parameters may be defined using the `parameter` function
|
13
|
+
parameter :VpcName, default: 'Example VPC'
|
14
|
+
|
15
|
+
# Resources are created using the `resource` function, accepting the following arguments:
|
16
|
+
# 1) The resource name (string or symbol)
|
17
|
+
# 2) The resource type. See the AWS CloudFormation docs for the available resource types: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html
|
18
|
+
resource :vpc, 'AWS::EC2::VPC' do
|
19
|
+
# Each line within the resource block sets a single property.
|
20
|
+
# These properties are simply camelized using the ActiveSupport gem's `camelize` function.
|
21
|
+
# This means that the `cidr_block` function will set the `CidrBlock` property.
|
22
|
+
cidr_block '172.42.0.0/16'
|
23
|
+
|
24
|
+
# Following this pattern, `enable_dns_support` sets the `EnableDnsSupport` property.
|
25
|
+
enable_dns_support true
|
26
|
+
enable_dns_hostnames true
|
27
|
+
instance_tenancy 'default'
|
28
|
+
|
29
|
+
# The `tag` function is available on all resources, and adds keys to the resource's `Tags` property. It accepts the following arguments:
|
30
|
+
# 1) Tag name (symbol or string)
|
31
|
+
# 2) Tag value
|
32
|
+
tag :DefaultVpc, true
|
33
|
+
|
34
|
+
# Parameters are required at template generation time, and therefore may be referenced using the `parameters` hash anywhere in a template.
|
35
|
+
# This will render the parameter value as a string constant in the CloudFormation JSON output
|
36
|
+
tag :Name, parameters[:VpcName]
|
37
|
+
end
|
38
|
+
|
39
|
+
# If there are no properties to set on a resource, the block may be omitted entirely
|
40
|
+
resource :defaultigw, 'AWS::EC2::InternetGateway'
|
41
|
+
|
42
|
+
resource :vpcigw, 'AWS::EC2::VPCGatewayAttachment' do
|
43
|
+
# Fn::ref serves the same purpose as CloudFormation's {"Ref": ""} intrinsic function.
|
44
|
+
vpc_id Fn::ref(:vpc)
|
45
|
+
internet_gateway_id Fn::ref(:defaultigw)
|
46
|
+
end
|
47
|
+
|
48
|
+
resource :routetable, 'AWS::EC2::RouteTable' do
|
49
|
+
vpc_id Fn::ref(:vpc)
|
50
|
+
end
|
51
|
+
|
52
|
+
(1..3).each do |i|
|
53
|
+
resource "subnet#{i}", 'AWS::EC2::Subnet' do
|
54
|
+
# Other CloudFormation intrinsics, such as `Fn::Select` and `AWS::Region` are available as Ruby objects
|
55
|
+
# Inspecting these functions will reveal that they simply return a Ruby hash representing the same CloudFormation structures
|
56
|
+
availability_zone Fn::select(i, Fn::get_azs(AWS::region))
|
57
|
+
cidr_block "172.42.#{i}.0/24"
|
58
|
+
vpc_id Fn::ref(:vpc)
|
59
|
+
end
|
60
|
+
|
61
|
+
resource "srta#{i}".to_sym, 'AWS::EC2::SubnetRouteTableAssociation' do
|
62
|
+
subnet_id Fn::ref("subnet#{i}")
|
63
|
+
route_table_id Fn::ref(:routetable)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Functions do not need to be called in any particular order.
|
67
|
+
# The `output` function defines a stack output, which may be referenced from another stack using the `@stack_name.output_name` format
|
68
|
+
output "subnetid#{i}", Fn::ref("subnet#{i}")
|
69
|
+
end
|
70
|
+
|
71
|
+
# The `resource` function accepts one additional parameter that was not addressed above: the options hash
|
72
|
+
# Additional options passed here will be placed inside the resource, but outside the `Properties` block.
|
73
|
+
# In this case, we've specified that the default route explicitly depends on the VPC Internet Gateway.
|
74
|
+
# (As of this writing, this is actually a required workaround for this template,
|
75
|
+
# because the gateway must be attached to the VPC before a route can be created to it.)
|
76
|
+
resource :defaultroute, 'AWS::EC2::Route', DependsOn: [:vpcigw] do
|
77
|
+
route_table_id Fn::ref(:routetable)
|
78
|
+
gateway_id Fn::ref(:defaultigw)
|
79
|
+
destination_cidr_block '0.0.0.0/0'
|
80
|
+
end
|
81
|
+
|
82
|
+
output :vpcid, Fn::ref(:vpc)
|
83
|
+
|
84
|
+
|
data/lib/cfer.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'active_support/all'
|
2
|
+
require 'aws-sdk'
|
3
|
+
require 'logger'
|
4
|
+
require 'json'
|
5
|
+
require 'rugged'
|
6
|
+
require 'preconditions'
|
7
|
+
require 'rainbow'
|
8
|
+
|
9
|
+
|
10
|
+
# Contains extensions that Cfer will dynamically use
|
11
|
+
module CferExt
|
12
|
+
module AWS
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Contains the core Cfer logic
|
17
|
+
module Cfer
|
18
|
+
module Cfn
|
19
|
+
end
|
20
|
+
|
21
|
+
module Core
|
22
|
+
end
|
23
|
+
|
24
|
+
# The Cfer logger
|
25
|
+
LOGGER = Logger.new(STDERR)
|
26
|
+
LOGGER.level = Logger::INFO
|
27
|
+
LOGGER.formatter = proc { |severity, datetime, progname, msg|
|
28
|
+
msg = case severity
|
29
|
+
when 'FATAL'
|
30
|
+
Rainbow(msg).red.bright
|
31
|
+
when 'ERROR'
|
32
|
+
Rainbow(msg).red
|
33
|
+
when 'WARN'
|
34
|
+
Rainbow(msg).yellow
|
35
|
+
when 'DEBUG'
|
36
|
+
Rainbow(msg).black.bright
|
37
|
+
else
|
38
|
+
msg
|
39
|
+
end
|
40
|
+
|
41
|
+
"#{msg}\n"
|
42
|
+
}
|
43
|
+
|
44
|
+
class << self
|
45
|
+
|
46
|
+
def converge!(stack_name, options = {})
|
47
|
+
config(options)
|
48
|
+
tmpl = options[:template] || "#{stack_name}.rb"
|
49
|
+
cfn = options[:aws_options] || {}
|
50
|
+
|
51
|
+
cfn_stack = Cfer::Cfn::Client.new(cfn.merge(stack_name: stack_name))
|
52
|
+
stack = Cfer::stack_from_file(tmpl, options.merge(client: cfn_stack))
|
53
|
+
|
54
|
+
begin
|
55
|
+
cfn_stack.converge(stack, options)
|
56
|
+
if options[:follow]
|
57
|
+
tail! stack_name, options
|
58
|
+
end
|
59
|
+
rescue Aws::CloudFormation::Errors::ValidationError => e
|
60
|
+
Cfer::LOGGER.info "CFN validation error: #{e.message}"
|
61
|
+
end
|
62
|
+
describe! stack_name, options
|
63
|
+
end
|
64
|
+
|
65
|
+
def describe!(stack_name, options = {})
|
66
|
+
config(options)
|
67
|
+
cfn = options[:aws_options] || {}
|
68
|
+
cfn_stack = Cfer::Cfn::Client.new(cfn.merge(stack_name: stack_name)).fetch_stack
|
69
|
+
|
70
|
+
Cfer::LOGGER.debug "Describe stack: #{cfn_stack}"
|
71
|
+
|
72
|
+
case options[:output_format] || 'table'
|
73
|
+
when 'json'
|
74
|
+
puts render_json(cfn_stack, options)
|
75
|
+
when 'table'
|
76
|
+
puts "Status: #{cfn_stack[:stack_status]}"
|
77
|
+
puts "Description: #{cfn_stack[:description]}" if cfn_stack[:description]
|
78
|
+
puts ""
|
79
|
+
parameters = (cfn_stack[:parameters] || []).map { |param| {:Type => "Parameter", :Key => param[:parameter_key], :Value => param[:parameter_value]} }
|
80
|
+
outputs = (cfn_stack[:outputs] || []).map { |output| {:Type => "Output", :Key => output[:output_key], :Value => output[:output_value]} }
|
81
|
+
tp parameters + outputs, :Type, :Key, {:Value => {:width => 80}}
|
82
|
+
else
|
83
|
+
raise Cfer::Util::CferError, "Invalid output format #{options[:output_format]}."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def tail!(stack_name, options = {}, &block)
|
88
|
+
config(options)
|
89
|
+
cfn = options[:aws_options] || {}
|
90
|
+
cfn_client = Cfer::Cfn::Client.new(cfn.merge(stack_name: stack_name))
|
91
|
+
if block
|
92
|
+
cfn_client.tail(options, &block)
|
93
|
+
else
|
94
|
+
cfn_client.tail(options) do |event|
|
95
|
+
Cfer::LOGGER.info "%s %-30s %-40s %-20s %s" % [event.timestamp, color_map(event.resource_status), event.resource_type, event.logical_resource_id, event.resource_status_reason]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
describe! stack_name, options
|
99
|
+
end
|
100
|
+
|
101
|
+
def generate!(tmpl, options = {})
|
102
|
+
config(options)
|
103
|
+
cfn_stack = Cfer::Cfn::Client.new(options[:aws_options] || {})
|
104
|
+
stack = Cfer::stack_from_file(tmpl, options.merge(client: cfn_stack)).to_h
|
105
|
+
puts render_json(stack, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Builds a Cfer::Core::Stack from a Ruby block
|
109
|
+
#
|
110
|
+
# @param options [Hash] The stack options
|
111
|
+
# @param block The block containing the Cfn DSL
|
112
|
+
# @option options [Hash] :parameters The CloudFormation stack parameters
|
113
|
+
# @return [Cfer::Core::Stack] The assembled stack object
|
114
|
+
def stack_from_block(options = {}, &block)
|
115
|
+
s = Cfer::Core::Stack.new(options)
|
116
|
+
templatize_errors('block') do
|
117
|
+
s.build_from_block(&block)
|
118
|
+
end
|
119
|
+
s
|
120
|
+
end
|
121
|
+
|
122
|
+
# Builds a Cfer::Core::Stack from a ruby script
|
123
|
+
#
|
124
|
+
# @param file [String] The file containing the Cfn DSL
|
125
|
+
# @param options [Hash] (see #stack_from_block)
|
126
|
+
# @return [Cfer::Core::Stack] The assembled stack object
|
127
|
+
def stack_from_file(file, options = {})
|
128
|
+
s = Cfer::Core::Stack.new(options)
|
129
|
+
templatize_errors(file) do
|
130
|
+
s.build_from_file file
|
131
|
+
end
|
132
|
+
s
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def config(options)
|
138
|
+
Cfer::LOGGER.debug "Options: #{options}"
|
139
|
+
Cfer::LOGGER.level = Logger::DEBUG if options[:verbose]
|
140
|
+
|
141
|
+
Aws.config.update region: options[:region] if options[:region]
|
142
|
+
Aws.config.update credentials: Aws::SharedCredentials.new(profile_name: options[:profile]) if options[:profile]
|
143
|
+
end
|
144
|
+
|
145
|
+
def render_json(obj, options = {})
|
146
|
+
if options[:pretty_print]
|
147
|
+
puts JSON.pretty_generate(obj)
|
148
|
+
else
|
149
|
+
puts obj.to_json
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def templatize_errors(base_loc)
|
154
|
+
yield
|
155
|
+
rescue SyntaxError => e
|
156
|
+
raise Cfer::Util::TemplateError.new([]), e.message
|
157
|
+
rescue StandardError => e
|
158
|
+
raise Cfer::Util::TemplateError.new(convert_backtrace(base_loc, e)), e.message
|
159
|
+
end
|
160
|
+
|
161
|
+
def convert_backtrace(base_loc, exception)
|
162
|
+
continue_search = true
|
163
|
+
exception.backtrace_locations.take_while { |loc|
|
164
|
+
continue_search = false if loc.path == base_loc
|
165
|
+
continue_search || loc.path == base_loc
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
COLORS_MAP = {
|
171
|
+
'CREATE_IN_PROGRESS' => {
|
172
|
+
color: :yellow
|
173
|
+
},
|
174
|
+
'DELETE_IN_PROGRESS' => {
|
175
|
+
color: :yellow
|
176
|
+
},
|
177
|
+
'UPDATE_IN_PROGRESS' => {
|
178
|
+
color: :green
|
179
|
+
},
|
180
|
+
|
181
|
+
'CREATE_FAILED' => {
|
182
|
+
color: :red,
|
183
|
+
finished: true
|
184
|
+
},
|
185
|
+
'DELETE_FAILED' => {
|
186
|
+
color: :red,
|
187
|
+
finished: true
|
188
|
+
},
|
189
|
+
'UPDATE_FAILED' => {
|
190
|
+
color: :red,
|
191
|
+
finished: true
|
192
|
+
},
|
193
|
+
|
194
|
+
'CREATE_COMPLETE' => {
|
195
|
+
color: :green,
|
196
|
+
finished: true
|
197
|
+
},
|
198
|
+
'DELETE_COMPLETE' => {
|
199
|
+
color: :green,
|
200
|
+
finished: true
|
201
|
+
},
|
202
|
+
'UPDATE_COMPLETE' => {
|
203
|
+
color: :green,
|
204
|
+
finished: true
|
205
|
+
},
|
206
|
+
|
207
|
+
'DELETE_SKIPPED' => {
|
208
|
+
color: :yellow
|
209
|
+
},
|
210
|
+
|
211
|
+
'ROLLBACK_IN_PROGRESS' => {
|
212
|
+
color: :red
|
213
|
+
},
|
214
|
+
'ROLLBACK_COMPLETE' => {
|
215
|
+
color: :red,
|
216
|
+
finished: true
|
217
|
+
}
|
218
|
+
}
|
219
|
+
|
220
|
+
def color_map(str)
|
221
|
+
if COLORS_MAP.include?(str)
|
222
|
+
Rainbow(str).send(COLORS_MAP[str][:color])
|
223
|
+
else
|
224
|
+
str
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def stopped_state?(str)
|
229
|
+
COLORS_MAP[str][:finished] || false
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
Dir["#{File.dirname(__FILE__)}/cfer/**/*.rb"].each { |f| require(f) }
|
235
|
+
Dir["#{File.dirname(__FILE__)}/cferext/**/*.rb"].each { |f| require(f) }
|
236
|
+
|
data/lib/cfer/block.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'docile'
|
2
|
+
|
3
|
+
module Cfer
|
4
|
+
# Represents the base class of a Cfer DSL
|
5
|
+
class Block < ActiveSupport::HashWithIndifferentAccess
|
6
|
+
# Evaluates a DSL directly from a Ruby block, calling pre- and post- hooks.
|
7
|
+
# @param args [Array<Object>] Extra arguments to be passed into the block.
|
8
|
+
def build_from_block(*args, &block)
|
9
|
+
pre_block
|
10
|
+
Docile.dsl_eval(self, *args, &block) if block
|
11
|
+
post_block
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Evaluates a DSL from a Ruby script file
|
16
|
+
# @param args [Array<Object>] (see: #build_from_block)
|
17
|
+
# @param file [File] The Ruby script to evaluate
|
18
|
+
def build_from_file(*args, file)
|
19
|
+
build_from_block(*args) do
|
20
|
+
instance_eval File.read(file), file
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# Executed just before the DSL is evaluated
|
26
|
+
def pre_block
|
27
|
+
end
|
28
|
+
|
29
|
+
# Executed just after the DSL is evaluated
|
30
|
+
def post_block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|