Skip to content

Commit

Permalink
Added ability to push CF template to S3 (#239)
Browse files Browse the repository at this point in the history
* added ability to push CF template to S3

* code style fixes

* using InteractiveLogger; include timestamp in uploaded S3 object name; fixed code style

* test the s3client.put_object gets called if the template_s3_bucket is configured

* improved tests
  • Loading branch information
smatyas authored and askreet committed Nov 11, 2017
1 parent a0cfe5f commit 616fbb9
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 9 deletions.
1 change: 1 addition & 0 deletions lib/moonshot/controller_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ControllerConfig
attr_accessor :ssh_command
attr_accessor :ssh_config
attr_accessor :ssh_instance
attr_accessor :template_s3_bucket

def initialize
@default_parameter_source = AskUserSource.new
Expand Down
50 changes: 43 additions & 7 deletions lib/moonshot/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,39 @@ def get_stack(name)
raise "Could not describe stack: #{name}"
end

def upload_template_to_s3
unless @config.template_s3_bucket
raise 'The S3 bucket to store the template in is not configured.'
end

s3_object_key = "#{@name}-#{Time.now.getutc.to_i}-#{File.basename(template.filename)}"
template_url = "http://#{@config.template_s3_bucket}.s3.amazonaws.com/#{s3_object_key}"

@ilog.start "Uploading template to #{template_url}" do |s|
s3_client.put_object(
bucket: @config.template_s3_bucket,
key: s3_object_key,
body: template.body
)
s.success "Template has been uploaded successfully to #{template_url}"
end

template_url
end

def create_stack
cf_client.create_stack(
parameters = {
stack_name: @name,
template_body: template.body,
capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM),
parameters: @config.parameters.values.map(&:to_cf),
tags: make_tags
)
}
if @config.template_s3_bucket
parameters[:template_url] = upload_template_to_s3
else
parameters[:template_body] = template.body
end
cf_client.create_stack(parameters)
rescue Aws::CloudFormation::Errors::AccessDenied
raise 'You are not authorized to perform create_stack calls.'
end
Expand All @@ -195,14 +220,20 @@ def new_change_set
Time.now.utc.to_i.to_s
].join('-')

cf_client.create_change_set(
parameters = {
change_set_name: change_set_name,
description: "Moonshot update command for application '#{Moonshot.config.app_name}'",
stack_name: @name,
template_body: template.body,
capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM),
parameters: @config.parameters.values.map(&:to_cf)
)
}
if @config.template_s3_bucket
parameters[:template_url] = upload_template_to_s3
else
parameters[:template_body] = template.body
end

cf_client.create_change_set(parameters)

change_set_name
end
Expand Down Expand Up @@ -289,7 +320,12 @@ def doctor_check_template_exists
end

def doctor_check_template_against_aws
cf_client.validate_template(template_body: template.body)
if @config.template_s3_bucket
parameters[:template_url] = upload_template_to_s3
else
parameters[:template_body] = template.body
end
cf_client.validate_template(parameters)
success('CloudFormation template is valid.')
rescue => e
critical('Invalid CloudFormation template!', e.message)
Expand Down
36 changes: 34 additions & 2 deletions spec/moonshot/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
let(:ilog) { Moonshot::InteractiveLoggerProxy.new(log) }
let(:parent_stacks) { [] }
let(:cf_client) { instance_double(Aws::CloudFormation::Client) }
let(:s3_client) { instance_double(Aws::S3::Client) }

let(:config) { Moonshot::ControllerConfig.new }
before(:each) do
Expand All @@ -14,6 +15,7 @@
config.parent_stacks = parent_stacks

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

subject { described_class.new(config) }
Expand All @@ -23,9 +25,9 @@
let(:stack_exists) { false }

before(:each) do
expect(ilog).to receive(:start).and_yield(step)
expect(ilog).to receive(:start).at_least(:once).and_yield(step)
expect(subject).to receive(:stack_exists?).and_return(stack_exists)
expect(step).to receive(:success)
expect(step).to receive(:success).at_least(:once)
end

context 'when the stack creation takes too long' do
Expand Down Expand Up @@ -88,10 +90,40 @@

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

context 'when template_s3_bucket is set' do
before(:each) do
config.template_s3_bucket = 'rspec-bucket'
allow(Time).to receive(:now).and_return(Time.new('2017-11-07 12:00:00 +0000'))
end

let(:expected_put_object_options) do
{
bucket: config.template_s3_bucket,
key: 'rspec-app-staging-1483228800-template.yml',
body: an_instance_of(String)
}
end

let(:expected_create_stack_options) do
{
template_url: 'http://rspec-bucket.s3.amazonaws.com/rspec-app-staging-1483228800-template.yml'
}
end

it 'should call put_object and create_stack with template_url parameter' do
expect(s3_client).to receive(:put_object)
.with(hash_including(expected_put_object_options))
expect(cf_client).to receive(:create_stack)
.with(hash_including(expected_create_stack_options))
subject.create
end
end
end
end

Expand Down

0 comments on commit 616fbb9

Please sign in to comment.