Skip to content

Commit d7f6f28

Browse files
committed
Template: Trivy exception convention, example docs, sort/docs and CONTRIBUTING updates
Ship the work staged across recent changes: - .trivyignore.yaml waiver convention (machine-applied source of truth) and the Security scan exceptions table in the README, with informational lower-severity findings shown. - examples/{minimal,complete} HEADER.md + .terraform-docs.yml + generated README. - Sort-LdoTerraform.ps1 regenerates each example README from its HEADER.md. - HEADER/README reframe (template is the base every module is generated from) and the recipe list (update-ldo-pwsh, scan, pwsh-analyze, e2e). - CONTRIBUTING.md documents the Trivy gating/exception process and the sorting and docs style. - Alphabetised example variables.tf.
1 parent c605ac1 commit d7f6f28

13 files changed

Lines changed: 333 additions & 37 deletions

.trivyignore.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Trivy scan waivers for this module.
2+
#
3+
# This is the machine-applied source of truth for security-scan exceptions: Invoke-LdoTrivy (and
4+
# therefore `just scan`, `just validate`, and the CI scan) passes this file with --ignorefile, so a
5+
# waived finding stops failing the build. Every entry MUST also have a row in the "Security scan
6+
# exceptions" table in HEADER.md, with the same id and a justification, so the waiver is auditable.
7+
#
8+
# Rules:
9+
# - Only waive a finding you have a real, defensible reason to waive. HIGH/CRITICAL is otherwise
10+
# merge-blocking (see the Libre DevOps Terraform standard); a waiver is a deliberate, reviewed
11+
# decision, never a way to quiet a finding you should fix.
12+
# - Prefer scoping with paths so the waiver only applies where intended.
13+
# - Where the finding is out of this module's scope, point the justification at the Libre DevOps
14+
# module that does address it (for example the private-endpoint module).
15+
#
16+
# Format (uncomment and adapt; keep HEADER.md in sync):
17+
#
18+
# misconfigurations:
19+
# - id: AVD-AZU-0012
20+
# paths:
21+
# - "main.tf"
22+
# statement: >-
23+
# This module only deploys the resource group. Network rules and private networking are
24+
# configured by the consuming stack; use the Libre DevOps private-endpoint module for PEs.
25+
26+
misconfigurations: []

CONTRIBUTING.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,47 @@ The most effective way to propose a change is a pull request following the
1515
1. Fork the repository and branch from `main`.
1616
2. Make your change, keeping it consistent with the
1717
[Libre DevOps standards](https://libredevops.org/docs/documents).
18-
3. Verify your Terraform: `terraform fmt -check -recursive`, `terraform validate`, `tflint`, and
19-
a `trivy config` scan. The engine that the action runs is `Invoke-LdoTerraform.ps1`, which
20-
wraps this lifecycle using the
18+
3. Verify your Terraform with `just validate` (format check, validate, tflint, and a `trivy config`
19+
scan), or `just scan` for the Trivy scan on its own. See "Security scanning and exceptions" below.
20+
The engine that the action runs is `Invoke-LdoTerraform.ps1`, which wraps this lifecycle using the
2121
[LibreDevOpsHelpers](https://www.powershellgallery.com/packages/LibreDevOpsHelpers) module.
22-
4. For Terraform module repositories, run `Sort-LdoTerraform.ps1` to sort variables and outputs,
23-
format, and regenerate the `terraform-docs` section of the README from `HEADER.md`.
22+
4. For Terraform module repositories, run `just docs` (`Sort-LdoTerraform.ps1`) to sort variables and
23+
outputs, format, and regenerate the README from `HEADER.md`. See "Sorting and docs style" below.
2424
5. Keep PowerShell clean: PSScriptAnalyzer and the Pester tests under `Tests/` must pass.
2525
6. Follow the naming convention `terraform-${provider}-${purpose}` for module repositories, and
2626
the [Azure naming convention](https://libredevops.org/docs/documents/azure-naming-convention)
2727
for resources.
2828

29+
## Security scanning and exceptions (Trivy)
30+
31+
The `trivy config` scan gates on HIGH and CRITICAL findings (these fail the build). MEDIUM, LOW, and
32+
informational findings are reported for awareness but do not fail. Run `just scan` for the scan on
33+
its own, or `just validate` for the full offline gates.
34+
35+
A finding may only be waived with a real, defensible reason, never to silence something that should
36+
be fixed. Record every waiver in two places, kept in sync:
37+
38+
- `.trivyignore.yaml` at the module root, the machine-applied source of truth, with the `id`,
39+
optional `paths` to scope the waiver, and a `statement` recording why; and
40+
- the "Security scan exceptions" table in the README, so the reason is auditable.
41+
42+
Where a finding is out of the module's scope, point the justification at the Libre DevOps module that
43+
does address it (for example the private-endpoint module). Both the file and the table are reviewed
44+
in the pull request.
45+
46+
## Sorting and docs style
47+
48+
Resources live in `main.tf`. Keep `variables.tf` and `outputs.tf` declarations sorted alphabetically
49+
by name, one blank line between blocks, and put any comment describing a variable or output directly
50+
above its block (the comment travels with the block when it is sorted). Give every variable a `type`
51+
and a `description`.
52+
53+
Do not hand-edit the generated parts of a README. Run `just docs` (which calls
54+
`Sort-LdoTerraform.ps1 -IncludeExamples`) to sort the variables and outputs, format the Terraform,
55+
and regenerate the `terraform-docs` section of the module README and each example README from its
56+
`HEADER.md`. Edit `HEADER.md` for the hand-written header above the markers, then commit the
57+
regenerated `README.md` files alongside your change.
58+
2959
## Pull requests
3060

3161
- Keep changes focused and the history readable.

HEADER.md

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,38 @@ module "this" {
4545

4646
## Developing
4747

48-
This repo is a local on-ramp to the `libre-devops/terraform-azure` action. It needs **PowerShell 7+**
49-
and **[`just`](https://github.com/casey/just)**, because the recipes wrap the
50-
[LibreDevOpsHelpers](https://www.powershellgallery.com/packages/LibreDevOpsHelpers) PowerShell
51-
module that the action uses. Install just with `brew install just`, or `uv tool add rust-just` then
52-
`uv run just <recipe>`.
53-
54-
Run `just` to list recipes: `just validate`, `just plan`, `just apply`, `just destroy`, `just test`,
55-
and `just docs` (the plan/apply/destroy recipes mirror the action, including the storage firewall
56-
dance). Releasing is also `just`: `just increment-release [patch|minor|major]` bumps, tags, and
57-
publishes a GitHub release, and the Terraform Registry picks up the tag.
48+
This repo is the template every Libre DevOps Terraform module is generated from: clone it, swap in
49+
your resources, and you inherit the standard layout, examples, tests, CI, and release tooling. Local
50+
work needs **PowerShell 7+** and **[`just`](https://github.com/casey/just)**, because the recipes
51+
wrap the [LibreDevOpsHelpers](https://www.powershellgallery.com/packages/LibreDevOpsHelpers)
52+
PowerShell module (the same engine the `libre-devops/terraform-azure` action runs in CI). Install
53+
just with `brew install just`, or `uv tool add rust-just` then `uv run just <recipe>`.
54+
55+
Run `just` to list recipes: `just update-ldo-pwsh` (install or force-update LibreDevOpsHelpers from
56+
PSGallery), `just validate`, `just scan` (Trivy only), `just pwsh-analyze` (PSScriptAnalyzer only),
57+
`just plan`, `just apply`, `just destroy`, `just e2e`, `just test`, and `just docs` (the
58+
plan/apply/destroy recipes mirror the action, including the storage firewall dance; `just e2e`
59+
applies an example then always destroys it, defaulting to `minimal`, so nothing is left running).
60+
Releasing is also `just`:
61+
`just increment-release [patch|minor|major]` bumps, tags, and publishes a GitHub release, and the
62+
Terraform Registry picks up the tag.
63+
64+
## Security scan exceptions
65+
66+
This module is scanned with [Trivy](https://github.com/aquasecurity/trivy); HIGH and CRITICAL
67+
findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a
68+
finding that should be fixed. Waivers live in [`.trivyignore.yaml`](./.trivyignore.yaml) (the
69+
machine-applied source of truth, passed to Trivy with `--ignorefile`) and are mirrored in the table
70+
below so the reason is auditable.
71+
72+
| Trivy ID | Resource | Finding | Justification |
73+
|----------|----------|---------|---------------|
74+
| _None_ | | | |
75+
76+
To add an exception: add an entry to `.trivyignore.yaml` (`id`, optional `paths` to scope it, and a
77+
`statement` recording why), then add a matching row here. Where the finding is out of this module's
78+
scope, point the justification at the Libre DevOps module that does address it (for example the
79+
private-endpoint module). Both the file and this table are reviewed in the pull request.
5880

5981
## Reference
6082

README.md

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,38 @@ module "this" {
4545

4646
## Developing
4747

48-
This repo is a local on-ramp to the `libre-devops/terraform-azure` action. It needs **PowerShell 7+**
49-
and **[`just`](https://github.com/casey/just)**, because the recipes wrap the
50-
[LibreDevOpsHelpers](https://www.powershellgallery.com/packages/LibreDevOpsHelpers) PowerShell
51-
module that the action uses. Install just with `brew install just`, or `uv tool add rust-just` then
52-
`uv run just <recipe>`.
53-
54-
Run `just` to list recipes: `just validate`, `just plan`, `just apply`, `just destroy`, `just test`,
55-
and `just docs` (the plan/apply/destroy recipes mirror the action, including the storage firewall
56-
dance). Releasing is also `just`: `just increment-release [patch|minor|major]` bumps, tags, and
57-
publishes a GitHub release, and the Terraform Registry picks up the tag.
48+
This repo is the template every Libre DevOps Terraform module is generated from: clone it, swap in
49+
your resources, and you inherit the standard layout, examples, tests, CI, and release tooling. Local
50+
work needs **PowerShell 7+** and **[`just`](https://github.com/casey/just)**, because the recipes
51+
wrap the [LibreDevOpsHelpers](https://www.powershellgallery.com/packages/LibreDevOpsHelpers)
52+
PowerShell module (the same engine the `libre-devops/terraform-azure` action runs in CI). Install
53+
just with `brew install just`, or `uv tool add rust-just` then `uv run just <recipe>`.
54+
55+
Run `just` to list recipes: `just update-ldo-pwsh` (install or force-update LibreDevOpsHelpers from
56+
PSGallery), `just validate`, `just scan` (Trivy only), `just pwsh-analyze` (PSScriptAnalyzer only),
57+
`just plan`, `just apply`, `just destroy`, `just e2e`, `just test`, and `just docs` (the
58+
plan/apply/destroy recipes mirror the action, including the storage firewall dance; `just e2e`
59+
applies an example then always destroys it, defaulting to `minimal`, so nothing is left running).
60+
Releasing is also `just`:
61+
`just increment-release [patch|minor|major]` bumps, tags, and publishes a GitHub release, and the
62+
Terraform Registry picks up the tag.
63+
64+
## Security scan exceptions
65+
66+
This module is scanned with [Trivy](https://github.com/aquasecurity/trivy); HIGH and CRITICAL
67+
findings fail the build. Any waiver is a deliberate, reviewed decision, never a way to quiet a
68+
finding that should be fixed. Waivers live in [`.trivyignore.yaml`](./.trivyignore.yaml) (the
69+
machine-applied source of truth, passed to Trivy with `--ignorefile`) and are mirrored in the table
70+
below so the reason is auditable.
71+
72+
| Trivy ID | Resource | Finding | Justification |
73+
|----------|----------|---------|---------------|
74+
| _None_ | | | |
75+
76+
To add an exception: add an entry to `.trivyignore.yaml` (`id`, optional `paths` to scope it, and a
77+
`statement` recording why), then add a matching row here. Where the finding is out of this module's
78+
scope, point the justification at the Libre DevOps module that does address it (for example the
79+
private-endpoint module). Both the file and this table are reviewed in the pull request.
5880

5981
## Reference
6082

Sort-LdoTerraform.ps1

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ if ($IncludeExamples) {
4343
if (Test-Path $examplesRoot) {
4444
Get-ChildItem -Path $examplesRoot -Directory | ForEach-Object {
4545
Format-LdoTerraformCode -CodePath $_.FullName
46+
47+
# Regenerate the example README too when it carries its own header file, so each
48+
# example folder gets the same terraform-docs treatment as the module root.
49+
$exampleHeader = Join-Path $_.FullName $ReadmeHeaderFile
50+
if (Test-Path $exampleHeader -PathType Leaf) {
51+
Update-LdoReadmeWithTerraformDocs -CodePath $_.FullName -ReadmeHeaderFile $ReadmeHeaderFile
52+
}
53+
else {
54+
Write-Verbose "No $ReadmeHeaderFile in $($_.FullName); skipping README generation for this example."
55+
}
4656
}
4757
}
4858
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
formatter: "markdown table"
2+
version: ">= 0.19.0"
3+
4+
# terraform.tf has no leading comment block, so the generated header is empty and HEADER.md
5+
# (above the markers) is the only header.
6+
header-from: terraform.tf
7+
8+
recursive:
9+
enabled: false
10+
11+
output:
12+
file: README.md
13+
mode: inject
14+
template: |-
15+
<!-- BEGIN_TF_DOCS -->
16+
{{ .Content }}
17+
<!-- END_TF_DOCS -->
18+
19+
sort:
20+
enabled: true
21+
by: name
22+
23+
settings:
24+
anchor: true
25+
color: true
26+
default: true
27+
description: true
28+
escape: true
29+
hide-empty: false
30+
html: true
31+
indent: 2
32+
lockfile: false
33+
read-comments: true
34+
required: true
35+
sensitive: true
36+
type: true

examples/complete/HEADER.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!--
2+
Header for the complete example README. Edit this file, then run `just docs`
3+
(or ./Sort-LdoTerraform.ps1 -IncludeExamples) to regenerate the section between the markers.
4+
-->
5+
# Complete example
6+
7+
Exercises every supported input: multiple resource groups with tags, demonstrating the
8+
`list(object)` interface. The environment comes from the Terraform workspace
9+
(`terraform.workspace`), not a variable. Run it with `just e2e complete`, which applies the stack
10+
then always destroys it.

examples/complete/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!--
2+
Header for the complete example README. Edit this file, then run `just docs`
3+
(or ./Sort-LdoTerraform.ps1 -IncludeExamples) to regenerate the section between the markers.
4+
-->
5+
# Complete example
6+
7+
Exercises every supported input: multiple resource groups with tags, demonstrating the
8+
`list(object)` interface. The environment comes from the Terraform workspace
9+
(`terraform.workspace`), not a variable. Run it with `just e2e complete`, which applies the stack
10+
then always destroys it.
11+
12+
<!-- BEGIN_TF_DOCS -->
13+
## Requirements
14+
15+
| Name | Version |
16+
|------|---------|
17+
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.9.0, < 2.0.0 |
18+
| <a name="requirement_azurerm"></a> [azurerm](#requirement\_azurerm) | >= 4.0.0, < 5.0.0 |
19+
20+
## Providers
21+
22+
No providers.
23+
24+
## Modules
25+
26+
| Name | Source | Version |
27+
|------|--------|---------|
28+
| <a name="module_this"></a> [this](#module\_this) | ../../ | n/a |
29+
30+
## Resources
31+
32+
No resources.
33+
34+
## Inputs
35+
36+
| Name | Description | Type | Default | Required |
37+
|------|-------------|------|---------|:--------:|
38+
| <a name="input_loc"></a> [loc](#input\_loc) | Outfix: short Azure region code used in resource names (for example uks). | `string` | `"uks"` | no |
39+
| <a name="input_regions"></a> [regions](#input\_regions) | Map of short region codes to Azure region slugs. | `map(string)` | <pre>{<br/> "eus": "eastus",<br/> "euw": "westeurope",<br/> "uks": "uksouth",<br/> "ukw": "ukwest"<br/>}</pre> | no |
40+
| <a name="input_short"></a> [short](#input\_short) | Infix: short product code used in resource names. | `string` | `"ldo"` | no |
41+
42+
## Outputs
43+
44+
| Name | Description |
45+
|------|-------------|
46+
| <a name="output_ids"></a> [ids](#output\_ids) | Map of resource group name to id. |
47+
| <a name="output_names"></a> [names](#output\_names) | Map of resource group name to name. |
48+
<!-- END_TF_DOCS -->

examples/complete/variables.tf

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
variable "short" {
2-
description = "Infix: short product code used in resource names."
3-
type = string
4-
default = "ldo"
5-
}
6-
71
variable "loc" {
82
description = "Outfix: short Azure region code used in resource names (for example uks)."
93
type = string
@@ -20,3 +14,9 @@ variable "regions" {
2014
euw = "westeurope"
2115
}
2216
}
17+
18+
variable "short" {
19+
description = "Infix: short product code used in resource names."
20+
type = string
21+
default = "ldo"
22+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
formatter: "markdown table"
2+
version: ">= 0.19.0"
3+
4+
# terraform.tf has no leading comment block, so the generated header is empty and HEADER.md
5+
# (above the markers) is the only header.
6+
header-from: terraform.tf
7+
8+
recursive:
9+
enabled: false
10+
11+
output:
12+
file: README.md
13+
mode: inject
14+
template: |-
15+
<!-- BEGIN_TF_DOCS -->
16+
{{ .Content }}
17+
<!-- END_TF_DOCS -->
18+
19+
sort:
20+
enabled: true
21+
by: name
22+
23+
settings:
24+
anchor: true
25+
color: true
26+
default: true
27+
description: true
28+
escape: true
29+
hide-empty: false
30+
html: true
31+
indent: 2
32+
lockfile: false
33+
read-comments: true
34+
required: true
35+
sensitive: true
36+
type: true

0 commit comments

Comments
 (0)