Skip to content

Commit 639bc07

Browse files
committed
Add policies validation workflow
1 parent 70a77f6 commit 639bc07

4 files changed

Lines changed: 304 additions & 0 deletions

File tree

.ci/scripts/docgen.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import yaml
5+
from dataclasses import dataclass
6+
from typing import List
7+
from urllib.parse import urlparse, urlunparse
8+
9+
@dataclass
10+
class PolicyMetadata:
11+
dir: str
12+
version: str
13+
description: str
14+
path: str
15+
16+
def find_policy_yaml(start_dir=".") -> List[str]:
17+
"""Recursively search for files named 'Policy.yaml'."""
18+
matches = []
19+
for root, _, files in os.walk(start_dir):
20+
if "Policy.yaml" in files:
21+
matches.append(os.path.join(root, "Policy.yaml"))
22+
return sorted(matches)
23+
24+
def load_policy_metadata(file_path: str) -> PolicyMetadata:
25+
"""Load a Policy.yaml file and unmarshal it into a PolicyMetadata object."""
26+
with open(file_path, "r", encoding="utf-8") as f:
27+
data = yaml.safe_load(f) or {}
28+
29+
dir_name = os.path.dirname(file_path)
30+
return PolicyMetadata(
31+
dir=data.get("dir", dir_name),
32+
version=data.get("version", ""),
33+
description=data.get("description", ""),
34+
path=file_path,
35+
)
36+
37+
def generate_markdown_table(policies: List[PolicyMetadata]) -> str:
38+
"""Generate a Markdown table from a list of PolicyMetadata objects."""
39+
header = "| URL | Description | Link |\n"
40+
separator = "|------------|-----------|---------| \n"
41+
rows = []
42+
for p in policies:
43+
description = p.description.replace("\n", " ").strip()
44+
ghcr_path = f"ghcr.io/{os.path.normpath(os.path.dirname(p.path))}"
45+
readme_url = replace_filename_in_url(f"https://github.com/ContainerCraft/updatecli-policies/tree/main/{p.path}", "README.md")
46+
rows.append(f"| `{ghcr_path}:{p.version}` | {description or '-'} | {f"[link]({readme_url})" } |")
47+
return header + separator + "\n".join(rows)
48+
49+
def replace_filename_in_url(url: str, new_filename: str) -> str:
50+
# Parse the URL
51+
parsed = urlparse(url)
52+
53+
# Split the path and replace the last part with the new filename
54+
path_parts = parsed.path.split('/')
55+
path_parts[-1] = new_filename
56+
new_path = '/'.join(path_parts)
57+
58+
# Rebuild the URL with the new path
59+
new_url = urlunparse(parsed._replace(path=new_path))
60+
return new_url
61+
62+
# Example usage
63+
original_url = "https://github.com/ContainerCraft/updatecli-policies/blob/main/updatecli/policies/crd-pulumi-sdk/Policy.yaml"
64+
new_url = replace_filename_in_url(original_url, "README.md")
65+
66+
def main():
67+
policies = []
68+
for policy_file in find_policy_yaml("."):
69+
try:
70+
metadata = load_policy_metadata(policy_file)
71+
policies.append(metadata)
72+
except Exception as e:
73+
print(f"⚠️ Error parsing {policy_file}: {e}")
74+
75+
if not policies:
76+
print("No Policy.yaml files found.")
77+
return
78+
79+
markdown = generate_markdown_table(policies)
80+
81+
with open("POLICIES.md", "w", encoding="utf-8") as f:
82+
f.write(markdown)
83+
84+
if __name__ == "__main__":
85+
main()

.ci/scripts/release.bash

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Ensure we work from the Updatecli directory
5+
# This is even more important as we use the policy path to generate the policy reference
6+
pushd updatecli
7+
8+
: "${POLICIES_ROOT_DIR:=policies}"
9+
: "${POLICY_ERROR:=false}"
10+
: "${OCI_REPOSITORY:=ghcr.io/ContainerCraft/updatecli-policies}"
11+
12+
: "${GITHUB_REGISTRY:=}"
13+
14+
POLICIES=$(find "$POLICIES_ROOT_DIR" -name "Policy.yaml")
15+
16+
# release publish an Updatecli policy version to the registry
17+
function release(){
18+
local POLICY_ROOT_DIR="$1"
19+
# Trim policy path with root directory path
20+
local POLICY_DIR="${1#"$POLICIES_ROOT_DIR/"}"
21+
22+
updatecli manifest push \
23+
--config updatecli.d \
24+
--values values.yaml \
25+
--policy Policy.yaml \
26+
--tag "$OCI_REPOSITORY/$POLICY_DIR" \
27+
"$POLICY_ROOT_DIR"
28+
}
29+
30+
function runUpdatecliDiff(){
31+
local POLICY_ROOT_DIR=""
32+
POLICY_ROOT_DIR="$1"
33+
34+
updatecli diff \
35+
--config "$POLICY_ROOT_DIR/updatecli.d" \
36+
--values "$POLICY_ROOT_DIR/values.yaml" \
37+
--values "$POLICY_ROOT_DIR/testdata/values.yaml"
38+
}
39+
40+
function validateRequiredFile(){
41+
local POLICY_ROOT_DIR="$1"
42+
local POLICY_VALUES="$POLICY_ROOT_DIR/values.yaml"
43+
local POLICY_README="$POLICY_ROOT_DIR/README.md"
44+
local POLICY_METADATA="$POLICY_ROOT_DIR/Policy.yaml"
45+
local POLICY_CHANGELOG="$POLICY_ROOT_DIR/CHANGELOG.md"
46+
47+
echo "* validating policy $POLICY_ROOT_DIR"
48+
49+
50+
# Checking for files
51+
for POLICY_FILE in "$POLICY_VALUES" "$POLICY_CHANGELOG" "$POLICY_README" "$POLICY_METADATA"
52+
do
53+
if [[ ! -f "$POLICY_FILE" ]]; then
54+
55+
POLICY_ERROR=true
56+
echo " * file '$POLICY_FILE' missing for policy $POLICY_ROOT_DIR"
57+
true
58+
fi
59+
done
60+
61+
local POLICY_MANIFEST="$POLICY_ROOT_DIR/updatecli.d"
62+
# Checking for directories
63+
if [[ ! -d "$POLICY_MANIFEST" ]]; then
64+
65+
POLICY_ERROR=true
66+
echo " * directory '$POLICY_MANIFEST' missing for policy $POLICY_ROOT_DIR"
67+
true
68+
fi
69+
70+
## Testing that Policy.yaml contains the required information
71+
local sourceInformation=""
72+
sourceInformation=$(sed -n -e 's/^source: //p' "$POLICY_METADATA" )
73+
local expectedSourceInformation="\"https://github.com/ContainerCraft/updatecli-policies/tree/main/updatecli/$POLICY_ROOT_DIR/\""
74+
if [[ ! $sourceInformation == "$expectedSourceInformation" ]]; then
75+
POLICY_ERROR=true
76+
echo " * policy $POLICY_ROOT_DIR missing the right source information in Policy.yaml"
77+
echo " expected: $expectedSourceInformation"
78+
echo " got: $sourceInformation"
79+
fi
80+
81+
local documentationInformation=""
82+
documentationInformation=$(sed -n -e 's/^documentation: //p' "$POLICY_METADATA")
83+
local expectedDocumentationInformation="\"https://github.com/ContainerCraft/updatecli-policies/tree/main/updatecli/$POLICY_ROOT_DIR/README.md\""
84+
if [[ ! $documentationInformation == "$expectedDocumentationInformation" ]]; then
85+
POLICY_ERROR=true
86+
echo " * policy $POLICY_ROOT_DIR missing the right documentation information in Policy.yaml"
87+
echo " expected: $expectedDocumentationInformation"
88+
echo " got: $documentationInformation"
89+
fi
90+
91+
# Testing url annotation is defined
92+
local urlInformation=""
93+
urlInformation=$(sed -n -e 's/^url: //p' "$POLICY_METADATA")
94+
local expectedUrlInformation="\"https://github.com/ContainerCraft/updatecli-policies/\""
95+
if [[ ! $urlInformation == "$expectedUrlInformation" ]]; then
96+
POLICY_ERROR=true
97+
echo " * policy $POLICY_ROOT_DIR missing the right url information in Policy.yaml"
98+
echo " expected: $expectedUrlInformation"
99+
echo " got: $urlInformation"
100+
fi
101+
102+
# Testing version annotation is defined
103+
local versionInformation=""
104+
versionInformation=$(sed -n -e 's/^version: //p' "$POLICY_METADATA")
105+
if [[ $versionInformation == "" ]]; then
106+
POLICY_ERROR=true
107+
echo " * policy $POLICY_ROOT_DIR missing a version information in Policy.yaml"
108+
fi
109+
110+
# Testing that the latest version has a changelog entry
111+
local versionChangelogEntry=""
112+
versionChangelogEntry=$(grep " $versionInformation" "$POLICY_CHANGELOG")
113+
if [[ $versionChangelogEntry == "" ]]; then
114+
POLICY_ERROR=true
115+
echo " * Changelog missing a version entry such as '## $versionInformation' in $POLICY_CHANGELOG"
116+
fi
117+
118+
# Testing that the latest changelog version is used in the Policy.yaml
119+
latestVersionChangelogEntry=$(sed -n -e '1,4s/^.*## //p' "$POLICY_CHANGELOG")
120+
if [[ "$latestVersionChangelogEntry" != "$versionInformation" ]]; then
121+
POLICY_ERROR=true
122+
echo " * Latest Changelog version isn't the one used in Policy.yaml"
123+
echo " '## $latestVersionChangelogEntry' in $POLICY_CHANGELOG"
124+
echo " '## $versionInformation' in $POLICY_METADATA"
125+
fi
126+
}
127+
128+
function main(){
129+
130+
PARAM="$1"
131+
132+
GLOBAL_ERROR=0
133+
134+
for POLICY in $POLICIES
135+
do
136+
echo ""
137+
138+
POLICY_ROOT_DIR=$(dirname "$POLICY")
139+
POLICY_ERROR=false
140+
141+
if [[ "$PARAM" == "--e2e-test" ]]; then
142+
runUpdatecliDiff "$POLICY_ROOT_DIR"
143+
fi
144+
145+
if [[ "$PARAM" == "--unit-test" || "$PARAM" == "" ]]; then
146+
validateRequiredFile "$POLICY_ROOT_DIR"
147+
fi
148+
149+
if [[ "$POLICY_ERROR" = "false" ]]; then
150+
echo " => all is good"
151+
152+
if [[ "$PARAM" == "--publish" ]]; then
153+
release "$POLICY_ROOT_DIR"
154+
fi
155+
156+
else
157+
echo ""
158+
echo " => validation test not passing"
159+
160+
GLOBAL_ERROR=1
161+
fi
162+
163+
done
164+
165+
exit "$GLOBAL_ERROR"
166+
}
167+
168+
main "${1:-}"

.github/workflows/validate.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: Validate Policies
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
jobs:
8+
validate:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
15+
- name: Setup updatecli
16+
uses: updatecli/updatecli-action@2c3221bc5f4499a99fec2c87d9de4a83cb30e990 # v3.1.3
17+
- name: Setup releasepost
18+
uses: updatecli/releasepost-action@864390bddae97db06ee881ab4a08d159b4464643 # v0.5.0
19+
- name: Validate
20+
run: make test
21+
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
22+
# Only run e2e tests from the main branch as we need some credentials
23+
# that we don't want to risk leaking from pullrequest opened by random contributors
24+
if: github.ref == 'refs/heads/main'
25+
id: generate_testing_token
26+
with:
27+
client-id: ${{ secrets.CONTAINERCRAFTER_APP_CLIENT_ID }}
28+
private-key: ${{ secrets.CONTAINERCRAFTER_APP_PRIVATE_KEY }}
29+
- name: e2e tests
30+
# Only run e2e tests from the main branch as we need some credentials
31+
# that we don't want to risk leaking from pullrequest opened by random contributors
32+
if: github.ref == 'refs/heads/main'
33+
run: make e2e-test
34+
env:
35+
GITHUB_TOKEN: ${{ steps.generate_testing_token.outputs.token }}
36+
RELEASEPOST_GITHUB_TOKEN: ${{ steps.generate_testing_token.outputs.token }}

Makefile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.PHONY: release
2+
release: ## Release checks for each policy if they can be published on ghcr.io
3+
.ci/scripts/release.bash --publish
4+
5+
.PHONY: validate
6+
test: ## Release checks for each policy if they can be published on ghcr.io
7+
.ci/scripts/release.bash
8+
9+
.PHONY: validate
10+
e2e-test: ## Release checks for each policy if they can be published on ghcr.io
11+
.ci/scripts/release.bash --e2e-test
12+
13+
.PHONY: help
14+
help: ## Show this Makefile's help
15+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

0 commit comments

Comments
 (0)