Skip to content

Commit

Permalink
Support templates at moonshot/template.{yml,json} (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
askreet committed Nov 25, 2016
1 parent ed3e624 commit 152d81c
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 135 deletions.
2 changes: 1 addition & 1 deletion docs/user-guide/include_in_your_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ end
```

This example assumes:
- You have a CloudFormation JSON template in folder called "cloud_formation/my-service.json".
- You have a CloudFormation JSON template in folder called "moonshot/template.json".
- You have an S3 bucket called "my-service-builds".
- You have a script in "script/build.sh" that will build a tarball output.tar.gz.
- You have a working CodeDeploy setup, including the CodeDeployRole.
Expand Down
5 changes: 5 additions & 0 deletions lib/default/moonshot/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Place your CloudFormation template here!
---
Resources:
Parameters:
Outputs:
74 changes: 26 additions & 48 deletions lib/moonshot/commands/new.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,82 +16,60 @@ def run!(application_name)

create_project_dir
copy_defaults
create_file(parameter_path)
create_file(template_path)
fill_moonfile
print_success_message
end

private

def cwd
Dir.pwd
end

def create_project_dir
raise "Directory '#{@application_name}' already exists!" \
if Dir.exist?(project_path)
Dir.mkdir(project_path)
end

def project_path
@project_path ||= File.join(cwd, @application_name)
@project_path ||= File.join(Dir.pwd, @application_name)
end

def copy_defaults
target_path = File.join(DEFAULT_DIRECTORY.dup, '.')
FileUtils.cp_r(target_path, project_path)
end

def create_file(path)
FileUtils.touch(path)
end

def moonfile_path
File.join(project_path, 'Moonfile.rb')
end

def parameter_path
File.join(cf_dir, 'parameters', "#{@application_name}.yml")
end

def template_path
File.join(cf_dir, "#{@application_name}.json")
end

def cf_dir
File.join(project_path, 'cloud_formation')
end

def fill_moonfile
File.open(moonfile_path, 'w') { |f| f.write generate_moonfile }
File.open(File.join(project_path, 'Moonfile.rb'), 'w') { |f| f.write generate_moonfile }
end

def generate_moonfile
<<-EOF
Moonshot.config do |m|
m.app_name = '#{@application_name}'
m.artifact_repository = S3Bucket.new('<your_bucket>')
m.build_mechanism = Script.new('bin/build.sh')
m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
end
EOF
Moonshot.config do |m|
m.app_name = '#{@application_name}'
m.artifact_repository = S3Bucket.new('<your_bucket>')
m.build_mechanism = Script.new('bin/build.sh')
m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
end
EOF
end

def print_success_message
warn 'Your application is configured, the following changes have '\
'been made to your project directory:'
warn ''
warn '- Created a Moonfile.rb where you can configure your project.'
warn '- Created moonshot/plugins where you can add hooks to core '\
'Moonshot actions.'
warn '- Created moonshot/cli_extensions where you can create '\
'project-specific commands.'
warn ''
warn 'You will also need to ensure your Amazon account is configured'\
' for CodeDeploy, by creating a role that allows deployments. '\
'See: http://moonshot.readthedocs.io/en/latest/mechanisms/'\
'deployment/'
warn <<-EOF
Your application is configured, the following changes have been made
to your project directory:
* Created Moonfile.rb, where you can configure your project.
* Created moonshot/plugins, where you can place custom Ruby code
to add hooks to core Moonshot actions (create, update, delete, etc.)
* Created moonshot/cli_extensions, where you can place custom Ruby
code to add your own project-specific commands to Moonshot.
* Created moonshot/template.yml, where you can build your
CloudFormation template.
You will also need to ensure your Amazon account is configured for
CodeDeploy by creating a role that allows deployments.
See: http://moonshot.readthedocs.io/en/latest/mechanisms/deployment/
EOF
end
end
end
Expand Down
66 changes: 16 additions & 50 deletions lib/moonshot/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,16 +131,6 @@ def resources_of_type(type)
end
end

# Build a hash of overrides that would be applied to this stack by an
# update.
def overrides
if File.exist?(parameters_file)
YAML.load_file(parameters_file) || {}
else
{}
end
end

# Return a Hash of the default values defined in the stack template.
def default_values
h = {}
Expand All @@ -151,25 +141,12 @@ def default_values
end

def template
@template ||= load_template_file
load_template_file
end

# @return [String] the path to the template file.
def template_file
json = json_template_path
yaml = yaml_template_path

@template_file ||= Dir[json].first || Dir[yaml].first

raise 'CloudFormation template not found at'\
"#{json} or #{yaml}!" unless @template_file

@template_file
end

# @return [String] the path to the parameters file.
def parameters_file
File.join(@config.project_root, 'cloud_formation', 'parameters', "#{@name}.yml")
load_template_file.filename
end

private
Expand All @@ -178,32 +155,21 @@ def stack_name
"CloudFormation Stack #{@name.blue}"
end

def json_template_path
"#{raw_template_file_name}.json"
end

def yaml_template_path
"#{raw_template_file_name}.yml"
end

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

def load_template_file
json_template = JsonStackTemplate.new(json_template_path)
yaml_template = YamlStackTemplate.new(yaml_template_path)
case
when json_template.exist?
json_template
when yaml_template.exist?
yaml_template
else
raise "CloudFormation template not found at #{json_template_path} "\
"or #{yaml_template_path}!" unless @template_file
end
templates = [
YamlStackTemplate.new(File.join(@config.project_root, 'moonshot', 'template.yml')),
JsonStackTemplate.new(File.join(@config.project_root, 'moonshot', 'template.json')),

# Support the legacy file location from Moonshot 1.0.
YamlStackTemplate.new(
File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.yml")),
JsonStackTemplate.new(
File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.json"))
]

template = templates.find(&:exist?)
raise 'No template found in moonshot/template.{yml,json}!' unless template
template
end

def stack_parameters
Expand Down
2 changes: 2 additions & 0 deletions lib/moonshot/stack_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module Moonshot
# A StackTemplate loads the template from disk and stores information
# about it.
class StackTemplate
attr_reader :filename

def initialize(filename)
@filename = filename
end
Expand Down
12 changes: 12 additions & 0 deletions spec/fs_fixtures/moonshot/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Resources": {

},

"Parameters": {
"Parent1": {
"Type": "String",
"Description": "This is imported from the parent stack on create."
}
}
}
6 changes: 6 additions & 0 deletions spec/fs_fixtures/moonshot/template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
- Resources:
- Parameters:
- Parent1:
- Type: String
- Description: This is imported from the parent stack on create.
52 changes: 17 additions & 35 deletions spec/moonshot/stack_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,46 +97,28 @@

describe '#template_file' do
it 'should return the template file path' do
path = File.join(Dir.pwd, 'cloud_formation', 'rspec-app.json')
path = File.join(Dir.pwd, 'moonshot', 'template.yml')
expect(subject.template_file).to eq(path)
end
end

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

describe '#template' do
let(:yaml_path) { File.join(Dir.pwd, 'cloud_formation', 'rspec-app.yml') }
let(:json_path) { File.join(Dir.pwd, 'cloud_formation', 'rspec-app.json') }

context 'when there is a template file in both formats' do
it 'should prefer the JSON formatted template file' do
expect(subject.template).to be_an_instance_of(Moonshot::JsonStackTemplate)
end
end

context 'when there is only one kind of template file available' do
it 'should pick the JSON template file by default' do
FakeFS::File.delete(yaml_path)
expect(subject.template).to be_an_instance_of(Moonshot::JsonStackTemplate)
end

it 'should fall back to YAML if no JSON template was found' do
FakeFS::File.delete(json_path)
expect(subject.template).to be_an_instance_of(Moonshot::YamlStackTemplate)
end
end

context 'when there is no template file available' do
it 'should raise RuntimeError' do
[yaml_path, json_path].each { |p| FakeFS::File.delete(p) }

expect { subject.template }.to raise_error(RuntimeError)
end
let(:yaml_path) { File.join(Dir.pwd, 'moonshot', 'template.yml') }
let(:json_path) { File.join(Dir.pwd, 'moonshot', 'template.json') }
let(:yaml_legacy_path) { File.join(Dir.pwd, 'cloud_formation', 'rspec-app.yml') }
let(:json_legacy_path) { File.join(Dir.pwd, 'cloud_formation', 'rspec-app.json') }

it 'should look for templates in the preferred order' do
expect(subject.template.filename).to eq(yaml_path)
FakeFS::File.delete(yaml_path)
expect(subject.template.filename).to eq(json_path)
FakeFS::File.delete(json_path)
expect(subject.template.filename).to eq(yaml_legacy_path)
FakeFS::File.delete(yaml_legacy_path)
expect(subject.template.filename).to eq(json_legacy_path)
FakeFS::File.delete(json_legacy_path)
expect { subject.template }
.to raise_error(RuntimeError, /No template found/)
end
end
end
1 change: 0 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ def info(msg)
end

before(:each) do
FileUtils.mkdir_p '/cloud_formation/parameters'
FakeFS::FileSystem.clone(File.join(File.dirname(__FILE__), 'fs_fixtures'), '/')
end
end
Expand Down

0 comments on commit 152d81c

Please sign in to comment.