Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion infrastructure/modules/inspector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ No providers.
| Name | Source | Version |
| ---- | ------ | ------- |
| <a name="module_inspector"></a> [inspector](#module\_inspector) | cloudposse/inspector/aws | 0.4.0 |
| <a name="module_this"></a> [this](#module\_this) | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | v2.0.0 |
| <a name="module_this"></a> [this](#module\_this) | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | v2.1.0 |

## Resources

Expand Down
2 changes: 1 addition & 1 deletion infrastructure/modules/inspector/context.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#

module "this" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=v2.0.0"
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=v2.1.0"

service = var.service
project = var.project
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/modules/kms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ No providers.
| Name | Source | Version |
| ---- | ------ | ------- |
| <a name="module_kms_key"></a> [kms\_key](#module\_kms\_key) | terraform-aws-modules/kms/aws | 4.2.0 |
| <a name="module_this"></a> [this](#module\_this) | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | v2.0.0 |
| <a name="module_this"></a> [this](#module\_this) | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | v2.1.0 |

## Resources

Expand Down
2 changes: 1 addition & 1 deletion infrastructure/modules/kms/context.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#

module "this" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=v2.0.0"
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=v2.1.0"

service = var.service
project = var.project
Expand Down
2 changes: 1 addition & 1 deletion infrastructure/modules/tags/exports/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ No providers.

| Name | Source | Version |
| ---- | ------ | ------- |
| <a name="module_this"></a> [this](#module\_this) | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | v2.0.0 |
| <a name="module_this"></a> [this](#module\_this) | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | v2.1.0 |

## Resources

Expand Down
2 changes: 1 addition & 1 deletion infrastructure/modules/tags/exports/context.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
#

module "this" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=v2.0.0"
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=v2.1.0"

service = var.service
project = var.project
Expand Down
144 changes: 123 additions & 21 deletions scripts/release/update-tags-module-version.cjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node

/**
* Update references to the local tags module between semantic-release versions.
* Update local module source version references between semantic-release versions.
*
* Why this exists:
* - Keeping release-time string replacements in a standalone script is easier
Expand All @@ -14,13 +14,24 @@
*
* Called by semantic-release exec plugin as:
* node scripts/release/update-tags-module-version.cjs "${lastRelease.version}" "${nextRelease.version}"
*
* Optional control markers:
* - semantic-release:pin => do not auto-update that .tf line
* - semantic-release:unpin => update now and then remove this marker on that .tf line
*
* Notes:
* - README.md rows are always updated regardless of markers, because README
* content is generated by terraform-docs and marker semantics are only
* intended for hand-edited Terraform source lines.
*/

const fs = require("fs");
const path = require("path");
const fs = require("node:fs");
const path = require("node:path");

const MODULES_ROOT = path.join("infrastructure", "modules");
const TARGET_FILE_NAMES = new Set(["context.tf", "readme.md"]);
const PIN_TOKEN = "semantic-release:pin";
const UNPIN_TOKEN = "semantic-release:unpin";
const REGEX_SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;

const [lastVersion, nextVersion] = process.argv.slice(2);

Expand Down Expand Up @@ -53,18 +64,106 @@ function listFilesRecursively(dirPath) {
}

/**
* Replace all occurrences of release-pinned tags module references.
* Ensure a semantic version has a leading v (for example 1.2.3 -> v1.2.3).
*/
function withVPrefix(version) {
return version.startsWith("v") ? version : `v${version}`;
}

/**
* Build equivalent from/to version pairs so replacements work for both:
* - plain versions (1.2.3)
* - v-prefixed tags (v1.2.3)
*/
function buildVersionPairs(fromVersion, toVersion) {
const pairs = [
{ from: fromVersion, to: toVersion },
{ from: withVPrefix(fromVersion), to: withVPrefix(toVersion) }
];

// De-duplicate if the input was already v-prefixed.
return pairs.filter(
(pair, index, all) =>
all.findIndex((item) => item.from === pair.from && item.to === pair.to) === index
);
}

/**
* Return true for files that may contain version-pinned local module references:
* - Terraform source files (.tf)
* - Module README files (README.md / readme.md)
*/
function isTargetFile(filePath) {
const fileExt = path.extname(filePath).toLowerCase();
const baseName = path.basename(filePath).toLowerCase();

return fileExt === ".tf" || baseName === "readme.md";
}

/**
* Escape user-provided values before embedding into a regular expression.
*/
function updateContent(content, fromVersion, toVersion) {
return content
.replaceAll(
`//infrastructure/modules/tags?ref=${fromVersion}`,
`//infrastructure/modules/tags?ref=${toVersion}`
)
.replaceAll(
`//infrastructure/modules/tags | ${fromVersion} |`,
`//infrastructure/modules/tags | ${toVersion} |`
);
function escapeRegex(value) {
return value.replace(REGEX_SPECIAL_CHARS, String.raw`\$&`);
}

/**
* Replace all release-pinned local module source references, for example:
* git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/<any-module>?ref=<version>
* and README table rows such as:
* | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/<any-module> | <version> |
*
* Set applyPinningControls=true only for .tf files.
*/
function updateContentWithMode(content, fromVersion, toVersion, applyPinningControls) {
const eol = content.includes("\r\n") ? "\r\n" : "\n";
const lines = content.split(/\r?\n/);
const updatedLines = [];

for (const line of lines) {
const hasPin = line.toLowerCase().includes(PIN_TOKEN);
const hasUnpin = line.toLowerCase().includes(UNPIN_TOKEN);

// Explicitly pinned .tf lines are never auto-updated.
if (applyPinningControls && hasPin && !hasUnpin) {
updatedLines.push(line);
continue;
}

// "Unpin" updates this .tf line once and removes the marker.
let updated = applyPinningControls && hasUnpin
? line
.replace(/\s*#\s*semantic-release:unpin\b/gi, "")
.replace(/\s*\/\/\s*semantic-release:unpin\b/gi, "")
.replace(/\s*<!--\s*semantic-release:unpin\s*-->/gi, "")
.replace(/\s*semantic-release:unpin\b/gi, "")
.replace(/\s+$/, "")
: line;

for (const pair of buildVersionPairs(fromVersion, toVersion)) {
const sourcePrefixPattern = String.raw`(git::https://github\.com/NHSDigital/screening-terraform-modules-aws\.git//infrastructure/modules/[^?\s"']+\?ref=)`;
const readmePrefixPattern = String.raw`(\|\s*git::https://github\.com/NHSDigital/screening-terraform-modules-aws\.git//infrastructure/modules/[^|\s]+\s*\|\s*)`;
const readmeSuffixPattern = String.raw`(\s*\|)`;

const sourcePattern = new RegExp(
sourcePrefixPattern + escapeRegex(pair.from),
"g"
);

const readmeTablePattern = new RegExp(
readmePrefixPattern + escapeRegex(pair.from) + readmeSuffixPattern,
"g"
);

updated = updated
.replace(sourcePattern, `$1${pair.to}`)
.replace(readmeTablePattern, `$1${pair.to}$2`);
}

updatedLines.push(updated);
}

return updatedLines.join(eol);
}

if (!fs.existsSync(MODULES_ROOT)) {
Expand All @@ -76,13 +175,16 @@ const allFiles = listFilesRecursively(MODULES_ROOT);
let updatedFilesCount = 0;

for (const filePath of allFiles) {
const fileName = path.basename(filePath).toLowerCase();

// Only process files where these references are expected.
if (!TARGET_FILE_NAMES.has(fileName)) continue;
if (!isTargetFile(filePath)) continue;

const original = fs.readFileSync(filePath, "utf8");
const updated = updateContent(original, lastVersion, nextVersion);
const applyPinningControls = path.extname(filePath).toLowerCase() === ".tf";
const updated = updateContentWithMode(
original,
lastVersion,
nextVersion,
applyPinningControls
);

// Avoid touching unchanged files to keep release commits clean.
if (updated === original) continue;
Expand All @@ -92,5 +194,5 @@ for (const filePath of allFiles) {
}

console.log(
`Updated tags module references from ${lastVersion} to ${nextVersion} in ${updatedFilesCount} file(s).`
`Updated local module source references from ${lastVersion} to ${nextVersion} in ${updatedFilesCount} file(s).`
);
82 changes: 82 additions & 0 deletions scripts/tests/release-updater.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/bin/bash

set -euo pipefail

cd "$(git rev-parse --show-toplevel)"

# Build an isolated fixture so this smoke test never mutates real repo files.
tmp_dir="$(mktemp -d)"
trap 'rm -rf "${tmp_dir}"' EXIT

mkdir -p "${tmp_dir}/infrastructure/modules/example"

cat > "${tmp_dir}/infrastructure/modules/example/main.tf" <<'EOF'
module "plain" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags?ref=2.0.0"
}

module "prefixed" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/inspector?ref=v2.0.0"
}

module "pinned" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/kms?ref=v2.0.0" # semantic-release:pin
}

module "unpinned_now" {
source = "git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/vpc?ref=v2.0.0" # semantic-release:unpin
}
EOF

cat > "${tmp_dir}/infrastructure/modules/example/README.md" <<'EOF'
| Name | Source | Version |
| ---- | ------ | ------- |
| this | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/tags | 2.0.0 |
| this2 | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/inspector | v2.0.0 |
| this3 | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/kms | v2.0.0 | <!-- semantic-release:pin -->
| this4 | git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/vpc | v2.0.0 | <!-- semantic-release:unpin -->
EOF

# Show the key lines before update for quick local debugging.
echo "Before update:"
grep -nE "source =|\| git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/" \
"${tmp_dir}/infrastructure/modules/example/main.tf" \
"${tmp_dir}/infrastructure/modules/example/README.md"

(
cd "${tmp_dir}"
node "${OLDPWD}/scripts/release/update-tags-module-version.cjs" 2.0.0 2.1.0
)

# Assertions: both plain and v-prefixed references must be updated.
grep -q "tags?ref=2.1.0" "${tmp_dir}/infrastructure/modules/example/main.tf"
grep -q "inspector?ref=v2.1.0" "${tmp_dir}/infrastructure/modules/example/main.tf"
grep -q "modules/tags | 2.1.0 |" "${tmp_dir}/infrastructure/modules/example/README.md"
grep -q "modules/inspector | v2.1.0 |" "${tmp_dir}/infrastructure/modules/example/README.md"

# Assertions: pinned .tf references remain unchanged.
grep -q "kms?ref=v2.0.0\" # semantic-release:pin" "${tmp_dir}/infrastructure/modules/example/main.tf"
grep -q "modules/kms | v2.1.0 | <!-- semantic-release:pin -->" "${tmp_dir}/infrastructure/modules/example/README.md"

# Assertions: .tf unpin marker updates now and removes marker.
grep -q "vpc?ref=v2.1.0\"" "${tmp_dir}/infrastructure/modules/example/main.tf"
if grep -q "semantic-release:unpin" "${tmp_dir}/infrastructure/modules/example/main.tf"; then
echo "Smoke test failed: .tf unpin marker should be removed after update." >&2
exit 1
fi

# Assertions: README markers do not control behavior; value updates and marker text remains.
grep -q "modules/vpc | v2.1.0 | <!-- semantic-release:unpin -->" "${tmp_dir}/infrastructure/modules/example/README.md"

# Guard against old values remaining in the fixture.
if grep -nE "\?ref=(2\.0\.0|v2\.0\.0)|\|\s*(2\.0\.0|v2\.0\.0)\s*\|" "${tmp_dir}/infrastructure/modules/example/main.tf" "${tmp_dir}/infrastructure/modules/example/README.md" | grep -vq "semantic-release:pin"; then
echo "Smoke test failed: old version references are still present." >&2
exit 1
fi

echo "After update:"
grep -nE "source =|\| git::https://github.com/NHSDigital/screening-terraform-modules-aws.git//infrastructure/modules/" \
"${tmp_dir}/infrastructure/modules/example/main.tf" \
"${tmp_dir}/infrastructure/modules/example/README.md"

echo "release-updater smoke test passed"
2 changes: 1 addition & 1 deletion scripts/tests/unit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ cd "$(git rev-parse --show-toplevel)"
# tests from here. If you want to run other test suites, see the predefined
# tasks in scripts/test.mk.

echo "Unit tests are not yet implemented. See scripts/tests/unit.sh for more."
./scripts/tests/release-updater.sh
Loading