Skip to content

Commit

Permalink
Fixes #99: Change Stack tagging. (#145)
Browse files Browse the repository at this point in the history
* Updates tags on UpdateStack, as well as CreateStack.
* Uses two new tags: moonshot_application, moonshot_environment.
* Supports an additional tag for internal use, defaults to off.
  • Loading branch information
askreet committed Oct 10, 2016
1 parent f4e49e2 commit 317222a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 55 deletions.
32 changes: 2 additions & 30 deletions lib/moonshot/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

module Moonshot
# The Controller coordinates and performs all Moonshot actions.
class Controller # rubocop:disable ClassLength
class Controller
attr_accessor :config

def initialize
Expand Down Expand Up @@ -94,39 +94,11 @@ def ssh
end

def stack
@stack ||= Stack.new(stack_name,
app_name: @config.app_name,
ilog: @config.interactive_logger) do |config|
config.parent_stacks = @config.parent_stacks
config.show_all_events = @config.show_all_stack_events
config.parameter_strategy = @config.parameter_strategy
end
@stack ||= Stack.new(@config)
end

private

def default_stack_name
user = ENV.fetch('USER').gsub(/\W/, '')
"#{@config.app_name}-dev-#{user}"
end

def ensure_prefix(name)
if name.start_with?(@config.app_name + '-')
name
else
@config.app_name + "-#{name}"
end
end

def stack_name
name = @config.environment_name || default_stack_name
if @config.auto_prefix_stack == false
name
else
ensure_prefix(name)
end
end

def resources
@resources ||=
Resources.new(stack: stack, ilog: @config.interactive_logger)
Expand Down
6 changes: 4 additions & 2 deletions lib/moonshot/controller_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
module Moonshot
# Holds configuration for Moonshot::Controller
class ControllerConfig
attr_accessor :additional_tag
attr_accessor :app_name
attr_accessor :artifact_repository
attr_accessor :auto_prefix_stack
attr_accessor :build_mechanism
attr_accessor :deployment_mechanism
attr_accessor :environment_name
Expand All @@ -22,13 +22,15 @@ class ControllerConfig
attr_accessor :ssh_instance

def initialize
@auto_prefix_stack = true
@interactive_logger = InteractiveLogger.new
@parent_stacks = []
@plugins = []
@show_all_stack_events = false
@parameter_strategy = Moonshot::ParameterStrategy::DefaultStrategy.new
@ssh_config = SSHConfig.new

user = ENV.fetch('USER', 'default-user').gsub(/\W/, '')
@environment_name = "dev-#{user}"
end
end
end
33 changes: 22 additions & 11 deletions lib/moonshot/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ class Stack # rubocop:disable ClassLength
attr_reader :app_name
attr_reader :name

# TODO: Refactor more of these parameters into the config object.
def initialize(name, app_name:, ilog:, config: StackConfig.new)
@name = name
@app_name = app_name
@ilog = ilog
def initialize(config)
@config = config
@ilog = config.interactive_logger
@name = [@config.app_name, @config.environment_name].join('-')

yield @config if block_given?
end

Expand Down Expand Up @@ -213,7 +212,7 @@ def yaml_template_path

# @return [String] the path to the template file without extension.
def raw_template_file_name
@raw_template_file_name ||= File.join(Dir.pwd, 'cloud_formation', @app_name)
@raw_template_file_name ||= File.join(Dir.pwd, 'cloud_formation', @config.app_name)
end

def load_template_file
Expand Down Expand Up @@ -280,9 +279,7 @@ def create_stack
template_body: template.body,
capabilities: ['CAPABILITY_IAM'],
parameters: load_parameters_file,
tags: [
{ key: 'ah_stage', value: @name }
]
tags: make_tags
)
rescue Aws::CloudFormation::Errors::AccessDenied
raise 'You are not authorized to perform create_stack calls.'
Expand All @@ -299,7 +296,8 @@ def update_stack
overrides,
parameters,
template
)
),
tags: make_tags
)
true
rescue Aws::CloudFormation::Errors::ValidationError => e
Expand All @@ -315,7 +313,7 @@ def wait_for_stack_state(wait_target, past_tense_verb) # rubocop:disable AbcSize
stack_id = get_stack(@name).stack_id

events = StackEventsPoller.new(cf_client, stack_id)
events.show_only_errors unless @config.show_all_events
events.show_only_errors unless @config.show_all_stack_events

@ilog.start_threaded "Waiting for #{stack_name} to be successfully #{past_tense_verb}." do |s|
begin
Expand Down Expand Up @@ -353,6 +351,19 @@ def wait_for_stack_state(wait_target, past_tense_verb) # rubocop:disable AbcSize
result
end

def make_tags
default_tags = [
{ key: 'moonshot_application', value: @config.app_name },
{ key: 'moonshot_environment', value: @config.environment_name }
]

if @config.additional_tag
default_tags << { key: @config.additional_tag, value: @name }
end

default_tags
end

def format_event(event)
str = case event.resource_status
when /FAILED/
Expand Down
67 changes: 55 additions & 12 deletions spec/moonshot/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
let(:parent_stacks) { [] }
let(:cf_client) { instance_double(Aws::CloudFormation::Client) }

subject do
described_class.new('test', app_name: 'rspec-app', ilog: ilog) do |c|
c.parent_stacks = parent_stacks
end
end

let(:config) { Moonshot::ControllerConfig.new }
before(:each) do
config.app_name = 'rspec-app'
config.environment_name = 'staging'
config.interactive_logger = ilog
config.parent_stacks = parent_stacks

allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf_client)
end

subject { described_class.new(config) }

describe '#create' do
let(:step) { instance_double('InteractiveLogger::Step') }
let(:stack_exists) { false }
Expand Down Expand Up @@ -53,6 +55,46 @@
end
end

context 'under normal circumstances' do
let(:parent_stacks) { [] }
let(:expected_create_stack_options) do
{
stack_name: 'rspec-app-staging',
template_body: an_instance_of(String),
tags: [
{ key: 'moonshot_application', value: 'rspec-app' },
{ key: 'moonshot_environment', value: 'staging' },
{ key: 'ah_stage', value: 'rspec-app-staging' }
],
parameters: [],
capabilities: ['CAPABILITY_IAM']
}
end

let(:cf_client) do
stubs = {
describe_stacks: {
stacks: [
{
stack_name: 'rspec-app-staging',
creation_time: Time.now,
stack_status: 'CREATE_COMPLETE',
outputs: []
}
]
}
}
Aws::CloudFormation::Client.new(stub_responses: stubs)
end

it 'should call CreateStack, then wait for completion' do
config.additional_tag = 'ah_stage'
expect(cf_client).to receive(:create_stack)
.with(hash_including(expected_create_stack_options))
subject.create
end
end

context 'when a parent stack is specified' do
let(:parent_stacks) { ['myappdc-dc1'] }
let(:cf_client) do
Expand All @@ -75,10 +117,11 @@
end
let(:expected_create_stack_options) do
{
stack_name: 'test',
stack_name: 'rspec-app-staging',
template_body: an_instance_of(String),
tags: [
{ key: 'ah_stage', value: 'test' }
{ key: 'moonshot_application', value: 'rspec-app' },
{ key: 'moonshot_environment', value: 'staging' }
],
parameters: [
{ parameter_key: 'Parent1', parameter_value: 'parents value' }
Expand All @@ -93,7 +136,7 @@
.with(hash_including(expected_create_stack_options))
subject.create

expect(File.exist?('/cloud_formation/parameters/test.yml')).to eq(true)
expect(File.exist?('/cloud_formation/parameters/rspec-app-staging.yml')).to eq(true)
yaml_data = subject.overrides
expected_data = {
'Parent1' => 'parents value'
Expand All @@ -104,7 +147,7 @@

context 'when the local yml file does not contain the override' do
it 'should import outputs as paramters for this stack' do
File.open('/cloud_formation/parameters/test.yml', 'w') do |fp|
File.open('/cloud_formation/parameters/rspec-app-staging.yml', 'w') do |fp|
data = {
'Parent1' => 'Existing Value!'
}
Expand All @@ -115,7 +158,7 @@
.with(hash_including(expected_create_stack_options))
subject.create

expect(File.exist?('/cloud_formation/parameters/test.yml')).to eq(true)
expect(File.exist?('/cloud_formation/parameters/rspec-app-staging.yml')).to eq(true)
yaml_data = subject.overrides
expected_data = {
'Parent1' => 'Existing Value!'
Expand All @@ -135,7 +178,7 @@

describe '#parameters_file' do
it 'should return the parameters file path' do
path = File.join(Dir.pwd, 'cloud_formation', 'parameters', 'test.yml')
path = File.join(Dir.pwd, 'cloud_formation', 'parameters', 'rspec-app-staging.yml')
expect(subject.parameters_file).to eq(path)
end
end
Expand Down

0 comments on commit 317222a

Please sign in to comment.