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
85 changes: 85 additions & 0 deletions .ci/scripts/docgen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env python3

import os
import yaml
from dataclasses import dataclass
from typing import List
from urllib.parse import urlparse, urlunparse

@dataclass
class PolicyMetadata:
dir: str
version: str
description: str
path: str

def find_policy_yaml(start_dir=".") -> List[str]:
"""Recursively search for files named 'Policy.yaml'."""
matches = []
for root, _, files in os.walk(start_dir):
if "Policy.yaml" in files:
matches.append(os.path.join(root, "Policy.yaml"))
return sorted(matches)

def load_policy_metadata(file_path: str) -> PolicyMetadata:
"""Load a Policy.yaml file and unmarshal it into a PolicyMetadata object."""
with open(file_path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f) or {}

dir_name = os.path.dirname(file_path)
return PolicyMetadata(
dir=data.get("dir", dir_name),
version=data.get("version", ""),
description=data.get("description", ""),
path=file_path,
)

def generate_markdown_table(policies: List[PolicyMetadata]) -> str:
"""Generate a Markdown table from a list of PolicyMetadata objects."""
header = "| URL | Description | Link |\n"
separator = "|------------|-----------|---------| \n"
rows = []
for p in policies:
description = p.description.replace("\n", " ").strip()
ghcr_path = f"ghcr.io/{os.path.normpath(os.path.dirname(p.path))}"
readme_url = replace_filename_in_url(f"https://github.com/ContainerCraft/updatecli-policies/tree/main/{p.path}", "README.md")
rows.append(f"| `{ghcr_path}:{p.version}` | {description or '-'} | [link]({readme_url}) |")
return header + separator + "\n".join(rows)

def replace_filename_in_url(url: str, new_filename: str) -> str:
# Parse the URL
parsed = urlparse(url)

# Split the path and replace the last part with the new filename
path_parts = parsed.path.split('/')
path_parts[-1] = new_filename
new_path = '/'.join(path_parts)

# Rebuild the URL with the new path
new_url = urlunparse(parsed._replace(path=new_path))
return new_url

# Example usage
original_url = "https://github.com/ContainerCraft/updatecli-policies/blob/main/updatecli/policies/crd-pulumi-sdk/Policy.yaml"
new_url = replace_filename_in_url(original_url, "README.md")

def main():
policies = []
for policy_file in find_policy_yaml("."):
try:
metadata = load_policy_metadata(policy_file)
policies.append(metadata)
except Exception as e:
print(f"⚠️ Error parsing {policy_file}: {e}")

if not policies:
print("No Policy.yaml files found.")
return

markdown = generate_markdown_table(policies)

with open("POLICIES.md", "w", encoding="utf-8") as f:
f.write(markdown)

if __name__ == "__main__":
main()
168 changes: 168 additions & 0 deletions .ci/scripts/release.bash
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env bash
set -euo pipefail

# Ensure we work from the Updatecli directory
# This is even more important as we use the policy path to generate the policy reference
pushd updatecli

: "${POLICIES_ROOT_DIR:=policies}"
: "${POLICY_ERROR:=false}"
: "${OCI_REPOSITORY:=ghcr.io/ContainerCraft/updatecli-policies}"

: "${GITHUB_REGISTRY:=}"

POLICIES=$(find "$POLICIES_ROOT_DIR" -name "Policy.yaml")

# release publish an Updatecli policy version to the registry
function release(){
local POLICY_ROOT_DIR="$1"
# Trim policy path with root directory path
local POLICY_DIR="${1#"$POLICIES_ROOT_DIR/"}"

updatecli manifest push \
--config updatecli.d \
--values values.yaml \
--policy Policy.yaml \
--tag "$OCI_REPOSITORY/$POLICY_DIR" \
"$POLICY_ROOT_DIR"
}

function runUpdatecliDiff(){
local POLICY_ROOT_DIR=""
POLICY_ROOT_DIR="$1"

updatecli diff \
--config "$POLICY_ROOT_DIR/updatecli.d" \
--values "$POLICY_ROOT_DIR/values.yaml" \
--values "$POLICY_ROOT_DIR/testdata/values.yaml"
}

function validateRequiredFile(){
local POLICY_ROOT_DIR="$1"
local POLICY_VALUES="$POLICY_ROOT_DIR/values.yaml"
local POLICY_README="$POLICY_ROOT_DIR/README.md"
local POLICY_METADATA="$POLICY_ROOT_DIR/Policy.yaml"
local POLICY_CHANGELOG="$POLICY_ROOT_DIR/CHANGELOG.md"

echo "* validating policy $POLICY_ROOT_DIR"


# Checking for files
for POLICY_FILE in "$POLICY_VALUES" "$POLICY_CHANGELOG" "$POLICY_README" "$POLICY_METADATA"
do
if [[ ! -f "$POLICY_FILE" ]]; then

POLICY_ERROR=true
echo " * file '$POLICY_FILE' missing for policy $POLICY_ROOT_DIR"
true
fi
done

local POLICY_MANIFEST="$POLICY_ROOT_DIR/updatecli.d"
# Checking for directories
if [[ ! -d "$POLICY_MANIFEST" ]]; then

POLICY_ERROR=true
echo " * directory '$POLICY_MANIFEST' missing for policy $POLICY_ROOT_DIR"
true
fi

## Testing that Policy.yaml contains the required information
local sourceInformation=""
sourceInformation=$(sed -n -e 's/^source: //p' "$POLICY_METADATA" )
local expectedSourceInformation="\"https://github.com/ContainerCraft/updatecli-policies/tree/main/updatecli/$POLICY_ROOT_DIR/\""
if [[ ! $sourceInformation == "$expectedSourceInformation" ]]; then
POLICY_ERROR=true
echo " * policy $POLICY_ROOT_DIR missing the right source information in Policy.yaml"
echo " expected: $expectedSourceInformation"
echo " got: $sourceInformation"
fi

local documentationInformation=""
documentationInformation=$(sed -n -e 's/^documentation: //p' "$POLICY_METADATA")
local expectedDocumentationInformation="\"https://github.com/ContainerCraft/updatecli-policies/tree/main/updatecli/$POLICY_ROOT_DIR/README.md\""
if [[ ! $documentationInformation == "$expectedDocumentationInformation" ]]; then
POLICY_ERROR=true
echo " * policy $POLICY_ROOT_DIR missing the right documentation information in Policy.yaml"
echo " expected: $expectedDocumentationInformation"
echo " got: $documentationInformation"
fi

# Testing url annotation is defined
local urlInformation=""
urlInformation=$(sed -n -e 's/^url: //p' "$POLICY_METADATA")
local expectedUrlInformation="\"https://github.com/ContainerCraft/updatecli-policies/\""
if [[ ! $urlInformation == "$expectedUrlInformation" ]]; then
POLICY_ERROR=true
echo " * policy $POLICY_ROOT_DIR missing the right url information in Policy.yaml"
echo " expected: $expectedUrlInformation"
echo " got: $urlInformation"
fi

# Testing version annotation is defined
local versionInformation=""
versionInformation=$(sed -n -e 's/^version: //p' "$POLICY_METADATA")
if [[ $versionInformation == "" ]]; then
POLICY_ERROR=true
echo " * policy $POLICY_ROOT_DIR missing a version information in Policy.yaml"
fi

# Testing that the latest version has a changelog entry
local versionChangelogEntry=""
versionChangelogEntry=$(grep " $versionInformation" "$POLICY_CHANGELOG")
if [[ $versionChangelogEntry == "" ]]; then
POLICY_ERROR=true
echo " * Changelog missing a version entry such as '## $versionInformation' in $POLICY_CHANGELOG"
fi

# Testing that the latest changelog version is used in the Policy.yaml
latestVersionChangelogEntry=$(sed -n -e '1,4s/^.*## //p' "$POLICY_CHANGELOG")
if [[ "$latestVersionChangelogEntry" != "$versionInformation" ]]; then
POLICY_ERROR=true
echo " * Latest Changelog version isn't the one used in Policy.yaml"
echo " '## $latestVersionChangelogEntry' in $POLICY_CHANGELOG"
echo " '## $versionInformation' in $POLICY_METADATA"
fi
}

function main(){

PARAM="$1"

GLOBAL_ERROR=0

for POLICY in $POLICIES
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Unquoted expansion of POLICIES in the loop may misbehave if paths contain whitespace or glob characters.

for POLICY in $POLICIES depends on word-splitting and globbing and will break if any path has spaces or wildcard chars. Consider iterating safely, e.g.:

mapfile -t POLICIES < <(find "$POLICIES_ROOT_DIR" -name 'Policy.yaml')
for POLICY in "${POLICIES[@]}"; do
  ...
done

Suggested implementation:

  GLOBAL_ERROR=0

  mapfile -t POLICIES < <(find "$POLICIES_ROOT_DIR" -name 'Policy.yaml')

  for POLICY in "${POLICIES[@]}"
  do
  1. Ensure that POLICIES_ROOT_DIR is defined before calling main (for example, the root directory under which policies live).
  2. If POLICIES was previously being set elsewhere (e.g., as a space-separated list), remove or adjust that logic so that mapfile is the single source of truth for the list of policy files.

do
echo ""

POLICY_ROOT_DIR=$(dirname "$POLICY")
POLICY_ERROR=false

if [[ "$PARAM" == "--e2e-test" ]]; then
runUpdatecliDiff "$POLICY_ROOT_DIR"
fi

if [[ "$PARAM" == "--unit-test" || "$PARAM" == "" ]]; then
validateRequiredFile "$POLICY_ROOT_DIR"
fi

if [[ "$POLICY_ERROR" = "false" ]]; then
echo " => all is good"

if [[ "$PARAM" == "--publish" ]]; then
release "$POLICY_ROOT_DIR"
fi

else
echo ""
echo " => validation test not passing"

GLOBAL_ERROR=1
fi

done

exit "$GLOBAL_ERROR"
}

main "${1:-}"
36 changes: 36 additions & 0 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: Validate Policies
on:
workflow_dispatch:
pull_request:
push:
jobs:
validate:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup updatecli
uses: updatecli/updatecli-action@2c3221bc5f4499a99fec2c87d9de4a83cb30e990 # v3.1.3
- name: Setup releasepost
uses: updatecli/releasepost-action@864390bddae97db06ee881ab4a08d159b4464643 # v0.5.0
- name: Validate
run: make test
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
# Only run e2e tests from the main branch as we need some credentials
# that we don't want to risk leaking from pullrequest opened by random contributors
if: github.ref == 'refs/heads/main'
id: generate_testing_token
with:
client-id: ${{ secrets.CONTAINERCRAFTER_APP_CLIENT_ID }}
private-key: ${{ secrets.CONTAINERCRAFTER_APP_PRIVATE_KEY }}
- name: e2e tests
# Only run e2e tests from the main branch as we need some credentials
# that we don't want to risk leaking from pullrequest opened by random contributors
if: github.ref == 'refs/heads/main'
run: make e2e-test
env:
GITHUB_TOKEN: ${{ steps.generate_testing_token.outputs.token }}
RELEASEPOST_GITHUB_TOKEN: ${{ steps.generate_testing_token.outputs.token }}
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.PHONY: release
release: ## Publish policies on ghcr.io
.ci/scripts/release.bash --publish

.PHONY: validate
test: ## Release checks for each policy if they can be published on ghcr.io
.ci/scripts/release.bash

.PHONY: validate
e2e-test: ## End-to-end checks for each policy if they can be published on ghcr.io
.ci/scripts/release.bash --e2e-test

.PHONY: help
help: ## Show this Makefile's help
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Loading