Skip to content

Commit 1932b57

Browse files
committed
feat: v2.3.0 - comprehensive review, backports, and hardening
BREAKING: shebang changed to #!/usr/bin/env bash, set -uo pipefail enforced, bootstrap tag keys lowercased to tfscaffold:<key> Features: - error_and_die accepts optional custom exit code - TF_VAR_aws_account_id and TF_VAR_environment auto-exported - S3 backend encrypt=true and AWS_PROFILE injection - Remote multi-file tfvars (*.tfvars, *.tfvars.json) from S3 - New output action with JSON file support - IAM role sts:ExternalId condition support - SNS content_based_deduplication support - Lambda reserved_concurrent_executions support Bug fixes: - ~20 unquoted variable expansions fixed incl trap rm paths - set -u safety: declares initialised, array guard, region default - Cognito access_token_validity default key typo (validity->value) - KMS alias default string "null" to HCL null - VPC force_destroy string "true" to bool true - Missing semicolons throughout terraform.sh Chores: - Removed dead code: bootstrap default_assumerole, lambda xray dupe - Removed committed state files from bootstrap - Renamed module.s3bucket_other.tf to match module name - Cognito SNS policy TODO replaced with explanation - Regenerated all terraform-docs READMEs - Updated CHANGELOG and README for v2.3.0 - Added vim modelines to all scripts
1 parent 7622c46 commit 1932b57

19 files changed

Lines changed: 265 additions & 113 deletions

CHANGELOG.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,58 @@
1+
## 2.3.0 (24/04/2026)
2+
3+
BREAKING CHANGES:
4+
5+
* Shebang changed from `#!/bin/bash` to `#!/usr/bin/env bash` for portability.
6+
* `set -uo pipefail` now enforced across all scripts. Unbound variable access
7+
will now cause failures rather than silent empty expansion.
8+
* Bootstrap tag keys changed from capitalised (`tfscaffold:Environment`) to
9+
lowercase (`tfscaffold:environment`) for consistency with module conventions.
10+
11+
FEATURES:
12+
13+
* `error_and_die` now accepts an optional second argument for custom exit codes.
14+
* `TF_VAR_aws_account_id` and `TF_VAR_environment` are now exported automatically,
15+
making them available to Terraform without explicit variable passthrough.
16+
* S3 backend configuration now always includes `encrypt = true`.
17+
* S3 backend configuration now injects `profile` when `AWS_PROFILE` is set,
18+
enabling named profile support for state access.
19+
* Remote dynamic tfvars now supports multiple files: all `*.tfvars` and
20+
`*.tfvars.json` files under the environment's S3 prefix are downloaded,
21+
replacing the previous single `dynamic.tfvars` pattern.
22+
* New `output` action for standalone terraform output retrieval, with optional
23+
JSON file output support.
24+
* IAM role module now supports `sts:ExternalId` conditions via optional
25+
`external_id` field on trusted principals.
26+
* SNS module now supports `content_based_deduplication` for FIFO topics.
27+
* Lambda module now supports `reserved_concurrent_executions`.
28+
29+
BUG FIXES:
30+
31+
* Fixed unquoted variable expansions throughout `terraform.sh` (~20 instances),
32+
including two HIGH-severity `rm` commands inside `trap` statements.
33+
* Fixed `set -u` safety: all `declare` statements now initialised, empty array
34+
access guarded, and `${AWS_DEFAULT_REGION:-}` defaulted.
35+
* Fixed cognito module `access_token_validity` default key typo:
36+
`validity` corrected to `value`.
37+
* Fixed KMS module `alias` variable default from string `"null"` to HCL `null`.
38+
* Fixed VPC module `force_destroy` from string `"true"` to boolean `true`.
39+
* Fixed missing semicolons throughout `terraform.sh` for style consistency.
40+
* Fixed `lockfile` variable quoting in conditional test.
41+
42+
CHORES:
43+
44+
* Removed dead code: `data.aws_iam_policy_document.default_assumerole` from
45+
bootstrap (never referenced).
46+
* Removed duplicate `data.aws_iam_policy_document.xray` from lambda module
47+
(functionality already in `lambda_core.tf`).
48+
* Removed committed state files from bootstrap directory.
49+
* Renamed `module.s3bucket_other.tf` to `module.s3bucket_bestpractice.tf` to
50+
match the module name it declares.
51+
* Replaced TODO comment on cognito SNS policy with explanation of why
52+
`sns:Publish` on `*` is required for Cognito MFA/SMS.
53+
* Regenerated all terraform-docs README files.
54+
* Added vim modelines to all shell scripts.
55+
156
## 2.0.1 (12/02/2025)
257

358
* Updated included modules to follow new standard and include other generics

README.md

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,23 +54,32 @@ Credentials can be provided in any of the standard mechanisms provided by the Bo
5454

5555
If you want to make use of instance profiles, MFA tokens, AWS STS, Cross Account Roles or other fantastic IAM trickery, the recommended practice is to use a static access key or instance profile to call AWS STS using the AWS CLI tools, and then assign the temporary credentials that are generated to the AWS credential environment variables so that terraform can make use of them. This is done externally to Scaffold and would normally be integrated into a Jenkins job as a step to perform to prepare the environment before calling Scaffold.
5656

57-
### pre_apply.sh & post_apply.sh
57+
### pre.sh & post.sh
5858

59-
Although as yet somewhat unrefined, Scaffold provides the capacity to incorporate additional scripted actions to take prior to and after running terraform on a given component. If there is a file called "pre_apply.sh" present in the top level of the component you are working with, then it will be executed as a bash script prior to any terraform action. If a file called post_apply.sh is present it will be executed immediately following any terraform action. This capability clearly could do with some improvement to support complex deployments with script dependencies, but as yet I have none to play with.
59+
Scaffold supports hook scripts at two scopes — global and component — that run before and after terraform actions. Each hook is sourced (not executed) so it shares the calling environment, and receives three positional arguments: `region`, `environment`, and `action`.
60+
61+
| Hook | Location | When it runs |
62+
|------|----------|-------------|
63+
| Global pre | `pre.sh` in the project root | Before entering the component directory |
64+
| Component pre | `pre.sh` in the component directory | After entering the component directory, before terraform runs |
65+
| Component post | `post.sh` in the component directory | After the terraform action completes |
66+
| Global post | `post.sh` in the project root | After leaving the component directory |
67+
68+
Any hook that exits non-zero will abort the scaffold run.
6069

6170
### Encrypted Variables / Secrets
6271

6372
This is an experimental feature that is not necessarily an appropriate solution for secrets management in production systems due to the way state files are stored with all terraform variables included. In general a resource that requires secret information should look up that information itself once it has been created using role based credentials. If however you are simply looking for a solution to move secrets out of your Git repository and into S3 which can be locked down, then this might be useful.
6473

65-
On invocation, Scaffold checks for a file at _s3://${bucket}/${project}/secrets/secret_${region}_${environment}.tfvars.enc_. If it finds one, it attempts to use KMS to decrypt the contents into an array and then process each line as a key=value set of variables. Each one is then provided to terraform as an input variable in the form: -var 'key=value'. This ensures that the secret is never stored unencrypted on disk even temporarily during terraform apply. However.. the completely unencrypted format of the terraform tfplan and tfstate files means these secrets will still make it to disk and be stored in the clear, hence the above caveats. This is not a complete secrets management solution, but it is a useful quick fix to get your secrets out of Git while you work on a better long term plan.
74+
On invocation, Scaffold checks for a file at _s3://${bucket}/${project}/${aws_account_id}/${region}/${environment}/secret.tfvars.enc_. If it finds one, it attempts to use KMS to decrypt the contents into an array and then process each line as a key=value set of variables. Each one is then provided to terraform as an input variable in the form: -var 'key=value'. This ensures that the secret is never stored unencrypted on disk even temporarily during terraform apply. However.. the completely unencrypted format of the terraform tfplan and tfstate files means these secrets will still make it to disk and be stored in the clear, hence the above caveats. This is not a complete secrets management solution, but it is a useful quick fix to get your secrets out of Git while you work on a better long term plan.
6675

6776
### Provider Plugins
6877

6978
Since 0.10 terraform has split its providers out into plugins which are downloaded separately. This has caused some issues in automation where you can be downloading the same provider endlessly. A long term solution to this has not yet been decided upon as there are many ways to implement a solution, but none really suit all likely scenarios. Ideally I would like to see management of this handled by kamatama41/tfenv. For the moment, terraformscaffold will instruct terraform to cache plugins in the plugin-cache/ top level directory by default. This can be overridden by exporting the TF_PLUGIN_CACHE_DIR variable with an appropriate value. This at least means that within one code checkout, regardless of swapping around components to plan or apply, you will only need to download providers once. If you have a local artifact repository or some other preference for keeping copies of providers locally you can use it with this variable.
7079

7180
## Usage
7281
### Bootstrapping
73-
Before using Scaffold, a bootstrapping stage is required. Scaffold is responsible for creating and maintaining the S3 buckets it uses to store component state files and even keeps the state file that defines the scaffold bucket in the same bucket. This is done with a special bootstrap mode within the script, invoked with the '--bootstrap' parameter. When used with the "apply" action, this will cause the script to create a bootstrap bucket and then configure the bucket as a remote state location for itself. nd upload the tfstate used for managing the bucket to the bucket. Once created, the bucket can then be used for any terraform apply for the specific combination of project, region and AWS account.
82+
Before using Scaffold, a bootstrapping stage is required. Scaffold is responsible for creating and maintaining the S3 buckets it uses to store component state files and even keeps the state file that defines the scaffold bucket in the same bucket. This is done with a special bootstrap mode within the script, invoked with the '--bootstrap' parameter. When used with the "apply" action, this will cause the script to create a bootstrap bucket and then configure the bucket as a remote state location for itself. And upload the tfstate used for managing the bucket to the bucket. Once created, the bucket can then be used for any terraform apply for the specific combination of project, region and AWS account.
7483

7584
It is not recommended to modify the bootstrap code after creation as it risks the integrity of the state files stored in the bucket that manage other deployments; however this can be mitigated by configuring synchronisation with a master backup bucket external to Scaffold management.
7685

@@ -101,7 +110,7 @@ Where:
101110

102111
### Running
103112

104-
The terraformscaffold script is invoked as bin/terraform.sh. Once a state bucket has been bootstrapped, bin/terraform.sh can be run to apply terraform code. Its usage as of 25/01/2017 is:
113+
The terraformscaffold script is invoked as bin/terraform.sh. Once a state bucket has been bootstrapped, bin/terraform.sh can be run to apply terraform code. Its usage is:
105114

106115
```bash
107116
bin/terraform.sh \
@@ -124,7 +133,7 @@ bin/terraform.sh \
124133
```
125134

126135
Where:
127-
* `action`: Terraform action (or pseudo-action) to take, e.g. plan, apply, plan-destroy (runs plan with the -destroy flag), destroy, show
136+
* `action`: Terraform action (or pseudo-action) to take, e.g. plan, apply, plan-destroy (runs plan with the -destroy flag), destroy, output, show, graph
128137
* `bucket_prefix` (optional): Defaults to: `${project_name}-tfscaffold` - Only for use where a different bucket prefix has been bootstrapped
129138
* `build_id` (optional): Used in conjunction with the plan and apply actions, `build_id` causes the creation and consumption of terraform plan files (.tfplan)
130139
* When `build_id` is omitted:
@@ -146,3 +155,37 @@ Where:
146155
* `no-color` (optional): Passes no-color flag to terraform.
147156
* `compact-warnings` (optional): Passes compact-warnings flag to terraform.
148157
* `additional arguments`: Any arguments provided after "--" will be passed directly to terraform as its own arguments, e.g. allowing the provision of a 'target=value' parameter.
158+
159+
### Automatic TF_VAR Exports
160+
161+
Scaffold automatically exports the following as terraform variables, making them available without explicit variable passthrough:
162+
163+
* `TF_VAR_aws_account_id` - The AWS account ID resolved by the running credentials
164+
* `TF_VAR_environment` - The environment name passed to scaffold
165+
166+
### Remote Dynamic Variables
167+
168+
Scaffold downloads all `*.tfvars` and `*.tfvars.json` files from the S3 state bucket under the path `${project}/${aws_account_id}/${region}/${environment}/`. These are passed to terraform as additional variable files, allowing variables to be managed outside of the repository. This is useful for values that change frequently or are generated by external processes.
169+
170+
### Output Action
171+
172+
The `output` action retrieves terraform outputs for a component without running plan or apply:
173+
174+
```bash
175+
bin/terraform.sh \
176+
-a output \
177+
-p project \
178+
-c component \
179+
-e environment \
180+
-r region
181+
```
182+
183+
When `-j` is not set (the default), outputs are also written to `.terraform.output.json` in the component directory.
184+
185+
### AWS Profile Support
186+
187+
When the `AWS_PROFILE` environment variable is set, scaffold automatically includes it in the S3 backend configuration. This enables use of named profiles for state file access without additional configuration.
188+
189+
### State Encryption
190+
191+
S3 backend state is always configured with `encrypt = true`, ensuring state files are encrypted at rest in S3 using server-side encryption.

bin/docs.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
#!/usr/bin/env bash
22

3-
set -euo pipefail
3+
set -uo pipefail;
44

55
declare script_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
66
declare base_path="${script_path%%\/bin}";
77
find "${base_path}" -name 'variables.tf' -exec sh -c 'terraform-docs markdown table --output-file README.md $(dirname "$1")' sh {} \;
8+
9+
# vim:set et ts=2 sw=2:

0 commit comments

Comments
 (0)