-
Notifications
You must be signed in to change notification settings - Fork 0
Add policies validation workflow #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| 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:-}" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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}' |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 $POLICIESdepends on word-splitting and globbing and will break if any path has spaces or wildcard chars. Consider iterating safely, e.g.:Suggested implementation:
POLICIES_ROOT_DIRis defined before callingmain(for example, the root directory under which policies live).POLICIESwas previously being set elsewhere (e.g., as a space-separated list), remove or adjust that logic so thatmapfileis the single source of truth for the list of policy files.