Skip to content

Commit

Permalink
Provide user_data_file helper method for YAML ERB templates
Browse files Browse the repository at this point in the history
  • Loading branch information
orien committed Feb 2, 2024
1 parent 968b914 commit d61039c
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ The format is based on [Keep a Changelog], and this project adheres to

- Test on Ruby 3.3 in the CI build ([#376]).

- Introduce `user_data_file`, `user_data_file_as_lines`, and `include_file`
convenience methods to the YAML ERB template compiler ([#377]).

[Unreleased]: https://github.com/envato/stack_master/compare/v2.13.4...HEAD
[#376]: https://github.com/envato/stack_master/pull/376
[#377]: https://github.com/envato/stack_master/pull/377

## [2.13.4] - 2023-08-02

Expand Down
1 change: 1 addition & 0 deletions lib/stack_master.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ module StackMaster
autoload :TemplateCompiler, 'stack_master/template_compiler'
autoload :Identity, 'stack_master/identity'
autoload :CloudFormationInterpolatingEruby, 'stack_master/cloudformation_interpolating_eruby'
autoload :CloudFormationTemplateEruby, 'stack_master/cloudformation_template_eruby'

autoload :StackDiffer, 'stack_master/stack_differ'
autoload :Validator, 'stack_master/validator'
Expand Down
32 changes: 32 additions & 0 deletions lib/stack_master/cloudformation_template_eruby.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require 'erubis'
require 'json'

module StackMaster
# This class is a modified version of `Erubis::Eruby`. It provides extra
# helper methods to ease the dynamic creation of CloudFormation templates
# with ERB. These helper methods are available within `<%= %>` expressions.
class CloudFormationTemplateEruby < Erubis::Eruby
# Adds the contents of an EC2 userdata script to the CloudFormation
# template. Allows using the ERB `<%= %>` expressions within the user data
# script to interpolate CloudFormation values.
def user_data_file(filepath)
JSON.pretty_generate({ 'Fn::Base64' => { 'Fn::Join' => ['', user_data_file_as_lines(filepath)] } })
end

# Evaluate the ERB template at the specified filepath and return the result
# as an array of lines. Allows using ERB `<%= %>` expressions to interpolate
# CloudFormation objects into the result.
def user_data_file_as_lines(filepath)
StackMaster::CloudFormationInterpolatingEruby.evaluate_file(filepath, self)
end

# Add the contents of another file into the CloudFormation template as a
# string. ERB `<%= %>` expressions within the referenced file are not
# evaluated.
def include_file(filepath)
JSON.pretty_generate(File.read(filepath))
end
end
end
3 changes: 1 addition & 2 deletions lib/stack_master/template_compilers/yaml_erb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
module StackMaster::TemplateCompilers
class YamlErb
def self.require_dependencies
require 'erubis'
require 'yaml'
end

def self.compile(template_dir, template, compile_time_parameters, _compiler_options = {})
template_file_path = File.join(template_dir, template)
template = Erubis::Eruby.new(File.read(template_file_path))
template = StackMaster::CloudFormationTemplateEruby.new(File.read(template_file_path))
template.filename = template_file_path

template.result(params: compile_time_parameters)
Expand Down
124 changes: 124 additions & 0 deletions spec/stack_master/cloudformation_template_eruby_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
RSpec.describe(StackMaster::CloudFormationTemplateEruby) do
subject(:evaluate) do
eruby = described_class.new(template)
eruby.evaluate(eruby)
end

describe('.user_data_file') do
context('given a template that loads a simple user data script file') do
let(:template) { <<~YAML}
Resources:
LaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
UserData: <%= user_data_file('my/userdata.sh') %>
YAML

before do
allow(File).to receive(:read).with('my/userdata.sh').and_return(<<~SHELL)
#!/bin/bash
REGION=ap-southeast-2
echo $REGION
SHELL
end

it 'embeds the script in the evaluated CFN template' do
expect(evaluate).to eq(<<~YAML)
Resources:
LaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
UserData: {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash\\n",
"\\n",
"REGION=ap-southeast-2\\n",
"echo $REGION\\n"
]
]
}
}
YAML
end
end

context('given a template that loads a user data script file that includes another file') do
let(:template) { <<~YAML}
Resources:
LaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
UserData: <%= user_data_file('my/userdata.sh') %>
YAML

before do
allow(File).to receive(:read).with('my/userdata.sh').and_return(<<~SHELL)
#!/bin/bash
echo 'Hello from userdata.sh'
<%= user_data_file_as_lines('my/other.sh') %>
SHELL
allow(File).to receive(:read).with('my/other.sh').and_return(<<~SHELL)
echo 'Hello from other.sh'
SHELL
end

it 'embeds the script in the evaluated CFN template' do
expect(evaluate).to eq(<<~YAML)
Resources:
LaunchConfig:
Type: 'AWS::AutoScaling::LaunchConfiguration'
Properties:
UserData: {
"Fn::Base64": {
"Fn::Join": [
"",
[
"#!/bin/bash\\n",
"echo 'Hello from userdata.sh'\\n",
"echo 'Hello from other.sh'\\n",
"\\n"
]
]
}
}
YAML
end
end
end

describe('.include_file') do
context('given a template that loads a lambda script') do
let(:template) { <<~YAML}
Resources:
Function:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: <%= include_file('my/lambda.sh') %>
YAML

before do
allow(File).to receive(:read).with('my/lambda.sh').and_return(<<~SHELL)
#!/bin/bash
echo 'Hello, world!'
SHELL
end

it 'embeds the script in the evaluated CFN template' do
expect(evaluate).to eq(<<~YAML)
Resources:
Function:
Type: 'AWS::Lambda::Function'
Properties:
Code:
ZipFile: "#!/bin/bash\\n\\necho 'Hello, world!'\\n"
YAML
end
end
end
end

0 comments on commit d61039c

Please sign in to comment.