stack_master 2.3.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +22 -0
- data/README.md +43 -16
- data/lib/stack_master.rb +4 -2
- data/lib/stack_master/change_set.rb +2 -2
- data/lib/stack_master/cli.rb +17 -14
- data/lib/stack_master/commands/apply.rb +2 -14
- data/lib/stack_master/commands/status.rb +1 -1
- data/lib/stack_master/commands/tidy.rb +1 -1
- data/lib/stack_master/commands/validate.rb +1 -1
- data/lib/stack_master/config.rb +3 -0
- data/lib/stack_master/identity.rb +25 -4
- data/lib/stack_master/parameter_loader.rb +3 -4
- data/lib/stack_master/parameter_validator.rb +53 -0
- data/lib/stack_master/role_assumer.rb +1 -0
- data/lib/stack_master/stack.rb +20 -7
- data/lib/stack_master/stack_definition.rb +27 -10
- data/lib/stack_master/stack_differ.rb +1 -1
- data/lib/stack_master/stack_events/presenter.rb +1 -1
- data/lib/stack_master/template_utils.rb +9 -3
- data/lib/stack_master/validator.rb +25 -8
- data/lib/stack_master/version.rb +1 -1
- metadata +22 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f94d2bab69225428425d5e3bc426ca572c781aa5d9e069420c198c86640f84c5
|
4
|
+
data.tar.gz: f4f164e4390d568e9a7511013a33796ad04862d18510ec5683c2024b9952781f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81122997265f0aa25d24f54ee3ccbb5a0a3689b362b37445bd0c12435dc0fcbb24e0d8ce277c949c1e3af5c01d3b5ff938227f6b6f051b97cc8da66f400519d6
|
7
|
+
data.tar.gz: b5f8114a90fc7cc402e07e34676b7142b3849c4e8eecdc43cb67651358e5e7e6228ab04b68ef5b769b98ff91e7efd2ec9667a5c197699bccbfb96d49848585ed
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Steve Hodgkiss
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -53,7 +53,7 @@ Stacks are defined inside a `stack_master.yml` YAML file. When running
|
|
53
53
|
directory, or that the file is passed in with `--config
|
54
54
|
/path/to/stack_master.yml`. Here's an example configuration file:
|
55
55
|
|
56
|
-
```
|
56
|
+
```yaml
|
57
57
|
region_aliases:
|
58
58
|
production: us-east-1
|
59
59
|
staging: ap-southeast-2
|
@@ -123,6 +123,7 @@ stack_defaults:
|
|
123
123
|
```
|
124
124
|
|
125
125
|
Additional files can be configured to be uploaded to S3 alongside the templates:
|
126
|
+
|
126
127
|
```yaml
|
127
128
|
stacks:
|
128
129
|
production:
|
@@ -131,6 +132,7 @@ stacks:
|
|
131
132
|
files:
|
132
133
|
- userdata.sh
|
133
134
|
```
|
135
|
+
|
134
136
|
## Directories
|
135
137
|
|
136
138
|
- `templates` - CloudFormation, SparkleFormation or CfnDsl templates.
|
@@ -155,7 +157,8 @@ template_compilers:
|
|
155
157
|
|
156
158
|
## Parameters
|
157
159
|
|
158
|
-
|
160
|
+
By default, parameters are loaded from multiple YAML files, merged from the
|
161
|
+
following lookup paths from bottom to top:
|
159
162
|
|
160
163
|
- parameters/[stack_name].yaml
|
161
164
|
- parameters/[stack_name].yml
|
@@ -166,10 +169,34 @@ Parameters are loaded from multiple YAML files, merged from the following lookup
|
|
166
169
|
|
167
170
|
A simple parameter file could look like this:
|
168
171
|
|
169
|
-
```
|
172
|
+
```yaml
|
170
173
|
key_name: myapp-us-east-1
|
171
174
|
```
|
172
175
|
|
176
|
+
Alternatively, a `parameter_files` array can be defined to explicitly list
|
177
|
+
parameter files that will be loaded. If `parameter_files` are defined, the
|
178
|
+
automatic search locations will not be used.
|
179
|
+
|
180
|
+
```yaml
|
181
|
+
parameters_dir: parameters # the default
|
182
|
+
stacks:
|
183
|
+
us-east-1:
|
184
|
+
my-app:
|
185
|
+
parameter_files:
|
186
|
+
- my-app.yml # parameters/my-app.yml
|
187
|
+
```
|
188
|
+
|
189
|
+
Parameters can also be defined inline with stack definitions:
|
190
|
+
|
191
|
+
```yaml
|
192
|
+
stacks:
|
193
|
+
us-east-1:
|
194
|
+
my-app:
|
195
|
+
parameters:
|
196
|
+
VpcId:
|
197
|
+
stack_output: my-vpc/VpcId
|
198
|
+
```
|
199
|
+
|
173
200
|
### Compile Time Parameters
|
174
201
|
|
175
202
|
Compile time parameters can be used for [SparkleFormation](http://www.sparkleformation.io) templates. It conforms and
|
@@ -177,7 +204,7 @@ allows you to use the [Compile Time Parameters](http://www.sparkleformation.io/d
|
|
177
204
|
|
178
205
|
A simple example looks like this
|
179
206
|
|
180
|
-
```
|
207
|
+
```yaml
|
181
208
|
vpc_cidr: 10.0.0.0/16
|
182
209
|
compile_time_parameters:
|
183
210
|
subnet_cidrs:
|
@@ -286,8 +313,8 @@ db_password:
|
|
286
313
|
An alternative to the secrets store is accessing 1password secrets using the 1password cli (`op`).
|
287
314
|
You declare a 1password lookup with the following parameters in your parameters file:
|
288
315
|
|
289
|
-
```
|
290
|
-
parameters/database.yml
|
316
|
+
```yaml
|
317
|
+
# parameters/database.yml
|
291
318
|
database_password:
|
292
319
|
one_password:
|
293
320
|
title: production database
|
@@ -477,7 +504,7 @@ name of the original resolver.
|
|
477
504
|
|
478
505
|
When creating a new resolver, one can automatically create the array resolver by adding a `array_resolver` statement
|
479
506
|
in the class definition, with an optional class name if different from the default one.
|
480
|
-
```
|
507
|
+
```ruby
|
481
508
|
module StackMaster
|
482
509
|
module ParameterResolvers
|
483
510
|
class MyResolver < Resolver
|
@@ -488,7 +515,7 @@ module StackMaster
|
|
488
515
|
end
|
489
516
|
```
|
490
517
|
In that example, using the array resolver would look like:
|
491
|
-
```
|
518
|
+
```yaml
|
492
519
|
my_parameter:
|
493
520
|
my_custom_array_resolver:
|
494
521
|
- value1
|
@@ -498,13 +525,13 @@ my_parameter:
|
|
498
525
|
Array parameter values can include nested parameter resolvers.
|
499
526
|
|
500
527
|
For example, given the following parameter definition:
|
501
|
-
```
|
528
|
+
```yaml
|
502
529
|
my_parameter:
|
503
530
|
- stack_output: my-stack/output # value resolves to 'value1'
|
504
531
|
- value2
|
505
532
|
```
|
506
533
|
The parameter value will resolve to:
|
507
|
-
```
|
534
|
+
```yaml
|
508
535
|
my_parameter: 'value1,value2'
|
509
536
|
```
|
510
537
|
|
@@ -520,7 +547,7 @@ ROLE=<%= role %>
|
|
520
547
|
|
521
548
|
And used like this in SparkleFormation templates:
|
522
549
|
|
523
|
-
```
|
550
|
+
```ruby
|
524
551
|
# templates/app.rb
|
525
552
|
user_data user_data_file!('app.erb', role: :worker)
|
526
553
|
```
|
@@ -533,7 +560,7 @@ my_variable=<%= ref!(:foo) %>
|
|
533
560
|
my_other_variable=<%= account_id! %>
|
534
561
|
```
|
535
562
|
|
536
|
-
```
|
563
|
+
```ruby
|
537
564
|
# templates/ecs_task.rb
|
538
565
|
container_definitions array!(
|
539
566
|
-> {
|
@@ -565,7 +592,7 @@ project-root
|
|
565
592
|
|
566
593
|
Your env-1/stack_master.yml files can reference common templates by setting:
|
567
594
|
|
568
|
-
```
|
595
|
+
```yaml
|
569
596
|
template_dir: ../../sparkle/templates
|
570
597
|
stack_defaults:
|
571
598
|
compiler_options:
|
@@ -625,7 +652,7 @@ stacks:
|
|
625
652
|
|
626
653
|
## Allowed accounts
|
627
654
|
|
628
|
-
The AWS account the command is executing in can be restricted to a specific list of allowed accounts. This is useful in reducing the possibility of applying non-production changes in a production account. Each stack definition can specify the `allowed_accounts` property with an array of AWS account IDs the stack is allowed to work with.
|
655
|
+
The AWS account the command is executing in can be restricted to a specific list of allowed accounts. This is useful in reducing the possibility of applying non-production changes in a production account. Each stack definition can specify the `allowed_accounts` property with an array of AWS account IDs or aliases the stack is allowed to work with.
|
629
656
|
|
630
657
|
This is an opt-in feature which is enabled by specifying at least one account to allow.
|
631
658
|
|
@@ -644,7 +671,7 @@ stacks:
|
|
644
671
|
template: myapp_db.rb
|
645
672
|
allowed_accounts: # only allow these accounts (overrides the stack defaults)
|
646
673
|
- '1234567890'
|
647
|
-
-
|
674
|
+
- my-account-alias
|
648
675
|
tags:
|
649
676
|
purpose: back-end
|
650
677
|
myapp-web:
|
@@ -659,7 +686,7 @@ stacks:
|
|
659
686
|
purpose: back-end
|
660
687
|
```
|
661
688
|
|
662
|
-
In the cases where you want to bypass the account check, there is StackMaster flag `--skip-account-check` that can be used.
|
689
|
+
In the cases where you want to bypass the account check, there is the StackMaster flag `--skip-account-check` that can be used.
|
663
690
|
|
664
691
|
## Commands
|
665
692
|
|
data/lib/stack_master.rb
CHANGED
@@ -7,7 +7,8 @@ require 'aws-sdk-ecr'
|
|
7
7
|
require 'aws-sdk-s3'
|
8
8
|
require 'aws-sdk-sns'
|
9
9
|
require 'aws-sdk-ssm'
|
10
|
-
require '
|
10
|
+
require 'aws-sdk-iam'
|
11
|
+
require 'rainbow'
|
11
12
|
require 'active_support/core_ext/hash/keys'
|
12
13
|
require 'active_support/core_ext/object/blank'
|
13
14
|
require 'active_support/core_ext/string/inflections'
|
@@ -45,6 +46,7 @@ module StackMaster
|
|
45
46
|
|
46
47
|
autoload :StackDiffer, 'stack_master/stack_differ'
|
47
48
|
autoload :Validator, 'stack_master/validator'
|
49
|
+
autoload :ParameterValidator, 'stack_master/parameter_validator'
|
48
50
|
|
49
51
|
require 'stack_master/template_compilers/sparkle_formation'
|
50
52
|
require 'stack_master/template_compilers/json'
|
@@ -128,7 +130,7 @@ module StackMaster
|
|
128
130
|
|
129
131
|
def debug(message)
|
130
132
|
return unless debug?
|
131
|
-
stderr.puts "[DEBUG] #{message}".
|
133
|
+
stderr.puts Rainbow("[DEBUG] #{message}").color(:green)
|
132
134
|
end
|
133
135
|
|
134
136
|
def quiet!
|
@@ -75,7 +75,7 @@ io.puts "========================================"
|
|
75
75
|
end
|
76
76
|
message = "#{action_name} #{resource_change.resource_type} #{resource_change.logical_resource_id}"
|
77
77
|
color = action_color(action_name)
|
78
|
-
io.puts message.
|
78
|
+
io.puts Rainbow(message).color(color)
|
79
79
|
resource_change.details.each do |detail|
|
80
80
|
display_resource_change_detail(io, action_name, color, detail)
|
81
81
|
end
|
@@ -92,7 +92,7 @@ io.puts "========================================"
|
|
92
92
|
triggered_by << "(#{detail.evaluation})"
|
93
93
|
end
|
94
94
|
detail_messages << "Triggered by: #{triggered_by}"
|
95
|
-
io.puts "- #{detail_messages.join('. ')}. ".
|
95
|
+
io.puts Rainbow("- #{detail_messages.join('. ')}. ").color(color)
|
96
96
|
end
|
97
97
|
|
98
98
|
def action_color(action_name)
|
data/lib/stack_master/cli.rb
CHANGED
@@ -46,7 +46,7 @@ module StackMaster
|
|
46
46
|
c.option '--on-failure ACTION', String, "Action to take on CREATE_FAILURE. Valid Values: [ DO_NOTHING | ROLLBACK | DELETE ]. Default: ROLLBACK\nNote: You cannot use this option with Serverless Application Model (SAM) templates."
|
47
47
|
c.option '--yes-param PARAM_NAME', String, "Auto-approve stack updates when only parameter PARAM_NAME changes"
|
48
48
|
c.action do |args, options|
|
49
|
-
options.
|
49
|
+
options.default config: default_config_file
|
50
50
|
execute_stacks_command(StackMaster::Commands::Apply, args, options)
|
51
51
|
end
|
52
52
|
end
|
@@ -56,7 +56,7 @@ module StackMaster
|
|
56
56
|
c.summary = 'Displays outputs for a stack'
|
57
57
|
c.description = "Displays outputs for a stack"
|
58
58
|
c.action do |args, options|
|
59
|
-
options.
|
59
|
+
options.default config: default_config_file
|
60
60
|
execute_stacks_command(StackMaster::Commands::Outputs, args, options)
|
61
61
|
end
|
62
62
|
end
|
@@ -67,7 +67,7 @@ module StackMaster
|
|
67
67
|
c.description = 'Initialises the expected directory structure and stack_master.yml file'
|
68
68
|
c.option('--overwrite', 'Overwrite existing files')
|
69
69
|
c.action do |args, options|
|
70
|
-
options.
|
70
|
+
options.default config: default_config_file
|
71
71
|
unless args.size == 2
|
72
72
|
say "Invalid arguments. stack_master init [region] [stack_name]"
|
73
73
|
else
|
@@ -82,7 +82,7 @@ module StackMaster
|
|
82
82
|
c.description = "Shows a diff of the proposed stack's template and parameters"
|
83
83
|
c.example 'diff a stack named myapp-vpc in us-east-1', 'stack_master diff us-east-1 myapp-vpc'
|
84
84
|
c.action do |args, options|
|
85
|
-
options.
|
85
|
+
options.default config: default_config_file
|
86
86
|
execute_stacks_command(StackMaster::Commands::Diff, args, options)
|
87
87
|
end
|
88
88
|
end
|
@@ -96,7 +96,7 @@ module StackMaster
|
|
96
96
|
c.option '--all', 'Show all events'
|
97
97
|
c.option '--tail', 'Tail events'
|
98
98
|
c.action do |args, options|
|
99
|
-
options.
|
99
|
+
options.default config: default_config_file
|
100
100
|
execute_stacks_command(StackMaster::Commands::Events, args, options)
|
101
101
|
end
|
102
102
|
end
|
@@ -106,7 +106,7 @@ module StackMaster
|
|
106
106
|
c.summary = "Shows stack resources"
|
107
107
|
c.description = "Shows stack resources"
|
108
108
|
c.action do |args, options|
|
109
|
-
options.
|
109
|
+
options.default config: default_config_file
|
110
110
|
execute_stacks_command(StackMaster::Commands::Resources, args, options)
|
111
111
|
end
|
112
112
|
end
|
@@ -116,7 +116,7 @@ module StackMaster
|
|
116
116
|
c.summary = 'List stack definitions'
|
117
117
|
c.description = 'List stack definitions'
|
118
118
|
c.action do |args, options|
|
119
|
-
options.
|
119
|
+
options.default config: default_config_file
|
120
120
|
say "Invalid arguments." if args.size > 0
|
121
121
|
config = load_config(options.config)
|
122
122
|
StackMaster::Commands::ListStacks.perform(config, nil, options)
|
@@ -128,8 +128,9 @@ module StackMaster
|
|
128
128
|
c.summary = 'Validate a template'
|
129
129
|
c.description = 'Validate a template'
|
130
130
|
c.example 'validate a stack named myapp-vpc in us-east-1', 'stack_master validate us-east-1 myapp-vpc'
|
131
|
+
c.option '--[no-]validate-template-parameters', 'Validate template parameters. Default: validate'
|
131
132
|
c.action do |args, options|
|
132
|
-
options.
|
133
|
+
options.default config: default_config_file, validate_template_parameters: true
|
133
134
|
execute_stacks_command(StackMaster::Commands::Validate, args, options)
|
134
135
|
end
|
135
136
|
end
|
@@ -140,7 +141,7 @@ module StackMaster
|
|
140
141
|
c.description = "Runs cfn-lint on the template which would be sent to AWS on apply"
|
141
142
|
c.example 'run cfn-lint on stack myapp-vpc with us-east-1 settings', 'stack_master lint us-east-1 myapp-vpc'
|
142
143
|
c.action do |args, options|
|
143
|
-
options.
|
144
|
+
options.default config: default_config_file
|
144
145
|
execute_stacks_command(StackMaster::Commands::Lint, args, options)
|
145
146
|
end
|
146
147
|
end
|
@@ -151,7 +152,7 @@ module StackMaster
|
|
151
152
|
c.description = "Processes the stack and prints out a compiled version - same we'd send to AWS"
|
152
153
|
c.example 'print compiled stack myapp-vpc with us-east-1 settings', 'stack_master compile us-east-1 myapp-vpc'
|
153
154
|
c.action do |args, options|
|
154
|
-
options.
|
155
|
+
options.default config: default_config_file
|
155
156
|
execute_stacks_command(StackMaster::Commands::Compile, args, options)
|
156
157
|
end
|
157
158
|
end
|
@@ -162,7 +163,7 @@ module StackMaster
|
|
162
163
|
c.description = 'Checks the status of all stacks defined in the stack_master.yml file. Warning this operation can be somewhat slow.'
|
163
164
|
c.example 'description', 'Check the status of all stack definitions'
|
164
165
|
c.action do |args, options|
|
165
|
-
options.
|
166
|
+
options.default config: default_config_file
|
166
167
|
say "Invalid arguments. stack_master status" and return unless args.size == 0
|
167
168
|
config = load_config(options.config)
|
168
169
|
StackMaster::Commands::Status.perform(config, nil, options)
|
@@ -175,7 +176,7 @@ module StackMaster
|
|
175
176
|
c.description = 'Cross references stack_master.yml with the template and parameter directories to identify extra or missing files.'
|
176
177
|
c.example 'description', 'Check for missing or extra files'
|
177
178
|
c.action do |args, options|
|
178
|
-
options.
|
179
|
+
options.default config: default_config_file
|
179
180
|
say "Invalid arguments. stack_master tidy" and return unless args.size == 0
|
180
181
|
config = load_config(options.config)
|
181
182
|
StackMaster::Commands::Tidy.perform(config, nil, options)
|
@@ -262,13 +263,15 @@ module StackMaster
|
|
262
263
|
if running_in_allowed_account?(allowed_accounts)
|
263
264
|
block.call
|
264
265
|
else
|
265
|
-
|
266
|
+
account_text = "'#{identity.account}'"
|
267
|
+
account_text << " (#{identity.account_aliases.join(', ')})" if identity.account_aliases.any?
|
268
|
+
StackMaster.stdout.puts "Account #{account_text} is not an allowed account. Allowed accounts are #{allowed_accounts}."
|
266
269
|
false
|
267
270
|
end
|
268
271
|
end
|
269
272
|
|
270
273
|
def running_in_allowed_account?(allowed_accounts)
|
271
|
-
StackMaster.skip_account_check? || identity.
|
274
|
+
StackMaster.skip_account_check? || identity.running_in_account?(allowed_accounts)
|
272
275
|
end
|
273
276
|
|
274
277
|
def identity
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
|
3
1
|
module StackMaster
|
4
2
|
module Commands
|
5
3
|
class Apply
|
@@ -205,18 +203,8 @@ module StackMaster
|
|
205
203
|
end
|
206
204
|
|
207
205
|
def ensure_valid_parameters!
|
208
|
-
|
209
|
-
|
210
|
-
Empty/blank parameters detected, ensure values exist for those parameters.
|
211
|
-
Parameters will be read from the following locations:
|
212
|
-
MESSAGE
|
213
|
-
base_dir = Pathname.new(@stack_definition.base_dir)
|
214
|
-
@stack_definition.parameter_file_globs.each do |glob|
|
215
|
-
parameter_file = Pathname.new(glob).relative_path_from(base_dir)
|
216
|
-
message << " - #{parameter_file}\n"
|
217
|
-
end
|
218
|
-
failed!(message)
|
219
|
-
end
|
206
|
+
pv = ParameterValidator.new(stack: @proposed_stack, stack_definition: @stack_definition)
|
207
|
+
failed!(pv.error_message) if pv.missing_parameters?
|
220
208
|
end
|
221
209
|
|
222
210
|
def ensure_valid_template_body_size!
|
@@ -44,7 +44,7 @@ module StackMaster
|
|
44
44
|
end
|
45
45
|
|
46
46
|
def running_in_allowed_account?(allowed_accounts)
|
47
|
-
StackMaster.skip_account_check? || identity.
|
47
|
+
StackMaster.skip_account_check? || identity.running_in_account?(allowed_accounts)
|
48
48
|
end
|
49
49
|
|
50
50
|
def identity
|
@@ -12,7 +12,7 @@ module StackMaster
|
|
12
12
|
parameter_files = Set.new(find_parameter_files())
|
13
13
|
|
14
14
|
status = @config.stacks.each do |stack_definition|
|
15
|
-
parameter_files.subtract(stack_definition.
|
15
|
+
parameter_files.subtract(stack_definition.parameter_files_from_globs)
|
16
16
|
template = File.absolute_path(stack_definition.template_file_path)
|
17
17
|
|
18
18
|
if template
|
data/lib/stack_master/config.rb
CHANGED
@@ -17,6 +17,7 @@ module StackMaster
|
|
17
17
|
attr_accessor :stacks,
|
18
18
|
:base_dir,
|
19
19
|
:template_dir,
|
20
|
+
:parameters_dir,
|
20
21
|
:stack_defaults,
|
21
22
|
:region_defaults,
|
22
23
|
:region_aliases,
|
@@ -39,6 +40,7 @@ module StackMaster
|
|
39
40
|
@config = config
|
40
41
|
@base_dir = base_dir
|
41
42
|
@template_dir = config.fetch('template_dir', nil)
|
43
|
+
@parameters_dir = config.fetch('parameters_dir', nil)
|
42
44
|
@stack_defaults = config.fetch('stack_defaults', {})
|
43
45
|
@region_aliases = Utils.underscore_keys_to_hyphen(config.fetch('region_aliases', {}))
|
44
46
|
@region_to_aliases = @region_aliases.inject({}) do |hash, (key, value)|
|
@@ -115,6 +117,7 @@ module StackMaster
|
|
115
117
|
'stack_name' => stack_name,
|
116
118
|
'base_dir' => @base_dir,
|
117
119
|
'template_dir' => @template_dir,
|
120
|
+
'parameters_dir' => @parameters_dir,
|
118
121
|
'additional_parameter_lookup_dirs' => @region_to_aliases[region])
|
119
122
|
stack_attributes['allowed_accounts'] = attributes['allowed_accounts'] if attributes['allowed_accounts']
|
120
123
|
@stacks << StackDefinition.new(stack_attributes)
|
@@ -1,16 +1,25 @@
|
|
1
1
|
module StackMaster
|
2
2
|
class Identity
|
3
|
-
|
4
|
-
|
3
|
+
MissingIamPermissionsError = Class.new(StandardError)
|
4
|
+
|
5
|
+
def running_in_account?(accounts)
|
6
|
+
accounts.nil? ||
|
7
|
+
accounts.empty? ||
|
8
|
+
contains_account_id?(accounts) ||
|
9
|
+
contains_account_alias?(accounts)
|
5
10
|
end
|
6
11
|
|
7
12
|
def account
|
8
13
|
@account ||= sts.get_caller_identity.account
|
9
14
|
end
|
10
15
|
|
11
|
-
|
16
|
+
def account_aliases
|
17
|
+
@aliases ||= iam.list_account_aliases.account_aliases
|
18
|
+
rescue Aws::IAM::Errors::AccessDenied
|
19
|
+
raise MissingIamPermissionsError, 'Failed to retrieve account aliases. Missing required IAM permission: iam:ListAccountAliases'
|
20
|
+
end
|
12
21
|
|
13
|
-
|
22
|
+
private
|
14
23
|
|
15
24
|
def region
|
16
25
|
@region ||= ENV['AWS_REGION'] || Aws.config[:region] || Aws.shared_config.region || 'us-east-1'
|
@@ -19,5 +28,17 @@ module StackMaster
|
|
19
28
|
def sts
|
20
29
|
@sts ||= Aws::STS::Client.new(region: region)
|
21
30
|
end
|
31
|
+
|
32
|
+
def iam
|
33
|
+
@iam ||= Aws::IAM::Client.new(region: region)
|
34
|
+
end
|
35
|
+
|
36
|
+
def contains_account_id?(ids)
|
37
|
+
ids.include?(account)
|
38
|
+
end
|
39
|
+
|
40
|
+
def contains_account_alias?(aliases)
|
41
|
+
account_aliases.any? { |account_alias| aliases.include?(account_alias) }
|
42
|
+
end
|
22
43
|
end
|
23
44
|
end
|
@@ -5,10 +5,10 @@ module StackMaster
|
|
5
5
|
|
6
6
|
COMPILE_TIME_PARAMETERS_KEY = 'compile_time_parameters'
|
7
7
|
|
8
|
-
def self.load(parameter_files)
|
8
|
+
def self.load(parameter_files: [], parameters: {})
|
9
9
|
StackMaster.debug 'Searching for parameter files...'
|
10
|
-
parameter_files.
|
11
|
-
|
10
|
+
all_parameters = parameter_files.map { |file_name| load_parameters(file_name) } + [parameters]
|
11
|
+
all_parameters.reduce({template_parameters: {}, compile_time_parameters: {}}) do |hash, parameters|
|
12
12
|
template_parameters = create_template_parameters(parameters)
|
13
13
|
compile_time_parameters = create_compile_time_parameters(parameters)
|
14
14
|
|
@@ -16,7 +16,6 @@ module StackMaster
|
|
16
16
|
merge_and_camelize(hash[:compile_time_parameters], compile_time_parameters)
|
17
17
|
hash
|
18
18
|
end
|
19
|
-
|
20
19
|
end
|
21
20
|
|
22
21
|
private
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module StackMaster
|
4
|
+
class ParameterValidator
|
5
|
+
def initialize(stack:, stack_definition:)
|
6
|
+
@stack = stack
|
7
|
+
@stack_definition = stack_definition
|
8
|
+
end
|
9
|
+
|
10
|
+
def error_message
|
11
|
+
return nil unless missing_parameters?
|
12
|
+
message = "Empty/blank parameters detected. Please provide values for these parameters:\n"
|
13
|
+
missing_parameters.each do |parameter_name|
|
14
|
+
message << " - #{parameter_name}\n"
|
15
|
+
end
|
16
|
+
if @stack_definition.parameter_files.empty?
|
17
|
+
message << message_for_parameter_globs
|
18
|
+
else
|
19
|
+
message << message_for_parameter_files
|
20
|
+
end
|
21
|
+
message
|
22
|
+
end
|
23
|
+
|
24
|
+
def missing_parameters?
|
25
|
+
missing_parameters.any?
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def message_for_parameter_files
|
31
|
+
"Parameters are configured to be read from the following files:\n".tap do |message|
|
32
|
+
@stack_definition.parameter_files.each do |parameter_file|
|
33
|
+
message << " - #{parameter_file}\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def message_for_parameter_globs
|
39
|
+
"Parameters will be read from files matching the following globs:\n".tap do |message|
|
40
|
+
base_dir = Pathname.new(@stack_definition.base_dir)
|
41
|
+
@stack_definition.parameter_file_globs.each do |glob|
|
42
|
+
parameter_file = Pathname.new(glob).relative_path_from(base_dir)
|
43
|
+
message << " - #{parameter_file}\n"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def missing_parameters
|
49
|
+
@missing_parameters ||=
|
50
|
+
@stack.parameters_with_defaults.select { |_key, value| value.nil? }.keys
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -45,6 +45,7 @@ module StackMaster
|
|
45
45
|
credentials_key = "#{account}:#{role}"
|
46
46
|
@credentials.fetch(credentials_key) do
|
47
47
|
@credentials[credentials_key] = Aws::AssumeRoleCredentials.new(
|
48
|
+
region: StackMaster.cloud_formation_driver.region,
|
48
49
|
role_arn: "arn:aws:iam::#{account}:role/#{role}",
|
49
50
|
role_session_name: "stack-master-role-assumer"
|
50
51
|
)
|
data/lib/stack_master/stack.rb
CHANGED
@@ -27,12 +27,6 @@ module StackMaster
|
|
27
27
|
template_default_parameters.merge(parameters)
|
28
28
|
end
|
29
29
|
|
30
|
-
def missing_parameters?
|
31
|
-
parameters_with_defaults.any? do |key, value|
|
32
|
-
value == nil
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
30
|
def self.find(region, stack_name)
|
37
31
|
cf = StackMaster.cloud_formation_driver
|
38
32
|
cf_stack = cf.describe_stacks(stack_name: stack_name).stacks.first
|
@@ -62,7 +56,7 @@ module StackMaster
|
|
62
56
|
end
|
63
57
|
|
64
58
|
def self.generate(stack_definition, config)
|
65
|
-
parameter_hash = ParameterLoader.load(stack_definition.
|
59
|
+
parameter_hash = ParameterLoader.load(parameter_files: stack_definition.all_parameter_files, parameters: stack_definition.parameters)
|
66
60
|
template_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:template_parameters])
|
67
61
|
compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
|
68
62
|
template_body = TemplateCompiler.compile(config, stack_definition.compiler, stack_definition.template_dir, stack_definition.template, compile_time_parameters, stack_definition.compiler_options)
|
@@ -81,6 +75,25 @@ module StackMaster
|
|
81
75
|
stack_policy_body: stack_policy_body)
|
82
76
|
end
|
83
77
|
|
78
|
+
def self.generate_without_parameters(stack_definition, config)
|
79
|
+
parameter_hash = ParameterLoader.load(parameter_files: stack_definition.all_parameter_files, parameters: stack_definition.parameters)
|
80
|
+
compile_time_parameters = ParameterResolver.resolve(config, stack_definition, parameter_hash[:compile_time_parameters])
|
81
|
+
template_body = TemplateCompiler.compile(config, stack_definition.compiler, stack_definition.template_dir, stack_definition.template, compile_time_parameters, stack_definition.compiler_options)
|
82
|
+
template_format = TemplateUtils.identify_template_format(template_body)
|
83
|
+
stack_policy_body = if stack_definition.stack_policy_file_path
|
84
|
+
File.read(stack_definition.stack_policy_file_path)
|
85
|
+
end
|
86
|
+
new(region: stack_definition.region,
|
87
|
+
stack_name: stack_definition.stack_name,
|
88
|
+
tags: stack_definition.tags,
|
89
|
+
parameters: {},
|
90
|
+
template_body: template_body,
|
91
|
+
template_format: template_format,
|
92
|
+
role_arn: stack_definition.role_arn,
|
93
|
+
notification_arns: stack_definition.notification_arns,
|
94
|
+
stack_policy_body: stack_policy_body)
|
95
|
+
end
|
96
|
+
|
84
97
|
def max_template_size(use_s3)
|
85
98
|
return TemplateUtils::MAX_S3_TEMPLATE_SIZE if use_s3
|
86
99
|
TemplateUtils::MAX_TEMPLATE_SIZE
|
@@ -16,7 +16,10 @@ module StackMaster
|
|
16
16
|
:additional_parameter_lookup_dirs,
|
17
17
|
:s3,
|
18
18
|
:files,
|
19
|
-
:compiler_options
|
19
|
+
:compiler_options,
|
20
|
+
:parameters_dir,
|
21
|
+
:parameters,
|
22
|
+
:parameter_files
|
20
23
|
|
21
24
|
attr_reader :compiler
|
22
25
|
|
@@ -32,8 +35,12 @@ module StackMaster
|
|
32
35
|
@compiler = nil
|
33
36
|
super
|
34
37
|
@additional_parameter_lookup_dirs ||= []
|
38
|
+
@base_dir ||= ""
|
35
39
|
@template_dir ||= File.join(@base_dir, 'templates')
|
40
|
+
@parameters_dir ||= File.join(@base_dir, 'parameters')
|
36
41
|
@allowed_accounts = Array(@allowed_accounts)
|
42
|
+
@parameters ||= {}
|
43
|
+
@parameter_files ||= []
|
37
44
|
end
|
38
45
|
|
39
46
|
def ==(other)
|
@@ -56,13 +63,9 @@ module StackMaster
|
|
56
63
|
@compiler_options == other.compiler_options
|
57
64
|
end
|
58
65
|
|
59
|
-
def compiler=(compiler)
|
60
|
-
@compiler = compiler.&to_sym
|
61
|
-
end
|
62
|
-
|
63
66
|
def template_file_path
|
64
67
|
return unless template
|
65
|
-
File.expand_path(
|
68
|
+
File.expand_path(template, template_dir)
|
66
69
|
end
|
67
70
|
|
68
71
|
def files_dir
|
@@ -85,7 +88,15 @@ module StackMaster
|
|
85
88
|
Utils.change_extension(template, 'json')
|
86
89
|
end
|
87
90
|
|
88
|
-
def
|
91
|
+
def all_parameter_files
|
92
|
+
if parameter_files.empty?
|
93
|
+
parameter_files_from_globs
|
94
|
+
else
|
95
|
+
parameter_files
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def parameter_files_from_globs
|
89
100
|
parameter_file_globs.map(&Dir.method(:glob)).flatten
|
90
101
|
end
|
91
102
|
|
@@ -101,20 +112,26 @@ module StackMaster
|
|
101
112
|
!s3.nil?
|
102
113
|
end
|
103
114
|
|
115
|
+
def parameter_files
|
116
|
+
Array(@parameter_files).map do |file|
|
117
|
+
File.expand_path(file, parameters_dir)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
104
121
|
private
|
105
122
|
|
106
123
|
def additional_parameter_lookup_globs
|
107
124
|
additional_parameter_lookup_dirs.map do |a|
|
108
|
-
File.join(
|
125
|
+
File.join(parameters_dir, a, "#{stack_name_glob}.y*ml")
|
109
126
|
end
|
110
127
|
end
|
111
128
|
|
112
129
|
def region_parameter_glob
|
113
|
-
File.join(
|
130
|
+
File.join(parameters_dir, "#{region}", "#{stack_name_glob}.y*ml")
|
114
131
|
end
|
115
132
|
|
116
133
|
def default_parameter_glob
|
117
|
-
File.join(
|
134
|
+
File.join(parameters_dir, "#{stack_name_glob}.y*ml")
|
118
135
|
end
|
119
136
|
|
120
137
|
def stack_name_glob
|
@@ -10,7 +10,7 @@ module StackMaster
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def print_event(event)
|
13
|
-
@io.puts "#{event.timestamp.localtime} #{event.logical_resource_id} #{event.resource_type} #{event.resource_status} #{event.resource_status_reason}".
|
13
|
+
@io.puts Rainbow("#{event.timestamp.localtime} #{event.logical_resource_id} #{event.resource_type} #{event.resource_status} #{event.resource_status_reason}").color(event_colour(event))
|
14
14
|
end
|
15
15
|
|
16
16
|
def event_colour(event)
|
@@ -2,12 +2,18 @@ module StackMaster
|
|
2
2
|
module TemplateUtils
|
3
3
|
MAX_TEMPLATE_SIZE = 51200
|
4
4
|
MAX_S3_TEMPLATE_SIZE = 460800
|
5
|
+
# Matches if the first non-whitespace character is a '{', handling cases
|
6
|
+
# with leading whitespace and extra (whitespace-only) lines.
|
7
|
+
JSON_IDENTIFICATION_PATTERN = Regexp.new('\A\s*{', Regexp::MULTILINE)
|
5
8
|
|
6
9
|
extend self
|
7
10
|
|
8
11
|
def identify_template_format(template_body)
|
9
|
-
|
10
|
-
|
12
|
+
if template_body =~ JSON_IDENTIFICATION_PATTERN
|
13
|
+
:json
|
14
|
+
else
|
15
|
+
:yaml
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
def template_hash(template_body=nil)
|
@@ -28,4 +34,4 @@ module StackMaster
|
|
28
34
|
JSON.dump(template_hash(template_body))
|
29
35
|
end
|
30
36
|
end
|
31
|
-
end
|
37
|
+
end
|
@@ -1,21 +1,22 @@
|
|
1
1
|
module StackMaster
|
2
2
|
class Validator
|
3
|
-
def self.valid?(stack_definition, config)
|
4
|
-
new(stack_definition, config).perform
|
3
|
+
def self.valid?(stack_definition, config, options)
|
4
|
+
new(stack_definition, config, options).perform
|
5
5
|
end
|
6
6
|
|
7
|
-
def initialize(stack_definition, config)
|
7
|
+
def initialize(stack_definition, config, options)
|
8
8
|
@stack_definition = stack_definition
|
9
9
|
@config = config
|
10
|
+
@options = options
|
10
11
|
end
|
11
12
|
|
12
13
|
def perform
|
13
|
-
parameter_hash = ParameterLoader.load(@stack_definition.parameter_files)
|
14
|
-
compile_time_parameters = ParameterResolver.resolve(@config, @stack_definition, parameter_hash[:compile_time_parameters])
|
15
|
-
|
16
14
|
StackMaster.stdout.print "#{@stack_definition.stack_name}: "
|
17
|
-
|
18
|
-
|
15
|
+
if validate_template_parameters? && parameter_validator.missing_parameters?
|
16
|
+
StackMaster.stdout.puts "invalid\n#{parameter_validator.error_message}"
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
cf.validate_template(template_body: TemplateUtils.maybe_compressed_template_body(stack.template_body))
|
19
20
|
StackMaster.stdout.puts "valid"
|
20
21
|
true
|
21
22
|
rescue Aws::CloudFormation::Errors::ValidationError => e
|
@@ -25,8 +26,24 @@ module StackMaster
|
|
25
26
|
|
26
27
|
private
|
27
28
|
|
29
|
+
def validate_template_parameters?
|
30
|
+
@options.validate_template_parameters
|
31
|
+
end
|
32
|
+
|
28
33
|
def cf
|
29
34
|
@cf ||= StackMaster.cloud_formation_driver
|
30
35
|
end
|
36
|
+
|
37
|
+
def stack
|
38
|
+
@stack ||= if validate_template_parameters?
|
39
|
+
Stack.generate(@stack_definition, @config)
|
40
|
+
else
|
41
|
+
Stack.generate_without_parameters(@stack_definition, @config)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parameter_validator
|
46
|
+
@parameter_validator ||= ParameterValidator.new(stack: stack, stack_definition: @stack_definition)
|
47
|
+
end
|
31
48
|
end
|
32
49
|
end
|
data/lib/stack_master/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stack_master
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steve Hodgkiss
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-06-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -255,6 +255,20 @@ dependencies:
|
|
255
255
|
- - "~>"
|
256
256
|
- !ruby/object:Gem::Version
|
257
257
|
version: '1'
|
258
|
+
- !ruby/object:Gem::Dependency
|
259
|
+
name: aws-sdk-iam
|
260
|
+
requirement: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - "~>"
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '1'
|
265
|
+
type: :runtime
|
266
|
+
prerelease: false
|
267
|
+
version_requirements: !ruby/object:Gem::Requirement
|
268
|
+
requirements:
|
269
|
+
- - "~>"
|
270
|
+
- !ruby/object:Gem::Version
|
271
|
+
version: '1'
|
258
272
|
- !ruby/object:Gem::Dependency
|
259
273
|
name: diffy
|
260
274
|
requirement: !ruby/object:Gem::Requirement
|
@@ -284,7 +298,7 @@ dependencies:
|
|
284
298
|
- !ruby/object:Gem::Version
|
285
299
|
version: '0'
|
286
300
|
- !ruby/object:Gem::Dependency
|
287
|
-
name:
|
301
|
+
name: rainbow
|
288
302
|
requirement: !ruby/object:Gem::Requirement
|
289
303
|
requirements:
|
290
304
|
- - ">="
|
@@ -432,6 +446,7 @@ executables:
|
|
432
446
|
extensions: []
|
433
447
|
extra_rdoc_files: []
|
434
448
|
files:
|
449
|
+
- LICENSE.txt
|
435
450
|
- README.md
|
436
451
|
- bin/stack_master
|
437
452
|
- lib/stack_master.rb
|
@@ -472,6 +487,7 @@ files:
|
|
472
487
|
- lib/stack_master/parameter_resolvers/security_group.rb
|
473
488
|
- lib/stack_master/parameter_resolvers/sns_topic_name.rb
|
474
489
|
- lib/stack_master/parameter_resolvers/stack_output.rb
|
490
|
+
- lib/stack_master/parameter_validator.rb
|
475
491
|
- lib/stack_master/prompter.rb
|
476
492
|
- lib/stack_master/resolver_array.rb
|
477
493
|
- lib/stack_master/role_assumer.rb
|
@@ -523,8 +539,8 @@ licenses:
|
|
523
539
|
metadata:
|
524
540
|
bug_tracker_uri: https://github.com/envato/stack_master/issues
|
525
541
|
changelog_uri: https://github.com/envato/stack_master/blob/master/CHANGELOG.md
|
526
|
-
documentation_uri: https://www.rubydoc.info/gems/stack_master/2.
|
527
|
-
source_code_uri: https://github.com/envato/stack_master/tree/v2.
|
542
|
+
documentation_uri: https://www.rubydoc.info/gems/stack_master/2.7.0
|
543
|
+
source_code_uri: https://github.com/envato/stack_master/tree/v2.7.0
|
528
544
|
post_install_message:
|
529
545
|
rdoc_options: []
|
530
546
|
require_paths:
|
@@ -540,7 +556,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
540
556
|
- !ruby/object:Gem::Version
|
541
557
|
version: '0'
|
542
558
|
requirements: []
|
543
|
-
rubygems_version: 3.
|
559
|
+
rubygems_version: 3.0.3
|
544
560
|
signing_key:
|
545
561
|
specification_version: 4
|
546
562
|
summary: StackMaster is a sure-footed way of creating, updating and keeping track
|