stack_master 2.3.0 → 2.14.1
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 +100 -23
- data/lib/stack_master/aws_driver/cloud_formation.rb +5 -2
- data/lib/stack_master/aws_driver/s3.rb +5 -5
- data/lib/stack_master/change_set.rb +5 -5
- data/lib/stack_master/cli.rb +49 -15
- data/lib/stack_master/cloudformation_interpolating_eruby.rb +57 -0
- data/lib/stack_master/cloudformation_template_eruby.rb +32 -0
- data/lib/stack_master/commands/apply.rb +2 -14
- data/lib/stack_master/commands/compile.rb +1 -1
- data/lib/stack_master/commands/drift.rb +118 -0
- data/lib/stack_master/commands/init.rb +1 -1
- data/lib/stack_master/commands/nag.rb +30 -0
- data/lib/stack_master/commands/resources.rb +1 -1
- 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 +9 -1
- data/lib/stack_master/diff.rb +45 -0
- data/lib/stack_master/identity.rb +37 -5
- data/lib/stack_master/parameter_loader.rb +4 -5
- data/lib/stack_master/parameter_resolvers/acm_certificate.rb +2 -2
- data/lib/stack_master/parameter_resolvers/ami_finder.rb +3 -3
- data/lib/stack_master/parameter_resolvers/latest_container.rb +1 -1
- data/lib/stack_master/parameter_resolvers/parameter_store.rb +3 -3
- data/lib/stack_master/parameter_resolvers/stack_output.rb +1 -1
- data/lib/stack_master/parameter_validator.rb +53 -0
- data/lib/stack_master/role_assumer.rb +3 -2
- data/lib/stack_master/security_group_finder.rb +1 -1
- data/lib/stack_master/sns_topic_finder.rb +1 -1
- data/lib/stack_master/sparkle_formation/compile_time/empty_validator.rb +1 -1
- data/lib/stack_master/sparkle_formation/template_file.rb +2 -50
- data/lib/stack_master/stack.rb +23 -10
- data/lib/stack_master/stack_definition.rb +27 -10
- data/lib/stack_master/stack_differ.rb +15 -39
- data/lib/stack_master/stack_events/presenter.rb +1 -1
- data/lib/stack_master/template_compilers/cfndsl.rb +3 -2
- data/lib/stack_master/template_compilers/sparkle_formation.rb +1 -1
- data/lib/stack_master/template_compilers/yaml_erb.rb +19 -0
- 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
- data/lib/stack_master.rb +23 -2
- metadata +73 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bacf542a4b317cf98eb9e39a70740e393ddea1eb9363e65340975e568d3191e1
|
|
4
|
+
data.tar.gz: 4582583ac6fa9681edf9f3a8bf4ed478e728b09e0aaebce244234ab50ff0376c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bce540f043c5010594155cfd41fab249b531e80febb96cc46cebce7b957e8de2f7aca20f8b0dc09cf13050338e0455d3438ec36341d49d838002ab9fa8b21905
|
|
7
|
+
data.tar.gz: 61dae834acf176052b3fe8c2bb53443fc44dc0b68dbf33f7b053365088624b664aa6ba504697e839a18a63c5192a961e80d1736ce7efbdc82351616faf9bb8d7
|
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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/envato/stack_master/blob/master/LICENSE.md)
|
|
4
4
|
[](https://badge.fury.io/rb/stack_master)
|
|
5
|
-
[](https://github.com/envato/stack_master/actions?query=workflow%3Atests+branch%3Amaster)
|
|
6
6
|
|
|
7
7
|
StackMaster is a CLI tool to manage [CloudFormation](https://aws.amazon.com/cloudformation/) stacks, with the following features:
|
|
8
8
|
|
|
@@ -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.
|
|
@@ -141,7 +143,8 @@ stacks:
|
|
|
141
143
|
## Templates
|
|
142
144
|
|
|
143
145
|
StackMaster supports CloudFormation templates in plain JSON or YAML. Any `.yml` or `.yaml` file will be processed as
|
|
144
|
-
YAML, while any `.json` file will be processed as JSON.
|
|
146
|
+
YAML, while any `.json` file will be processed as JSON. Additionally, YAML files can be pre-processed using ERB and
|
|
147
|
+
compile-time parameters.
|
|
145
148
|
|
|
146
149
|
### Ruby DSLs
|
|
147
150
|
By default, any template ending with `.rb` will be processed as a [SparkleFormation](https://github.com/sparkleformation/sparkle_formation)
|
|
@@ -155,7 +158,8 @@ template_compilers:
|
|
|
155
158
|
|
|
156
159
|
## Parameters
|
|
157
160
|
|
|
158
|
-
|
|
161
|
+
By default, parameters are loaded from multiple YAML files, merged from the
|
|
162
|
+
following lookup paths from bottom to top:
|
|
159
163
|
|
|
160
164
|
- parameters/[stack_name].yaml
|
|
161
165
|
- parameters/[stack_name].yml
|
|
@@ -166,18 +170,43 @@ Parameters are loaded from multiple YAML files, merged from the following lookup
|
|
|
166
170
|
|
|
167
171
|
A simple parameter file could look like this:
|
|
168
172
|
|
|
169
|
-
```
|
|
173
|
+
```yaml
|
|
170
174
|
key_name: myapp-us-east-1
|
|
171
175
|
```
|
|
172
176
|
|
|
173
|
-
|
|
177
|
+
Alternatively, a `parameter_files` array can be defined to explicitly list
|
|
178
|
+
parameter files that will be loaded. If `parameter_files` are defined, the
|
|
179
|
+
automatic search locations will not be used.
|
|
174
180
|
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
```yaml
|
|
182
|
+
parameters_dir: parameters # the default
|
|
183
|
+
stacks:
|
|
184
|
+
us-east-1:
|
|
185
|
+
my-app:
|
|
186
|
+
parameter_files:
|
|
187
|
+
- my-app.yml # parameters/my-app.yml
|
|
188
|
+
```
|
|
177
189
|
|
|
178
|
-
|
|
190
|
+
Parameters can also be defined inline with stack definitions:
|
|
179
191
|
|
|
192
|
+
```yaml
|
|
193
|
+
stacks:
|
|
194
|
+
us-east-1:
|
|
195
|
+
my-app:
|
|
196
|
+
parameters:
|
|
197
|
+
VpcId:
|
|
198
|
+
stack_output: my-vpc/VpcId
|
|
180
199
|
```
|
|
200
|
+
|
|
201
|
+
### Compile Time Parameters
|
|
202
|
+
|
|
203
|
+
Compile time parameters can be defined in a stack's parameters file, using the key `compile_time_parameters`. Keys in
|
|
204
|
+
parameter files are automatically converted to camel case.
|
|
205
|
+
|
|
206
|
+
As an example:
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
# parameters/some_stack.yml
|
|
181
210
|
vpc_cidr: 10.0.0.0/16
|
|
182
211
|
compile_time_parameters:
|
|
183
212
|
subnet_cidrs:
|
|
@@ -185,7 +214,37 @@ compile_time_parameters:
|
|
|
185
214
|
- 10.0.2.0/28
|
|
186
215
|
```
|
|
187
216
|
|
|
188
|
-
|
|
217
|
+
#### SparkleFormation
|
|
218
|
+
|
|
219
|
+
Compile time parameters can be used for [SparkleFormation](http://www.sparkleformation.io) templates. It conforms and
|
|
220
|
+
allows you to use the [Compile Time Parameters](http://www.sparkleformation.io/docs/sparkle_formation/compile-time-parameters.html) feature.
|
|
221
|
+
|
|
222
|
+
#### CloudFormation YAML ERB
|
|
223
|
+
|
|
224
|
+
Compile time parameters can be used to pre-process YAML CloudFormation templates. An example template:
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
# templates/some_stack_template.yml.erb
|
|
228
|
+
Parameters:
|
|
229
|
+
VpcCidr:
|
|
230
|
+
Type: String
|
|
231
|
+
Resources:
|
|
232
|
+
Vpc:
|
|
233
|
+
Type: AWS::EC2::VPC
|
|
234
|
+
Properties:
|
|
235
|
+
CidrBlock: !Ref VpcCidr
|
|
236
|
+
# Given the two subnet_cidrs parameters, this creates two resources:
|
|
237
|
+
# SubnetPrivate0 with a CidrBlock of 10.0.0.0/28, and
|
|
238
|
+
# SubnetPrivate1 with a CidrBlock of 10.0.2.0/28
|
|
239
|
+
<% params["SubnetCidrs"].each_with_index do |cidr, index| %>
|
|
240
|
+
SubnetPrivate<%= index %>:
|
|
241
|
+
Type: AWS::EC2::Subnet
|
|
242
|
+
Properties:
|
|
243
|
+
VpcId: !Ref Vpc
|
|
244
|
+
AvailabilityZone: ap-southeast-2
|
|
245
|
+
CidrBlock: <%= cidr %>
|
|
246
|
+
<% end %>
|
|
247
|
+
```
|
|
189
248
|
|
|
190
249
|
## Parameter Resolvers
|
|
191
250
|
|
|
@@ -286,8 +345,8 @@ db_password:
|
|
|
286
345
|
An alternative to the secrets store is accessing 1password secrets using the 1password cli (`op`).
|
|
287
346
|
You declare a 1password lookup with the following parameters in your parameters file:
|
|
288
347
|
|
|
289
|
-
```
|
|
290
|
-
parameters/database.yml
|
|
348
|
+
```yaml
|
|
349
|
+
# parameters/database.yml
|
|
291
350
|
database_password:
|
|
292
351
|
one_password:
|
|
293
352
|
title: production database
|
|
@@ -477,7 +536,7 @@ name of the original resolver.
|
|
|
477
536
|
|
|
478
537
|
When creating a new resolver, one can automatically create the array resolver by adding a `array_resolver` statement
|
|
479
538
|
in the class definition, with an optional class name if different from the default one.
|
|
480
|
-
```
|
|
539
|
+
```ruby
|
|
481
540
|
module StackMaster
|
|
482
541
|
module ParameterResolvers
|
|
483
542
|
class MyResolver < Resolver
|
|
@@ -488,7 +547,7 @@ module StackMaster
|
|
|
488
547
|
end
|
|
489
548
|
```
|
|
490
549
|
In that example, using the array resolver would look like:
|
|
491
|
-
```
|
|
550
|
+
```yaml
|
|
492
551
|
my_parameter:
|
|
493
552
|
my_custom_array_resolver:
|
|
494
553
|
- value1
|
|
@@ -498,13 +557,13 @@ my_parameter:
|
|
|
498
557
|
Array parameter values can include nested parameter resolvers.
|
|
499
558
|
|
|
500
559
|
For example, given the following parameter definition:
|
|
501
|
-
```
|
|
560
|
+
```yaml
|
|
502
561
|
my_parameter:
|
|
503
562
|
- stack_output: my-stack/output # value resolves to 'value1'
|
|
504
563
|
- value2
|
|
505
564
|
```
|
|
506
565
|
The parameter value will resolve to:
|
|
507
|
-
```
|
|
566
|
+
```yaml
|
|
508
567
|
my_parameter: 'value1,value2'
|
|
509
568
|
```
|
|
510
569
|
|
|
@@ -520,7 +579,7 @@ ROLE=<%= role %>
|
|
|
520
579
|
|
|
521
580
|
And used like this in SparkleFormation templates:
|
|
522
581
|
|
|
523
|
-
```
|
|
582
|
+
```ruby
|
|
524
583
|
# templates/app.rb
|
|
525
584
|
user_data user_data_file!('app.erb', role: :worker)
|
|
526
585
|
```
|
|
@@ -533,7 +592,7 @@ my_variable=<%= ref!(:foo) %>
|
|
|
533
592
|
my_other_variable=<%= account_id! %>
|
|
534
593
|
```
|
|
535
594
|
|
|
536
|
-
```
|
|
595
|
+
```ruby
|
|
537
596
|
# templates/ecs_task.rb
|
|
538
597
|
container_definitions array!(
|
|
539
598
|
-> {
|
|
@@ -565,7 +624,7 @@ project-root
|
|
|
565
624
|
|
|
566
625
|
Your env-1/stack_master.yml files can reference common templates by setting:
|
|
567
626
|
|
|
568
|
-
```
|
|
627
|
+
```yaml
|
|
569
628
|
template_dir: ../../sparkle/templates
|
|
570
629
|
stack_defaults:
|
|
571
630
|
compiler_options:
|
|
@@ -625,7 +684,7 @@ stacks:
|
|
|
625
684
|
|
|
626
685
|
## Allowed accounts
|
|
627
686
|
|
|
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.
|
|
687
|
+
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
688
|
|
|
630
689
|
This is an opt-in feature which is enabled by specifying at least one account to allow.
|
|
631
690
|
|
|
@@ -644,7 +703,7 @@ stacks:
|
|
|
644
703
|
template: myapp_db.rb
|
|
645
704
|
allowed_accounts: # only allow these accounts (overrides the stack defaults)
|
|
646
705
|
- '1234567890'
|
|
647
|
-
-
|
|
706
|
+
- my-account-alias
|
|
648
707
|
tags:
|
|
649
708
|
purpose: back-end
|
|
650
709
|
myapp-web:
|
|
@@ -659,7 +718,8 @@ stacks:
|
|
|
659
718
|
purpose: back-end
|
|
660
719
|
```
|
|
661
720
|
|
|
662
|
-
In the cases where you want to bypass the account check, there is StackMaster flag `--skip-account-check` that can be used.
|
|
721
|
+
In the cases where you want to bypass the account check, there is the StackMaster flag `--skip-account-check` that can be used.
|
|
722
|
+
|
|
663
723
|
|
|
664
724
|
## Commands
|
|
665
725
|
|
|
@@ -674,15 +734,20 @@ stack_master apply # Create or update all stacks
|
|
|
674
734
|
stack_master --changed apply # Create or update all stacks that have changed
|
|
675
735
|
stack_master --yes apply [region-or-alias] [stack-name] # Create or update a stack non-interactively (forcing yes)
|
|
676
736
|
stack_master diff [region-or-alias] [stack-name] # Display a stack template and parameter diff
|
|
737
|
+
stack_master drift [region-or-alias] [stack-name] # Detects and displays stack drift using the CloudFormation Drift API
|
|
677
738
|
stack_master delete [region-or-alias] [stack-name] # Delete a stack
|
|
678
739
|
stack_master events [region-or-alias] [stack-name] # Display events for a stack
|
|
679
740
|
stack_master outputs [region-or-alias] [stack-name] # Display outputs for a stack
|
|
680
741
|
stack_master resources [region-or-alias] [stack-name] # Display outputs for a stack
|
|
681
742
|
stack_master status # Displays the status of each stack
|
|
682
743
|
stack_master tidy # Find missing or extra templates or parameter files
|
|
744
|
+
stack_master compile # Print the compiled version of a given stack
|
|
745
|
+
stack_master validate # Validate a template
|
|
746
|
+
stack_master lint # Check the stack definition locally using cfn-lint
|
|
747
|
+
stack_master nag # Check the stack template with cfn_nag
|
|
683
748
|
```
|
|
684
749
|
|
|
685
|
-
## Applying updates
|
|
750
|
+
## Applying updates - `stack_master apply`
|
|
686
751
|
|
|
687
752
|
The apply command does the following:
|
|
688
753
|
|
|
@@ -699,6 +764,18 @@ Demo:
|
|
|
699
764
|
|
|
700
765
|

|
|
701
766
|
|
|
767
|
+
## Drift Detection - `stack_master drift`
|
|
768
|
+
|
|
769
|
+
`stack_master drift us-east-1 mystack` uses the CloudFormation APIs to trigger drift detection and display resources
|
|
770
|
+
that have changed outside of the CloudFormation stack. This can happen if a resource has been updated via the console or
|
|
771
|
+
CLI directly rather than via a stack update.
|
|
772
|
+
|
|
773
|
+
## Diff - `stack_master diff`
|
|
774
|
+
|
|
775
|
+
`stack_master diff us-east-1 mystack` displays whether the computed parameters or template differ to what was last
|
|
776
|
+
applied in CloudFormation. This can happen if the template or computed parameters have changed in code and the change
|
|
777
|
+
hasn't been applied to this stack.
|
|
778
|
+
|
|
702
779
|
## Maintainers
|
|
703
780
|
|
|
704
781
|
- [Steve Hodgkiss](https://github.com/stevehodgkiss)
|
|
@@ -28,12 +28,15 @@ module StackMaster
|
|
|
28
28
|
:update_stack,
|
|
29
29
|
:create_stack,
|
|
30
30
|
:validate_template,
|
|
31
|
-
:describe_stacks
|
|
31
|
+
:describe_stacks,
|
|
32
|
+
:detect_stack_drift,
|
|
33
|
+
:describe_stack_drift_detection_status,
|
|
34
|
+
:describe_stack_resource_drifts
|
|
32
35
|
|
|
33
36
|
private
|
|
34
37
|
|
|
35
38
|
def cf
|
|
36
|
-
@cf ||= Aws::CloudFormation::Client.new(region: region, retry_limit: 10)
|
|
39
|
+
@cf ||= Aws::CloudFormation::Client.new({ region: region, retry_limit: 10 })
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
end
|
|
@@ -17,10 +17,10 @@ module StackMaster
|
|
|
17
17
|
|
|
18
18
|
s3 = new_s3_client(region: region)
|
|
19
19
|
|
|
20
|
-
current_objects = s3.list_objects(
|
|
20
|
+
current_objects = s3.list_objects({
|
|
21
21
|
prefix: prefix,
|
|
22
22
|
bucket: bucket
|
|
23
|
-
).map(&:contents).flatten.inject({}){|h,obj|
|
|
23
|
+
}).map(&:contents).flatten.inject({}){|h,obj|
|
|
24
24
|
h.merge(obj.key => obj)
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -38,12 +38,12 @@ module StackMaster
|
|
|
38
38
|
s3_uri = "s3://#{bucket}/#{object_key}"
|
|
39
39
|
StackMaster.stdout.print "- #{File.basename(path)} => #{s3_uri} "
|
|
40
40
|
|
|
41
|
-
s3.put_object(
|
|
41
|
+
s3.put_object({
|
|
42
42
|
bucket: bucket,
|
|
43
43
|
key: object_key,
|
|
44
44
|
body: body,
|
|
45
45
|
metadata: { md5: compiled_template_md5 }
|
|
46
|
-
)
|
|
46
|
+
})
|
|
47
47
|
StackMaster.stdout.puts "done."
|
|
48
48
|
end
|
|
49
49
|
end
|
|
@@ -61,7 +61,7 @@ module StackMaster
|
|
|
61
61
|
private
|
|
62
62
|
|
|
63
63
|
def new_s3_client(region: nil)
|
|
64
|
-
Aws::S3::Client.new(region: region || @region)
|
|
64
|
+
Aws::S3::Client.new({ region: region || @region })
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
|
@@ -25,12 +25,12 @@ module StackMaster
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def self.delete(id)
|
|
28
|
-
cf.delete_change_set(change_set_name: id)
|
|
28
|
+
cf.delete_change_set({ change_set_name: id })
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def self.execute(id, stack_name)
|
|
32
|
-
cf.execute_change_set(change_set_name: id,
|
|
33
|
-
|
|
32
|
+
cf.execute_change_set({ change_set_name: id,
|
|
33
|
+
stack_name: stack_name })
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def self.cf
|
|
@@ -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
|
@@ -7,7 +7,7 @@ module StackMaster
|
|
|
7
7
|
|
|
8
8
|
def initialize(argv, stdin=STDIN, stdout=STDOUT, stderr=STDERR, kernel=Kernel)
|
|
9
9
|
@argv, @stdin, @stdout, @stderr, @kernel = argv, stdin, stdout, stderr, kernel
|
|
10
|
-
Commander::Runner.instance_variable_set('@
|
|
10
|
+
Commander::Runner.instance_variable_set('@instance', Commander::Runner.new(argv))
|
|
11
11
|
StackMaster.stdout = @stdout
|
|
12
12
|
StackMaster.stderr = @stderr
|
|
13
13
|
TablePrint::Config.io = StackMaster.stdout
|
|
@@ -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,18 +141,29 @@ 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
|
|
147
148
|
|
|
149
|
+
command :nag do |c|
|
|
150
|
+
c.syntax = 'stack_master nag [region_or_alias] [stack_name]'
|
|
151
|
+
c.summary = "Check this stack's template with cfn_nag"
|
|
152
|
+
c.description = "Runs SAST scan cfn_nag on the template"
|
|
153
|
+
c.example 'run cfn_nag on stack myapp-vpc with us-east-1 settings', 'stack_master nag us-east-1 myapp-vpc'
|
|
154
|
+
c.action do |args, options|
|
|
155
|
+
options.default config: default_config_file
|
|
156
|
+
execute_stacks_command(StackMaster::Commands::Nag, args, options)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
148
160
|
command :compile do |c|
|
|
149
161
|
c.syntax = 'stack_master compile [region_or_alias] [stack_name]'
|
|
150
162
|
c.summary = "Print the compiled version of a given stack"
|
|
151
163
|
c.description = "Processes the stack and prints out a compiled version - same we'd send to AWS"
|
|
152
164
|
c.example 'print compiled stack myapp-vpc with us-east-1 settings', 'stack_master compile us-east-1 myapp-vpc'
|
|
153
165
|
c.action do |args, options|
|
|
154
|
-
options.
|
|
166
|
+
options.default config: default_config_file
|
|
155
167
|
execute_stacks_command(StackMaster::Commands::Compile, args, options)
|
|
156
168
|
end
|
|
157
169
|
end
|
|
@@ -162,7 +174,7 @@ module StackMaster
|
|
|
162
174
|
c.description = 'Checks the status of all stacks defined in the stack_master.yml file. Warning this operation can be somewhat slow.'
|
|
163
175
|
c.example 'description', 'Check the status of all stack definitions'
|
|
164
176
|
c.action do |args, options|
|
|
165
|
-
options.
|
|
177
|
+
options.default config: default_config_file
|
|
166
178
|
say "Invalid arguments. stack_master status" and return unless args.size == 0
|
|
167
179
|
config = load_config(options.config)
|
|
168
180
|
StackMaster::Commands::Status.perform(config, nil, options)
|
|
@@ -175,7 +187,7 @@ module StackMaster
|
|
|
175
187
|
c.description = 'Cross references stack_master.yml with the template and parameter directories to identify extra or missing files.'
|
|
176
188
|
c.example 'description', 'Check for missing or extra files'
|
|
177
189
|
c.action do |args, options|
|
|
178
|
-
options.
|
|
190
|
+
options.default config: default_config_file
|
|
179
191
|
say "Invalid arguments. stack_master tidy" and return unless args.size == 0
|
|
180
192
|
config = load_config(options.config)
|
|
181
193
|
StackMaster::Commands::Tidy.perform(config, nil, options)
|
|
@@ -214,6 +226,18 @@ module StackMaster
|
|
|
214
226
|
end
|
|
215
227
|
end
|
|
216
228
|
|
|
229
|
+
command :drift do |c|
|
|
230
|
+
c.syntax = 'stack_master drift [region_or_alias] [stack_name]'
|
|
231
|
+
c.summary = 'Detects and displays stack drift using the CloudFormation Drift API'
|
|
232
|
+
c.description = 'Detects and displays stack drift'
|
|
233
|
+
c.option '--timeout SECONDS', Integer, "The number of seconds to wait for drift detection to complete"
|
|
234
|
+
c.example 'view stack drift for a stack named myapp-vpc in us-east-1', 'stack_master drift us-east-1 myapp-vpc'
|
|
235
|
+
c.action do |args, options|
|
|
236
|
+
options.default config: default_config_file, timeout: 120
|
|
237
|
+
execute_stacks_command(StackMaster::Commands::Drift, args, options)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
217
241
|
run!
|
|
218
242
|
end
|
|
219
243
|
|
|
@@ -241,6 +265,7 @@ module StackMaster
|
|
|
241
265
|
stack_definitions = config.filter(region, stack_name)
|
|
242
266
|
if stack_definitions.empty?
|
|
243
267
|
StackMaster.stdout.puts "Could not find stack definition #{stack_name} in region #{region}"
|
|
268
|
+
show_other_region_candidates(config, stack_name)
|
|
244
269
|
success = false
|
|
245
270
|
end
|
|
246
271
|
stack_definitions = stack_definitions.select do |stack_definition|
|
|
@@ -257,18 +282,27 @@ module StackMaster
|
|
|
257
282
|
@kernel.exit false unless success
|
|
258
283
|
end
|
|
259
284
|
|
|
285
|
+
def show_other_region_candidates(config, stack_name)
|
|
286
|
+
candidates = config.filter(region="", stack_name=stack_name)
|
|
287
|
+
return if candidates.empty?
|
|
288
|
+
|
|
289
|
+
StackMaster.stdout.puts "Stack name #{stack_name} exists in regions: #{candidates.map(&:region).join(', ')}"
|
|
290
|
+
end
|
|
291
|
+
|
|
260
292
|
def execute_if_allowed_account(allowed_accounts, &block)
|
|
261
293
|
raise ArgumentError, "Block required to execute this method" unless block_given?
|
|
262
294
|
if running_in_allowed_account?(allowed_accounts)
|
|
263
295
|
block.call
|
|
264
296
|
else
|
|
265
|
-
|
|
297
|
+
account_text = "'#{identity.account}'"
|
|
298
|
+
account_text << " (#{identity.account_aliases.join(', ')})" if identity.account_aliases.any?
|
|
299
|
+
StackMaster.stdout.puts "Account #{account_text} is not an allowed account. Allowed accounts are #{allowed_accounts}."
|
|
266
300
|
false
|
|
267
301
|
end
|
|
268
302
|
end
|
|
269
303
|
|
|
270
304
|
def running_in_allowed_account?(allowed_accounts)
|
|
271
|
-
StackMaster.skip_account_check? || identity.
|
|
305
|
+
StackMaster.skip_account_check? || identity.running_in_account?(allowed_accounts)
|
|
272
306
|
end
|
|
273
307
|
|
|
274
308
|
def identity
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erubis'
|
|
4
|
+
|
|
5
|
+
module StackMaster
|
|
6
|
+
# This class is a modified version of `Erubis::Eruby`. It allows using
|
|
7
|
+
# `<%= %>` ERB expressions to interpolate values into a source string. We use
|
|
8
|
+
# this capability to enrich user data scripts with data and parameters pulled
|
|
9
|
+
# from the AWS CloudFormation service. The evaluation produces an array of
|
|
10
|
+
# objects ready for use in a CloudFormation `Fn::Join` intrinsic function.
|
|
11
|
+
class CloudFormationInterpolatingEruby < Erubis::Eruby
|
|
12
|
+
include Erubis::ArrayEnhancer
|
|
13
|
+
|
|
14
|
+
# Load a template from a file at the specified path and evaluate it.
|
|
15
|
+
def self.evaluate_file(source_path, context = Erubis::Context.new)
|
|
16
|
+
template_contents = File.read(source_path)
|
|
17
|
+
eruby = new(template_contents)
|
|
18
|
+
eruby.filename = source_path
|
|
19
|
+
eruby.evaluate(context)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @return [Array] The result of evaluating the source: an array of strings
|
|
23
|
+
# from the source intermindled with Hash objects from the ERB
|
|
24
|
+
# expressions. To be included in a CloudFormation template, this
|
|
25
|
+
# value needs to be used in a CloudFormation `Fn::Join` intrinsic
|
|
26
|
+
# function.
|
|
27
|
+
# @see Erubis::Eruby#evaluate
|
|
28
|
+
# @example
|
|
29
|
+
# CloudFormationInterpolatingEruby.new("my_variable=<%= { 'Ref' => 'Param1' } %>;").evaluate
|
|
30
|
+
# #=> ['my_variable=', { 'Ref' => 'Param1' }, ';']
|
|
31
|
+
def evaluate(_context = Erubis::Context.new)
|
|
32
|
+
format_lines_for_cloudformation(super)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @see Erubis::Eruby#add_expr
|
|
36
|
+
def add_expr(src, code, indicator)
|
|
37
|
+
if indicator == '='
|
|
38
|
+
src << " #{@bufvar} << (" << code << ');'
|
|
39
|
+
else
|
|
40
|
+
super
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Split up long strings containing multiple lines. One string per line in the
|
|
47
|
+
# CloudFormation array makes the compiled template and diffs more readable.
|
|
48
|
+
def format_lines_for_cloudformation(source)
|
|
49
|
+
source.flat_map do |lines|
|
|
50
|
+
lines = lines.to_s if lines.is_a?(Symbol)
|
|
51
|
+
next(lines) unless lines.is_a?(String)
|
|
52
|
+
|
|
53
|
+
lines.scan(/[^\n]*\n?/).reject { |x| x == '' }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'erubis'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module StackMaster
|
|
7
|
+
# This class is a modified version of `Erubis::Eruby`. It provides extra
|
|
8
|
+
# helper methods to ease the dynamic creation of CloudFormation templates
|
|
9
|
+
# with ERB. These helper methods are available within `<%= %>` expressions.
|
|
10
|
+
class CloudFormationTemplateEruby < Erubis::Eruby
|
|
11
|
+
# Adds the contents of an EC2 userdata script to the CloudFormation
|
|
12
|
+
# template. Allows using the ERB `<%= %>` expressions within the user data
|
|
13
|
+
# script to interpolate CloudFormation values.
|
|
14
|
+
def user_data_file(filepath)
|
|
15
|
+
JSON.pretty_generate({ 'Fn::Base64' => { 'Fn::Join' => ['', user_data_file_as_lines(filepath)] } })
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Evaluate the ERB template at the specified filepath and return the result
|
|
19
|
+
# as an array of lines. Allows using ERB `<%= %>` expressions to interpolate
|
|
20
|
+
# CloudFormation objects into the result.
|
|
21
|
+
def user_data_file_as_lines(filepath)
|
|
22
|
+
StackMaster::CloudFormationInterpolatingEruby.evaluate_file(filepath, self)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Add the contents of another file into the CloudFormation template as a
|
|
26
|
+
# string. ERB `<%= %>` expressions within the referenced file are not
|
|
27
|
+
# evaluated.
|
|
28
|
+
def include_file(filepath)
|
|
29
|
+
JSON.pretty_generate(File.read(filepath))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|