diff --git a/lib/stack_master/cli.rb b/lib/stack_master/cli.rb index 6b29247a..a71b8f8d 100644 --- a/lib/stack_master/cli.rb +++ b/lib/stack_master/cli.rb @@ -185,7 +185,7 @@ def execute_stacks_command(command, args, options) 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) + stack_definitions = config.filter(region, stack_name, aliased_region) if stack_definitions.empty? StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}" end diff --git a/lib/stack_master/commands/list_stacks.rb b/lib/stack_master/commands/list_stacks.rb index 23220f3a..a21ba601 100644 --- a/lib/stack_master/commands/list_stacks.rb +++ b/lib/stack_master/commands/list_stacks.rb @@ -11,7 +11,7 @@ def initialize(config) def perform tp.set :max_width, self.window_size - tp @config.stacks, :region, :stack_name + tp @config.stacks, :region, :region_alias ,:raw_stack_name end end end diff --git a/lib/stack_master/config.rb b/lib/stack_master/config.rb index 697ab074..4ea3940d 100644 --- a/lib/stack_master/config.rb +++ b/lib/stack_master/config.rb @@ -16,6 +16,7 @@ def self.load!(config_file = 'stack_master.yml') :region_defaults, :region_aliases, :template_compilers, + :prepend_region_alias_to_stack_names, def self.search_up_and_chdir(config_file) return config_file unless File.dirname(config_file) == "." @@ -34,6 +35,7 @@ def initialize(config, base_dir) @config = config @base_dir = base_dir @stack_defaults = config.fetch('stack_defaults', {}) + @prepend_region_alias_to_stack_names = config.fetch('prepend_region_alias_to_stack_names', false) @region_aliases = Utils.underscore_keys_to_hyphen(config.fetch('region_aliases', {})) @region_to_aliases = @region_aliases.inject({}) do |hash, (key, value)| hash[value] ||= [] @@ -46,10 +48,11 @@ def initialize(config, base_dir) load_config end - def filter(region = nil, stack_name = nil) + def filter(region = nil, stack_name = nil, region_alias = nil) @stacks.select do |s| (region.blank? || s.region == region || s.region == region.gsub('_', '-')) && - (stack_name.blank? || s.stack_name == stack_name || s.stack_name == stack_name.gsub('_', '-')) + (stack_name.blank? || s.stack_name == stack_name || s.stack_name == stack_name.gsub('_', '-')) && + (region_alias.blank? || s.region_alias == region_alias || s.region_alias == region_alias.gsub('_', '-')) end end @@ -94,26 +97,34 @@ def load_config def resolve_region_aliases(stacks) stacks.inject({}) do |hash, (region, attributes)| - hash[unalias_region(region)] = attributes + hash.deeper_merge(unalias_region(region) => {region => attributes}) hash end end def load_stacks(stacks) - stacks.each do |region, stacks_for_region| - 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, - 'additional_parameter_lookup_dirs' => @region_to_aliases[region]) - @stacks << StackDefinition.new(stack_attributes) + stacks.each do |region, region_aliases| + region_aliases.each do |region_alias, stacks_for_region| + region = Utils.underscore_to_hyphen(region) + stacks_for_region.each do |stack_name, attributes| + stack_attributes = build_stack_defaults(region).deeper_merge!(attributes).merge( + 'region' => region, + 'region_alias' => region_alias, + 'stack_name' => computed_stack_name(stack_name, region, region_alias), + 'raw_stack_name' => Utils.underscore_to_hyphen(stack_name), + 'base_dir' => @base_dir, + 'additional_parameter_lookup_dirs' => @region_to_aliases[region]) + @stacks << StackDefinition.new(stack_attributes) + end end end end + def computed_stack_name(stack_name, region, region_alias) + prefix = region_alias if prepend_region_alias_to_stack_names && region != region_alias + [prefix, Utils.underscore_to_hyphen(stack_name)].compact.join('-') + end + def build_stack_defaults(region) region_defaults = @region_defaults.fetch(region, {}).deep_dup @stack_defaults.deep_dup.deeper_merge(region_defaults) diff --git a/lib/stack_master/stack_definition.rb b/lib/stack_master/stack_definition.rb index 7f1e5307..3430a789 100644 --- a/lib/stack_master/stack_definition.rb +++ b/lib/stack_master/stack_definition.rb @@ -4,7 +4,9 @@ class StackDefinition values do attribute :region, String + attribute :region_alias, String attribute :stack_name, String + attribute :raw_stack_name, String attribute :template, String attribute :tags, Hash attribute :notification_arns, Array[String] @@ -19,7 +21,12 @@ def template_file_path end def parameter_files - [ default_parameter_file_path, region_parameter_file_path ] + additional_parameter_lookup_file_paths + [ + default_parameter_file_path, + alias_parameter_file_path, + region_parameter_file_path, + additional_parameter_lookup_file_paths + ].flatten.uniq end def stack_policy_file_path @@ -34,6 +41,10 @@ def additional_parameter_lookup_file_paths end end + def alias_parameter_file_path + File.join(base_dir, 'parameters', "#{region_alias}", "#{underscored_stack_name}.yml") + end + def region_parameter_file_path File.join(base_dir, 'parameters', "#{region}", "#{underscored_stack_name}.yml") end @@ -43,7 +54,7 @@ def default_parameter_file_path end def underscored_stack_name - stack_name.gsub('-', '_') + raw_stack_name.to_s.gsub('-', '_') end end end diff --git a/spec/fixtures/stack_master.yml b/spec/fixtures/stack_master.yml index 59307756..ab77ae03 100644 --- a/spec/fixtures/stack_master.yml +++ b/spec/fixtures/stack_master.yml @@ -1,3 +1,4 @@ +prepend_region_alias_to_stack_names: true region_aliases: production: us-east-1 staging: ap-southeast-2 @@ -29,7 +30,7 @@ stacks: - test_arn_2 myapp_web: template: myapp_web.rb - ap-southeast-2: + staging: myapp_vpc: template: myapp_vpc.rb notification_arns: @@ -37,4 +38,4 @@ stacks: myapp_web: template: myapp_web tags: - test_override: 2 \ No newline at end of file + test_override: 2 diff --git a/spec/stack_master/commands/apply_spec.rb b/spec/stack_master/commands/apply_spec.rb index ba049d6e..32d4a755 100644 --- a/spec/stack_master/commands/apply_spec.rb +++ b/spec/stack_master/commands/apply_spec.rb @@ -13,6 +13,7 @@ before do allow(StackMaster::Stack).to receive(:find).with(region, stack_name).and_return(stack) allow(StackMaster::Stack).to receive(:generate).with(stack_definition, config).and_return(proposed_stack) + allow(config).to receive(:stack_defaults).and_return({}) allow(Aws::CloudFormation::Client).to receive(:new).and_return(cf) allow(cf).to receive(:create_stack) allow(StackMaster::StackDiffer).to receive(:new).with(proposed_stack, stack).and_return double.as_null_object diff --git a/spec/stack_master/commands/init_spec.rb b/spec/stack_master/commands/init_spec.rb index 485948fd..35e81dd3 100644 --- a/spec/stack_master/commands/init_spec.rb +++ b/spec/stack_master/commands/init_spec.rb @@ -6,7 +6,7 @@ describe "#perform" do it "creates all the expected files" do - expect(IO).to receive(:write).with("stack_master.yml", "stacks:\n us-east-1:\n test-stack:\n template: test-stack.json\n tags:\n environment: production\n") + expect(IO).to receive(:write).with("stack_master.yml", "# prepend_region_alias_to_stack_names: true\n# region_aliases:\n# stack_defaults:\n# tags:\nstacks:\n us-east-1:\n test-stack:\n template: test-stack.json\n tags:\n environment: production\n") expect(IO).to receive(:write).with("parameters/test_stack.yml", "# Add parameters here:\n# param1: value1\n# param2: value2\n") expect(IO).to receive(:write).with("parameters/us-east-1/test_stack.yml", "# Add parameters here:\n# param1: value1\n# param2: value2\n") expect(IO).to receive(:write).with("templates/test-stack.json", "{\n \"AWSTemplateFormatVersion\" : \"2010-09-09\",\n \"Description\" : \"Cloudformation stack for test-stack\",\n\n \"Parameters\" : {\n \"InstanceType\" : {\n \"Description\" : \"EC2 instance type\",\n \"Type\" : \"String\"\n }\n },\n\n \"Mappings\" : {\n },\n\n \"Resources\" : {\n },\n\n \"Outputs\" : {\n }\n}\n") diff --git a/spec/stack_master/config_spec.rb b/spec/stack_master/config_spec.rb index a20af733..5c796a57 100644 --- a/spec/stack_master/config_spec.rb +++ b/spec/stack_master/config_spec.rb @@ -4,7 +4,9 @@ let(:myapp_vpc_definition) { StackMaster::StackDefinition.new( region: 'us-east-1', + region_alias: 'us-east-1', stack_name: 'myapp-vpc', + raw_stack_name: 'myapp-vpc', template: 'myapp_vpc.json', tags: { 'application' => 'my-awesome-blog', 'environment' => 'production' }, notification_arns: ['test_arn', 'test_arn_2'], @@ -104,9 +106,11 @@ end it 'deep merges stack attributes' do - expect(loaded_config.find_stack('ap-southeast-2', 'myapp-vpc')).to eq(StackMaster::StackDefinition.new( - stack_name: 'myapp-vpc', + expect(loaded_config.find_stack('ap-southeast-2', 'staging-myapp-vpc')).to eq(StackMaster::StackDefinition.new( + stack_name: 'staging-myapp-vpc', + raw_stack_name: 'myapp-vpc', region: 'ap-southeast-2', + region_alias: 'staging', tags: { 'application' => 'my-awesome-blog', 'environment' => 'staging', @@ -118,9 +122,12 @@ secret_file: 'staging.yml.gpg', additional_parameter_lookup_dirs: ['staging'] )) - expect(loaded_config.find_stack('ap-southeast-2', 'myapp-web')).to eq(StackMaster::StackDefinition.new( - stack_name: 'myapp-web', + + expect(loaded_config.find_stack('ap-southeast-2', 'staging-myapp-web')).to eq(StackMaster::StackDefinition.new( + stack_name: 'staging-myapp-web', + raw_stack_name: 'myapp-web', region: 'ap-southeast-2', + region_alias: 'staging', tags: { 'application' => 'my-awesome-blog', 'environment' => 'staging', diff --git a/spec/stack_master/stack_definition_spec.rb b/spec/stack_master/stack_definition_spec.rb index ed8ce185..46535398 100644 --- a/spec/stack_master/stack_definition_spec.rb +++ b/spec/stack_master/stack_definition_spec.rb @@ -2,22 +2,26 @@ subject(:stack_definition) do StackMaster::StackDefinition.new( region: region, + region_alias: region_alias, stack_name: stack_name, + raw_stack_name: raw_stack_name, template: template, tags: tags, base_dir: base_dir) end let(:region) { 'us-east-1' } - let(:stack_name) { 'stack_name' } + let(:region_alias) { 'us-east-1' } + let(:stack_name) { 'production_stack_name' } + let(:raw_stack_name) { 'stack_name' } let(:template) { 'template.json' } let(:tags) { {'environment' => 'production'} } let(:base_dir) { '/base_dir' } it 'has default and region specific parameter file locations' do expect(stack_definition.parameter_files).to eq([ - "/base_dir/parameters/#{stack_name}.yml", - "/base_dir/parameters/#{region}/#{stack_name}.yml" + "/base_dir/parameters/#{raw_stack_name}.yml", + "/base_dir/parameters/#{region}/#{raw_stack_name}.yml" ]) end @@ -28,9 +32,21 @@ it 'includes a parameter lookup dir for it' do expect(stack_definition.parameter_files).to eq([ - "/base_dir/parameters/#{stack_name}.yml", - "/base_dir/parameters/#{region}/#{stack_name}.yml", - "/base_dir/parameters/production/#{stack_name}.yml" + "/base_dir/parameters/#{raw_stack_name}.yml", + "/base_dir/parameters/#{region}/#{raw_stack_name}.yml", + "/base_dir/parameters/production/#{raw_stack_name}.yml" + ]) + end + end + + context 'if a region alias is specified' do + let(:region_alias) { 'staging' } + + it 'has default, alias, and region specific parameter file locations' do + expect(stack_definition.parameter_files).to eq([ + "/base_dir/parameters/#{raw_stack_name}.yml", + "/base_dir/parameters/#{region_alias}/#{raw_stack_name}.yml", + "/base_dir/parameters/#{region}/#{raw_stack_name}.yml", ]) end end diff --git a/stacktemplates/stack_master.yml.erb b/stacktemplates/stack_master.yml.erb index 29aa2988..844508df 100644 --- a/stacktemplates/stack_master.yml.erb +++ b/stacktemplates/stack_master.yml.erb @@ -1,3 +1,7 @@ +# prepend_region_alias_to_stack_names: true +# region_aliases: +# stack_defaults: +# tags: stacks: <%= region %>: <%= stack_name %>: