Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parameter_files and parameters configuration options to stack definition #338

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@ The format is based on [Keep a Changelog], and this project adheres to
[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html

## [Unreleased]
## [2.7.0] - 2020-06-15

### Added

- `parameters_dir` is now configurable to match the existing `template_dir`.
- `parameter_files` configures an array of parameter files relative to
`parameters_dir` that will be used instead of automatic parameter file globs
based on region and stack name.
- `parameters` configures stack parameters directly on the stack definition
rather than requiring an external parameter file.

### Fixed

Expand Down
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ stack_defaults:
```

Additional files can be configured to be uploaded to S3 alongside the templates:

```yaml
stacks:
production:
Expand All @@ -131,6 +132,7 @@ stacks:
files:
- userdata.sh
```

## Directories

- `templates` - CloudFormation, SparkleFormation or CfnDsl templates.
Expand All @@ -155,7 +157,8 @@ template_compilers:

## Parameters

Parameters are loaded from multiple YAML files, merged from the following lookup paths from bottom to top:
By default, parameters are loaded from multiple YAML files, merged from the
following lookup paths from bottom to top:

- parameters/[stack_name].yaml
- parameters/[stack_name].yml
Expand All @@ -170,6 +173,30 @@ A simple parameter file could look like this:
key_name: myapp-us-east-1
```

Alternatively, a `parameter_files` array can be defined to explicitly list
parameter files that will be loaded. If `parameter_files` are defined, the
automatic search locations will not be used.

```yaml
parameters_dir: parameters # the default
stacks:
us-east-1:
my-app:
parameter_files:
- my-app.yml # parameters/my-app.yml
```

Parameters can also be defined inline with stack definitions:

```yaml
stacks:
us-east-1:
my-app:
parameters:
VpcId:
stack_output: my-vpc/VpcId
```

### Compile Time Parameters

Compile time parameters can be used for [SparkleFormation](http://www.sparkleformation.io) templates. It conforms and
Expand Down
65 changes: 65 additions & 0 deletions features/apply_with_explicit_parameter_files.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Feature: Apply command with explicit parameter files

Background:
Given a file named "stack_master.yml" with:
"""
stack_defaults:
tags:
Application: myapp
stacks:
us-east-1:
myapp-web:
template: myapp.rb
parameter_files:
- myapp-web-parameters.yml
"""
And a file named "parameters/us-east-1/myapp-web.yml" with:
"""
Color: blue
"""
And a file named "parameters/myapp-web-parameters.yml" with:
"""
KeyName: my-key
Color: red
"""
And a directory named "templates"
And a file named "templates/myapp.rb" with:
"""
SparkleFormation.new(:myapp) do
description "Test template"

parameters.key_name do
description 'Key name'
type 'String'
end

parameters.color do
description 'Color'
type 'String'
end

resources.instance do
type 'AWS::EC2::Instance'
properties do
image_id 'ami-0080e4c5bc078760e'
instance_type 't2.micro'
end
end
end
"""

Scenario: Run apply and create stack with explicit parameter files
Given I stub the following stack events:
| stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
| 1 | 1 | myapp-web | myapp-web | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
When I run `stack_master apply us-east-1 myapp-web --trace`
Then the output should match /2020-10-29 00:00:00 (\+|\-)[0-9]{4} myapp-web AWS::CloudFormation::Stack CREATE_COMPLETE/
And the output should contain all of these lines:
| Stack diff: |
| + "Instance": { |
| Parameters diff: |
| KeyName: my-key |
| Proposed change set: |
And the output should not contain "Color: blue"
And the output should contain "Color: red"
And the exit status should be 0
2 changes: 1 addition & 1 deletion features/apply_with_s3.feature
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Feature: Apply command
| Parameters diff: |
| KeyName: my-key |
And the output should match /2020-10-29 00:00:00 (\+|\-)[0-9]{4} myapp-vpc AWS::CloudFormation::Stack CREATE_COMPLETE/
And an S3 file in bucket "my-bucket" with key "cfn_templates/my-app/myapp_vpc.json" exists with content:
And an S3 file in bucket "my-bucket" with key "cfn_templates/my-app/myapp_vpc.json" exists with JSON content:
"""
{
"Description": "Test template",
Expand Down
46 changes: 46 additions & 0 deletions features/apply_with_stack_definition_parameters.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
Feature: Apply command with stack definition parameters

Background:
Given a file named "stack_master.yml" with:
"""
stacks:
us-east-1:
myapp_web:
template: myapp.rb
parameters:
KeyName: my-key
"""
And a directory named "templates"
And a file named "templates/myapp.rb" with:
"""
SparkleFormation.new(:myapp) do
description "Test template"

parameters.key_name do
description 'Key name'
type 'String'
end

resources.instance do
type 'AWS::EC2::Instance'
properties do
image_id 'ami-0080e4c5bc078760e'
instance_type 't2.micro'
end
end
end
"""

Scenario: Run apply with parameters contained in
Given I stub the following stack events:
| stack_id | event_id | stack_name | logical_resource_id | resource_status | resource_type | timestamp |
| 1 | 1 | myapp-web | myapp-web | CREATE_COMPLETE | AWS::CloudFormation::Stack | 2020-10-29 00:00:00 |
When I run `stack_master apply us-east-1 myapp-web --trace`
Then the output should match /2020-10-29 00:00:00 (\+|\-)[0-9]{4} myapp-web AWS::CloudFormation::Stack CREATE_COMPLETE/
And the output should contain all of these lines:
| Stack diff: |
| + "Instance": { |
| Parameters diff: |
| KeyName: my-key |
| Proposed change set: |
And the exit status should be 0
8 changes: 7 additions & 1 deletion features/step_definitions/stack_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ def extract_hash_from_kv_string(string)
allow(StackMaster.cloud_formation_driver.class).to receive(:new).and_return(StackMaster.cloud_formation_driver)
end

When(/^an S3 file in bucket "([^"]*)" with key "([^"]*)" exists with content:$/) do |bucket, key, body|
Then(/^an S3 file in bucket "([^"]*)" with key "([^"]*)" exists with content:$/) do |bucket, key, body|
file = StackMaster.s3_driver.find_file(bucket: bucket, object_key: key)
expect(file).to eq body
end

Then(/^an S3 file in bucket "([^"]*)" with key "([^"]*)" exists with JSON content:$/) do |bucket, key, body|
file = StackMaster.s3_driver.find_file(bucket: bucket, object_key: key)
parsed_file = JSON.parse(file)
expect(parsed_file).to eq JSON.parse(body)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this test achieves?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It checks the contents match by comparing parsed hashes rather than strings. For some reason apply_with_s3.feature started failing, and master and has the same issue - https://travis-ci.org/github/envato/stack_master/jobs/692383878#L2514 Maybe it's related to a sparkleformation upgrade?

2 changes: 1 addition & 1 deletion lib/stack_master/commands/tidy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def perform
parameter_files = Set.new(find_parameter_files())

status = @config.stacks.each do |stack_definition|
parameter_files.subtract(stack_definition.parameter_files)
parameter_files.subtract(stack_definition.parameter_files_from_globs)
template = File.absolute_path(stack_definition.template_file_path)

if template
Expand Down
3 changes: 3 additions & 0 deletions lib/stack_master/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def self.load!(config_file = 'stack_master.yml')
attr_accessor :stacks,
:base_dir,
:template_dir,
:parameters_dir,
:stack_defaults,
:region_defaults,
:region_aliases,
Expand All @@ -39,6 +40,7 @@ def initialize(config, base_dir)
@config = config
@base_dir = base_dir
@template_dir = config.fetch('template_dir', nil)
@parameters_dir = config.fetch('parameters_dir', nil)
@stack_defaults = config.fetch('stack_defaults', {})
@region_aliases = Utils.underscore_keys_to_hyphen(config.fetch('region_aliases', {}))
@region_to_aliases = @region_aliases.inject({}) do |hash, (key, value)|
Expand Down Expand Up @@ -115,6 +117,7 @@ def load_stacks(stacks)
'stack_name' => stack_name,
'base_dir' => @base_dir,
'template_dir' => @template_dir,
'parameters_dir' => @parameters_dir,
'additional_parameter_lookup_dirs' => @region_to_aliases[region])
stack_attributes['allowed_accounts'] = attributes['allowed_accounts'] if attributes['allowed_accounts']
@stacks << StackDefinition.new(stack_attributes)
Expand Down
7 changes: 3 additions & 4 deletions lib/stack_master/parameter_loader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ class ParameterLoader

COMPILE_TIME_PARAMETERS_KEY = 'compile_time_parameters'

def self.load(parameter_files)
def self.load(parameter_files: [], parameters: {})
StackMaster.debug 'Searching for parameter files...'
parameter_files.reduce({template_parameters: {}, compile_time_parameters: {}}) do |hash, file_name|
parameters = load_parameters(file_name)
all_parameters = parameter_files.map { |file_name| load_parameters(file_name) } + [parameters]
all_parameters.reduce({template_parameters: {}, compile_time_parameters: {}}) do |hash, parameters|
template_parameters = create_template_parameters(parameters)
compile_time_parameters = create_compile_time_parameters(parameters)

merge_and_camelize(hash[:template_parameters], template_parameters)
merge_and_camelize(hash[:compile_time_parameters], compile_time_parameters)
hash
end

end

private
Expand Down
31 changes: 24 additions & 7 deletions lib/stack_master/parameter_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ def initialize(stack:, stack_definition:)

def error_message
return nil unless missing_parameters?
message = "Empty/blank parameters detected. Please provide values for these parameters:"
message = "Empty/blank parameters detected. Please provide values for these parameters:\n"
missing_parameters.each do |parameter_name|
message << "\n - #{parameter_name}"
message << " - #{parameter_name}\n"
end
message << "\nParameters will be read from files matching the following globs:"
base_dir = Pathname.new(@stack_definition.base_dir)
@stack_definition.parameter_file_globs.each do |glob|
parameter_file = Pathname.new(glob).relative_path_from(base_dir)
message << "\n - #{parameter_file}"
if @stack_definition.parameter_files.empty?
message << message_for_parameter_globs
else
message << message_for_parameter_files
end
message
end
Expand All @@ -28,6 +27,24 @@ def missing_parameters?

private

def message_for_parameter_files
"Parameters are configured to be read from the following files:\n".tap do |message|
@stack_definition.parameter_files.each do |parameter_file|
message << " - #{parameter_file}\n"
end
end
end

def message_for_parameter_globs
"Parameters will be read from files matching the following globs:\n".tap do |message|
base_dir = Pathname.new(@stack_definition.base_dir)
@stack_definition.parameter_file_globs.each do |glob|
parameter_file = Pathname.new(glob).relative_path_from(base_dir)
message << " - #{parameter_file}\n"
end
end
end

def missing_parameters
@missing_parameters ||=
@stack.parameters_with_defaults.select { |_key, value| value.nil? }.keys
Expand Down
4 changes: 2 additions & 2 deletions lib/stack_master/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def self.find(region, stack_name)
end

def self.generate(stack_definition, config)
parameter_hash = ParameterLoader.load(stack_definition.parameter_files)
parameter_hash = ParameterLoader.load(parameter_files: stack_definition.all_parameter_files, parameters: stack_definition.parameters)
template_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:template_parameters])
compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
template_body = TemplateCompiler.compile(config, stack_definition.compiler, stack_definition.template_dir, stack_definition.template, compile_time_parameters, stack_definition.compiler_options)
Expand All @@ -76,7 +76,7 @@ def self.generate(stack_definition, config)
end

def self.generate_without_parameters(stack_definition, config)
parameter_hash = ParameterLoader.load(stack_definition.parameter_files)
parameter_hash = ParameterLoader.load(parameter_files: stack_definition.all_parameter_files, parameters: stack_definition.parameters)
compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
template_body = TemplateCompiler.compile(config, stack_definition.compiler, stack_definition.template_dir, stack_definition.template, compile_time_parameters, stack_definition.compiler_options)
template_format = TemplateUtils.identify_template_format(template_body)
Expand Down
Loading