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

Spike/WIP: Stack definition yaml files. Explicit parameter_files & parameters in stack definitions. #330

Closed
wants to merge 3 commits into from
Closed
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
54 changes: 54 additions & 0 deletions features/apply_with_explicit_parameter_files.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
Feature: Apply command with explicit parameter files

Background:
Given a file named "stack_master.yml" with:
"""
stack_defaults:
tags:
Application: myapp
region: us-east-1
"""
And a file named "myapp-web.yml" with:
"""
template: myapp.rb
parameter_files:
- myapp-web-parameters.yml
"""
And a file named "myapp-web-parameters.yml" with:
"""
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 stack-name.yml and 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 myapp-web.yml --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
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
50 changes: 50 additions & 0 deletions features/apply_with_stack_yml_file.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Feature: Apply command with stack.yml file

Background:
Given a file named "stack_master.yml" with:
"""
stack_defaults:
tags:
Application: myapp
region: us-east-1
"""
And a file named "myapp-web.yml" with:
"""
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 stack-name.yml argument
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 myapp-web.yml --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
48 changes: 30 additions & 18 deletions lib/stack_master/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,27 +234,39 @@ def load_config(file)
def execute_stacks_command(command, args, options)
success = true
config = load_config(options.config)
args = [nil, nil] if args.size == 0
args.each_slice(2) do |aliased_region, stack_name|
region = Utils.underscore_to_hyphen(config.unalias_region(aliased_region))
stack_name = Utils.underscore_to_hyphen(stack_name)
stack_definitions = config.filter(region, stack_name)
if stack_definitions.empty?
StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
success = false
end
stack_definitions = stack_definitions.select do |stack_definition|
running_in_allowed_account?(stack_definition.allowed_accounts) && StackStatus.new(config, stack_definition).changed?
end if options.changed
stack_definitions.each do |stack_definition|
StackMaster.cloud_formation_driver.set_region(stack_definition.region)
StackMaster.stdout.puts "Executing #{command.command_name} on #{stack_definition.stack_name} in #{stack_definition.region}"
success = execute_if_allowed_account(stack_definition.allowed_accounts) do
command.perform(config, stack_definition, options).success?
if args.size == 1 && (args.first.end_with?('.yml') || args.first.end_with?('.yaml'))
yaml_file_name = args.first
stack_definition = config.build_stack_definition(yaml_file_name)
success = run_command_with_stack_definition(command, stack_definition, config, options)
@kernel.exit false unless success
else
args = [nil, nil] if args.size == 0
args.each_slice(2) do |aliased_region, stack_name|
region = Utils.underscore_to_hyphen(config.unalias_region(aliased_region))
stack_name = Utils.underscore_to_hyphen(stack_name)
stack_definitions = config.filter(region, stack_name)
if stack_definitions.empty?
StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
success = false
end
stack_definitions = stack_definitions.select do |stack_definition|
running_in_allowed_account?(stack_definition.allowed_accounts) && StackStatus.new(config, stack_definition).changed?
end if options.changed
stack_definitions.each do |stack_definition|
success = run_command_with_stack_definition(command, stack_definition, config, options)
end
end
@kernel.exit false unless success
end
end

def run_command_with_stack_definition(command, stack_definition, config, options)
StackMaster.cloud_formation_driver.set_region(stack_definition.region)

StackMaster.stdout.puts "Executing #{command.command_name} on #{stack_definition.stack_name} in #{stack_definition.region}"
execute_if_allowed_account(stack_definition.allowed_accounts) do
command.perform(config, stack_definition, options).success?
end
@kernel.exit false unless success
end

def execute_if_allowed_account(allowed_accounts, &block)
Expand Down
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
31 changes: 23 additions & 8 deletions lib/stack_master/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,15 @@ def unalias_region(region)
@region_aliases.fetch(region) { region }
end

def build_stack_definition(yaml_file)
stack_name = yaml_file.sub('.yml', '').sub('.yaml', '')
attributes = YAML.load(File.read(yaml_file))
stack_attributes = merge_stack_attributes(nil, stack_name, attributes)
StackDefinition.new(stack_attributes)
end

private

def load_template_compilers(config)
@template_compilers = {}
populate_template_compilers(config.fetch('template_compilers', {}))
Expand All @@ -94,7 +102,7 @@ def default_template_compilers
end

def load_config
unaliased_stacks = resolve_region_aliases(@config.fetch('stacks'))
unaliased_stacks = resolve_region_aliases(@config.fetch('stacks', {}))
load_stacks(unaliased_stacks)
end

Expand All @@ -110,18 +118,25 @@ def load_stacks(stacks)
region = Utils.underscore_to_hyphen(region)
stacks_for_region.each do |stack_name, attributes|
stack_name = Utils.underscore_to_hyphen(stack_name)
stack_attributes = build_stack_defaults(region).deeper_merge!(attributes).merge(
'region' => region,
'stack_name' => stack_name,
'base_dir' => @base_dir,
'template_dir' => @template_dir,
'additional_parameter_lookup_dirs' => @region_to_aliases[region])
stack_attributes['allowed_accounts'] = attributes['allowed_accounts'] if attributes['allowed_accounts']
stack_attributes = merge_stack_attributes(region, stack_name, attributes)
@stacks << StackDefinition.new(stack_attributes)
end
end
end

def merge_stack_attributes(region, stack_name, attributes)
stack_attributes = build_stack_defaults(region)
.deeper_merge!(attributes)
.merge(
'region' => region,
'stack_name' => stack_name,
'base_dir' => @base_dir,
'template_dir' => @template_dir,
'additional_parameter_lookup_dirs' => @region_to_aliases[region])
stack_attributes['allowed_accounts'] = attributes['allowed_accounts'] if attributes['allowed_accounts']
stack_attributes
end

def build_stack_defaults(region)
region_defaults = @region_defaults.fetch(region, {}).deep_dup
@stack_defaults.deep_dup.deeper_merge(region_defaults)
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
2 changes: 1 addition & 1 deletion 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 Down
12 changes: 10 additions & 2 deletions lib/stack_master/stack_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ class StackDefinition
:additional_parameter_lookup_dirs,
:s3,
:files,
:compiler_options
:compiler_options,
:parameters,
:parameter_files

attr_reader :compiler

Expand All @@ -34,6 +36,8 @@ def initialize(attributes = {})
@additional_parameter_lookup_dirs ||= []
@template_dir ||= File.join(@base_dir, 'templates')
@allowed_accounts = Array(@allowed_accounts)
@parameters ||= {}
@parameter_files ||= []
end

def ==(other)
Expand Down Expand Up @@ -85,7 +89,11 @@ def s3_template_file_name
Utils.change_extension(template, 'json')
end

def parameter_files
def all_parameter_files
parameter_files_from_globs + parameter_files
end

def parameter_files_from_globs
parameter_file_globs.map(&Dir.method(:glob)).flatten
end

Expand Down
4 changes: 2 additions & 2 deletions spec/stack_master/parameter_loader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
let(:stack_file_name) { '/base_dir/parameters/stack_name.yml' }
let(:region_file_name) { '/base_dir/parameters/us-east-1/stack_name.yml' }

subject(:parameters) { StackMaster::ParameterLoader.load([stack_file_name, region_file_name]) }
subject(:parameters) { StackMaster::ParameterLoader.load(parameter_files: [stack_file_name, region_file_name]) }

before do
file_mock(stack_file_name, **stack_file_returns)
Expand Down Expand Up @@ -60,7 +60,7 @@
let(:region_yaml_file_returns) { {exists: true, read: "Param1: value1\nParam2: valueX"} }
let(:region_yaml_file_name) { "/base_dir/parameters/us-east-1/stack_name.yaml" }

subject(:parameters) { StackMaster::ParameterLoader.load([stack_file_name, region_yaml_file_name, region_file_name]) }
subject(:parameters) { StackMaster::ParameterLoader.load(parameter_files: [stack_file_name, region_yaml_file_name, region_file_name]) }

before do
file_mock(region_yaml_file_name, **region_yaml_file_returns)
Expand Down
4 changes: 2 additions & 2 deletions spec/stack_master/stack_definition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
end

it 'has default and region specific parameter file locations' do
expect(stack_definition.parameter_files).to eq([
expect(stack_definition.all_parameter_files).to eq([
"/base_dir/parameters/#{stack_name}.yaml",
"/base_dir/parameters/#{stack_name}.yml",
"/base_dir/parameters/#{region}/#{stack_name}.yaml",
Expand Down Expand Up @@ -75,7 +75,7 @@
end

it 'includes a parameter lookup dir for it' do
expect(stack_definition.parameter_files).to eq([
expect(stack_definition.all_parameter_files).to eq([
"/base_dir/parameters/#{stack_name}.yaml",
"/base_dir/parameters/#{stack_name}.yml",
"/base_dir/parameters/#{region}/#{stack_name}.yaml",
Expand Down
2 changes: 1 addition & 1 deletion spec/stack_master/template_compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def self.compile(template_dir, template, compile_time_parameters, compile_option
context 'when a template compiler is explicitly specified' do
it 'uses it' do
expect(StackMaster::TemplateCompilers::SparkleFormation).to receive(:compile).with('/base_dir/templates', 'template', compile_time_parameters, anything)
StackMaster::TemplateCompiler.compile(config, :sparkle_formation, '/base_dir/templates', 'template', compile_time_parameters, compile_time_parameters)
StackMaster::TemplateCompiler.compile(config, :sparkle_formation, '/base_dir/templates', 'template', compile_time_parameters, {})
end
end

Expand Down