diff --git a/.github/workflows/json-schema-validation.yaml b/.github/workflows/json-schema-validation.yaml index 25b9606..8c02b18 100644 --- a/.github/workflows/json-schema-validation.yaml +++ b/.github/workflows/json-schema-validation.yaml @@ -19,6 +19,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Run schemalint id: run-schemalint uses: giantswarm/schemalint/actions/verify-helm-schema@43a8314fa06020e2651cffc1617f5262b3d921a4 # v2.6.3 @@ -32,6 +34,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Run helm-values-gen id: run-helm-values-gen diff --git a/.github/workflows/selftest-json-schema-validation.yaml b/.github/workflows/selftest-json-schema-validation.yaml new file mode 100644 index 0000000..a6cd185 --- /dev/null +++ b/.github/workflows/selftest-json-schema-validation.yaml @@ -0,0 +1,29 @@ +# Self-test for `json-schema-validation.yaml`. +# +# That workflow is consumed org-wide at `@main`, so a bad action pin merged here is live in +# every cluster app repo within a minute. The repo's other checks are static (yamllint, +# action-validator, zizmor) and cannot see a broken action: schemalint v2.6.2 changed its +# release assets from tarballs to raw binaries, its action kept using install-binary-action's +# default tarball URL, and the resulting 404 only surfaced in consumer repos. +# +# This calls the real reusable workflow against the fixture chart in `helm/`, so the action +# pins under test are exactly the ones that ship - there is no second copy to drift. + +name: Self-test JSON schema validation + +on: + pull_request: + paths: + - '.github/workflows/json-schema-validation.yaml' + - '.github/workflows/selftest-json-schema-validation.yaml' + - 'helm/**' + workflow_dispatch: {} + +permissions: {} + +jobs: + selftest: + name: Run json-schema-validation against the fixture chart + uses: ./.github/workflows/json-schema-validation.yaml + permissions: + contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 104bc59..fbcf009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), however this project does not use Semantic Versioning and there are no releases. Instead this file uses a date-based structure. +## 2026-08-05 + +### Added + +- `selftest-json-schema-validation.yaml` — new check that runs `json-schema-validation.yaml` itself against a fixture chart in `helm/selftest-cluster-app/` on pull requests that touch it. Renovate bumping `verify-helm-schema` to v2.6.2 broke every cluster app repo because schemalint's release assets changed from tarballs to raw binaries while its action still requested the tarball URL; nothing here could catch that, since this repo is consumed at `@main` and its only checks are static. + +### Changed + +- `json-schema-validation.yaml` — both `actions/checkout` steps now set `persist-credentials: false`, per the repository's action rules. Neither job uses git. ## 2026-08-06 ### Added diff --git a/helm/selftest-cluster-app/README.md b/helm/selftest-cluster-app/README.md new file mode 100644 index 0000000..fd36a2f --- /dev/null +++ b/helm/selftest-cluster-app/README.md @@ -0,0 +1,20 @@ +# Self-test fixture chart + +Not a real chart, and not published anywhere. This exists only so +`.github/workflows/selftest-json-schema-validation.yaml` has something for +`json-schema-validation.yaml` to run against on pull requests. + +Both composite actions that workflow calls locate their input with +`find ./helm -maxdepth 2 -name values.schema.json`, and a `workflow_call` job cannot be given a +working directory, so the fixture has to sit at the repository root under `helm/`. + +- `values.schema.json` — copied from schemalint's own + `pkg/lint/rulesets/testdata/cluster_azure.json` (its `rulesets_test.go` asserts zero errors + under the `cluster-app` rule set), then passed through `schemalint normalize`, which `verify` + also requires. The ~90 recommendations it reports are advisory and do not fail the run. +- `values.yaml` — the exact stdout of `helm-values-gen values.schema.json`. The `generate` job + diffs the two, so regenerate this file whenever the schema changes: + + ```bash + helm-values-gen helm/selftest-cluster-app/values.schema.json > helm/selftest-cluster-app/values.yaml + ``` diff --git a/helm/selftest-cluster-app/values.schema.json b/helm/selftest-cluster-app/values.schema.json new file mode 100644 index 0000000..bf013bd --- /dev/null +++ b/helm/selftest-cluster-app/values.schema.json @@ -0,0 +1,471 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Cluster configuration", + "description": "Configuration of an Azure cluster using Cluster API", + "additionalProperties": false, + "properties": { + "baseDomain": { + "type": "string", + "title": "Base DNS domain", + "default": "azuretest.gigantic.io" + }, + "cluster-shared": { + "type": "object", + "title": "Library chart" + }, + "connectivity": { + "type": "object", + "title": "Connectivity", + "properties": { + "bastion": { + "type": "object", + "title": "Bastion host", + "properties": { + "enabled": { + "type": "boolean", + "title": "Enable bastion host for this cluster", + "default": true + }, + "instanceType": { + "type": "string", + "title": "VM size", + "description": "Type of virtual machine to use for the bastion host.", + "default": "Standard_D2s_v5" + } + } + }, + "network": { + "type": "object", + "title": "Network", + "properties": { + "controlPlane": { + "type": "object", + "title": "Control plane", + "properties": { + "cidr": { + "type": "string", + "title": "Subnet", + "default": "10.0.0.0/20" + } + } + }, + "hostCidr": { + "type": "string", + "title": "Node subnet", + "description": "IPv4 address range for nodes, in CIDR notation.", + "default": "10.0.0.0/16" + }, + "mode": { + "type": "string", + "title": "Network mode", + "description": "Specifying if the cluster resources are publicly accessible or not.", + "enum": [ + "public", + "private" + ], + "default": "public" + }, + "podCidr": { + "type": "string", + "title": "Pod subnet", + "description": "IPv4 address range for pods, in CIDR notation.", + "default": "192.168.0.0/16" + }, + "serviceCidr": { + "type": "string", + "title": "Service subnet", + "description": "IPv4 address range for services, in CIDR notation.", + "default": "172.31.0.0/16" + }, + "workers": { + "type": "object", + "title": "Workers", + "properties": { + "cidr": { + "type": "string", + "title": "Subnet", + "default": "10.0.16.0/20" + } + } + } + } + }, + "sshSSOPublicKey": { + "type": "string", + "title": "SSH Public key for single sign-on", + "default": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM4cvZ01fLmO9cJbWUj7sfF+NhECgy+Cl0bazSrZX7sU vault-ca@vault.operations.giantswarm.io" + } + } + }, + "controlPlane": { + "type": "object", + "title": "Control plane", + "properties": { + "etcdVolumeSizeGB": { + "type": "integer", + "title": "Etcd volume size (GB)", + "default": 10 + }, + "instanceType": { + "type": "string", + "title": "Node VM size", + "default": "Standard_D4s_v3" + }, + "oidc": { + "type": "object", + "title": "OIDC authentication", + "properties": { + "caPem": { + "type": "string", + "title": "Certificate authority", + "description": "Identity provider's CA certificate in PEM format.", + "default": "" + }, + "clientId": { + "type": "string", + "title": "Client ID", + "default": "" + }, + "groupsClaim": { + "type": "string", + "title": "Groups claim", + "default": "" + }, + "issuerUrl": { + "type": "string", + "title": "Issuer URL", + "default": "" + }, + "usernameClaim": { + "type": "string", + "title": "Username claim", + "default": "" + } + } + }, + "replicas": { + "type": "integer", + "title": "Number of nodes", + "default": 3 + }, + "rootVolumeSizeGB": { + "type": "integer", + "title": "Root volume size (GB)", + "default": 50 + } + } + }, + "internal": { + "type": "object", + "title": "Internal settings", + "properties": { + "defaults": { + "type": "object", + "title": "Default settings", + "properties": { + "evictionMinimumReclaim": { + "type": "string", + "title": "Default settings for eviction minimum reclaim", + "default": "imagefs.available=5%,memory.available=100Mi,nodefs.available=5%" + }, + "hardEvictionThresholds": { + "type": "string", + "title": "Default settings for hard eviction thresholds", + "default": "memory.available<200Mi,nodefs.available<10%,nodefs.inodesFree<3%,imagefs.available<10%,pid.available<20%" + }, + "softEvictionGracePeriod": { + "type": "string", + "title": "Default settings for soft eviction grace period", + "default": "memory.available=30s,nodefs.available=2m,nodefs.inodesFree=1m,imagefs.available=2m,pid.available=1m" + }, + "softEvictionThresholds": { + "type": "string", + "title": "Default settings for soft eviction thresholds", + "default": "memory.available<500Mi,nodefs.available<15%,nodefs.inodesFree<5%,imagefs.available<15%,pid.available<30%" + } + } + }, + "identity": { + "type": "object", + "title": "Identity", + "properties": { + "type": { + "type": "string", + "title": "Type of Identity", + "enum": [ + "SystemAssigned", + "UserAssigned" + ], + "default": "SystemAssigned" + }, + "attachCapzControllerUserAssignedIdentity": { + "type": "boolean", + "title": "Attach CAPZ controller UserAssigned identity", + "default": false + }, + "userAssignedCustomIdentities": { + "type": "array", + "title": "List of custom UserAssigned Identities to attach to all nodes", + "default": [] + } + } + }, + "image": { + "type": "object", + "title": "Node Image", + "properties": { + "gallery": { + "type": "string", + "title": "Gallery", + "description": "Name of the community gallery hosting the image", + "default": "gsCapzFlatcar-41c2d140-ac44-4d8b-b7e1-7b2f1ddbe4d0" + }, + "name": { + "type": "string", + "title": "Image Definition", + "description": "Name of the image definition in the Gallery", + "default": "" + }, + "version": { + "type": "string", + "title": "Image version", + "default": "3374.2.4" + } + } + }, + "kubernetesVersion": { + "type": "string", + "title": "Kubernetes version", + "default": "1.24.11" + }, + "network": { + "type": "object", + "title": "Network configuration", + "description": "Internal network configuration that is susceptible to more frequent change", + "properties": { + "vpn": { + "type": "object", + "title": "VPN configuration", + "description": "Internal VPN configuration that is susceptible to more frequent change", + "properties": { + "gatewayMode": { + "type": "string", + "title": "VPN gateway mode", + "enum": [ + "local", + "remote" + ], + "default": "remote" + } + } + } + } + } + } + }, + "managementCluster": { + "type": "string", + "title": "The capi MC managing this cluster" + }, + "metadata": { + "type": "object", + "title": "Metadata", + "properties": { + "description": { + "type": "string", + "title": "Cluster description", + "description": "User-friendly description of the cluster's purpose." + }, + "name": { + "type": "string", + "title": "Cluster name", + "description": "Unique identifier, cannot be changed after creation." + }, + "organization": { + "type": "string", + "title": "Organization" + }, + "servicePriority": { + "type": "string", + "title": "Service priority", + "description": "The relative importance of this cluster.", + "$comment": "Defined in https://github.com/giantswarm/rfc/tree/main/classify-cluster-priority", + "enum": [ + "highest", + "medium", + "lowest" + ], + "default": "highest" + } + } + }, + "nodePools": { + "type": "array", + "title": "Node pools", + "items": { + "type": "object", + "title": "Node pool", + "properties": { + "customNodeLabels": { + "type": "array", + "title": "Custom node labels", + "items": { + "type": "string", + "title": "Label" + } + }, + "customNodeTaints": { + "type": "array", + "title": "Custom node taints", + "items": { + "type": "object", + "title": "Node taint", + "required": [ + "effect", + "key", + "value" + ], + "properties": { + "effect": { + "type": "string", + "title": "Effect", + "enum": [ + "NoSchedule", + "PreferNoSchedule", + "NoExecute" + ] + }, + "key": { + "type": "string", + "title": "Key" + }, + "value": { + "type": "string", + "title": "Value" + } + } + }, + "descriptions": "Taints that will be set on all nodes in the node pool, to avoid the scheduling of certain workloads." + }, + "disableHealthCheck": { + "type": "boolean", + "title": "Disable HealthChecks for the MachineDeployment" + }, + "failureDomain": { + "type": "string", + "title": "Select zone where to deploy the nodePool", + "enum": [ + "1", + "2", + "3" + ] + }, + "instanceType": { + "type": "string", + "title": "VM size" + }, + "name": { + "type": "string", + "title": "Name", + "description": "Unique identifier, cannot be changed after creation." + }, + "replicas": { + "type": "integer", + "title": "Number of nodes" + }, + "rootVolumeSizeGB": { + "type": "integer", + "title": "Root volume size (GB)" + } + } + }, + "default": [ + { + "customNodeLabels": [], + "customNodeTaints": [], + "disableHealthCheck": false, + "instanceType": "Standard_D2s_v3", + "name": "md00", + "replicas": 3, + "rootVolumeSizeGB": 50 + } + ] + }, + "provider": { + "type": "string", + "title": "Cluster API provider name" + }, + "providerSpecific": { + "type": "object", + "title": "Azure settings", + "properties": { + "azureClusterIdentity": { + "type": "object", + "title": "Identity", + "description": "AzureClusterIdentity resource to use for this cluster.", + "properties": { + "name": { + "type": "string", + "title": "Name", + "default": "cluster-identity" + }, + "namespace": { + "type": "string", + "title": "Namespace", + "default": "org-giantswarm" + } + } + }, + "location": { + "type": "string", + "title": "Location", + "default": "westeurope" + }, + "network": { + "type": "object", + "title": "Azure network settings", + "description": "Azure VNet peering and other Azure-specific network settings.", + "additionalProperties": false, + "properties": { + "peerings": { + "type": "array", + "title": "VNet peerings", + "description": "Specifying VNets (their resource groups and names) to which the peering is established.", + "items": { + "type": "object", + "title": "VNet peering", + "additionalProperties": false, + "properties": { + "remoteVnetName": { + "type": "string", + "title": "VNet name", + "description": "Name of the remote VNet to which the peering is established.", + "maxLength": 64, + "minLength": 2, + "pattern": "^[-\\w\\._]+$" + }, + "resourceGroup": { + "type": "string", + "title": "Resource group name", + "description": "Resource group for the remote VNet to which the peering is established.", + "maxLength": 90, + "minLength": 1, + "pattern": "^[-\\w\\._\\(\\)]+$" + } + }, + "uniqueItems": true + }, + "default": [] + } + } + }, + "subscriptionId": { + "type": "string", + "title": "Subscription ID", + "default": "PLACEHOLDER" + } + } + } + } +} diff --git a/helm/selftest-cluster-app/values.yaml b/helm/selftest-cluster-app/values.yaml new file mode 100644 index 0000000..1c48365 --- /dev/null +++ b/helm/selftest-cluster-app/values.yaml @@ -0,0 +1,64 @@ +# Auto-generated by helm-values-gen, do not edit! + +baseDomain: azuretest.gigantic.io +connectivity: + bastion: + enabled: true + instanceType: Standard_D2s_v5 + network: + controlPlane: + cidr: 10.0.0.0/20 + hostCidr: 10.0.0.0/16 + mode: public + podCidr: 192.168.0.0/16 + serviceCidr: 172.31.0.0/16 + workers: + cidr: 10.0.16.0/20 + sshSSOPublicKey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM4cvZ01fLmO9cJbWUj7sfF+NhECgy+Cl0bazSrZX7sU vault-ca@vault.operations.giantswarm.io +controlPlane: + etcdVolumeSizeGB: 10 + instanceType: Standard_D4s_v3 + oidc: + caPem: "" + clientId: "" + groupsClaim: "" + issuerUrl: "" + usernameClaim: "" + replicas: 3 + rootVolumeSizeGB: 50 +internal: + defaults: + evictionMinimumReclaim: imagefs.available=5%,memory.available=100Mi,nodefs.available=5% + hardEvictionThresholds: memory.available<200Mi,nodefs.available<10%,nodefs.inodesFree<3%,imagefs.available<10%,pid.available<20% + softEvictionGracePeriod: memory.available=30s,nodefs.available=2m,nodefs.inodesFree=1m,imagefs.available=2m,pid.available=1m + softEvictionThresholds: memory.available<500Mi,nodefs.available<15%,nodefs.inodesFree<5%,imagefs.available<15%,pid.available<30% + identity: + attachCapzControllerUserAssignedIdentity: false + type: SystemAssigned + userAssignedCustomIdentities: [] + image: + gallery: gsCapzFlatcar-41c2d140-ac44-4d8b-b7e1-7b2f1ddbe4d0 + name: "" + version: 3374.2.4 + kubernetesVersion: 1.24.11 + network: + vpn: + gatewayMode: remote +metadata: + servicePriority: highest +nodePools: + - customNodeLabels: [] + customNodeTaints: [] + disableHealthCheck: false + instanceType: Standard_D2s_v3 + name: md00 + replicas: 3 + rootVolumeSizeGB: 50 +providerSpecific: + azureClusterIdentity: + name: cluster-identity + namespace: org-giantswarm + location: westeurope + network: + peerings: [] + subscriptionId: PLACEHOLDER diff --git a/renovate.json5 b/renovate.json5 index e0e01b5..8ed7d24 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -3,6 +3,10 @@ extends: [ 'github>giantswarm/renovate-presets:default.json5', ], + // `helm/` holds a self-test fixture chart, not a real one. See helm/selftest-cluster-app/README.md. + ignorePaths: [ + 'helm/**', + ], customManagers: [ { description: 'Allow updating binaries in workflows installed via install-binary-action',