diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/SKILL.md b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/SKILL.md index 4b33d37..035c13e 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/SKILL.md +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/SKILL.md @@ -1,13 +1,13 @@ --- compatibility: Requires flux-operator-mcp description: | - Debug and troubleshoot Flux CD on live Kubernetes clusters (not local repo files) via the Flux MCP server — inspects Flux resource status, reads controller logs, traces dependency chains, and performs installation health checks. Use when users report failing, stuck, or not-ready Flux resources on a cluster, reconciliation errors, controller issues, artifact pull failures, or need live cluster Flux Operator troubleshooting. + Debug and troubleshoot Flux CD on live Kubernetes clusters (not local repo files) via the Flux MCP server — inspects Flux resource status, reads controller logs, traces dependency chains, and performs installation health checks. Use when users report failing, stuck, or not-ready Flux resources on a cluster, reconciliation errors, controller issues, artifact pull failures, image automation not updating tags, alerts or webhooks not being delivered, or need live cluster Flux Operator troubleshooting. license: Apache-2.0 metadata: github-path: skills/gitops-cluster-debug - github-ref: refs/tags/v0.0.4 + github-ref: refs/tags/v0.1.0 github-repo: https://github.com/fluxcd/agent-skills - github-tree-sha: 434d303add349e64129103242e7ec7d58d60f8a1 + github-tree-sha: 5d21c19fc629322cb58d56fe6fe23b9702becacc name: gitops-cluster-debug --- # Flux Cluster Debugger @@ -26,8 +26,9 @@ root causes. - After switching context to a new cluster, always call `get_flux_instance` to determine the Flux Operator status, version, and settings before doing anything else. - When creating or updating resources on the cluster, generate a Kubernetes YAML manifest - and call the `apply_kubernetes_resource` tool. Do not apply resources unless explicitly - requested by the user. Before generating any YAML manifest, read the relevant OpenAPI + and call the `apply_kubernetes_manifest` tool. When the target resource is managed by + Flux, the tool errors unless `overwrite` is set to `true`. Do not apply resources unless + explicitly requested by the user. Before generating any YAML manifest, read the relevant OpenAPI schema from `assets/schemas/` to verify the exact field names and nesting. Schema files follow the naming convention `{kind}-{group}-{version}.json` (see the CRD reference table below). @@ -123,7 +124,71 @@ Follow these steps when troubleshooting a ResourceSet: 7. Create a root cause analysis report. Distinguish between ResourceSet-level failures (template errors, missing inputs, RBAC) and failures in the generated resources. -### Workflow 5: Kubernetes Logs Analysis +### Workflow 5: Source Debugging + +Follow these steps when a source (GitRepository, OCIRepository, HelmRepository, +HelmChart, Bucket) reports `FetchFailed` or downstream resources are stuck on +an old revision: + +1. Call `get_flux_instance` to check the source-controller deployment status and + the `apiVersion` of the source kind. +2. Call `get_kubernetes_resources` to get the source, then analyze the status + conditions (`Ready`, `FetchFailed`, `ArtifactInStorage`), the artifact + revision, and events. +3. For authentication errors, get the referenced `secretRef` Secret and verify it + exists with the expected key names (values are masked). For cloud registries + with no secret, check `.spec.provider` and workload identity. +4. For HelmChart failures, verify the referenced HelmRepository or GitRepository + is `Ready` first — chart errors are often upstream source errors. +5. Compare the last reconcile time against `.spec.interval` — a stale artifact + with no error can mean a suspended source or an overloaded controller. +6. Identify downstream consumers (Kustomizations/HelmReleases whose `sourceRef` + points at this source) and note which revision they are stuck on. +7. Create a root cause analysis report. Load `references/troubleshooting.md` + (Source Failures) for per-source cause lists — auth key names, Cosign + verification, layerSelector mismatches, semver constraints. + +### Workflow 6: Image Automation Debugging + +Follow these steps when image tags are not being detected or no update commits +appear in Git: + +1. Call `get_flux_instance` and verify `image-reflector-controller` and + `image-automation-controller` are listed in the components and running. +2. Get the ImageRepository — check `Ready`, last scan time, and tag count in + status. Auth failures point to the `secretRef` or `.spec.provider`. +3. Get the ImagePolicy — check `Ready` and `status.latestImage`. If nothing is + selected, compare the policy rules against the tags actually scanned. +4. Get the ImageUpdateAutomation — check `Ready`, last push time, and events. + Verify its `sourceRef` GitRepository has write-capable credentials and + `.spec.git.push.branch` is the branch the user is watching. +5. If everything is `Ready` but no commits appear: verify manifests under + `.spec.update.path` contain `$imagepolicy` markers for the right + `:` and that `latestImage` differs from Git. +6. Create a root cause analysis report tracing ImageRepository → ImagePolicy → + ImageUpdateAutomation → GitRepository. + +### Workflow 7: Notification Debugging + +Follow these steps when alerts are not being delivered or a webhook Receiver +does not trigger reconciliation: + +1. Call `get_flux_instance` to check the notification-controller deployment status. +2. Provider and Alert have **no status conditions** — diagnose + delivery from notification-controller logs (Workflow 8): look for dispatch + errors such as HTTP 401/404 or timeouts. +3. Get the Alert and verify `.spec.eventSources` matches the resources expected + to produce events and `.spec.eventSeverity` is not filtering them out. +4. Get the referenced Provider and verify `.spec.type`, `.spec.address`, and the + `secretRef` Secret key names. +5. For Receivers (these do have a `Ready` condition): verify `status.webhookPath` + and the webhook Secret, then check logs for incoming requests to that path — + none means the external service is not calling the webhook. +6. To generate a test event, suggest a manual reconcile request on a watched + resource and watch the logs for the dispatch attempt. Load + `references/troubleshooting.md` (Notification Failures) for cause lists. + +### Workflow 8: Kubernetes Logs Analysis When analyzing logs for any workload: diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/alert-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/alert-notification-v1beta2.json deleted file mode 100644 index 9ddaddb..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/alert-notification-v1beta2.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "description": "Alert is the Schema for the alerts API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "AlertSpec defines an alerting rule for events involving a list of objects.", - "properties": { - "eventMetadata": { - "additionalProperties": { - "type": "string" - }, - "description": "EventMetadata is an optional field for adding metadata to events dispatched by the\ncontroller. This can be used for enhancing the context of the event. If a field\nwould override one already present on the original event as generated by the emitter,\nthen the override doesn't happen, i.e. the original value is preserved, and an info\nlog is printed.", - "type": "object" - }, - "eventSeverity": { - "default": "info", - "description": "EventSeverity specifies how to filter events based on severity.\nIf set to 'info' no events will be filtered.", - "enum": [ - "info", - "error" - ], - "type": "string" - }, - "eventSources": { - "description": "EventSources specifies how to filter events based\non the involved object kind, name and namespace.", - "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", - "properties": { - "apiVersion": { - "description": "API version of the referent", - "type": "string" - }, - "kind": { - "description": "Kind of the referent", - "enum": [ - "Bucket", - "GitRepository", - "Kustomization", - "HelmRelease", - "HelmChart", - "HelmRepository", - "ImageRepository", - "ImagePolicy", - "ImageUpdateAutomation", - "OCIRepository", - "ArtifactGenerator", - "ExternalArtifact" - ], - "type": "string" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\nMatchLabels requires the name to be set to `*`.", - "type": "object" - }, - "name": { - "description": "Name of the referent\nIf multiple resources are targeted `*` may be set.", - "maxLength": 253, - "minLength": 1, - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent", - "maxLength": 253, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "exclusionList": { - "description": "ExclusionList specifies a list of Golang regular expressions\nto be used for excluding messages.", - "items": { - "type": "string" - }, - "type": "array" - }, - "inclusionList": { - "description": "InclusionList specifies a list of Golang regular expressions\nto be used for including messages.", - "items": { - "type": "string" - }, - "type": "array" - }, - "providerRef": { - "description": "ProviderRef specifies which Provider this Alert should use.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "summary": { - "description": "Summary holds a short description of the impact and affected cluster.", - "maxLength": 255, - "type": "string" - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this Alert.", - "type": "boolean" - } - }, - "required": [ - "eventSources", - "providerRef" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "AlertStatus defines the observed state of the Alert.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Alert.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last observed generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/artifactgenerator-source-v1beta1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/artifactgenerator-source-v1beta1.json index 014c6ad..b1c9351 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/artifactgenerator-source-v1beta1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/artifactgenerator-source-v1beta1.json @@ -25,7 +25,7 @@ "items": { "properties": { "exclude": { - "description": "Exclude specifies a list of glob patterns to exclude\nfiles and dirs matched by the 'From' field.", + "description": "Exclude specifies a list of glob patterns to exclude\nfiles and dirs matched by the 'From' field. Patterns are matched\nagainst paths relative to the source alias root or to the non-glob\nprefix of 'From'. Patterns without a separator (e.g. \"*.md\") match\nthe file name at any depth.", "items": { "type": "string" }, @@ -33,8 +33,9 @@ "type": "array" }, "from": { - "description": "From specifies the source (by alias) and the glob pattern to match files.\nThe format is \"@/\".", + "description": "From specifies the source (by alias) and the glob pattern to match files.\nThe format is \"@/\". When pathPattern is set,\nthe path may use capture placeholders such as \"{app}\".", "maxLength": 1024, + "minLength": 1, "pattern": "^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$", "type": "string" }, @@ -48,8 +49,9 @@ "type": "string" }, "to": { - "description": "To specifies the destination path within the artifact.\nThe format is \"@artifact/path\", the alias \"artifact\"\nrefers to the root path of the generated artifact.", + "description": "To specifies the destination path within the artifact.\nThe format is \"@artifact/path\", the alias \"artifact\"\nrefers to the root path of the generated artifact. When pathPattern\nis set, the path may use capture placeholders such as \"{app}\".", "maxLength": 1024, + "minLength": 1, "pattern": "^@(artifact)/(.*)$", "type": "string" } @@ -65,9 +67,9 @@ "type": "array" }, "name": { - "description": "Name is the name of the generated artifact.", + "description": "Name is the name of the generated artifact.\nWhen pathPattern is set, this field may use capture placeholders such as \"{app}\".", "maxLength": 253, - "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "minLength": 1, "type": "string" }, "originRevision": { @@ -94,6 +96,33 @@ "minItems": 1, "type": "array" }, + "commonMetadata": { + "description": "CommonMetadata specifies the common labels and annotations that are\napplied to all resources. Any existing label or annotation will be\noverridden if its key matches a common one.", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to the object's metadata.", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to be added to the object's metadata.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "pathPattern": { + "description": "PathPattern specifies a directory traversal pattern to match within the sources.\nThe format is \"@/\". Named captures in the pattern (e.g. \"{app}\")\ncan be used as placeholders in OutputArtifacts fields.", + "maxLength": 1024, + "pattern": "^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$", + "type": "string" + }, "sources": { "description": "Sources is a list of references to the Flux source-controller\nresources that will be used to generate the artifact.", "items": { @@ -148,6 +177,12 @@ "sources" ], "type": "object", + "x-kubernetes-validations": [ + { + "message": "artifact names must be valid Kubernetes object names when pathPattern is not set", + "rule": "has(self.pathPattern) && size(self.pathPattern) > 0 || self.artifacts.all(a, a.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'))" + } + ], "additionalProperties": false }, "status": { diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/fluxinstance-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/fluxinstance-fluxcd-v1.json index 1635135..63206b8 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/fluxinstance-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/fluxinstance-fluxcd-v1.json @@ -249,7 +249,7 @@ "type": "array" }, "storage": { - "description": "Storage defines if the source-controller shards\nshould use an emptyDir or a persistent volume claim for storage.\nAccepted values are 'ephemeral' or 'persistent', defaults to 'ephemeral'.\nFor 'persistent' to take effect, the '.spec.storage' field must be set.", + "description": "Storage defines if the source-controller shards\nshould use an emptyDir or a persistent volume claim for storage.\nAccepted values are 'ephemeral' or 'persistent', defaults to 'ephemeral'.\nWhen set to 'persistent', the '.spec.storage' field must be set.", "enum": [ "ephemeral", "persistent" @@ -366,7 +366,13 @@ "required": [ "distribution" ], - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": ".spec.storage must be set when .spec.sharding.storage is 'persistent'", + "rule": "!has(self.sharding) || !has(self.sharding.storage) || self.sharding.storage != 'persistent' || has(self.storage)" + } + ] }, "status": { "additionalProperties": false, diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/gitrepository-source-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/gitrepository-source-v1.json index 576baae..bcef2e3 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/gitrepository-source-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/gitrepository-source-v1.json @@ -61,9 +61,10 @@ "type": "string" }, "provider": { - "description": "Provider used for authentication, can be 'azure', 'github', 'generic'.\nWhen not specified, defaults to 'generic'.", + "description": "Provider used for authentication, can be 'aws', 'azure', 'github', 'generic'.\nWhen not specified, defaults to 'generic'.", "enum": [ "generic", + "aws", "azure", "github" ], @@ -129,7 +130,7 @@ "additionalProperties": false }, "serviceAccountName": { - "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to\nauthenticate to the GitRepository. This field is only supported for 'azure' provider.", + "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to\nauthenticate to the GitRepository. This field is only supported for 'azure' and 'aws' providers.", "type": "string" }, "sparseCheckout": { @@ -169,7 +170,7 @@ "type": "string" }, "secretRef": { - "description": "SecretRef specifies the Secret containing the public keys of trusted Git\nauthors.", + "description": "SecretRef specifies the Secret containing the public keys of trusted Git\nauthors. PGP public keys must be stored under keys with the .asc suffix,\nand SSH public keys must be stored under keys with the .sshpub suffix.", "properties": { "name": { "description": "Name of the referent.", @@ -197,8 +198,8 @@ "type": "object", "x-kubernetes-validations": [ { - "message": "serviceAccountName can only be set when provider is 'azure'", - "rule": "!has(self.serviceAccountName) || (has(self.provider) && self.provider == 'azure')" + "message": "serviceAccountName can only be set when provider is 'azure' or 'aws'", + "rule": "!has(self.serviceAccountName) || (has(self.provider) && (self.provider == 'azure' || self.provider == 'aws'))" } ], "additionalProperties": false diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/helmrelease-helm-v2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/helmrelease-helm-v2.json index 5cd8695..9401cba 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/helmrelease-helm-v2.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/helmrelease-helm-v2.json @@ -222,14 +222,14 @@ "dependsOn": { "description": "DependsOn may contain a DependencyReference slice with\nreferences to HelmRelease resources that must be ready before this HelmRelease\ncan be reconciled.", "items": { - "description": "DependencyReference defines a HelmRelease dependency on another HelmRelease resource.", + "description": "DependencyReference contains enough information to locate the referenced Kubernetes resource object\nand optional CEL expression to assess its readiness.", "properties": { "name": { "description": "Name of the referent.", "type": "string" }, "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the HelmRelease\nresource object that contains the reference.", + "description": "Namespace of the referent, defaults to the namespace of the resource\nobject that contains the reference.", "type": "string" }, "readyExpr": { @@ -345,8 +345,7 @@ }, "required": [ "apiVersion", - "current", - "kind" + "current" ], "type": "object", "additionalProperties": false @@ -524,6 +523,15 @@ "description": "PersistentClient tells the controller to use a persistent Kubernetes\nclient for this release. When enabled, the client will be reused for the\nduration of the reconciliation, instead of being created and destroyed\nfor each (step of a) Helm action.\n\nThis can improve performance, but may cause issues with some Helm charts\nthat for example do create Custom Resource Definitions during installation\noutside Helm's CRD lifecycle hooks, which are then not observed to be\navailable by e.g. post-install hooks.\n\nIf not set, it defaults to true.", "type": "boolean" }, + "postRenderStrategy": { + "description": "PostRenderStrategy defines the strategy for sending hooks to post-renderers.\nValid values are 'nohooks' (hooks not sent to post-renderers, Helm 3 behavior),\n'combined' (hooks and templates sent together, Helm 4 default), and 'separate'\n(hooks and templates sent in separate streams, Helm 4.2 opt-in).\nDefaults to 'combined', or 'nohooks' when the UseHelm3Defaults feature gate is enabled.", + "enum": [ + "nohooks", + "combined", + "separate" + ], + "type": "string" + }, "postRenderers": { "description": "PostRenderers holds an array of Helm PostRenderers, which will be applied in order\nof their definition.", "items": { @@ -784,6 +792,14 @@ "upgrade": { "description": "Upgrade holds the configuration for Helm upgrade actions for this HelmRelease.", "properties": { + "chartNameChangeStrategy": { + "description": "ChartNameChangeStrategy defines the strategy to use when a Helm chart name changes.\nValid values are 'Reinstall' or 'InPlaceUpdate'. Defaults to 'Reinstall' if omitted.\n\nReinstall: Reinstall the Helm release, uninstalling the existing Helm release.\n\nInPlaceUpdate: Update the Helm release in place.", + "enum": [ + "InPlaceUpdate", + "Reinstall" + ], + "type": "string" + }, "cleanupOnFail": { "description": "CleanupOnFail allows deletion of new resources created during the Helm\nupgrade action when it fails.", "type": "boolean" @@ -920,6 +936,10 @@ ], "type": "string" }, + "literal": { + "description": "Literal marks this ValuesReference as a literal value. When set in\ncombination with TargetPath, the referenced value is merged at the target\npath without interpreting Helm's `--set` syntax (commas, brackets, dots,\nequal signs, etc.), mirroring the behavior of `helm --set-literal`. This\nis the only safe way to inject arbitrary file content (config files, JSON\nblobs, multi-line strings containing special characters) through\n`valuesFrom`. Has no effect when TargetPath is empty: in that mode the\nreferenced value is always YAML-merged at the root.", + "type": "boolean" + }, "name": { "description": "Name of the values referent. Should reside in the same namespace as the\nreferring resource.", "maxLength": 253, diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imagepolicy-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imagepolicy-image-v1beta2.json deleted file mode 100644 index a4aee09..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imagepolicy-image-v1beta2.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "description": "ImagePolicy is the Schema for the imagepolicies API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImagePolicySpec defines the parameters for calculating the\nImagePolicy.", - "properties": { - "digestReflectionPolicy": { - "default": "Never", - "description": "DigestReflectionPolicy governs the setting of the `.status.latestRef.digest` field.\n\nNever: The digest field will always be set to the empty string.\n\nIfNotPresent: The digest field will be set to the digest of the elected\nlatest image if the field is empty and the image did not change.\n\nAlways: The digest field will always be set to the digest of the elected\nlatest image.\n\nDefault: Never.", - "enum": [ - "Always", - "IfNotPresent", - "Never" - ], - "type": "string" - }, - "filterTags": { - "description": "FilterTags enables filtering for only a subset of tags based on a set of\nrules. If no rules are provided, all the tags from the repository will be\nordered and compared.", - "properties": { - "extract": { - "description": "Extract allows a capture group to be extracted from the specified regular\nexpression pattern, useful before tag evaluation.", - "type": "string" - }, - "pattern": { - "description": "Pattern specifies a regular expression pattern used to filter for image\ntags.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "imageRepositoryRef": { - "description": "ImageRepositoryRef points at the object specifying the image\nbeing scanned", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent, when not specified it acts as LocalObjectReference.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "interval": { - "description": "Interval is the length of time to wait between\nrefreshing the digest of the latest tag when the\nreflection policy is set to \"Always\".\n\nDefaults to 10m.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "policy": { - "description": "Policy gives the particulars of the policy to be followed in\nselecting the most recent image", - "properties": { - "alphabetical": { - "description": "Alphabetical set of rules to use for alphabetical ordering of the tags.", - "properties": { - "order": { - "default": "asc", - "description": "Order specifies the sorting order of the tags. Given the letters of the\nalphabet as tags, ascending order would select Z, and descending order\nwould select A.", - "enum": [ - "asc", - "desc" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "numerical": { - "description": "Numerical set of rules to use for numerical ordering of the tags.", - "properties": { - "order": { - "default": "asc", - "description": "Order specifies the sorting order of the tags. Given the integer values\nfrom 0 to 9 as tags, ascending order would select 9, and descending order\nwould select 0.", - "enum": [ - "asc", - "desc" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "semver": { - "description": "SemVer gives a semantic version range to check against the tags\navailable.", - "properties": { - "range": { - "description": "Range gives a semver range for the image tag; the highest\nversion within the range that's a tag yields the latest image.", - "type": "string" - } - }, - "required": [ - "range" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "This flag tells the controller to suspend subsequent policy reconciliations.\nIt does not apply to already started reconciliations. Defaults to false.", - "type": "boolean" - } - }, - "required": [ - "imageRepositoryRef", - "policy" - ], - "type": "object", - "x-kubernetes-validations": [ - { - "message": "spec.interval is only accepted when spec.digestReflectionPolicy is set to 'Always'", - "rule": "!has(self.interval) || (has(self.digestReflectionPolicy) && self.digestReflectionPolicy == 'Always')" - }, - { - "message": "spec.interval must be set when spec.digestReflectionPolicy is set to 'Always'", - "rule": "has(self.interval) || !has(self.digestReflectionPolicy) || self.digestReflectionPolicy != 'Always'" - } - ], - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImagePolicyStatus defines the observed state of ImagePolicy", - "properties": { - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "latestRef": { - "description": "LatestRef gives the first in the list of images scanned by\nthe image repository, when filtered and ordered according\nto the policy.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - }, - "observedGeneration": { - "format": "int64", - "type": "integer" - }, - "observedPreviousRef": { - "description": "ObservedPreviousRef is the observed previous LatestRef. It is used\nto keep track of the previous and current images.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imagerepository-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imagerepository-image-v1beta2.json deleted file mode 100644 index cfb868d..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imagerepository-image-v1beta2.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "description": "ImageRepository is the Schema for the imagerepositories API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImageRepositorySpec defines the parameters for scanning an image\nrepository, e.g., `fluxcd/flux`.", - "properties": { - "accessFrom": { - "description": "AccessFrom defines an ACL for allowing cross-namespace references\nto the ImageRepository object based on the caller's namespace labels.", - "properties": { - "namespaceSelectors": { - "description": "NamespaceSelectors is the list of namespace selectors to which this ACL applies.\nItems in this list are evaluated using a logical OR operation.", - "items": { - "description": "NamespaceSelector selects the namespaces to which this ACL applies.\nAn empty map of MatchLabels matches all namespaces in a cluster.", - "properties": { - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object" - } - }, - "type": "object", - "additionalProperties": false - }, - "type": "array" - } - }, - "required": [ - "namespaceSelectors" - ], - "type": "object", - "additionalProperties": false - }, - "certSecretRef": { - "description": "CertSecretRef can be given the name of a Secret containing\neither or both of\n\n- a PEM-encoded client certificate (`tls.crt`) and private\nkey (`tls.key`);\n- a PEM-encoded CA certificate (`ca.crt`)\n\nand whichever are supplied, will be used for connecting to the\nregistry. The client cert and key are useful if you are\nauthenticating with a certificate; the CA cert is useful if\nyou are using a self-signed server certificate. The Secret must\nbe of type `Opaque` or `kubernetes.io/tls`.\n\nNote: Support for the `caFile`, `certFile` and `keyFile` keys has\nbeen deprecated.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "exclusionList": { - "default": [ - "^.*\\.sig$" - ], - "description": "ExclusionList is a list of regex strings used to exclude certain tags\nfrom being stored in the database.", - "items": { - "type": "string" - }, - "maxItems": 25, - "type": "array" - }, - "image": { - "description": "Image is the name of the image repository", - "type": "string" - }, - "insecure": { - "description": "Insecure allows connecting to a non-TLS HTTP container registry.", - "type": "boolean" - }, - "interval": { - "description": "Interval is the length of time to wait between\nscans of the image repository.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "provider": { - "default": "generic", - "description": "The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.\nWhen not specified, defaults to 'generic'.", - "enum": [ - "generic", - "aws", - "azure", - "gcp" - ], - "type": "string" - }, - "proxySecretRef": { - "description": "ProxySecretRef specifies the Secret containing the proxy configuration\nto use while communicating with the container registry.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "secretRef": { - "description": "SecretRef can be given the name of a secret containing\ncredentials to use for the image registry. The secret should be\ncreated with `kubectl create secret docker-registry`, or the\nequivalent.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "serviceAccountName": { - "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate\nthe image pull if the service account has attached pull secrets.", - "maxLength": 253, - "type": "string" - }, - "suspend": { - "description": "This flag tells the controller to suspend subsequent image scans.\nIt does not apply to already started scans. Defaults to false.", - "type": "boolean" - }, - "timeout": { - "description": "Timeout for image scanning.\nDefaults to 'Interval' duration.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m))+$", - "type": "string" - } - }, - "required": [ - "image", - "interval" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImageRepositoryStatus defines the observed state of ImageRepository", - "properties": { - "canonicalImageName": { - "description": "CanonicalName is the name of the image repository with all the\nimplied bits made explicit; e.g., `docker.io/library/alpine`\nrather than `alpine`.", - "type": "string" - }, - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "lastScanResult": { - "description": "LastScanResult contains the number of fetched tags.", - "properties": { - "latestTags": { - "description": "LatestTags is a small sample of the tags found in the last scan.\nIt's the first 10 tags when sorting all the tags in descending\nalphabetical order.", - "items": { - "type": "string" - }, - "type": "array" - }, - "revision": { - "description": "Revision is a stable hash of the scanned tags.", - "type": "string" - }, - "scanTime": { - "description": "ScanTime is the time when the last scan was performed.", - "format": "date-time", - "type": "string" - }, - "tagCount": { - "description": "TagCount is the number of tags found in the last scan.", - "type": "integer" - } - }, - "required": [ - "tagCount" - ], - "type": "object", - "additionalProperties": false - }, - "observedExclusionList": { - "description": "ObservedExclusionList is a list of observed exclusion list. It reflects\nthe exclusion rules used for the observed scan result in\nspec.lastScanResult.", - "items": { - "type": "string" - }, - "type": "array" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last reconciled generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1.json index 54da093..957d586 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1.json @@ -88,10 +88,10 @@ "type": "object" }, "signingKey": { - "description": "SigningKey provides the option to sign commits with a GPG key", + "description": "SigningKey provides the option to sign commits with an OpenPGP or\nSSH signing key, referenced from a Secret. See SigningKey.", "properties": { "secretRef": { - "description": "SecretRef holds the name to a secret that contains a 'git.asc' key\ncorresponding to the ASCII Armored file containing the GPG signing\nkeypair as the value. It must be in the same namespace as the\nImageUpdateAutomation.", + "description": "SecretRef references a Secret containing the signing key. For type\n'gpg', the Secret must contain a 'git.asc' (ASCII-armored OpenPGP\nkeypair) and may contain a 'passphrase'. For type 'ssh', the Secret\nmust contain an 'identity' (an SSH private key in any format\ngolang.org/x/crypto/ssh.ParsePrivateKey accepts; typically the\nOpenSSH format produced by 'ssh-keygen') and may contain a 'password'\n(the key's passphrase). The SSH conventions match the GitRepository\nSSH transport-auth Secret format, allowing a single Secret to serve\nboth transport and signing when the ImageUpdateAutomation lives in\nthe same namespace as the GitRepository.\n\nThe Secret itself must live in the same namespace as the\nImageUpdateAutomation.\n\nSupported SSH key algorithms: ed25519, ecdsa-sha2-nistp256/384/521,\nand rsa (>= 2048-bit).", "properties": { "name": { "description": "Name of the referent.", @@ -103,6 +103,14 @@ ], "type": "object", "additionalProperties": false + }, + "type": { + "description": "Type selects the signing-key format expected in the referenced\nSecret. When empty, the controller defaults to 'gpg'.", + "enum": [ + "gpg", + "ssh" + ], + "type": "string" } }, "required": [ diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1beta2.json deleted file mode 100644 index 54da093..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/imageupdateautomation-image-v1beta2.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "description": "ImageUpdateAutomation is the Schema for the imageupdateautomations API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation", - "properties": { - "git": { - "description": "GitSpec contains all the git-specific definitions. This is\ntechnically optional, but in practice mandatory until there are\nother kinds of source allowed.", - "properties": { - "checkout": { - "description": "Checkout gives the parameters for cloning the git repository,\nready to make changes. If not present, the `spec.ref` field from the\nreferenced `GitRepository` or its default will be used.", - "properties": { - "ref": { - "description": "Reference gives a branch, tag or commit to clone from the Git\nrepository.", - "properties": { - "branch": { - "description": "Branch to check out, defaults to 'master' if no other field is defined.", - "type": "string" - }, - "commit": { - "description": "Commit SHA to check out, takes precedence over all reference fields.\n\nThis can be combined with Branch to shallow clone the branch, in which\nthe commit is expected to exist.", - "type": "string" - }, - "name": { - "description": "Name of the reference to check out; takes precedence over Branch, Tag and SemVer.\n\nIt must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description\nExamples: \"refs/heads/main\", \"refs/tags/v0.1.0\", \"refs/pull/420/head\", \"refs/merge-requests/1/head\"", - "type": "string" - }, - "semver": { - "description": "SemVer tag expression to check out, takes precedence over Tag.", - "type": "string" - }, - "tag": { - "description": "Tag to check out, takes precedence over Branch.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "ref" - ], - "type": "object", - "additionalProperties": false - }, - "commit": { - "description": "Commit specifies how to commit to the git repository.", - "properties": { - "author": { - "description": "Author gives the email and optionally the name to use as the\nauthor of commits.", - "properties": { - "email": { - "description": "Email gives the email to provide when making a commit.", - "type": "string" - }, - "name": { - "description": "Name gives the name to provide when making a commit.", - "type": "string" - } - }, - "required": [ - "email" - ], - "type": "object", - "additionalProperties": false - }, - "messageTemplate": { - "description": "MessageTemplate provides a template for the commit message,\ninto which will be interpolated the details of the change made.\nNote: The `Updated` template field has been removed. Use `Changed` instead.", - "type": "string" - }, - "messageTemplateValues": { - "additionalProperties": { - "type": "string" - }, - "description": "MessageTemplateValues provides additional values to be available to the\ntemplating rendering.", - "type": "object" - }, - "signingKey": { - "description": "SigningKey provides the option to sign commits with a GPG key", - "properties": { - "secretRef": { - "description": "SecretRef holds the name to a secret that contains a 'git.asc' key\ncorresponding to the ASCII Armored file containing the GPG signing\nkeypair as the value. It must be in the same namespace as the\nImageUpdateAutomation.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "secretRef" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "author" - ], - "type": "object", - "additionalProperties": false - }, - "push": { - "description": "Push specifies how and where to push commits made by the\nautomation. If missing, commits are pushed (back) to\n`.spec.checkout.branch` or its default.", - "properties": { - "branch": { - "description": "Branch specifies that commits should be pushed to the branch\nnamed. The branch is created using `.spec.checkout.branch` as the\nstarting point, if it doesn't already exist.", - "type": "string" - }, - "options": { - "additionalProperties": { - "type": "string" - }, - "description": "Options specifies the push options that are sent to the Git\nserver when performing a push operation. For details, see:\nhttps://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt", - "type": "object" - }, - "refspec": { - "description": "Refspec specifies the Git Refspec to use for a push operation.\nIf both Branch and Refspec are provided, then the commit is pushed\nto the branch and also using the specified refspec.\nFor more details about Git Refspecs, see:\nhttps://git-scm.com/book/en/v2/Git-Internals-The-Refspec", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "commit" - ], - "type": "object", - "additionalProperties": false - }, - "interval": { - "description": "Interval gives an lower bound for how often the automation\nrun should be attempted.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "policySelector": { - "description": "PolicySelector allows to filter applied policies based on labels.\nBy default includes all policies in namespace.", - "properties": { - "matchExpressions": { - "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", - "items": { - "description": "A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values.", - "properties": { - "key": { - "description": "key is the label key that the selector applies to.", - "type": "string" - }, - "operator": { - "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist.", - "type": "string" - }, - "values": { - "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch.", - "items": { - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - } - }, - "required": [ - "key", - "operator" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object" - } - }, - "type": "object", - "x-kubernetes-map-type": "atomic", - "additionalProperties": false - }, - "sourceRef": { - "description": "SourceRef refers to the resource giving access details\nto a git repository.", - "properties": { - "apiVersion": { - "description": "API version of the referent.", - "type": "string" - }, - "kind": { - "default": "GitRepository", - "description": "Kind of the referent.", - "enum": [ - "GitRepository" - ], - "type": "string" - }, - "name": { - "description": "Name of the referent.", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.", - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to not run this automation, until\nit is unset (or set to false). Defaults to false.", - "type": "boolean" - }, - "update": { - "default": { - "strategy": "Setters" - }, - "description": "Update gives the specification for how to update the files in\nthe repository. This can be left empty, to use the default\nvalue.", - "properties": { - "path": { - "description": "Path to the directory containing the manifests to be updated.\nDefaults to 'None', which translates to the root path\nof the GitRepositoryRef.", - "type": "string" - }, - "strategy": { - "default": "Setters", - "description": "Strategy names the strategy to be used.", - "enum": [ - "Setters" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "interval", - "sourceRef" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation", - "properties": { - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastAutomationRunTime": { - "description": "LastAutomationRunTime records the last time the controller ran\nthis automation through to completion (even if no updates were\nmade).", - "format": "date-time", - "type": "string" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "lastPushCommit": { - "description": "LastPushCommit records the SHA1 of the last commit made by the\ncontroller, for this automation object", - "type": "string" - }, - "lastPushTime": { - "description": "LastPushTime records the time of the last pushed change.", - "format": "date-time", - "type": "string" - }, - "observedGeneration": { - "format": "int64", - "type": "integer" - }, - "observedPolicies": { - "additionalProperties": { - "description": "ImageRef represents an image reference.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - }, - "description": "ObservedPolicies is the list of observed ImagePolicies that were\nconsidered by the ImageUpdateAutomation update process.", - "type": "object" - }, - "observedSourceRevision": { - "description": "ObservedPolicies []ObservedPolicy `json:\"observedPolicies,omitempty\"`\nObservedSourceRevision is the last observed source revision. This can be\nused to determine if the source has been updated since last observation.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/kustomization-kustomize-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/kustomization-kustomize-v1.json index fcba8b6..abd3909 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/kustomization-kustomize-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/kustomization-kustomize-v1.json @@ -15,6 +15,18 @@ "spec": { "description": "KustomizationSpec defines the configuration to calculate the desired state\nfrom a Source using Kustomize.", "properties": { + "buildMetadata": { + "description": "BuildMetadata specifies which kustomize build metadata should be added\nto the built resources. The allowed values are 'originAnnotations' to\nannotate resources with their source origin, and 'transformerAnnotations'\nto annotate resources with the transformers that produced them.", + "items": { + "description": "BuildMetadataOption defines the supported buildMetadata options.", + "enum": [ + "originAnnotations", + "transformerAnnotations" + ], + "type": "string" + }, + "type": "array" + }, "commonMetadata": { "description": "CommonMetadata specifies the common labels and annotations that are\napplied to all resources. Any existing label or annotation will be\noverridden if its key matches a common one.", "properties": { @@ -91,14 +103,14 @@ "dependsOn": { "description": "DependsOn may contain a DependencyReference slice\nwith references to Kustomization resources that must be ready before this\nKustomization can be reconciled.", "items": { - "description": "DependencyReference defines a Kustomization dependency on another Kustomization resource.", + "description": "DependencyReference contains enough information to locate the referenced Kubernetes resource object\nand optional CEL expression to assess its readiness.", "properties": { "name": { "description": "Name of the referent.", "type": "string" }, "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the Kustomization\nresource object that contains the reference.", + "description": "Namespace of the referent, defaults to the namespace of the resource\nobject that contains the reference.", "type": "string" }, "readyExpr": { @@ -147,8 +159,7 @@ }, "required": [ "apiVersion", - "current", - "kind" + "current" ], "type": "object", "additionalProperties": false @@ -186,6 +197,62 @@ }, "type": "array" }, + "ignore": { + "description": "Ignore is a list of rules for specifying which changes to ignore\nduring drift detection. These rules are applied to the resources managed\nby the Kustomization and are used to exclude specific JSON pointer paths\nfrom the drift detection and apply process.", + "items": { + "description": "IgnoreRule defines a rule to selectively disregard specific changes during\nthe drift detection process.", + "properties": { + "paths": { + "description": "Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from\nconsideration in a Kubernetes object.", + "items": { + "type": "string" + }, + "type": "array" + }, + "target": { + "description": "Target is a selector for specifying Kubernetes objects to which this\nrule applies.\nIf Target is not set, the Paths will be ignored for all Kubernetes\nobjects within the manifest of the Kustomization.", + "properties": { + "annotationSelector": { + "description": "AnnotationSelector is a string that follows the label selection expression\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api\nIt matches with the resource annotations.", + "type": "string" + }, + "group": { + "description": "Group is the API group to select resources from.\nTogether with Version and Kind it is capable of unambiguously identifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + }, + "kind": { + "description": "Kind of the API Group to select resources from.\nTogether with Group and Version it is capable of unambiguously\nidentifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + }, + "labelSelector": { + "description": "LabelSelector is a string that follows the label selection expression\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api\nIt matches with the resource labels.", + "type": "string" + }, + "name": { + "description": "Name to match resources with.", + "type": "string" + }, + "namespace": { + "description": "Namespace to select resources from.", + "type": "string" + }, + "version": { + "description": "Version of the API Group to select resources from.\nTogether with Group and Kind it is capable of unambiguously identifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "paths" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, "ignoreMissingComponents": { "description": "IgnoreMissingComponents instructs the controller to ignore Components paths\nnot found in source by removing them from the generated kustomization.yaml\nbefore running kustomize build.", "type": "boolean" @@ -386,6 +453,14 @@ "additionalProperties": false }, "type": "array" + }, + "substituteStrategy": { + "description": "SubstituteStrategy defines the strategy for substituting variables in the YAML manifests.\nValid values are:\n\n - WithVariables (the default): require at least one variable to be defined,\n either through the inline map or through the resolved references to ConfigMaps\n and Secrets.\n - Always: perform the substitution even if no variables are defined.", + "enum": [ + "WithVariables", + "Always" + ], + "type": "string" } }, "type": "object", diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/ocirepository-source-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/ocirepository-source-v1.json index a80528c..f7f351d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/ocirepository-source-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/ocirepository-source-v1.json @@ -190,6 +190,20 @@ ], "type": "object", "additionalProperties": false + }, + "trustedRootSecretRef": { + "description": "TrustedRootSecretRef specifies the Kubernetes Secret containing a\nSigstore trusted_root.json file. This enables verification against\nself-hosted Sigstore infrastructure (custom Fulcio CA, self-hosted\nRekor instance). The Secret must contain a key named \"trusted_root.json\".", + "properties": { + "name": { + "description": "Name of the referent.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false } }, "required": [ diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/provider-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/provider-notification-v1beta2.json deleted file mode 100644 index ace15b2..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/provider-notification-v1beta2.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "description": "Provider is the Schema for the providers API.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ProviderSpec defines the desired state of the Provider.", - "properties": { - "address": { - "description": "Address specifies the endpoint, in a generic sense, to where alerts are sent.\nWhat kind of endpoint depends on the specific Provider type being used.\nFor the generic Provider, for example, this is an HTTP/S address.\nFor other Provider types this could be a project ID or a namespace.", - "maxLength": 2048, - "type": "string" - }, - "certSecretRef": { - "description": "CertSecretRef specifies the Secret containing\na PEM-encoded CA certificate (in the `ca.crt` key).\n\nNote: Support for the `caFile` key has\nbeen deprecated.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "channel": { - "description": "Channel specifies the destination channel where events should be posted.", - "maxLength": 2048, - "type": "string" - }, - "interval": { - "description": "Interval at which to reconcile the Provider with its Secret references.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "proxy": { - "description": "Proxy the HTTP/S address of the proxy server.", - "maxLength": 2048, - "pattern": "^(http|https)://.*$", - "type": "string" - }, - "secretRef": { - "description": "SecretRef specifies the Secret containing the authentication\ncredentials for this Provider.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this Provider.", - "type": "boolean" - }, - "timeout": { - "description": "Timeout for sending alerts to the Provider.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m))+$", - "type": "string" - }, - "type": { - "description": "Type specifies which Provider implementation to use.", - "enum": [ - "slack", - "discord", - "msteams", - "rocket", - "generic", - "generic-hmac", - "github", - "gitlab", - "gitea", - "bitbucketserver", - "bitbucket", - "azuredevops", - "googlechat", - "googlepubsub", - "webex", - "sentry", - "azureeventhub", - "telegram", - "lark", - "matrix", - "opsgenie", - "alertmanager", - "grafana", - "githubdispatch", - "pagerduty", - "datadog" - ], - "type": "string" - }, - "username": { - "description": "Username specifies the name under which events are posted.", - "maxLength": 2048, - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ProviderStatus defines the observed state of the Provider.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Provider.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last reconciled generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1.json index 90141f3..c8eda12 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1.json @@ -28,19 +28,98 @@ "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", "type": "string" }, + "oidcProviders": { + "description": "OIDCProviders specifies the OIDC providers used to authenticate incoming\nrequests when Type is 'generic-oidc'. The provider whose IssuerURL matches\nthe token's 'iss' claim is used to verify the token signature, expiration\nand audience, and to evaluate the configured CEL validations against the\ntoken claims.", + "items": { + "description": "OIDCProvider configures an OIDC issuer used to authenticate requests for a\n'generic-oidc' Receiver.", + "properties": { + "audience": { + "description": "Audience is the expected audience ('aud' claim) for tokens issued by\nthis provider. Defaults to 'notification-controller'.", + "type": "string" + }, + "issuerURL": { + "description": "IssuerURL is the OIDC issuer URL used for provider discovery. It must\nmatch the 'iss' claim of tokens issued by this provider.", + "pattern": "^https?://", + "type": "string" + }, + "validations": { + "description": "Validations is the list of CEL boolean expressions evaluated against the\ntoken claims and the variables. The request is accepted only if all of\nthem evaluate to true; the message of each failing expression is returned\nto the caller.\n\nAt least one validation is required. A valid signature alone does not\nauthorize a request: public issuers issue tokens to any caller on the\nplatform, so the validations must constrain the caller's identity claims\n(e.g. 'repository_owner' for GitHub Actions).", + "items": { + "description": "OIDCValidation is a CEL boolean expression evaluated against the OIDC token\nclaims and variables of a 'generic-oidc' Receiver.", + "properties": { + "expression": { + "description": "Expression is the CEL boolean expression to evaluate.", + "type": "string" + }, + "message": { + "description": "Message is returned to the caller when the expression evaluates to false.", + "type": "string" + } + }, + "required": [ + "expression", + "message" + ], + "type": "object", + "additionalProperties": false + }, + "minItems": 1, + "type": "array" + }, + "variables": { + "description": "Variables is an optional list of named CEL expressions, evaluated in order\nand exposed as 'vars.'. Each expression can read the token claims\nvia 'claims' and any variable defined before it. Use it to share\nsub-expressions across validations.", + "items": { + "description": "OIDCVariable is a named CEL expression evaluated against the OIDC token\nclaims of a 'generic-oidc' Receiver.", + "properties": { + "expression": { + "description": "Expression is the CEL expression that defines the variable value.", + "type": "string" + }, + "name": { + "description": "Name is the variable name; it must be a valid CEL identifier.", + "type": "string" + } + }, + "required": [ + "expression", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "required": [ + "issuerURL", + "validations" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "issuerURL" + ], + "x-kubernetes-list-type": "map" + }, "resourceFilter": { - "description": "ResourceFilter is a CEL expression expected to return a boolean that is\nevaluated for each resource referenced in the Resources field when a\nwebhook is received. If the expression returns false then the controller\nwill not request a reconciliation for the resource.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", + "description": "ResourceFilter is a CEL expression expected to return a boolean that is\nevaluated for each resource referenced in the Resources field when a\nwebhook is received. If the expression returns false then the controller\nwill not request a reconciliation for the resource.\nThe expression can read the resource metadata via 'res' and the webhook\nrequest body via 'req'. For generic-oidc receivers, the verified OIDC\ntoken claims are also available via 'claims'.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", "type": "string" }, "resources": { "description": "A list of resources to be notified about changes.", "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", + "description": "ReceiverResource references a resource to be notified about changes, with an\noptional per-resource CEL filter.", "properties": { "apiVersion": { "description": "API version of the referent", "type": "string" }, + "filter": { + "description": "Filter is a CEL expression expected to return a boolean that is evaluated\nfor each resource matched by this reference when a webhook is received,\nin addition to the top-level resourceFilter. A reconciliation is requested\nonly when both expressions (when set) return true.\nThe expression can read the resource metadata via 'res' and the webhook\nrequest body via 'req'. For generic-oidc receivers, the verified OIDC\ntoken claims are also available via 'claims'.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", + "type": "string" + }, "kind": { "description": "Kind of the referent", "enum": [ @@ -89,7 +168,7 @@ "type": "array" }, "secretRef": { - "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity. The Secret must contain a 'token'\nkey. For GCR receivers, the Secret must also contain an 'email' key\nwith the IAM service account email configured on the Pub/Sub push\nsubscription, and an 'audience' key with the expected OIDC token audience.", + "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity. The Secret must contain a 'token'\nkey. For GCR receivers, the Secret must also contain an 'email' key\nwith the IAM service account email configured on the Pub/Sub push\nsubscription, and an 'audience' key with the expected OIDC token audience.\n\nRequired for all receiver types except 'generic-oidc', which authenticates\nrequests using the OIDC token instead and must not set this field.", "properties": { "name": { "description": "Name of the referent.", @@ -111,6 +190,7 @@ "enum": [ "generic", "generic-hmac", + "generic-oidc", "github", "gitlab", "bitbucket", @@ -127,10 +207,27 @@ }, "required": [ "resources", - "secretRef", "type" ], "type": "object", + "x-kubernetes-validations": [ + { + "message": "generic-oidc receivers must define at least one oidcProvider", + "rule": "self.type != 'generic-oidc' || (has(self.oidcProviders) && size(self.oidcProviders) > 0)" + }, + { + "message": "oidcProviders can only be set when type is generic-oidc", + "rule": "self.type == 'generic-oidc' || !has(self.oidcProviders) || size(self.oidcProviders) == 0" + }, + { + "message": "secretRef cannot be set when type is generic-oidc", + "rule": "self.type != 'generic-oidc' || !has(self.secretRef)" + }, + { + "message": "secretRef is required when type is not generic-oidc", + "rule": "self.type == 'generic-oidc' || has(self.secretRef)" + } + ], "additionalProperties": false }, "status": { diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1beta2.json deleted file mode 100644 index 4b7cfe6..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/receiver-notification-v1beta2.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "description": "Receiver is the Schema for the receivers API.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ReceiverSpec defines the desired state of the Receiver.", - "properties": { - "events": { - "description": "Events specifies the list of event types to handle,\ne.g. 'push' for GitHub or 'Push Hook' for GitLab.", - "items": { - "type": "string" - }, - "type": "array" - }, - "interval": { - "description": "Interval at which to reconcile the Receiver with its Secret references.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "resources": { - "description": "A list of resources to be notified about changes.", - "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", - "properties": { - "apiVersion": { - "description": "API version of the referent", - "type": "string" - }, - "kind": { - "description": "Kind of the referent", - "enum": [ - "Bucket", - "GitRepository", - "Kustomization", - "HelmRelease", - "HelmChart", - "HelmRepository", - "ImageRepository", - "ImagePolicy", - "ImageUpdateAutomation", - "OCIRepository", - "ArtifactGenerator", - "ExternalArtifact" - ], - "type": "string" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\nMatchLabels requires the name to be set to `*`.", - "type": "object" - }, - "name": { - "description": "Name of the referent\nIf multiple resources are targeted `*` may be set.", - "maxLength": 253, - "minLength": 1, - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent", - "maxLength": 253, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "secretRef": { - "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this receiver.", - "type": "boolean" - }, - "type": { - "description": "Type of webhook sender, used to determine\nthe validation procedure and payload deserialization.", - "enum": [ - "generic", - "generic-hmac", - "github", - "gitlab", - "bitbucket", - "harbor", - "dockerhub", - "quay", - "gcr", - "nexus", - "acr" - ], - "type": "string" - } - }, - "required": [ - "resources", - "secretRef", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ReceiverStatus defines the observed state of the Receiver.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Receiver.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last observed generation of the Receiver object.", - "format": "int64", - "type": "integer" - }, - "url": { - "description": "URL is the generated incoming webhook address in the format\nof '/hook/sha256sum(token+name+namespace)'.\nDeprecated: Replaced by WebhookPath.", - "type": "string" - }, - "webhookPath": { - "description": "WebhookPath is the generated incoming webhook address in the format\nof '/hook/sha256sum(token+name+namespace)'.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourceset-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourceset-fluxcd-v1.json index f55e30e..b54ca2d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourceset-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourceset-fluxcd-v1.json @@ -205,6 +205,7 @@ "resources": { "description": "Resources contains the list of Kubernetes resources to reconcile.", "items": { + "type": "object", "x-kubernetes-preserve-unknown-fields": true }, "type": "array" @@ -217,12 +218,73 @@ "description": "The name of the Kubernetes service account to impersonate\nwhen reconciling the generated resources.", "type": "string" }, + "steps": { + "description": "Steps contains an ordered list of named steps to reconcile in sequence.\nEach step's resources are applied and health-checked before the next\nstep starts. Mutually exclusive with Resources and ResourcesTemplate.", + "items": { + "additionalProperties": false, + "description": "ResourceSetStep defines a named step in the ResourceSet reconciliation\nsequence. The step's resources are applied and health-checked before\nthe next step starts.", + "properties": { + "name": { + "description": "Name of the step, must be unique within the ResourceSet.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "resources": { + "description": "Resources contains the list of Kubernetes resources to reconcile.", + "items": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array" + }, + "resourcesTemplate": { + "description": "ResourcesTemplate is a Go template that generates the list of\nKubernetes resources to reconcile. The template is rendered\nas multi-document YAML, the resources should be separated by '---'.\nWhen both Resources and ResourcesTemplate are set, the resulting\nobjects are merged and deduplicated, with the ones from Resources taking precedence.", + "type": "string" + }, + "timeout": { + "description": "Timeout is the maximum time to wait for the step's resources to\nbecome ready. When not set, the ResourceSet reconciliation\ntimeout is used.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of resources or resourcesTemplate must be set", + "rule": "has(self.resources) || has(self.resourcesTemplate)" + } + ] + }, + "maxItems": 20, + "minItems": 1, + "type": "array", + "x-kubernetes-validations": [ + { + "message": "step names must be unique", + "rule": "self.all(s, self.exists_one(t, t.name == s.name))" + } + ] + }, "wait": { "description": "Wait instructs the controller to check the health\nof all the reconciled resources.", "type": "boolean" } }, - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": "steps is mutually exclusive with resources and resourcesTemplate", + "rule": "!has(self.steps) || (!has(self.resources) && !has(self.resourcesTemplate))" + }, + { + "message": "at least one of steps, resources or resourcesTemplate must be set", + "rule": "has(self.steps) || has(self.resources) || has(self.resourcesTemplate)" + } + ] }, "status": { "additionalProperties": false, diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourcesetinputprovider-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourcesetinputprovider-fluxcd-v1.json index 5e98e35..5e713f1 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourcesetinputprovider-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/assets/schemas/resourcesetinputprovider-fluxcd-v1.json @@ -230,11 +230,11 @@ }, { "message": "cannot specify spec.certSecretRef when spec.type is one of Static, AzureDevOps*, AWSCodeCommit*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag", - "rule": "!has(self.certSecretRef) || !(self.url == 'Static' || self.type.startsWith('AzureDevOps') || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" + "rule": "!has(self.certSecretRef) || !(self.type == 'Static' || self.type.startsWith('AzureDevOps') || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" }, { "message": "cannot specify spec.secretRef when spec.type is one of Static, AWSCodeCommit*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag", - "rule": "!has(self.secretRef) || !(self.url == 'Static' || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" + "rule": "!has(self.secretRef) || !(self.type == 'Static' || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" } ] }, diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/flux-crds.md b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/flux-crds.md index 07fc963..34ed3ce 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/flux-crds.md +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/flux-crds.md @@ -3,6 +3,8 @@ Detailed reference for Flux custom resource definitions, organized by controller. Use this when you need to understand CRD fields, status conditions, or common failure modes. +**Contents:** [Resource Relationships](#resource-relationships) | [Flux Operator](#flux-operator) | [Source Controller](#source-controller) | [Kustomize Controller](#kustomize-controller) | [Helm Controller](#helm-controller) | [Notification Controller](#notification-controller) | [Image Automation Controllers](#image-automation-controllers) + ## Resource Relationships ``` @@ -55,10 +57,11 @@ Use this when you need to understand CRD fields, status conditions, or common fa **Purpose**: Reflects the state of a Flux installation, providing cluster-wide summary. -**Key status fields**: -- `.spec.distribution` — Installed Flux version and components -- `.spec.conditions` — Aggregated health across all Flux resources -- `.spec.resources` — Summary of Flux resource counts and statuses +**Key fields**: +- `.spec.distribution` — Installed Flux version and entitlement status +- `.spec.components` — Status of the Flux controller deployments +- `.spec.reconcilers` — Flux resource statistics (failing, running, suspended) grouped by kind +- `.status.conditions` — Readiness of the report object itself **Common failures**: - Report not generated (FluxInstance not installed or not ready) @@ -68,14 +71,14 @@ Use this when you need to understand CRD fields, status conditions, or common fa **Purpose**: Manages groups of Kubernetes resources based on input matrices. **Key spec fields**: -- `.spec.inputsFrom` — References to ResourceSetInputProvider or ConfigMaps -- `.spec.resources` — Go templates for generating Kubernetes resources +- `.spec.inputsFrom` — References to ResourceSetInputProvider objects that supply dynamic inputs +- `.spec.resources` — Templated Kubernetes resources using `<< inputs.field >>` delimiters (not Go's `{{ }}`) - `.spec.serviceAccountName` — Service account for applying resources **Key status conditions**: `Ready`, `Reconciling`, `Stalled` **Common failures**: -- Template rendering errors (invalid Go templates, missing input values) +- Template rendering errors (invalid template syntax, missing input values) - RBAC errors (service account lacks permissions) - Input provider not ready @@ -83,7 +86,7 @@ Use this when you need to understand CRD fields, status conditions, or common fa **Purpose**: Provides input values for ResourceSets from external services or static definitions. -**Provider types**: `Static` (inline inputs), Git server (pull requests, branches, tags), OCI registry (artifact tags). +**Provider types**: `Static` (inline inputs), Git servers (`GitHub*`, `GitLab*`, `AzureDevOps*`, `AWSCodeCommit*`, `Gitea*` variants for branches, tags, and pull/merge requests, plus `GitLabEnvironment`), registry scanners (`OCIArtifactTag`, `ACRArtifactTag`, `ECRArtifactTag`, `GARArtifactTag`), and `ExternalService` (custom webhook-driven inputs). **Key spec fields**: - `.spec.type` — Provider type (see list above) diff --git a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/troubleshooting.md b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/troubleshooting.md index 26c349d..135e0b9 100644 --- a/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/troubleshooting.md +++ b/plugins/gitops-kubernetes/skills/gitops-cluster-debug/references/troubleshooting.md @@ -135,6 +135,50 @@ Common failure patterns and debugging procedures for Flux on Kubernetes clusters Suggest setting modern `install.strategy.name: RetryOnFailure` and `upgrade.strategy.name: RetryOnFailure` instead of the legacy remediation retries pattern. +## Image Automation Failures + +### Tags not detected + +**Symptoms**: ImageRepository `Ready: False`, or `Ready: True` but ImagePolicy selects no tag. + +**Common causes**: +- **Registry auth error**: Pull secret missing/expired, or `.spec.provider` not set for cloud registries (ECR, ACR, GAR) using workload identity. +- **Policy matches nothing**: Semver range excludes all published tags, or tags don't parse as semver (use `filterTags` with `extract` to strip prefixes). +- **Controllers not running**: `image-reflector-controller`/`image-automation-controller` are optional FluxInstance components — verify they are listed and running. + +### No commits pushed + +**Symptoms**: ImagePolicy has a new `latestImage` but no commits appear in Git. + +**Common causes**: +- **Read-only Git credentials**: The GitRepository `secretRef` used by ImageUpdateAutomation lacks push permission. +- **Missing markers**: Manifests under `.spec.update.path` have no `$imagepolicy` comment markers, or the marker references the wrong `:`. +- **Wrong branch**: Commits go to `.spec.git.push.branch` — the user may be watching a different branch. +- **Nothing to update**: The tag in Git already matches `latestImage`. + +## Notification Failures + +### Alerts not delivered + +**Symptoms**: Reconciliation events happen but nothing arrives in Slack/Teams/etc. + +Provider and Alert have no status conditions — diagnose from +notification-controller logs, not object status. + +**Common causes**: +- **Event filtered**: Alert `.spec.eventSeverity: error` drops info events; `.spec.eventSources` doesn't match the resource kind/name/namespace producing the event. +- **Provider auth/address error**: Wrong or expired token in the `secretRef`, wrong webhook `address` — look for HTTP 401/404/timeout errors in controller logs. +- **Wrong provider type**: `.spec.type` doesn't match the receiving service's expected payload format. + +### Receiver not triggering + +**Symptoms**: Pushes to the Git server don't trigger immediate reconciliation. + +**Common causes**: +- **Webhook not reaching the cluster**: No incoming requests in notification-controller logs — wrong URL on the Git server, ingress/firewall blocking, or webhook not configured at all. Verify `status.webhookPath`. +- **Signature validation failure**: Webhook secret mismatch between the Git server and the Receiver `secretRef`. +- **Wrong resources list**: `.spec.resources` doesn't include the source/Kustomization that should be reconciled. + ## General Debugging Checklist Use this checklist when the issue is unclear: diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/SKILL.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/SKILL.md index 19f801b..dd01c06 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/SKILL.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/SKILL.md @@ -4,9 +4,9 @@ description: | license: Apache-2.0 metadata: github-path: skills/gitops-knowledge - github-ref: refs/tags/v0.0.4 + github-ref: refs/tags/v0.1.0 github-repo: https://github.com/fluxcd/agent-skills - github-tree-sha: 87c84bc20bf3a0356deb68be41334149eb9a4371 + github-tree-sha: d47bb25f90b66b224297b75b4df34d554d2d6763 name: gitops-knowledge --- # Flux CD Knowledge Base @@ -18,6 +18,7 @@ to answer questions accurately, generate correct YAML manifests, and explain Flu - Always use the exact apiVersion/kind combinations from the CRD table below. Never invent API versions. - Before generating YAML for any CRD, read its OpenAPI schema from `assets/schemas/` to verify field names, types, and enum values. - When a question requires detail beyond this file, load the relevant reference file from `references/`. +- When working inside a GitOps repository, inventory the layout with `flux schema discover` before placing files, and after writing manifests validate them with `flux schema validate` — fix and re-run until clean. Load `references/flux-cli.md` for the full CLI workflow, local rendering, and overlay debugging. If the tools aren't installed, skip validation and say so. - Prefer Flux Operator (FluxInstance) for cluster setup. Do not reference `flux bootstrap` or legacy `gotk-*` files. ## What is Flux @@ -80,7 +81,7 @@ Namespaces, Sources, Kustomizations, HelmReleases, RBAC, ... | HelmChart | source.toolkit.fluxcd.io/v1 | source-controller | Fetch and package Helm charts | | Bucket | source.toolkit.fluxcd.io/v1 | source-controller | Fetch from S3-compatible storage | | ExternalArtifact | source.toolkit.fluxcd.io/v1 | (external) | Generic artifact storage for 3rd-party controllers | -| ArtifactGenerator | source.extensions.fluxcd.io/v1beta1 | source-controller | Compose/decompose artifacts from multiple sources | +| ArtifactGenerator | source.extensions.fluxcd.io/v1beta1 | source-watcher | Compose/decompose artifacts from multiple sources | | Kustomization | kustomize.toolkit.fluxcd.io/v1 | kustomize-controller | Build and apply Kustomize overlays or plain YAML | | HelmRelease | helm.toolkit.fluxcd.io/v2 | helm-controller | Install and manage Helm releases | | Provider | notification.toolkit.fluxcd.io/v1beta3 | notification-controller | External notification provider config | @@ -129,6 +130,9 @@ spec: readyExpr: "status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'True')" ``` +For ordering *within* a single ResourceSet, use `spec.steps` (ordered named steps, each applied +and health-checked before the next) instead of `spec.resources` — see `references/resourcesets.md`. + ### Reactivity with Watch Labels By default, Flux controllers poll sources at the configured interval. To react immediately @@ -412,6 +416,10 @@ load `references/notifications.md`. operation: copy ``` +**Drift control — pick the right knob:** +- Kustomization `spec.ignore` — exclude specific JSON-pointer fields from drift detection/apply (e.g. HPA `replicas`). Distinct from the `kustomize.toolkit.fluxcd.io/ssa: Ignore` annotation, which skips a whole object. +- HelmRelease `spec.driftDetection.ignore` — the HelmRelease equivalent, only active when `driftDetection.mode` is `warn`/`enabled`. + ## Reference Index Load reference files and OpenAPI schemas based on the question topic. @@ -446,5 +454,6 @@ Load at most 1-2 reference files per question. Read schemas for field-level vali | Web UI, dashboard, SSO, OIDC, Dex, Keycloak, Entra ID, RBAC | `references/web-ui.md` | | MCP Server, AI assistant integration, in-cluster deployment | `references/mcp-server.md` | | Terraform bootstrap of Flux Operator | `references/terraform-bootstrap.md` | +| Flux CLI and plugins: `flux schema` discover/validate/extract, local rendering with `flux build` and `flux operator build`, overlay debugging | `references/flux-cli.md` | | Gitless GitOps, Flux OCI artifacts, `flux push artifact`, registry-based delivery | `references/gitless-gitops.md` | | Gitless image automation (ResourceSet + OCIArtifactTag) | `references/gitless-image-automation.md` | diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/alert-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/alert-notification-v1beta2.json deleted file mode 100644 index 9ddaddb..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/alert-notification-v1beta2.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "description": "Alert is the Schema for the alerts API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "AlertSpec defines an alerting rule for events involving a list of objects.", - "properties": { - "eventMetadata": { - "additionalProperties": { - "type": "string" - }, - "description": "EventMetadata is an optional field for adding metadata to events dispatched by the\ncontroller. This can be used for enhancing the context of the event. If a field\nwould override one already present on the original event as generated by the emitter,\nthen the override doesn't happen, i.e. the original value is preserved, and an info\nlog is printed.", - "type": "object" - }, - "eventSeverity": { - "default": "info", - "description": "EventSeverity specifies how to filter events based on severity.\nIf set to 'info' no events will be filtered.", - "enum": [ - "info", - "error" - ], - "type": "string" - }, - "eventSources": { - "description": "EventSources specifies how to filter events based\non the involved object kind, name and namespace.", - "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", - "properties": { - "apiVersion": { - "description": "API version of the referent", - "type": "string" - }, - "kind": { - "description": "Kind of the referent", - "enum": [ - "Bucket", - "GitRepository", - "Kustomization", - "HelmRelease", - "HelmChart", - "HelmRepository", - "ImageRepository", - "ImagePolicy", - "ImageUpdateAutomation", - "OCIRepository", - "ArtifactGenerator", - "ExternalArtifact" - ], - "type": "string" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\nMatchLabels requires the name to be set to `*`.", - "type": "object" - }, - "name": { - "description": "Name of the referent\nIf multiple resources are targeted `*` may be set.", - "maxLength": 253, - "minLength": 1, - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent", - "maxLength": 253, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "exclusionList": { - "description": "ExclusionList specifies a list of Golang regular expressions\nto be used for excluding messages.", - "items": { - "type": "string" - }, - "type": "array" - }, - "inclusionList": { - "description": "InclusionList specifies a list of Golang regular expressions\nto be used for including messages.", - "items": { - "type": "string" - }, - "type": "array" - }, - "providerRef": { - "description": "ProviderRef specifies which Provider this Alert should use.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "summary": { - "description": "Summary holds a short description of the impact and affected cluster.", - "maxLength": 255, - "type": "string" - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this Alert.", - "type": "boolean" - } - }, - "required": [ - "eventSources", - "providerRef" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "AlertStatus defines the observed state of the Alert.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Alert.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last observed generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/artifactgenerator-source-v1beta1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/artifactgenerator-source-v1beta1.json index 014c6ad..b1c9351 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/artifactgenerator-source-v1beta1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/artifactgenerator-source-v1beta1.json @@ -25,7 +25,7 @@ "items": { "properties": { "exclude": { - "description": "Exclude specifies a list of glob patterns to exclude\nfiles and dirs matched by the 'From' field.", + "description": "Exclude specifies a list of glob patterns to exclude\nfiles and dirs matched by the 'From' field. Patterns are matched\nagainst paths relative to the source alias root or to the non-glob\nprefix of 'From'. Patterns without a separator (e.g. \"*.md\") match\nthe file name at any depth.", "items": { "type": "string" }, @@ -33,8 +33,9 @@ "type": "array" }, "from": { - "description": "From specifies the source (by alias) and the glob pattern to match files.\nThe format is \"@/\".", + "description": "From specifies the source (by alias) and the glob pattern to match files.\nThe format is \"@/\". When pathPattern is set,\nthe path may use capture placeholders such as \"{app}\".", "maxLength": 1024, + "minLength": 1, "pattern": "^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$", "type": "string" }, @@ -48,8 +49,9 @@ "type": "string" }, "to": { - "description": "To specifies the destination path within the artifact.\nThe format is \"@artifact/path\", the alias \"artifact\"\nrefers to the root path of the generated artifact.", + "description": "To specifies the destination path within the artifact.\nThe format is \"@artifact/path\", the alias \"artifact\"\nrefers to the root path of the generated artifact. When pathPattern\nis set, the path may use capture placeholders such as \"{app}\".", "maxLength": 1024, + "minLength": 1, "pattern": "^@(artifact)/(.*)$", "type": "string" } @@ -65,9 +67,9 @@ "type": "array" }, "name": { - "description": "Name is the name of the generated artifact.", + "description": "Name is the name of the generated artifact.\nWhen pathPattern is set, this field may use capture placeholders such as \"{app}\".", "maxLength": 253, - "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "minLength": 1, "type": "string" }, "originRevision": { @@ -94,6 +96,33 @@ "minItems": 1, "type": "array" }, + "commonMetadata": { + "description": "CommonMetadata specifies the common labels and annotations that are\napplied to all resources. Any existing label or annotation will be\noverridden if its key matches a common one.", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to the object's metadata.", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to be added to the object's metadata.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "pathPattern": { + "description": "PathPattern specifies a directory traversal pattern to match within the sources.\nThe format is \"@/\". Named captures in the pattern (e.g. \"{app}\")\ncan be used as placeholders in OutputArtifacts fields.", + "maxLength": 1024, + "pattern": "^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$", + "type": "string" + }, "sources": { "description": "Sources is a list of references to the Flux source-controller\nresources that will be used to generate the artifact.", "items": { @@ -148,6 +177,12 @@ "sources" ], "type": "object", + "x-kubernetes-validations": [ + { + "message": "artifact names must be valid Kubernetes object names when pathPattern is not set", + "rule": "has(self.pathPattern) && size(self.pathPattern) > 0 || self.artifacts.all(a, a.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'))" + } + ], "additionalProperties": false }, "status": { diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/fluxinstance-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/fluxinstance-fluxcd-v1.json index 1635135..63206b8 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/fluxinstance-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/fluxinstance-fluxcd-v1.json @@ -249,7 +249,7 @@ "type": "array" }, "storage": { - "description": "Storage defines if the source-controller shards\nshould use an emptyDir or a persistent volume claim for storage.\nAccepted values are 'ephemeral' or 'persistent', defaults to 'ephemeral'.\nFor 'persistent' to take effect, the '.spec.storage' field must be set.", + "description": "Storage defines if the source-controller shards\nshould use an emptyDir or a persistent volume claim for storage.\nAccepted values are 'ephemeral' or 'persistent', defaults to 'ephemeral'.\nWhen set to 'persistent', the '.spec.storage' field must be set.", "enum": [ "ephemeral", "persistent" @@ -366,7 +366,13 @@ "required": [ "distribution" ], - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": ".spec.storage must be set when .spec.sharding.storage is 'persistent'", + "rule": "!has(self.sharding) || !has(self.sharding.storage) || self.sharding.storage != 'persistent' || has(self.storage)" + } + ] }, "status": { "additionalProperties": false, diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/gitrepository-source-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/gitrepository-source-v1.json index 576baae..bcef2e3 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/gitrepository-source-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/gitrepository-source-v1.json @@ -61,9 +61,10 @@ "type": "string" }, "provider": { - "description": "Provider used for authentication, can be 'azure', 'github', 'generic'.\nWhen not specified, defaults to 'generic'.", + "description": "Provider used for authentication, can be 'aws', 'azure', 'github', 'generic'.\nWhen not specified, defaults to 'generic'.", "enum": [ "generic", + "aws", "azure", "github" ], @@ -129,7 +130,7 @@ "additionalProperties": false }, "serviceAccountName": { - "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to\nauthenticate to the GitRepository. This field is only supported for 'azure' provider.", + "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to\nauthenticate to the GitRepository. This field is only supported for 'azure' and 'aws' providers.", "type": "string" }, "sparseCheckout": { @@ -169,7 +170,7 @@ "type": "string" }, "secretRef": { - "description": "SecretRef specifies the Secret containing the public keys of trusted Git\nauthors.", + "description": "SecretRef specifies the Secret containing the public keys of trusted Git\nauthors. PGP public keys must be stored under keys with the .asc suffix,\nand SSH public keys must be stored under keys with the .sshpub suffix.", "properties": { "name": { "description": "Name of the referent.", @@ -197,8 +198,8 @@ "type": "object", "x-kubernetes-validations": [ { - "message": "serviceAccountName can only be set when provider is 'azure'", - "rule": "!has(self.serviceAccountName) || (has(self.provider) && self.provider == 'azure')" + "message": "serviceAccountName can only be set when provider is 'azure' or 'aws'", + "rule": "!has(self.serviceAccountName) || (has(self.provider) && (self.provider == 'azure' || self.provider == 'aws'))" } ], "additionalProperties": false diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/helmrelease-helm-v2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/helmrelease-helm-v2.json index 5cd8695..9401cba 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/helmrelease-helm-v2.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/helmrelease-helm-v2.json @@ -222,14 +222,14 @@ "dependsOn": { "description": "DependsOn may contain a DependencyReference slice with\nreferences to HelmRelease resources that must be ready before this HelmRelease\ncan be reconciled.", "items": { - "description": "DependencyReference defines a HelmRelease dependency on another HelmRelease resource.", + "description": "DependencyReference contains enough information to locate the referenced Kubernetes resource object\nand optional CEL expression to assess its readiness.", "properties": { "name": { "description": "Name of the referent.", "type": "string" }, "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the HelmRelease\nresource object that contains the reference.", + "description": "Namespace of the referent, defaults to the namespace of the resource\nobject that contains the reference.", "type": "string" }, "readyExpr": { @@ -345,8 +345,7 @@ }, "required": [ "apiVersion", - "current", - "kind" + "current" ], "type": "object", "additionalProperties": false @@ -524,6 +523,15 @@ "description": "PersistentClient tells the controller to use a persistent Kubernetes\nclient for this release. When enabled, the client will be reused for the\nduration of the reconciliation, instead of being created and destroyed\nfor each (step of a) Helm action.\n\nThis can improve performance, but may cause issues with some Helm charts\nthat for example do create Custom Resource Definitions during installation\noutside Helm's CRD lifecycle hooks, which are then not observed to be\navailable by e.g. post-install hooks.\n\nIf not set, it defaults to true.", "type": "boolean" }, + "postRenderStrategy": { + "description": "PostRenderStrategy defines the strategy for sending hooks to post-renderers.\nValid values are 'nohooks' (hooks not sent to post-renderers, Helm 3 behavior),\n'combined' (hooks and templates sent together, Helm 4 default), and 'separate'\n(hooks and templates sent in separate streams, Helm 4.2 opt-in).\nDefaults to 'combined', or 'nohooks' when the UseHelm3Defaults feature gate is enabled.", + "enum": [ + "nohooks", + "combined", + "separate" + ], + "type": "string" + }, "postRenderers": { "description": "PostRenderers holds an array of Helm PostRenderers, which will be applied in order\nof their definition.", "items": { @@ -784,6 +792,14 @@ "upgrade": { "description": "Upgrade holds the configuration for Helm upgrade actions for this HelmRelease.", "properties": { + "chartNameChangeStrategy": { + "description": "ChartNameChangeStrategy defines the strategy to use when a Helm chart name changes.\nValid values are 'Reinstall' or 'InPlaceUpdate'. Defaults to 'Reinstall' if omitted.\n\nReinstall: Reinstall the Helm release, uninstalling the existing Helm release.\n\nInPlaceUpdate: Update the Helm release in place.", + "enum": [ + "InPlaceUpdate", + "Reinstall" + ], + "type": "string" + }, "cleanupOnFail": { "description": "CleanupOnFail allows deletion of new resources created during the Helm\nupgrade action when it fails.", "type": "boolean" @@ -920,6 +936,10 @@ ], "type": "string" }, + "literal": { + "description": "Literal marks this ValuesReference as a literal value. When set in\ncombination with TargetPath, the referenced value is merged at the target\npath without interpreting Helm's `--set` syntax (commas, brackets, dots,\nequal signs, etc.), mirroring the behavior of `helm --set-literal`. This\nis the only safe way to inject arbitrary file content (config files, JSON\nblobs, multi-line strings containing special characters) through\n`valuesFrom`. Has no effect when TargetPath is empty: in that mode the\nreferenced value is always YAML-merged at the root.", + "type": "boolean" + }, "name": { "description": "Name of the values referent. Should reside in the same namespace as the\nreferring resource.", "maxLength": 253, diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imagepolicy-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imagepolicy-image-v1beta2.json deleted file mode 100644 index a4aee09..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imagepolicy-image-v1beta2.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "description": "ImagePolicy is the Schema for the imagepolicies API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImagePolicySpec defines the parameters for calculating the\nImagePolicy.", - "properties": { - "digestReflectionPolicy": { - "default": "Never", - "description": "DigestReflectionPolicy governs the setting of the `.status.latestRef.digest` field.\n\nNever: The digest field will always be set to the empty string.\n\nIfNotPresent: The digest field will be set to the digest of the elected\nlatest image if the field is empty and the image did not change.\n\nAlways: The digest field will always be set to the digest of the elected\nlatest image.\n\nDefault: Never.", - "enum": [ - "Always", - "IfNotPresent", - "Never" - ], - "type": "string" - }, - "filterTags": { - "description": "FilterTags enables filtering for only a subset of tags based on a set of\nrules. If no rules are provided, all the tags from the repository will be\nordered and compared.", - "properties": { - "extract": { - "description": "Extract allows a capture group to be extracted from the specified regular\nexpression pattern, useful before tag evaluation.", - "type": "string" - }, - "pattern": { - "description": "Pattern specifies a regular expression pattern used to filter for image\ntags.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "imageRepositoryRef": { - "description": "ImageRepositoryRef points at the object specifying the image\nbeing scanned", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent, when not specified it acts as LocalObjectReference.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "interval": { - "description": "Interval is the length of time to wait between\nrefreshing the digest of the latest tag when the\nreflection policy is set to \"Always\".\n\nDefaults to 10m.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "policy": { - "description": "Policy gives the particulars of the policy to be followed in\nselecting the most recent image", - "properties": { - "alphabetical": { - "description": "Alphabetical set of rules to use for alphabetical ordering of the tags.", - "properties": { - "order": { - "default": "asc", - "description": "Order specifies the sorting order of the tags. Given the letters of the\nalphabet as tags, ascending order would select Z, and descending order\nwould select A.", - "enum": [ - "asc", - "desc" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "numerical": { - "description": "Numerical set of rules to use for numerical ordering of the tags.", - "properties": { - "order": { - "default": "asc", - "description": "Order specifies the sorting order of the tags. Given the integer values\nfrom 0 to 9 as tags, ascending order would select 9, and descending order\nwould select 0.", - "enum": [ - "asc", - "desc" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "semver": { - "description": "SemVer gives a semantic version range to check against the tags\navailable.", - "properties": { - "range": { - "description": "Range gives a semver range for the image tag; the highest\nversion within the range that's a tag yields the latest image.", - "type": "string" - } - }, - "required": [ - "range" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "This flag tells the controller to suspend subsequent policy reconciliations.\nIt does not apply to already started reconciliations. Defaults to false.", - "type": "boolean" - } - }, - "required": [ - "imageRepositoryRef", - "policy" - ], - "type": "object", - "x-kubernetes-validations": [ - { - "message": "spec.interval is only accepted when spec.digestReflectionPolicy is set to 'Always'", - "rule": "!has(self.interval) || (has(self.digestReflectionPolicy) && self.digestReflectionPolicy == 'Always')" - }, - { - "message": "spec.interval must be set when spec.digestReflectionPolicy is set to 'Always'", - "rule": "has(self.interval) || !has(self.digestReflectionPolicy) || self.digestReflectionPolicy != 'Always'" - } - ], - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImagePolicyStatus defines the observed state of ImagePolicy", - "properties": { - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "latestRef": { - "description": "LatestRef gives the first in the list of images scanned by\nthe image repository, when filtered and ordered according\nto the policy.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - }, - "observedGeneration": { - "format": "int64", - "type": "integer" - }, - "observedPreviousRef": { - "description": "ObservedPreviousRef is the observed previous LatestRef. It is used\nto keep track of the previous and current images.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imagerepository-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imagerepository-image-v1beta2.json deleted file mode 100644 index cfb868d..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imagerepository-image-v1beta2.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "description": "ImageRepository is the Schema for the imagerepositories API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImageRepositorySpec defines the parameters for scanning an image\nrepository, e.g., `fluxcd/flux`.", - "properties": { - "accessFrom": { - "description": "AccessFrom defines an ACL for allowing cross-namespace references\nto the ImageRepository object based on the caller's namespace labels.", - "properties": { - "namespaceSelectors": { - "description": "NamespaceSelectors is the list of namespace selectors to which this ACL applies.\nItems in this list are evaluated using a logical OR operation.", - "items": { - "description": "NamespaceSelector selects the namespaces to which this ACL applies.\nAn empty map of MatchLabels matches all namespaces in a cluster.", - "properties": { - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object" - } - }, - "type": "object", - "additionalProperties": false - }, - "type": "array" - } - }, - "required": [ - "namespaceSelectors" - ], - "type": "object", - "additionalProperties": false - }, - "certSecretRef": { - "description": "CertSecretRef can be given the name of a Secret containing\neither or both of\n\n- a PEM-encoded client certificate (`tls.crt`) and private\nkey (`tls.key`);\n- a PEM-encoded CA certificate (`ca.crt`)\n\nand whichever are supplied, will be used for connecting to the\nregistry. The client cert and key are useful if you are\nauthenticating with a certificate; the CA cert is useful if\nyou are using a self-signed server certificate. The Secret must\nbe of type `Opaque` or `kubernetes.io/tls`.\n\nNote: Support for the `caFile`, `certFile` and `keyFile` keys has\nbeen deprecated.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "exclusionList": { - "default": [ - "^.*\\.sig$" - ], - "description": "ExclusionList is a list of regex strings used to exclude certain tags\nfrom being stored in the database.", - "items": { - "type": "string" - }, - "maxItems": 25, - "type": "array" - }, - "image": { - "description": "Image is the name of the image repository", - "type": "string" - }, - "insecure": { - "description": "Insecure allows connecting to a non-TLS HTTP container registry.", - "type": "boolean" - }, - "interval": { - "description": "Interval is the length of time to wait between\nscans of the image repository.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "provider": { - "default": "generic", - "description": "The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.\nWhen not specified, defaults to 'generic'.", - "enum": [ - "generic", - "aws", - "azure", - "gcp" - ], - "type": "string" - }, - "proxySecretRef": { - "description": "ProxySecretRef specifies the Secret containing the proxy configuration\nto use while communicating with the container registry.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "secretRef": { - "description": "SecretRef can be given the name of a secret containing\ncredentials to use for the image registry. The secret should be\ncreated with `kubectl create secret docker-registry`, or the\nequivalent.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "serviceAccountName": { - "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate\nthe image pull if the service account has attached pull secrets.", - "maxLength": 253, - "type": "string" - }, - "suspend": { - "description": "This flag tells the controller to suspend subsequent image scans.\nIt does not apply to already started scans. Defaults to false.", - "type": "boolean" - }, - "timeout": { - "description": "Timeout for image scanning.\nDefaults to 'Interval' duration.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m))+$", - "type": "string" - } - }, - "required": [ - "image", - "interval" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImageRepositoryStatus defines the observed state of ImageRepository", - "properties": { - "canonicalImageName": { - "description": "CanonicalName is the name of the image repository with all the\nimplied bits made explicit; e.g., `docker.io/library/alpine`\nrather than `alpine`.", - "type": "string" - }, - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "lastScanResult": { - "description": "LastScanResult contains the number of fetched tags.", - "properties": { - "latestTags": { - "description": "LatestTags is a small sample of the tags found in the last scan.\nIt's the first 10 tags when sorting all the tags in descending\nalphabetical order.", - "items": { - "type": "string" - }, - "type": "array" - }, - "revision": { - "description": "Revision is a stable hash of the scanned tags.", - "type": "string" - }, - "scanTime": { - "description": "ScanTime is the time when the last scan was performed.", - "format": "date-time", - "type": "string" - }, - "tagCount": { - "description": "TagCount is the number of tags found in the last scan.", - "type": "integer" - } - }, - "required": [ - "tagCount" - ], - "type": "object", - "additionalProperties": false - }, - "observedExclusionList": { - "description": "ObservedExclusionList is a list of observed exclusion list. It reflects\nthe exclusion rules used for the observed scan result in\nspec.lastScanResult.", - "items": { - "type": "string" - }, - "type": "array" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last reconciled generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1.json index 54da093..957d586 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1.json @@ -88,10 +88,10 @@ "type": "object" }, "signingKey": { - "description": "SigningKey provides the option to sign commits with a GPG key", + "description": "SigningKey provides the option to sign commits with an OpenPGP or\nSSH signing key, referenced from a Secret. See SigningKey.", "properties": { "secretRef": { - "description": "SecretRef holds the name to a secret that contains a 'git.asc' key\ncorresponding to the ASCII Armored file containing the GPG signing\nkeypair as the value. It must be in the same namespace as the\nImageUpdateAutomation.", + "description": "SecretRef references a Secret containing the signing key. For type\n'gpg', the Secret must contain a 'git.asc' (ASCII-armored OpenPGP\nkeypair) and may contain a 'passphrase'. For type 'ssh', the Secret\nmust contain an 'identity' (an SSH private key in any format\ngolang.org/x/crypto/ssh.ParsePrivateKey accepts; typically the\nOpenSSH format produced by 'ssh-keygen') and may contain a 'password'\n(the key's passphrase). The SSH conventions match the GitRepository\nSSH transport-auth Secret format, allowing a single Secret to serve\nboth transport and signing when the ImageUpdateAutomation lives in\nthe same namespace as the GitRepository.\n\nThe Secret itself must live in the same namespace as the\nImageUpdateAutomation.\n\nSupported SSH key algorithms: ed25519, ecdsa-sha2-nistp256/384/521,\nand rsa (>= 2048-bit).", "properties": { "name": { "description": "Name of the referent.", @@ -103,6 +103,14 @@ ], "type": "object", "additionalProperties": false + }, + "type": { + "description": "Type selects the signing-key format expected in the referenced\nSecret. When empty, the controller defaults to 'gpg'.", + "enum": [ + "gpg", + "ssh" + ], + "type": "string" } }, "required": [ diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1beta2.json deleted file mode 100644 index 54da093..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/imageupdateautomation-image-v1beta2.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "description": "ImageUpdateAutomation is the Schema for the imageupdateautomations API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation", - "properties": { - "git": { - "description": "GitSpec contains all the git-specific definitions. This is\ntechnically optional, but in practice mandatory until there are\nother kinds of source allowed.", - "properties": { - "checkout": { - "description": "Checkout gives the parameters for cloning the git repository,\nready to make changes. If not present, the `spec.ref` field from the\nreferenced `GitRepository` or its default will be used.", - "properties": { - "ref": { - "description": "Reference gives a branch, tag or commit to clone from the Git\nrepository.", - "properties": { - "branch": { - "description": "Branch to check out, defaults to 'master' if no other field is defined.", - "type": "string" - }, - "commit": { - "description": "Commit SHA to check out, takes precedence over all reference fields.\n\nThis can be combined with Branch to shallow clone the branch, in which\nthe commit is expected to exist.", - "type": "string" - }, - "name": { - "description": "Name of the reference to check out; takes precedence over Branch, Tag and SemVer.\n\nIt must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description\nExamples: \"refs/heads/main\", \"refs/tags/v0.1.0\", \"refs/pull/420/head\", \"refs/merge-requests/1/head\"", - "type": "string" - }, - "semver": { - "description": "SemVer tag expression to check out, takes precedence over Tag.", - "type": "string" - }, - "tag": { - "description": "Tag to check out, takes precedence over Branch.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "ref" - ], - "type": "object", - "additionalProperties": false - }, - "commit": { - "description": "Commit specifies how to commit to the git repository.", - "properties": { - "author": { - "description": "Author gives the email and optionally the name to use as the\nauthor of commits.", - "properties": { - "email": { - "description": "Email gives the email to provide when making a commit.", - "type": "string" - }, - "name": { - "description": "Name gives the name to provide when making a commit.", - "type": "string" - } - }, - "required": [ - "email" - ], - "type": "object", - "additionalProperties": false - }, - "messageTemplate": { - "description": "MessageTemplate provides a template for the commit message,\ninto which will be interpolated the details of the change made.\nNote: The `Updated` template field has been removed. Use `Changed` instead.", - "type": "string" - }, - "messageTemplateValues": { - "additionalProperties": { - "type": "string" - }, - "description": "MessageTemplateValues provides additional values to be available to the\ntemplating rendering.", - "type": "object" - }, - "signingKey": { - "description": "SigningKey provides the option to sign commits with a GPG key", - "properties": { - "secretRef": { - "description": "SecretRef holds the name to a secret that contains a 'git.asc' key\ncorresponding to the ASCII Armored file containing the GPG signing\nkeypair as the value. It must be in the same namespace as the\nImageUpdateAutomation.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "secretRef" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "author" - ], - "type": "object", - "additionalProperties": false - }, - "push": { - "description": "Push specifies how and where to push commits made by the\nautomation. If missing, commits are pushed (back) to\n`.spec.checkout.branch` or its default.", - "properties": { - "branch": { - "description": "Branch specifies that commits should be pushed to the branch\nnamed. The branch is created using `.spec.checkout.branch` as the\nstarting point, if it doesn't already exist.", - "type": "string" - }, - "options": { - "additionalProperties": { - "type": "string" - }, - "description": "Options specifies the push options that are sent to the Git\nserver when performing a push operation. For details, see:\nhttps://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt", - "type": "object" - }, - "refspec": { - "description": "Refspec specifies the Git Refspec to use for a push operation.\nIf both Branch and Refspec are provided, then the commit is pushed\nto the branch and also using the specified refspec.\nFor more details about Git Refspecs, see:\nhttps://git-scm.com/book/en/v2/Git-Internals-The-Refspec", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "commit" - ], - "type": "object", - "additionalProperties": false - }, - "interval": { - "description": "Interval gives an lower bound for how often the automation\nrun should be attempted.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "policySelector": { - "description": "PolicySelector allows to filter applied policies based on labels.\nBy default includes all policies in namespace.", - "properties": { - "matchExpressions": { - "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", - "items": { - "description": "A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values.", - "properties": { - "key": { - "description": "key is the label key that the selector applies to.", - "type": "string" - }, - "operator": { - "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist.", - "type": "string" - }, - "values": { - "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch.", - "items": { - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - } - }, - "required": [ - "key", - "operator" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object" - } - }, - "type": "object", - "x-kubernetes-map-type": "atomic", - "additionalProperties": false - }, - "sourceRef": { - "description": "SourceRef refers to the resource giving access details\nto a git repository.", - "properties": { - "apiVersion": { - "description": "API version of the referent.", - "type": "string" - }, - "kind": { - "default": "GitRepository", - "description": "Kind of the referent.", - "enum": [ - "GitRepository" - ], - "type": "string" - }, - "name": { - "description": "Name of the referent.", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.", - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to not run this automation, until\nit is unset (or set to false). Defaults to false.", - "type": "boolean" - }, - "update": { - "default": { - "strategy": "Setters" - }, - "description": "Update gives the specification for how to update the files in\nthe repository. This can be left empty, to use the default\nvalue.", - "properties": { - "path": { - "description": "Path to the directory containing the manifests to be updated.\nDefaults to 'None', which translates to the root path\nof the GitRepositoryRef.", - "type": "string" - }, - "strategy": { - "default": "Setters", - "description": "Strategy names the strategy to be used.", - "enum": [ - "Setters" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "interval", - "sourceRef" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation", - "properties": { - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastAutomationRunTime": { - "description": "LastAutomationRunTime records the last time the controller ran\nthis automation through to completion (even if no updates were\nmade).", - "format": "date-time", - "type": "string" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "lastPushCommit": { - "description": "LastPushCommit records the SHA1 of the last commit made by the\ncontroller, for this automation object", - "type": "string" - }, - "lastPushTime": { - "description": "LastPushTime records the time of the last pushed change.", - "format": "date-time", - "type": "string" - }, - "observedGeneration": { - "format": "int64", - "type": "integer" - }, - "observedPolicies": { - "additionalProperties": { - "description": "ImageRef represents an image reference.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - }, - "description": "ObservedPolicies is the list of observed ImagePolicies that were\nconsidered by the ImageUpdateAutomation update process.", - "type": "object" - }, - "observedSourceRevision": { - "description": "ObservedPolicies []ObservedPolicy `json:\"observedPolicies,omitempty\"`\nObservedSourceRevision is the last observed source revision. This can be\nused to determine if the source has been updated since last observation.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/kustomization-kustomize-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/kustomization-kustomize-v1.json index fcba8b6..abd3909 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/kustomization-kustomize-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/kustomization-kustomize-v1.json @@ -15,6 +15,18 @@ "spec": { "description": "KustomizationSpec defines the configuration to calculate the desired state\nfrom a Source using Kustomize.", "properties": { + "buildMetadata": { + "description": "BuildMetadata specifies which kustomize build metadata should be added\nto the built resources. The allowed values are 'originAnnotations' to\nannotate resources with their source origin, and 'transformerAnnotations'\nto annotate resources with the transformers that produced them.", + "items": { + "description": "BuildMetadataOption defines the supported buildMetadata options.", + "enum": [ + "originAnnotations", + "transformerAnnotations" + ], + "type": "string" + }, + "type": "array" + }, "commonMetadata": { "description": "CommonMetadata specifies the common labels and annotations that are\napplied to all resources. Any existing label or annotation will be\noverridden if its key matches a common one.", "properties": { @@ -91,14 +103,14 @@ "dependsOn": { "description": "DependsOn may contain a DependencyReference slice\nwith references to Kustomization resources that must be ready before this\nKustomization can be reconciled.", "items": { - "description": "DependencyReference defines a Kustomization dependency on another Kustomization resource.", + "description": "DependencyReference contains enough information to locate the referenced Kubernetes resource object\nand optional CEL expression to assess its readiness.", "properties": { "name": { "description": "Name of the referent.", "type": "string" }, "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the Kustomization\nresource object that contains the reference.", + "description": "Namespace of the referent, defaults to the namespace of the resource\nobject that contains the reference.", "type": "string" }, "readyExpr": { @@ -147,8 +159,7 @@ }, "required": [ "apiVersion", - "current", - "kind" + "current" ], "type": "object", "additionalProperties": false @@ -186,6 +197,62 @@ }, "type": "array" }, + "ignore": { + "description": "Ignore is a list of rules for specifying which changes to ignore\nduring drift detection. These rules are applied to the resources managed\nby the Kustomization and are used to exclude specific JSON pointer paths\nfrom the drift detection and apply process.", + "items": { + "description": "IgnoreRule defines a rule to selectively disregard specific changes during\nthe drift detection process.", + "properties": { + "paths": { + "description": "Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from\nconsideration in a Kubernetes object.", + "items": { + "type": "string" + }, + "type": "array" + }, + "target": { + "description": "Target is a selector for specifying Kubernetes objects to which this\nrule applies.\nIf Target is not set, the Paths will be ignored for all Kubernetes\nobjects within the manifest of the Kustomization.", + "properties": { + "annotationSelector": { + "description": "AnnotationSelector is a string that follows the label selection expression\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api\nIt matches with the resource annotations.", + "type": "string" + }, + "group": { + "description": "Group is the API group to select resources from.\nTogether with Version and Kind it is capable of unambiguously identifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + }, + "kind": { + "description": "Kind of the API Group to select resources from.\nTogether with Group and Version it is capable of unambiguously\nidentifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + }, + "labelSelector": { + "description": "LabelSelector is a string that follows the label selection expression\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api\nIt matches with the resource labels.", + "type": "string" + }, + "name": { + "description": "Name to match resources with.", + "type": "string" + }, + "namespace": { + "description": "Namespace to select resources from.", + "type": "string" + }, + "version": { + "description": "Version of the API Group to select resources from.\nTogether with Group and Kind it is capable of unambiguously identifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "paths" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, "ignoreMissingComponents": { "description": "IgnoreMissingComponents instructs the controller to ignore Components paths\nnot found in source by removing them from the generated kustomization.yaml\nbefore running kustomize build.", "type": "boolean" @@ -386,6 +453,14 @@ "additionalProperties": false }, "type": "array" + }, + "substituteStrategy": { + "description": "SubstituteStrategy defines the strategy for substituting variables in the YAML manifests.\nValid values are:\n\n - WithVariables (the default): require at least one variable to be defined,\n either through the inline map or through the resolved references to ConfigMaps\n and Secrets.\n - Always: perform the substitution even if no variables are defined.", + "enum": [ + "WithVariables", + "Always" + ], + "type": "string" } }, "type": "object", diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/ocirepository-source-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/ocirepository-source-v1.json index a80528c..f7f351d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/ocirepository-source-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/ocirepository-source-v1.json @@ -190,6 +190,20 @@ ], "type": "object", "additionalProperties": false + }, + "trustedRootSecretRef": { + "description": "TrustedRootSecretRef specifies the Kubernetes Secret containing a\nSigstore trusted_root.json file. This enables verification against\nself-hosted Sigstore infrastructure (custom Fulcio CA, self-hosted\nRekor instance). The Secret must contain a key named \"trusted_root.json\".", + "properties": { + "name": { + "description": "Name of the referent.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false } }, "required": [ diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/provider-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/provider-notification-v1beta2.json deleted file mode 100644 index ace15b2..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/provider-notification-v1beta2.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "description": "Provider is the Schema for the providers API.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ProviderSpec defines the desired state of the Provider.", - "properties": { - "address": { - "description": "Address specifies the endpoint, in a generic sense, to where alerts are sent.\nWhat kind of endpoint depends on the specific Provider type being used.\nFor the generic Provider, for example, this is an HTTP/S address.\nFor other Provider types this could be a project ID or a namespace.", - "maxLength": 2048, - "type": "string" - }, - "certSecretRef": { - "description": "CertSecretRef specifies the Secret containing\na PEM-encoded CA certificate (in the `ca.crt` key).\n\nNote: Support for the `caFile` key has\nbeen deprecated.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "channel": { - "description": "Channel specifies the destination channel where events should be posted.", - "maxLength": 2048, - "type": "string" - }, - "interval": { - "description": "Interval at which to reconcile the Provider with its Secret references.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "proxy": { - "description": "Proxy the HTTP/S address of the proxy server.", - "maxLength": 2048, - "pattern": "^(http|https)://.*$", - "type": "string" - }, - "secretRef": { - "description": "SecretRef specifies the Secret containing the authentication\ncredentials for this Provider.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this Provider.", - "type": "boolean" - }, - "timeout": { - "description": "Timeout for sending alerts to the Provider.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m))+$", - "type": "string" - }, - "type": { - "description": "Type specifies which Provider implementation to use.", - "enum": [ - "slack", - "discord", - "msteams", - "rocket", - "generic", - "generic-hmac", - "github", - "gitlab", - "gitea", - "bitbucketserver", - "bitbucket", - "azuredevops", - "googlechat", - "googlepubsub", - "webex", - "sentry", - "azureeventhub", - "telegram", - "lark", - "matrix", - "opsgenie", - "alertmanager", - "grafana", - "githubdispatch", - "pagerduty", - "datadog" - ], - "type": "string" - }, - "username": { - "description": "Username specifies the name under which events are posted.", - "maxLength": 2048, - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ProviderStatus defines the observed state of the Provider.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Provider.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last reconciled generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1.json index 90141f3..c8eda12 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1.json @@ -28,19 +28,98 @@ "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", "type": "string" }, + "oidcProviders": { + "description": "OIDCProviders specifies the OIDC providers used to authenticate incoming\nrequests when Type is 'generic-oidc'. The provider whose IssuerURL matches\nthe token's 'iss' claim is used to verify the token signature, expiration\nand audience, and to evaluate the configured CEL validations against the\ntoken claims.", + "items": { + "description": "OIDCProvider configures an OIDC issuer used to authenticate requests for a\n'generic-oidc' Receiver.", + "properties": { + "audience": { + "description": "Audience is the expected audience ('aud' claim) for tokens issued by\nthis provider. Defaults to 'notification-controller'.", + "type": "string" + }, + "issuerURL": { + "description": "IssuerURL is the OIDC issuer URL used for provider discovery. It must\nmatch the 'iss' claim of tokens issued by this provider.", + "pattern": "^https?://", + "type": "string" + }, + "validations": { + "description": "Validations is the list of CEL boolean expressions evaluated against the\ntoken claims and the variables. The request is accepted only if all of\nthem evaluate to true; the message of each failing expression is returned\nto the caller.\n\nAt least one validation is required. A valid signature alone does not\nauthorize a request: public issuers issue tokens to any caller on the\nplatform, so the validations must constrain the caller's identity claims\n(e.g. 'repository_owner' for GitHub Actions).", + "items": { + "description": "OIDCValidation is a CEL boolean expression evaluated against the OIDC token\nclaims and variables of a 'generic-oidc' Receiver.", + "properties": { + "expression": { + "description": "Expression is the CEL boolean expression to evaluate.", + "type": "string" + }, + "message": { + "description": "Message is returned to the caller when the expression evaluates to false.", + "type": "string" + } + }, + "required": [ + "expression", + "message" + ], + "type": "object", + "additionalProperties": false + }, + "minItems": 1, + "type": "array" + }, + "variables": { + "description": "Variables is an optional list of named CEL expressions, evaluated in order\nand exposed as 'vars.'. Each expression can read the token claims\nvia 'claims' and any variable defined before it. Use it to share\nsub-expressions across validations.", + "items": { + "description": "OIDCVariable is a named CEL expression evaluated against the OIDC token\nclaims of a 'generic-oidc' Receiver.", + "properties": { + "expression": { + "description": "Expression is the CEL expression that defines the variable value.", + "type": "string" + }, + "name": { + "description": "Name is the variable name; it must be a valid CEL identifier.", + "type": "string" + } + }, + "required": [ + "expression", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "required": [ + "issuerURL", + "validations" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "issuerURL" + ], + "x-kubernetes-list-type": "map" + }, "resourceFilter": { - "description": "ResourceFilter is a CEL expression expected to return a boolean that is\nevaluated for each resource referenced in the Resources field when a\nwebhook is received. If the expression returns false then the controller\nwill not request a reconciliation for the resource.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", + "description": "ResourceFilter is a CEL expression expected to return a boolean that is\nevaluated for each resource referenced in the Resources field when a\nwebhook is received. If the expression returns false then the controller\nwill not request a reconciliation for the resource.\nThe expression can read the resource metadata via 'res' and the webhook\nrequest body via 'req'. For generic-oidc receivers, the verified OIDC\ntoken claims are also available via 'claims'.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", "type": "string" }, "resources": { "description": "A list of resources to be notified about changes.", "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", + "description": "ReceiverResource references a resource to be notified about changes, with an\noptional per-resource CEL filter.", "properties": { "apiVersion": { "description": "API version of the referent", "type": "string" }, + "filter": { + "description": "Filter is a CEL expression expected to return a boolean that is evaluated\nfor each resource matched by this reference when a webhook is received,\nin addition to the top-level resourceFilter. A reconciliation is requested\nonly when both expressions (when set) return true.\nThe expression can read the resource metadata via 'res' and the webhook\nrequest body via 'req'. For generic-oidc receivers, the verified OIDC\ntoken claims are also available via 'claims'.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", + "type": "string" + }, "kind": { "description": "Kind of the referent", "enum": [ @@ -89,7 +168,7 @@ "type": "array" }, "secretRef": { - "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity. The Secret must contain a 'token'\nkey. For GCR receivers, the Secret must also contain an 'email' key\nwith the IAM service account email configured on the Pub/Sub push\nsubscription, and an 'audience' key with the expected OIDC token audience.", + "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity. The Secret must contain a 'token'\nkey. For GCR receivers, the Secret must also contain an 'email' key\nwith the IAM service account email configured on the Pub/Sub push\nsubscription, and an 'audience' key with the expected OIDC token audience.\n\nRequired for all receiver types except 'generic-oidc', which authenticates\nrequests using the OIDC token instead and must not set this field.", "properties": { "name": { "description": "Name of the referent.", @@ -111,6 +190,7 @@ "enum": [ "generic", "generic-hmac", + "generic-oidc", "github", "gitlab", "bitbucket", @@ -127,10 +207,27 @@ }, "required": [ "resources", - "secretRef", "type" ], "type": "object", + "x-kubernetes-validations": [ + { + "message": "generic-oidc receivers must define at least one oidcProvider", + "rule": "self.type != 'generic-oidc' || (has(self.oidcProviders) && size(self.oidcProviders) > 0)" + }, + { + "message": "oidcProviders can only be set when type is generic-oidc", + "rule": "self.type == 'generic-oidc' || !has(self.oidcProviders) || size(self.oidcProviders) == 0" + }, + { + "message": "secretRef cannot be set when type is generic-oidc", + "rule": "self.type != 'generic-oidc' || !has(self.secretRef)" + }, + { + "message": "secretRef is required when type is not generic-oidc", + "rule": "self.type == 'generic-oidc' || has(self.secretRef)" + } + ], "additionalProperties": false }, "status": { diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1beta2.json deleted file mode 100644 index 4b7cfe6..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/receiver-notification-v1beta2.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "description": "Receiver is the Schema for the receivers API.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ReceiverSpec defines the desired state of the Receiver.", - "properties": { - "events": { - "description": "Events specifies the list of event types to handle,\ne.g. 'push' for GitHub or 'Push Hook' for GitLab.", - "items": { - "type": "string" - }, - "type": "array" - }, - "interval": { - "description": "Interval at which to reconcile the Receiver with its Secret references.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "resources": { - "description": "A list of resources to be notified about changes.", - "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", - "properties": { - "apiVersion": { - "description": "API version of the referent", - "type": "string" - }, - "kind": { - "description": "Kind of the referent", - "enum": [ - "Bucket", - "GitRepository", - "Kustomization", - "HelmRelease", - "HelmChart", - "HelmRepository", - "ImageRepository", - "ImagePolicy", - "ImageUpdateAutomation", - "OCIRepository", - "ArtifactGenerator", - "ExternalArtifact" - ], - "type": "string" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\nMatchLabels requires the name to be set to `*`.", - "type": "object" - }, - "name": { - "description": "Name of the referent\nIf multiple resources are targeted `*` may be set.", - "maxLength": 253, - "minLength": 1, - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent", - "maxLength": 253, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "secretRef": { - "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this receiver.", - "type": "boolean" - }, - "type": { - "description": "Type of webhook sender, used to determine\nthe validation procedure and payload deserialization.", - "enum": [ - "generic", - "generic-hmac", - "github", - "gitlab", - "bitbucket", - "harbor", - "dockerhub", - "quay", - "gcr", - "nexus", - "acr" - ], - "type": "string" - } - }, - "required": [ - "resources", - "secretRef", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ReceiverStatus defines the observed state of the Receiver.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Receiver.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last observed generation of the Receiver object.", - "format": "int64", - "type": "integer" - }, - "url": { - "description": "URL is the generated incoming webhook address in the format\nof '/hook/sha256sum(token+name+namespace)'.\nDeprecated: Replaced by WebhookPath.", - "type": "string" - }, - "webhookPath": { - "description": "WebhookPath is the generated incoming webhook address in the format\nof '/hook/sha256sum(token+name+namespace)'.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourceset-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourceset-fluxcd-v1.json index f55e30e..b54ca2d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourceset-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourceset-fluxcd-v1.json @@ -205,6 +205,7 @@ "resources": { "description": "Resources contains the list of Kubernetes resources to reconcile.", "items": { + "type": "object", "x-kubernetes-preserve-unknown-fields": true }, "type": "array" @@ -217,12 +218,73 @@ "description": "The name of the Kubernetes service account to impersonate\nwhen reconciling the generated resources.", "type": "string" }, + "steps": { + "description": "Steps contains an ordered list of named steps to reconcile in sequence.\nEach step's resources are applied and health-checked before the next\nstep starts. Mutually exclusive with Resources and ResourcesTemplate.", + "items": { + "additionalProperties": false, + "description": "ResourceSetStep defines a named step in the ResourceSet reconciliation\nsequence. The step's resources are applied and health-checked before\nthe next step starts.", + "properties": { + "name": { + "description": "Name of the step, must be unique within the ResourceSet.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "resources": { + "description": "Resources contains the list of Kubernetes resources to reconcile.", + "items": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array" + }, + "resourcesTemplate": { + "description": "ResourcesTemplate is a Go template that generates the list of\nKubernetes resources to reconcile. The template is rendered\nas multi-document YAML, the resources should be separated by '---'.\nWhen both Resources and ResourcesTemplate are set, the resulting\nobjects are merged and deduplicated, with the ones from Resources taking precedence.", + "type": "string" + }, + "timeout": { + "description": "Timeout is the maximum time to wait for the step's resources to\nbecome ready. When not set, the ResourceSet reconciliation\ntimeout is used.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of resources or resourcesTemplate must be set", + "rule": "has(self.resources) || has(self.resourcesTemplate)" + } + ] + }, + "maxItems": 20, + "minItems": 1, + "type": "array", + "x-kubernetes-validations": [ + { + "message": "step names must be unique", + "rule": "self.all(s, self.exists_one(t, t.name == s.name))" + } + ] + }, "wait": { "description": "Wait instructs the controller to check the health\nof all the reconciled resources.", "type": "boolean" } }, - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": "steps is mutually exclusive with resources and resourcesTemplate", + "rule": "!has(self.steps) || (!has(self.resources) && !has(self.resourcesTemplate))" + }, + { + "message": "at least one of steps, resources or resourcesTemplate must be set", + "rule": "has(self.steps) || has(self.resources) || has(self.resourcesTemplate)" + } + ] }, "status": { "additionalProperties": false, diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourcesetinputprovider-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourcesetinputprovider-fluxcd-v1.json index 5e98e35..5e713f1 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourcesetinputprovider-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/assets/schemas/resourcesetinputprovider-fluxcd-v1.json @@ -230,11 +230,11 @@ }, { "message": "cannot specify spec.certSecretRef when spec.type is one of Static, AzureDevOps*, AWSCodeCommit*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag", - "rule": "!has(self.certSecretRef) || !(self.url == 'Static' || self.type.startsWith('AzureDevOps') || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" + "rule": "!has(self.certSecretRef) || !(self.type == 'Static' || self.type.startsWith('AzureDevOps') || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" }, { "message": "cannot specify spec.secretRef when spec.type is one of Static, AWSCodeCommit*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag", - "rule": "!has(self.secretRef) || !(self.url == 'Static' || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" + "rule": "!has(self.secretRef) || !(self.type == 'Static' || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" } ] }, diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/evals/evals.json b/plugins/gitops-kubernetes/skills/gitops-knowledge/evals/evals.json index 7f1cbee..4dfe890 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/evals/evals.json +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/evals/evals.json @@ -1,5 +1,5 @@ { - "skill_name": "gitops-fluxcd-knowledge", + "skill_name": "gitops-knowledge", "evals": [ { "id": 1, @@ -22,22 +22,25 @@ }, { "id": 2, - "prompt": "We use GitHub pull requests for our app at https://github.com/acme-corp/web-platform. I need a ResourceSet that automatically creates preview environments for PRs labeled 'preview'. Each preview should get its own namespace named 'preview-', a GitRepository pointing to the repo with the PR's commit SHA as the ref, and a Kustomization deploying from that source. The ResourceSetInputProvider should check every 2 minutes. Generate the complete manifests.", - "expected_output": "A ResourceSetInputProvider of type GitHubPullRequest with filter for 'preview' label and reconcileEvery annotation, plus a ResourceSet with inputsFrom referencing the provider. Templates must use << >> delimiters.", + "prompt": "We use GitHub pull requests for our app at https://github.com/acme-corp/web-platform. Set up preview environments for every PR labeled 'deploy-preview'. Requirements: (1) preview code is untrusted, so it must run under least privilege; use a namespace-scoped ServiceAccount named 'flux'. (2) Deploy all previews into a single shared 'preview' namespace. (3) The repo is private; authenticate with a GitHub App using a Secret named 'github-app-auth'. (4) Our CI builds and pushes an image tagged 'preview-' for each PR, and the deployment must use that image. (5) The provider should poll GitHub every 2 minutes. Generate the complete manifests and note the one-time preview namespace and RBAC setup.", + "expected_output": "A ResourceSetInputProvider (type GitHubPullRequest, deploy-preview label filter, reconcileEvery 2m annotation) and a ResourceSet both in the 'preview' namespace. The ResourceSet runs under spec.serviceAccountName 'flux' (namespace-scoped, not cluster-admin), templates a GitRepository (provider github, secretRef github-app-auth, ref.commit inputs.sha) and a Kustomization (serviceAccountName flux, targetNamespace preview, nameSuffix per inputs.id, prune, an image override newTag preview-<< inputs.sha >>) into the shared preview namespace. No Namespace object is templated per PR. Uses << >> delimiters.", "files": [], "expectations": [ - "The ResourceSetInputProvider uses apiVersion fluxcd.controlplane.io/v1", "The ResourceSetInputProvider spec.type is 'GitHubPullRequest' (exact casing)", - "The ResourceSetInputProvider has a filter.labels array containing 'preview'", - "The ResourceSetInputProvider has annotation fluxcd.controlplane.io/reconcileEvery set to '2m'", - "The ResourceSet uses apiVersion fluxcd.controlplane.io/v1", - "The ResourceSet uses << >> template delimiters (not {{ }})", + "The ResourceSetInputProvider has a filter.labels array containing 'deploy-preview'", + "The ResourceSetInputProvider sets its poll cadence via the fluxcd.controlplane.io/reconcileEvery annotation set to '2m' (does not support a spec.interval field)", + "Both the ResourceSetInputProvider and the ResourceSet are in the dedicated 'preview' namespace (not flux-system)", "The ResourceSet references the input provider via spec.inputsFrom", - "The namespace template uses inputs.id (e.g., << inputs.id >>)", - "The GitRepository uses apiVersion source.toolkit.fluxcd.io/v1", - "The GitRepository ref.commit uses inputs.sha (the PR commit SHA)", + "The ResourceSet sets spec.serviceAccountName to 'flux' so previews reconcile under a namespace-scoped identity rather than the kustomize-controller's default cluster-admin", + "The ResourceSet does NOT template a per-PR Namespace object — all previews share the single pre-provisioned 'preview' namespace (isolation is by name, not by namespace)", + "The GitRepository uses apiVersion source.toolkit.fluxcd.io/v1 and is created in the 'preview' namespace", + "The GitRepository ref.commit uses inputs.sha (the PR head commit)", + "The GitRepository uses spec.provider 'github' and a secretRef.name 'github-app-auth' for GitHub App authentication", "The Kustomization uses apiVersion kustomize.toolkit.fluxcd.io/v1", - "The Kustomization includes spec.prune (required field, not omitted)" + "The Kustomization sets spec.serviceAccountName to 'flux' (least-privilege, matches the ResourceSet identity)", + "The Kustomization sets spec.targetNamespace to 'preview'", + "The Kustomization uses spec.nameSuffix templating inputs.id (e.g. '-pr<< inputs.id >>') to isolate each PR's workloads within the shared namespace", + "The Kustomization sets spec.images to override the app image with newTag 'preview-<< inputs.sha >>' (the image CI built for the PR commit)" ] }, { @@ -60,7 +63,7 @@ "The Receiver uses apiVersion notification.toolkit.fluxcd.io/v1 (not v1beta3)", "The Receiver spec.type is 'github'", "The Receiver has a secretRef pointing to a Secret for webhook validation", - "The Receiver spec.resources includes a GitRepository named 'infra'", + "The Receiver spec.resources references a resource named 'infra' to reconcile on push — either a GitRepository source or the infra Kustomization is acceptable", "The Receiver spec.events includes 'push' or a list of relevant GitHub events" ] }, @@ -100,7 +103,8 @@ "The dependsOn includes ready: true (a readyExpr CEL expression is optional, not required)", "The infra ResourceSet inputs include entries for cert-manager and monitoring", "The apps ResourceSet inputs include entries for frontend and backend", - "Generated Kustomizations include spec.prune (required field)" + "Because the cluster is multitenant, each generated Kustomization (and OCIRepository) sets spec.serviceAccountName for tenant impersonation, and each tenant namespace is given a ServiceAccount plus RoleBinding — not bare namespaces", + "Each component gets its own OCIRepository with a distinct url/path (per-component sources), not a single shared OCIRepository reused across every component" ] }, { @@ -125,13 +129,14 @@ }, { "id": 7, - "prompt": "Our team wants to adopt Gitless GitOps with Flux. We keep rendered Kubernetes manifests in a GitHub repo at https://github.com/acme-corp/k8s-infra under ./deploy. I need: (1) a GitHub Actions workflow that on push to main and on version tags packages ./deploy as an OCI artifact in ghcr.io, applies our channel tagging conventions (immutable build tags, 'latest' for main, 'latest-stable' for releases), and signs the artifact with keyless Cosign; (2) the Flux CLI command to push the artifact manually with the Git origin metadata embedded; (3) the OCIRepository and Kustomization manifests for the production cluster that pull the 'latest-stable' channel, enforce Cosign signature verification of the GitHub Actions identity, and deploy to the 'apps' namespace.", + "prompt": "Our team wants to adopt Gitless GitOps with Flux. We keep rendered Kubernetes manifests in a GitHub repo at https://github.com/acme-corp/k8s-infra under ./deploy. I need: (1) a GitHub Actions workflow that on push to main and on version tags validates the manifests against the Flux/Kubernetes schemas, then packages ./deploy as an OCI artifact in ghcr.io, applies our channel tagging conventions (immutable build tags, 'latest' for main, 'latest-stable' for releases), and signs the artifact with keyless Cosign; (2) the Flux CLI command to push the artifact manually with the Git origin metadata embedded; (3) the OCIRepository and Kustomization manifests for the production cluster that pull the 'latest-stable' channel, enforce Cosign signature verification of the GitHub Actions identity, and deploy to the 'apps' namespace.", "expected_output": "A GitHub Actions workflow with packages:write and id-token:write permissions, using the Flux CLI or the controlplaneio-fluxcd push action and cosign-installer, signing the pushed digest with 'cosign sign --yes'. Tag conventions: immutable build tags plus 'latest' (main) and 'latest-stable' (releases). A 'flux push artifact' command with --path, --source, and --revision in '@sha1:' format. An OCIRepository (source.toolkit.fluxcd.io/v1) with ref.tag latest-stable and verify.provider cosign with matchOIDCIdentity, plus a Kustomization (kustomize.toolkit.fluxcd.io/v1) with prune and targetNamespace apps.", "files": [], "expectations": [ "The GitHub Actions workflow grants 'id-token: write' permission (required for keyless Cosign signing)", "The GitHub Actions workflow grants 'packages: write' permission (required for pushing to ghcr.io)", - "The workflow uses the controlplaneio-fluxcd/distribution push action", + "The workflow validates the manifests with the Flux Schema plugin before publishing — via the 'fluxcd/flux-schema/actions/validate' action or a 'flux schema validate' step — so a schema-invalid manifest fails the build ahead of the push", + "The workflow pushes the OCI artifact to ghcr.io either via the controlplaneio-fluxcd/distribution push action OR via the Flux CLI ('flux push artifact')", "The workflow installs Cosign (e.g. sigstore/cosign-installer) and signs the pushed artifact digest with 'cosign sign --yes'", "The tagging follows the channel conventions: 'latest' for main branch builds and 'latest-stable' for release tags, alongside immutable build tags (commit SHA or version)", "The manual CLI command is 'flux push artifact oci://...' with a --path flag pointing at the manifests directory", @@ -140,7 +145,73 @@ "The OCIRepository url starts with 'oci://' and ref.tag is 'latest-stable'", "The OCIRepository spec.verify.provider is 'cosign' with matchOIDCIdentity matching the GitHub Actions OIDC issuer (token.actions.githubusercontent.com) and the repo's workflow subject", "The Kustomization uses apiVersion kustomize.toolkit.fluxcd.io/v1 with sourceRef kind OCIRepository", - "The Kustomization includes spec.prune and targetNamespace 'apps'" + "The Kustomization includes spec.prune and targetNamespace 'apps'", + "The keyless Cosign signing targets the artifact's immutable digest (signs the pushed digest), not a mutable channel tag like 'latest-stable'" + ] + }, + { + "id": 8, + "prompt": "We run an air-gapped on-prem cluster with our own Sigstore stack (self-hosted Fulcio and Rekor). Deploy the podinfo Helm chart from our internal registry at oci://harbor.internal/charts/podinfo (version 6.x), with keyless Cosign verification against our custom Sigstore trusted root, which we stored in a Secret named 'sigstore-trusted-root'. For the HelmRelease: use the kstatus poller wait strategy; we inject an Istio sidecar via a Kustomize post-renderer, but Helm hooks must NOT be passed through the post-renderer (keep the Helm 3 behavior); and feed our comma-separated ALLOWED_HOSTS value ('svc-a.internal,svc-b.internal') into the chart values at config.allowedHosts as a literal string so Helm doesn't split it. Separately, apply our platform addons from oci://harbor.internal/platform/addons with a Kustomization into the 'platform' namespace — an HPA manages the addon Deployment's replicas, so Flux must not revert /spec/replicas during drift detection. Generate all manifests.", + "expected_output": "An OCIRepository (source.toolkit.fluxcd.io/v1) for the chart with layerSelector for the Helm media type and verify.provider cosign plus verify.trustedRootSecretRef pointing at 'sigstore-trusted-root'. A HelmRelease (helm.toolkit.fluxcd.io/v2) using chartRef, waitStrategy.name 'poller', postRenderStrategy 'nohooks', a postRenderers kustomize patch for the sidecar, and a valuesFrom entry with literal: true and targetPath config.allowedHosts. A second OCIRepository plus a Kustomization (kustomize.toolkit.fluxcd.io/v1) into the 'platform' namespace with spec.ignore excluding /spec/replicas on the Deployment and spec.prune set.", + "files": [], + "expectations": [ + "The chart OCIRepository uses apiVersion source.toolkit.fluxcd.io/v1 with url starting oci:// and a Helm chart layerSelector (mediaType 'application/vnd.cncf.helm.chart.content.v1.tar+gzip', operation 'copy')", + "The chart OCIRepository spec.verify.provider is 'cosign'", + "The chart OCIRepository spec.verify.trustedRootSecretRef.name is 'sigstore-trusted-root' (custom Sigstore root for air-gapped keyless verification)", + "The HelmRelease uses apiVersion helm.toolkit.fluxcd.io/v2 and references the chart via spec.chartRef (not spec.chart.spec)", + "The HelmRelease spec.waitStrategy.name is 'poller' (not 'legacy')", + "The HelmRelease spec.postRenderStrategy is 'nohooks' (so hooks are not sent to post-renderers)", + "The HelmRelease spec.postRenderers contains a kustomize patch injecting the Istio sidecar", + "The HelmRelease has a spec.valuesFrom entry with literal: true and targetPath 'config.allowedHosts' (so the comma-separated value is merged verbatim, not split by Helm --set syntax)", + "The Kustomization uses apiVersion kustomize.toolkit.fluxcd.io/v1 with sourceRef kind OCIRepository and targetNamespace 'platform'", + "The Kustomization spec.ignore contains a rule with paths ['/spec/replicas'] and a target selecting kind Deployment", + "The Kustomization does NOT use the 'kustomize.toolkit.fluxcd.io/ssa: Ignore' annotation for this (that would skip the whole object, not just the replicas field)" + ] + }, + { + "id": 9, + "prompt": "Our fleet GitOps monorepo is at {FIXTURE:tests/gitops-knowledge/add-app-repo} (copy it to a scratch directory first). Add a new app called 'backend' deployed from the Helm chart at oci://ghcr.io/acme-corp/charts/backend - track 1.x releases. It should run in its own 'backend' namespace on both the staging and production clusters; the chart's replicaCount value should be 1 in staging and 3 in production. Follow the repo's existing conventions for structure and style, and make sure everything you leave behind is correct.", + "expected_output": "Eight new files mirroring the podinfo base+overlay layout: apps/base/backend with Namespace, OCIRepository (source.toolkit.fluxcd.io/v1, semver 1.x) and HelmRelease (helm.toolkit.fluxcd.io/v2, chartRef, RetryOnFailure), plus staging/production overlays patching replicaCount to 1 and 3. The agent inventories the repo (flux schema discover) before placing files and validates the rendered overlays (kustomize build | flux schema validate) before finishing.", + "files": [ + "tests/gitops-knowledge/add-app-repo" + ], + "expectations": [ + "Base directory apps/base/backend exists with a kustomization.yaml, a Namespace, an OCIRepository, and a HelmRelease, mirroring the podinfo layout", + "Staging and production overlays exist at apps/staging/backend and apps/production/backend, and 'kustomize build' succeeds for both", + "The OCIRepository uses apiVersion source.toolkit.fluxcd.io/v1 with url oci://ghcr.io/acme-corp/charts/backend and a 1.x semver ref (layerSelector optional, matching repo convention)", + "The HelmRelease uses apiVersion helm.toolkit.fluxcd.io/v2 with chartRef (the repo's convention) and RetryOnFailure install/upgrade strategy", + "Rendered replicaCount is 1 in the staging build output and 3 in the production build output", + "The rendered build output of both new overlays passes 'flux schema validate' with zero invalid documents", + "The final report states that schema validation (flux schema validate) was run as part of verification" + ] + }, + { + "id": 10, + "prompt": "Flux on our staging cluster reports the Kustomization flux-system/apps as failing - the podinfo app errors with a kustomize build failure. A checkout of our GitOps monorepo is at {FIXTURE:tests/gitops-knowledge/debug-repo} (copy it to a scratch directory first). I don't have cluster access from here. Find and fix what's broken, and while you're in there make sure the rest of the repo's manifests are actually valid so we don't hit another failure right after this one.", + "expected_output": "Three planted bugs found and fixed: (1) apps/staging/podinfo/kustomization.yaml patch path typo podinfo-values.yml -> .yaml (kustomize build failure), (2) apps/base/podinfo/release.yaml interval '1 hour' -> valid duration (schema pattern violation that survives the build), (3) infrastructure/controllers/metrics-server/release.yaml sets both spec.chart and spec.chartRef (CEL violation). Agent reproduces the failure locally with kustomize build, fixes, and re-validates the whole repo (overlays via build output, plain dirs directly).", + "files": [ + "tests/gitops-knowledge/debug-repo" + ], + "expectations": [ + "The staging podinfo overlay builds again: 'kustomize build apps/staging/podinfo' succeeds and the staging values patch is applied (build output contains 'Hello from staging')", + "The base HelmRelease interval is a valid duration again (the '1 hour' schema violation is fixed)", + "The metrics-server HelmRelease CEL violation is fixed: it no longer sets both spec.chart and spec.chartRef", + "The whole repo is schema-clean: plain-manifest directories validate directly and overlays validate via their build output (--skip-missing-schemas for the third-party ClusterIssuer), with zero invalid documents", + "The final report identifies all three distinct issues (broken patch path, invalid interval duration, chart/chartRef conflict)" + ] + }, + { + "id": 11, + "prompt": "We onboard internal teams onto our platform cluster with Flux Operator. In a scratch directory, create a tenants.yaml with a ResourceSet that, for each tenant (team-alpha and team-bravo), creates: the tenant namespace, a ServiceAccount named 'flux' in it, and a Flux Kustomization in the tenant namespace impersonating that ServiceAccount, applying path ./tenants/ from our existing GitRepository named 'tenants' in flux-system (every 10 minutes, with pruning). Last time we did this by hand a templating typo took down onboarding, so double-check your work before you hand it back.", + "expected_output": "A ResourceSet (fluxcd.controlplane.io/v1) with inputs for both tenants templating Namespace, ServiceAccount and Kustomization per tenant using << inputs.* >> delimiters; the Kustomization uses serviceAccountName flux and a cross-namespace sourceRef with namespace flux-system. The agent renders it locally (flux operator build resourceset) and schema-validates the rendered output.", + "files": [], + "expectations": [ + "tenants.yaml contains a ResourceSet with apiVersion fluxcd.controlplane.io/v1 and inputs for both team-alpha and team-bravo", + "The template uses << inputs.* >> delimiters and contains no Go-template {{ }} expressions", + "'flux operator build resourceset -f tenants.yaml' renders successfully and produces 2 Namespaces, 2 ServiceAccounts named 'flux', and 2 Flux Kustomizations", + "Rendered Kustomizations are correct: in the tenant namespace, serviceAccountName 'flux', path ./tenants/, sourceRef GitRepository 'tenants' with namespace flux-system (cross-namespace ref), interval 10m, prune true", + "The rendered build output passes 'flux schema validate' with zero invalid documents", + "The final report states the ResourceSet was rendered locally (flux-operator build) and schema-validated as verification" ] } ] diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-cli.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-cli.md new file mode 100644 index 0000000..2de2ac5 --- /dev/null +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-cli.md @@ -0,0 +1,205 @@ +# Flux CLI and Plugins for Agents + +The Flux CLI and its plugins turn manifest authoring from an open-loop guess into a +closed loop: discover the repository layout before placing files, render overlays and +templates locally to see exactly what Flux would apply, and validate every document +against OpenAPI schemas before handing the result back to the user. All commands here +run offline against local files — no cluster access is required unless noted. + +Use this reference when working inside a GitOps repository, when generated manifests +need schema validation, when a Kustomize overlay or ResourceSet must be rendered and +debugged locally, or when users ask about the `flux schema` plugin, `flux build`, +`flux envsubst`, or `flux-operator build`. + +**Contents:** [Plugin Setup](#plugin-setup) | [Repository Discovery](#repository-discovery-with-flux-schema-discover) | [The Authoring Control Loop](#the-authoring-control-loop-with-flux-schema-validate) | [Schemas for Custom CRDs](#schemas-for-custom-crds) | [Rendering Manifests Locally](#rendering-manifests-locally) | [Debugging a Failing Overlay](#debugging-a-failing-overlay) | [When Tools Are Missing](#when-tools-are-missing) + +## Plugin Setup + +The `flux schema` commands come from the flux-schema plugin, installed via the +Flux CLI plugin catalog: + +```bash +flux plugin install schema # installs flux-schema +flux plugin install operator # installs flux-operator (for ResourceSet/FluxInstance builds) +flux plugin list # show installed plugins and versions +flux plugin update # update all installed plugins +``` + +Installed plugins are invoked through the Flux CLI (`flux schema ...`, +`flux operator ...`) or directly as `flux-schema` / `flux-operator` binaries — +both forms are equivalent. + +## Repository Discovery with `flux schema discover` + +Before creating or moving files in a GitOps repository, inventory it. One command +replaces a long series of glob and grep probes, and the classification tells you where +new manifests belong so you follow the repository's existing conventions: + +```bash +# Human-readable inventory of the current directory +flux schema discover + +# Machine-readable, for picking paths programmatically +flux schema discover -o json + +# Scope to a subtree, skip test fixtures +flux schema discover ./clusters/production --skip-file 'tests' +``` + +The output lists Flux custom resources per file, counts plain Kubernetes resources by +kind, and classifies every directory as `manifests`, `kustomize-overlay`, `helm-chart`, +or `terraform`. Dotfiles and dot-directories (`.git`, `.github`) are skipped by default. + +Read the inventory before answering questions like "where should this HelmRelease go?" +— the existing sources, Kustomizations, and overlay structure define the answer far +better than generic conventions do. + +## The Authoring Control Loop with `flux schema validate` + +Reading the bundled OpenAPI schemas before generating YAML prevents most mistakes, but +not all of them: indentation slips, fields pasted at the wrong nesting level, and +non-Flux resources (Deployments, Services, HPAs) that the bundled schemas don't cover. +Validation catches what reading misses, so treat it as part of authoring, not a +separate QA step: + +1. Write or edit the manifests. +2. Run `flux schema validate` on the files (or on the rendered build output). +3. Read the errors — each one names the file, the document, and the JSON path at fault. +4. Fix and re-run until the command exits clean. + +```bash +# Plain-manifest directories: validate the files directly +flux schema validate ./clusters/production --verbose + +# Kustomize overlays: validate the rendered build output, not the source files +kustomize build ./apps/staging/podinfo | flux schema validate + +# Tolerate third-party CRDs that have no schema in the catalog +flux schema validate ./clusters/production --skip-missing-schemas +``` + +Pick the mode per directory using the `flux schema discover` classification: +directories classified `kubernetes-manifests` contain complete documents and validate +directly; directories classified `kustomize-overlay` contain partial documents — +`kustomization.yaml` build configs (no `metadata.name`) and strategic-merge patches +(e.g. a HelmRelease values fragment with no `spec.interval`) — which fail standalone +validation with false positives. Always validate an overlay through its build output. + +The default catalog covers the latest stable Kubernetes and Flux APIs, and validation +also evaluates the CEL rules (`x-kubernetes-validations`) embedded in the Flux CRDs — +it catches cross-field mistakes like mutually exclusive fields both being set, which +plain structural checks miss. + +Important flags: + +| Flag | Purpose | +|---|---| +| `--verbose` | Print a line per document, including valid and skipped ones — confirms nothing was silently skipped | +| `--skip-missing-schemas` | Skip documents with no schema in the catalog instead of failing (third-party CRDs) | +| `--schema-location` | Add a schema source (repeatable): `default` for the built-in catalog, a local directory written by `flux schema extract`, or a URL such as `https://raw.githubusercontent.com/datreeio/CRDs-catalog/main` | +| `--skip-kind` | Skip documents by `Kind` or `apiVersion/Kind`, e.g. `--skip-kind v1/Secret` | +| `--skip-json-path` | Strip a field before validation, e.g. `--skip-json-path v1/Secret:/sops` for SOPS metadata that Flux removes at apply time | +| `--skip-file` | Glob matched against file and directory basenames, e.g. `--skip-file 'kustomization.yaml'` when validating overlay sources rather than build output | +| `-o json` | Structured results for programmatic triage | + +Repositories can pin their validation conventions in a checked-in config so every run +uses the same skips and schema sources — look for `.fluxschema.yml` in the repo root +and pass it with `--config` (CLI flags still override). + +Encrypted SOPS Secrets and files containing unresolved substitution variables are the +usual sources of false positives: skip Secrets with `--skip-kind v1/Secret` or strip +metadata with `--skip-json-path`, and resolve variables with `flux envsubst` before +validating (see below). + +## Schemas for Custom CRDs + +When a repository ships CRDs the catalog doesn't know (internal operators, niche +projects), extract schemas from the CRD manifests themselves and feed them back into +validation: + +```bash +# Extract JSON Schemas from CRD YAML files (or stdin) into a local directory +flux schema extract crd ./crds/my-operator-crds.yaml -d ./my-schemas +kustomize build ./config/crd | flux schema extract crd -d ./my-schemas + +# Validate with the default catalog plus the extracted schemas +flux schema validate ./apps --schema-location default --schema-location ./my-schemas +``` + +`flux schema extract k8s --version 1.35.0 -d ./my-schemas` does the same from a +Kubernetes OpenAPI v2 swagger document, which is how you validate against a specific +Kubernetes minor version instead of the catalog default. + +## Rendering Manifests Locally + +Flux applies *rendered* output — Kustomize overlays after patching, ResourceSet +templates after input expansion, variables after substitution. Rendering locally shows +that exact output without a cluster, and piping it into `flux schema validate` closes +the loop on the real thing rather than the source files: + +```bash +# Does the overlay even build? (kubectl kustomize works too) +kustomize build ./apps/staging + +# Render exactly what kustomize-controller would apply for a Flux Kustomization, +# including postBuild substitutions — offline with --dry-run +flux build kustomization my-app \ + --path ./apps/staging \ + --kustomization-file ./clusters/staging/my-app.yaml \ + --dry-run | flux schema validate + +# Replicate postBuild.substitute locally with environment variables; +# --strict fails on variables without a default that are missing from the env +export cluster_region=eu-central-1 +kustomize build ./clusters/staging | flux envsubst --strict + +# Expand a ResourceSet template into the objects it would generate +flux operator build resourceset -f my-resourceset.yaml \ + --inputs-from my-inputs.yaml | flux schema validate + +# Render the full Flux installation a FluxInstance would produce +flux operator build instance -f flux.yaml +``` + +Notes: + +- `flux build kustomization --dry-run` needs no cluster, but skips substitutions that + come from in-cluster Secrets and ConfigMaps — use `flux envsubst` with exported + variables to fill those in locally. Without `--dry-run` it fetches the Kustomization + from the cluster instead of `--kustomization-file`. +- `--recursive` with `--local-sources GitRepository/flux-system/my-repo=./` builds a + whole Kustomization tree from a local checkout — useful for validating an entire + cluster directory in one pass. +- `flux operator build resourceset` accepts inputs from the ResourceSet itself + (`spec.inputs`), a separate inputs file (`--inputs-from`), or static + ResourceSetInputProvider manifests (`--inputs-from-provider`). +- `flux operator build instance` pulls the distribution artifact from + `ghcr.io/controlplaneio-fluxcd` — it needs registry network access, unlike the + other commands here. + +## Debugging a Failing Overlay + +When a user reports that a Kustomization fails to build or apply, reproduce it locally +instead of reasoning about the YAML in the abstract: + +1. `flux schema discover` — locate the Flux Kustomization definition and the overlay + path it points at. +2. `kustomize build ` — surfaces broken patches, missing resources, and bad + `kustomization.yaml` references with exact error messages. +3. `flux build kustomization --path --kustomization-file --dry-run` + — adds the Flux layer: postBuild substitutions, name/namespace overrides. +4. Pipe the successful build into `flux schema validate` — catches documents that + build fine but would be rejected by the API server or a Flux controller. +5. Fix the source files and re-run from the failing step until the whole chain is clean. + +Each step isolates one layer (Kustomize syntax → Flux transformations → schema +correctness), so the first failing step tells you which layer holds the bug. + +## When Tools Are Missing + +These commands are force multipliers, not hard requirements. If `flux`, a plugin, or +`kustomize` isn't installed, don't fail the task and don't install tools unprompted: +fall back to reading the bundled schemas in `assets/schemas/`, complete the work, and +tell the user which verification was skipped and how to enable it +(`flux plugin install schema`). A correct answer with a noted verification gap beats +a stalled task. diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-operator.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-operator.md index d16a588..1dc4539 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-operator.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/flux-operator.md @@ -4,6 +4,8 @@ The Flux Operator manages the Flux installation lifecycle through two CRDs: - **FluxInstance** (`fluxcd.controlplane.io/v1`) — declarative Flux installation and configuration - **FluxReport** (`fluxcd.controlplane.io/v1`) — read-only observed state of Flux +**Contents:** [Installation](#installation) | [FluxInstance](#fluxinstance) | [FluxReport](#fluxreport) + ## Installation Install the Flux Operator before creating a FluxInstance. @@ -176,16 +178,8 @@ When enabled: Configure FluxInstance to sync manifests from a source repository. -**OCI sync (Gitless GitOps):** -```yaml -spec: - sync: - kind: OCIRepository - url: "oci://ghcr.io/my-org/fleet" - ref: "latest" - path: "clusters/production" - pullSecret: "registry-auth" -``` +**OCI sync (Gitless GitOps):** see the Canonical YAML above — its `spec.sync` block +(`kind: OCIRepository`, `oci://` url, `ref`, `path`, `pullSecret`) is the complete shape. **Git sync:** ```yaml @@ -258,6 +252,33 @@ metadata: reconcile.fluxcd.io/watch: Enabled ``` +### Controller Sharding + +`spec.sharding` horizontally partitions reconciliation across multiple controller replicas. +Each shard runs a dedicated set of controllers that only process resources carrying a matching +shard label, spreading load on clusters with very large numbers of Flux resources. + +```yaml +spec: + sharding: + key: sharding.fluxcd.io/key # label key selecting a shard (default) + shards: + - shard1 + - shard2 + storage: ephemeral # ephemeral (default) or persistent +``` + +Assign a resource to a shard by labelling it, e.g. `sharding.fluxcd.io/key: shard1`. + +| Field | Type | Description | +|-------|------|-------------| +| `sharding.key` | string | Label key used to shard resources (default `sharding.fluxcd.io/key`) | +| `sharding.shards` | []string | Shard names (at least one) | +| `sharding.storage` | string | `ephemeral` (emptyDir, default) or `persistent` (PVC). When `persistent`, the top-level `spec.storage` must also be set | + +Invalid sharding configurations — such as `storage: persistent` without a `spec.storage` +PVC spec — are rejected by the operator's admission validation. + ### Kustomize Patches Customize Flux controller Deployments, ServiceAccounts, or other resources created by the operator: @@ -313,14 +334,3 @@ spec: Configure FluxReport with annotations on the resource: - `fluxcd.controlplane.io/reconcile: enabled|disabled` — enable/disable reporting - `fluxcd.controlplane.io/reconcileEvery: 5m` — reporting interval - -## Reconciliation Annotations - -Trigger immediate reconciliation: -```yaml -metadata: - annotations: - reconcile.fluxcd.io/requestedAt: "2024-01-01T00:00:00Z" # any unique value -``` - -Works on FluxInstance, FluxReport, ResourceSet, and ResourceSetInputProvider. diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-gitops.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-gitops.md index a5da3d0..5f28389 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-gitops.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-gitops.md @@ -7,7 +7,12 @@ pushes them to a container registry, and Flux pulls from the registry using Use this reference when users ask about Flux OCI artifacts, `flux push artifact`, GitHub Actions publishing, artifact signing, registry-based promotion, or running Flux in -clusters that should not access Git. +clusters that should not access Git. For automatically upgrading workloads when new +image tags are published (registry scanning), load +`references/gitless-image-automation.md` instead — this file covers the publish/consume +pipeline for rendered manifests. + +**Contents:** [Why Gitless GitOps](#why-gitless-gitops) | [Artifact Types](#artifact-types) | [Validating Manifests with `flux schema`](#validating-manifests-with-flux-schema) | [Publishing Manifests with `flux push artifact`](#publishing-manifests-with-flux-push-artifact) | [GitHub Actions Publisher](#github-actions-publisher) | [Consuming Manifest Artifacts](#consuming-manifest-artifacts) | [FluxInstance OCI Sync](#fluxinstance-oci-sync) | [Authentication](#authentication) | [Signing and Verification](#signing-and-verification) | [Promotion Patterns](#promotion-patterns) | [Observability and Tracing](#observability-and-tracing) ## Why Gitless GitOps @@ -59,6 +64,33 @@ spec: operation: copy ``` +## Validating Manifests with `flux schema` + +Validate the rendered manifests before packaging them as an OCI artifact: + +```bash +# Install the schema plugin +flux plugin install schema + +# Validate a directory of manifests against the default catalog +flux schema validate ./deploy/production --verbose --skip-missing-schemas + +# Validate the output of a build step (Kustomize overlay, ResourceSet, etc.) +kustomize build ./deploy/production | flux schema validate +``` + +Important flags: + +| Flag | Purpose | +|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--schema-location` | Add a schema source (repeatable); `default` keeps the built-in catalog, then append a URL e.g. `https://raw.githubusercontent.com/datreeio/CRDs-catalog/main` for out-of-tree CRDs | +| `--skip-missing-schemas` | Skip documents whose schema isn't in the catalog instead of failing (tolerates third-party CRDs without wiring up a schema source) | +| `--skip-json-path` | Strip a field before validation, e.g. `--skip-json-path v1/Secret:/sops` for SOPS metadata Flux removes at apply time | + +In CI, run the same check as a gate ahead of `flux push artifact`. The GitHub Actions +workflow below does this with the `fluxcd/flux-schema/actions/validate` action so the +artifact is only published when every manifest is schema-valid. + ## Publishing Manifests with `flux push artifact` Push a directory of rendered manifests using the short Git SHA as the OCI tag: @@ -127,7 +159,8 @@ Prefer these conventions: ## GitHub Actions Publisher -Example workflow that packages Kubernetes manifests and signs the OCI artifact with keyless Cosign. +Example workflow that validates the Kubernetes manifests with Flux Schema plugin, +packages and signs the OCI artifact with keyless Cosign. ```yaml name: publish @@ -149,11 +182,18 @@ jobs: id-token: write # for signing steps: - name: Checkout - uses: actions/checkout@v4 - - name: Setup Flux CLI - uses: fluxcd/flux2/action@main + uses: actions/checkout@v7 - name: Setup cosign uses: sigstore/cosign-installer@main + - name: Setup Flux CLI with plugins + uses: fluxcd/flux2/action@main + with: + plugins: | + schema + - name: Validate manifests + uses: fluxcd/flux-schema/actions/validate@main + with: + path: "." - name: Prepare tags id: prep run: | @@ -166,7 +206,7 @@ jobs: echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "version=${VERSION}" >> $GITHUB_OUTPUT - name: Login to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} @@ -201,30 +241,30 @@ Use `OCIRepository` as the source and point `Kustomization` at it: apiVersion: source.toolkit.fluxcd.io/v1 kind: OCIRepository metadata: - name: podinfo + name: apps namespace: flux-system spec: interval: 5m - url: oci://ghcr.io/org/k8s-infra + url: oci://ghcr.io/org/k8s-apps ref: tag: latest verify: provider: cosign matchOIDCIdentity: - issuer: ^https://token\.actions\.githubusercontent\.com$ - subject: ^https://github\.com/org/k8s-infra/\.github/workflows/publish\.yaml@refs/heads/main$ + subject: ^https://github\.com/org/k8s-apps/\.github/workflows/publish\.yaml@refs/heads/main$ --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization metadata: - name: podinfo + name: infra namespace: flux-system spec: interval: 10m retryInterval: 2m sourceRef: kind: OCIRepository - name: podinfo + name: apps path: ./ prune: true wait: true diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-image-automation.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-image-automation.md index 6d6ee93..223ba32 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-image-automation.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/gitless-image-automation.md @@ -218,25 +218,25 @@ Suspend a single provider to pause updates for one artifact without freezing the of the app: ```shell -flux-operator -n apps suspend rsip redis-image -flux-operator -n apps resume rsip redis-image +flux operator -n apps suspend rsip redis-image +flux operator -n apps resume rsip redis-image ``` Suspend the `ResourceSet` to freeze the entire deployment workflow: ```shell -flux-operator -n apps suspend rset podinfo +flux operator -n apps suspend rset podinfo ``` Force an immediate registry scan: ```shell -flux-operator -n apps reconcile rsip podinfo-image +flux operator -n apps reconcile rsip podinfo-image ``` Dry-run the `ResourceSet` locally with mock inputs to verify the template renders: ```shell -flux-operator build rset -f podinfo-resourceset.yaml \ +flux operator build rset -f podinfo-resourceset.yaml \ --inputs-from-provider static-inputs.yaml ``` diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/helmrelease.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/helmrelease.md index bb4b927..27e0115 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/helmrelease.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/helmrelease.md @@ -5,6 +5,8 @@ HelmRelease installs, upgrades, tests, and manages Helm releases with drift detection and automated remediation. +**Contents:** [Canonical YAML — Chart from HTTPS Repo](#canonical-yaml--chart-from-https-repo) | [Canonical YAML — Chart from OCI Registry (Recommended)](#canonical-yaml--chart-from-oci-registry-recommended) | [Chart Source — Mutual Exclusivity](#chart-source--mutual-exclusivity) | [Key Spec Fields](#key-spec-fields) | [Values](#values) | [Install and Upgrade Strategy](#install-and-upgrade-strategy) | [Drift Detection](#drift-detection) | [Post-Renderers](#post-renderers) | [Test Configuration](#test-configuration) | [Dependencies](#dependencies) | [Health Check Expressions](#health-check-expressions) | [Remote Cluster Deployment](#remote-cluster-deployment) | [CRD Lifecycle](#crd-lifecycle) | [Uninstall Configuration](#uninstall-configuration) | [Status](#status) + ## Canonical YAML — Chart from HTTPS Repo ```yaml @@ -104,9 +106,10 @@ These are **mutually exclusive** — use one or the other, never both. |-------|------|----------|-------------| | `interval` | duration | yes | Reconciliation interval | | `releaseName` | string | no | Helm release name (defaults to `metadata.name`) | -| `targetNamespace` | string | no | Namespace for the Helm release | +| `targetNamespace` | string | no | Namespace for the Helm release. Prefer deploying into `metadata.namespace` and creating that namespace in the parent Kustomization/ResourceSet (see best-practices.md); if set, also set `storageNamespace` to match so the Helm release storage lives with the workloads | | `serviceAccountName` | string | no | Service account for impersonation | | `timeout` | duration | no | Timeout for Helm operations | +| `waitStrategy.name` | string | no | Readiness wait engine: `poller` (default, uses kstatus polling) or `legacy` (Helm v3 waiting logic) | | `suspend` | bool | no | Pause reconciliation | ## Values @@ -134,6 +137,20 @@ spec: targetPath: database.password ``` +**Literal values (`helm --set-literal`):** set `literal: true` together with `targetPath` +to merge the referenced value verbatim, without interpreting Helm's `--set` syntax (commas, +brackets, dots, equal signs). Use this when a value contains characters Helm would otherwise +split, such as a JSON blob or a comma-separated string: +```yaml +spec: + valuesFrom: + - kind: ConfigMap + name: app-config + valuesKey: allowed-origins # e.g. "https://a.com,https://b.com" + targetPath: cors.origins + literal: true # merged as a single literal string +``` + Merge order: `valuesFrom` entries are merged in order, then `values` inline is merged on top. ## Install and Upgrade Strategy @@ -217,6 +234,19 @@ spec: Post-renderers are applied in order. Each renderer's output feeds into the next. +**Hook handling (`postRenderStrategy`):** controls whether Helm hooks are passed through +the post-renderers. This matters for Helm 4, where hooks are rendered alongside templates: + +```yaml +spec: + postRenderStrategy: combined # nohooks | combined | separate +``` + +- `combined` (default) — hooks and templates are sent to post-renderers together (Helm CLI default) +- `nohooks` — hooks are NOT sent to post-renderers (Helm 3 behavior; also the default when the + `UseHelm3Defaults` feature gate is enabled) +- `separate` — hooks and templates are sent in separate streams + ## Test Configuration Run Helm test hooks after install/upgrade: diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/image-automation.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/image-automation.md index 6ce3527..e702f0e 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/image-automation.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/image-automation.md @@ -14,6 +14,8 @@ All three resources use `apiVersion: image.toolkit.fluxcd.io/v1`. Required controllers: `image-reflector-controller` and `image-automation-controller` (add to FluxInstance components only on clusters that run automation). +**Contents:** [ImageRepository](#imagerepository) | [ImagePolicy](#imagepolicy) | [ImageUpdateAutomation](#imageupdateautomation) | [Image Policy Markers](#image-policy-markers) | [Complete End-to-End Pipeline](#complete-end-to-end-pipeline) + ## ImageRepository Scans a container image repository at regular intervals and stores discovered tags. @@ -206,6 +208,7 @@ spec: | `git.commit.author.name` | string | no | Commit author name | | `git.commit.author.email` | string | yes | Commit author email | | `git.commit.messageTemplate` | string | no | Go template for commit message | +| `git.commit.signingKey.secretRef.name` | string | no | Secret with an OpenPGP or SSH key to sign the commit | | `git.push.branch` | string | no | Branch to push to (enables PR workflow) | | `update.path` | string | no | Directory to scan for image markers (default: repository root) | | `update.strategy` | string | no | `Setters` (default) | @@ -225,6 +228,27 @@ spec: This creates/updates a branch with the image changes. Set up a CI workflow or manual process to create PRs from this branch and merge into main. +### Signed Commits + +Sign the automation commits with an OpenPGP or SSH key so the pushed commits carry a +verifiable signature (which a downstream `GitRepository` with `spec.verify` can then enforce): + +```yaml +spec: + git: + commit: + author: + name: flux-bot + email: flux@example.com + signingKey: + secretRef: + name: git-signing-key +``` + +The referenced Secret holds either: +- **OpenPGP** — a `git.asc` ASCII-armored key pair (and optional `passphrase`), or +- **SSH** — an `identity` SSH private key. + ### Commit Message Template The commit message template uses Go templates with `{{ }}` delimiters (this is diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/kustomization.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/kustomization.md index 18918c6..4f8e50a 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/kustomization.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/kustomization.md @@ -8,6 +8,8 @@ artifact. It is Flux's primary mechanism for deploying manifests to a cluster. **Important:** This is the Flux `Kustomization` CRD, not Kustomize's `kustomization.yaml` file. They share the name but are different resources. +**Contents:** [Canonical YAML](#canonical-yaml) | [Key Spec Fields](#key-spec-fields) | [Dependencies](#dependencies) | [PostBuild Variable Substitution](#postbuild-variable-substitution) | [Ignore Rules (Server-Side Apply Field Exclusion)](#ignore-rules-server-side-apply-field-exclusion) | [SOPS Decryption](#sops-decryption) | [Health Checks](#health-checks) | [Remote Cluster Deployment](#remote-cluster-deployment) | [Patches](#patches) | [Status and Inventory](#status-and-inventory) | [Controlling Apply Behavior with Annotations](#controlling-apply-behavior-with-annotations) | [Triggering Reconciliation](#triggering-reconciliation) + ## Canonical YAML ```yaml @@ -52,6 +54,7 @@ spec: | `force` | bool | no | Recreate resources that cannot be patched (default: false) | | `suspend` | bool | no | Pause reconciliation | | `deletionPolicy` | string | no | `MirrorPrune` (default), `Delete`, `WaitForTermination`, `Orphan` | +| `ignore` | list | no | Server-side apply field ignore rules — exclude specific JSON pointer paths from drift detection and apply | | `commonMetadata.labels` | map | no | Labels applied to all managed resources | | `commonMetadata.annotations` | map | no | Annotations applied to all managed resources | | `namePrefix` | string | no | Prefix added to all resource names | @@ -131,6 +134,33 @@ entries are merged in order (later entries override earlier). To use `${VAR}` literally without substitution, escape with `$$`: `$${VAR}` renders as `${VAR}`. +## Ignore Rules (Server-Side Apply Field Exclusion) + +`spec.ignore` excludes specific fields from both drift detection and apply, so Flux never +manages or reverts them. Each rule lists JSON Pointer (RFC 6901) `paths` and an optional +`target` selector. This is the Kustomization-level equivalent of the HelmRelease +`driftDetection.ignore` rules — use it for fields owned by other controllers (HPA replica +counts, mutating webhooks injecting sidecars, cloud-provisioned annotations): + +```yaml +spec: + ignore: + # Ignore replicas on all Deployments (managed by HPA) + - paths: ["/spec/replicas"] + target: + kind: Deployment + # Ignore an injected annotation on one Service + - paths: ["/metadata/annotations/service.beta.kubernetes.io~1aws-load-balancer-arn"] + target: + kind: Service + name: my-service +``` + +If `target` is omitted, the paths are ignored on every object in the Kustomization. The +`target` selector supports `group`, `version`, `kind`, `name`, `namespace`, `labelSelector`, +and `annotationSelector`. Unlike the `kustomize.toolkit.fluxcd.io/ssa: Ignore` annotation +(which skips an entire object), `ignore` rules exclude only the listed fields. + ## SOPS Decryption Decrypt SOPS-encrypted files during build: @@ -143,9 +173,30 @@ spec: name: sops-age # Secret with decryption keys ``` -The Secret should contain Age private keys (`.agekey` suffix), OpenPGP keys (`.asc` suffix). +The Secret referenced by `secretRef` can hold Age private keys (`.agekey` suffix), OpenPGP keys +(`.asc` suffix), cloud KMS credentials, and a `sops.vault-token` for HashiCorp Vault / OpenBao. +Age decryption also supports the post-quantum cipher for SOPS-encrypted files. + +**Secret-less authentication.** By default the controller authenticates using its **own +ServiceAccount** (controller-level workload identity) — no `serviceAccountName` field and no +feature gate required. This covers: +- **Cloud KMS** (AWS KMS, GCP KMS, Azure Key Vault) via the controller's cloud workload identity, and +- **Vault / OpenBao** via the Kubernetes auth method, using the kustomize-controller's SA token. + +Setting `decryption.serviceAccountName` switches authentication from controller-level to +**object-level** — the named per-Kustomization ServiceAccount is used instead (e.g. for +multi-tenancy). This requires the `ObjectLevelWorkloadIdentity` feature gate on kustomize-controller: + +```yaml +spec: + decryption: + provider: sops + serviceAccountName: tenant-sa # object-level identity; needs ObjectLevelWorkloadIdentity gate +``` -Without a `secretRef`, the decryption uses Cloud KMS with workload identity (GCP, AWS, Azure). +For Vault/OpenBao specifically, the authentication precedence is: a static `sops.vault-token` +in the `secretRef` Secret **>** the Kubernetes auth method (controller or object-level SA) **>** +a global `VAULT_TOKEN` environment variable patched onto the controller Deployment. ## Health Checks diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/notifications.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/notifications.md index aa96ab5..9c89a44 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/notifications.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/notifications.md @@ -4,6 +4,8 @@ Flux notification-controller handles both outgoing notifications (Provider + Ale incoming webhooks (Receiver). All resources are in the same namespace as the notification target or source. +**Contents:** [Provider](#provider) | [Alert](#alert) | [Receiver](#receiver) | [Common Patterns](#common-patterns) + ## Provider `apiVersion: notification.toolkit.fluxcd.io/v1beta3` @@ -229,9 +231,10 @@ stringData: | Field | Type | Required | Description | |-------|------|----------|-------------| -| `type` | string | yes | `generic`, `generic-hmac`, `github`, `gitlab`, `bitbucket`, `harbor`, `dockerhub`, `quay`, `gcr`, `nexus`, `acr`, `cdevents` (no `gitea`/`azuredevops` Receiver type — Gitea uses the `github` type) | +| `type` | string | yes | `generic`, `generic-hmac`, `generic-oidc`, `github`, `gitlab`, `bitbucket`, `harbor`, `dockerhub`, `quay`, `gcr`, `nexus`, `acr`, `cdevents` (no `gitea`/`azuredevops` Receiver type — Gitea uses the `github` type) | | `events` | array | yes | Event types to accept (e.g., `push`, `ping`, `pull_request`) | -| `secretRef.name` | string | yes | Secret with `token` for HMAC webhook verification. The Secret should carry the label `reconcile.fluxcd.io/watch: Enabled` so the controller reconciles the Receiver when it changes | +| `secretRef.name` | string | no | Secret with `token` for HMAC webhook verification (required except for `generic-oidc`). The Secret should carry the label `reconcile.fluxcd.io/watch: Enabled` so the controller reconciles the Receiver when it changes | +| `oidcProviders` | array | no | OIDC issuers that authenticate incoming requests when `type: generic-oidc` (secret-less) | | `resources` | array | yes | Resources to trigger reconciliation on | | `suspend` | bool | no | Pause the receiver | @@ -288,6 +291,40 @@ spec: The `req` object exposes the webhook payload fields and `res` provides the resource metadata (`res.metadata.name`, `res.metadata.labels`, `res.metadata.annotations`). +### Secret-less OIDC-Secured Receiver + +`type: generic-oidc` authenticates incoming requests with a signed OIDC/JWT bearer token +instead of a shared HMAC secret — no webhook secret to provision or rotate. The controller +verifies the token's signature, expiry, and audience against the provider whose `issuerURL` +matches the token's `iss` claim, then evaluates optional CEL `validations` against the claims. +Ideal for callers that already have a workload identity token (CI runners, cloud services): + +```yaml +apiVersion: notification.toolkit.fluxcd.io/v1 +kind: Receiver +metadata: + name: ci-trigger + namespace: flux-system +spec: + type: generic-oidc + events: + - push + oidcProviders: + - issuerURL: https://token.actions.githubusercontent.com + audience: flux-notification-controller # expected 'aud' claim (defaults to 'notification-controller') + validations: + # only accept tokens from a specific repo's workflows + - "claims.repository == 'my-org/my-app'" + resources: + - apiVersion: source.toolkit.fluxcd.io/v1 + kind: OCIRepository + name: my-app +``` + +`validations` are CEL booleans over `claims` (the token claims); the request is accepted only +if all pass. `variables` can define named CEL expressions exposed as `vars.` for reuse +inside validations. No `secretRef` is needed for `generic-oidc`. + ## Common Patterns ### Slack Notifications for All Failures diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/repo-patterns.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/repo-patterns.md index 8fc1ae5..81e7acc 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/repo-patterns.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/repo-patterns.md @@ -3,6 +3,8 @@ All patterns use Flux Operator with FluxInstance for cluster bootstrap. Each cluster has a FluxInstance that syncs from a source and applies manifests via Kustomizations or ResourceSets. +**Contents:** [Monorepo Pattern](#monorepo-pattern) | [Multi-Repo Git-Based Pattern](#multi-repo-git-based-pattern) | [Multi-Repo OCI-Based Pattern (Gitless GitOps)](#multi-repo-oci-based-pattern-gitless-gitops) | [Per-Cluster Configuration](#per-cluster-configuration) + ## Monorepo Pattern Single Git repository containing all infrastructure and application manifests with diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/resourcesets.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/resourcesets.md index 729ed23..b5c2050 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/resourcesets.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/resourcesets.md @@ -6,6 +6,8 @@ ResourceSet generates Kubernetes resources from a matrix of input values using a approach. It is the primary mechanism for multi-tenant orchestration, fleet management, and self-service platforms. +**Contents:** [Canonical YAML](#canonical-yaml) | [Key Spec Fields](#key-spec-fields) | [Template Syntax](#template-syntax) | [Testing ResourceSets Locally](#testing-resourcesets-locally) | [Input Strategies](#input-strategies) | [Dependencies](#dependencies) | [Step-Based Reconciliation](#step-based-reconciliation) | [Advanced Features](#advanced-features) | [Built-in Input Fields](#built-in-input-fields) | [ResourceSetInputProvider](#resourcesetinputprovider) | [Use Cases](#use-cases) + ## Canonical YAML Based on the Gitless reference architecture fleet pattern — deploys per-tenant namespaces with @@ -118,6 +120,8 @@ spec: | `inputsFrom` | array | References to ResourceSetInputProvider resources | | `inputStrategy.name` | string | `Flatten` (default) or `Permute` (Cartesian product) | | `resources` | array | Templated Kubernetes resource definitions | +| `resourcesTemplate` | string | Go-template string rendered as multi-document YAML (alternative to `resources`) | +| `steps` | array | Ordered, named reconciliation steps — each applied and health-checked before the next (mutually exclusive with `resources`/`resourcesTemplate`) | | `commonMetadata` | object | Labels/annotations applied to all generated resources | | `dependsOn` | array | Prerequisites with optional readiness checks | @@ -144,6 +148,24 @@ Template functions come from slim-sprig (a subset of Go's sprig template functio **Important:** Template expressions are evaluated per input entry. For each entry in `inputs`, the entire `resources` array is rendered once. +## Testing ResourceSets Locally + +Render the generated Kubernetes objects without a cluster using the Flux CLI: + +```shell +# Inline inputs (spec.inputs) — renders directly +flux operator build resourceset -f resourceset.yaml + +# Inputs from a provider (spec.inputsFrom) — supply the exported inputs from a file +flux operator build resourceset -f resourceset.yaml --inputs-from inputs.yaml + +# Or supply Static ResourceSetInputProvider manifests +flux operator build resourceset -f resourceset.yaml --inputs-from-provider providers.yaml + +# Validate the generated objects against the Flux/Kubernetes schemas +flux operator build resourceset -f resourceset.yaml --inputs-from inputs.yaml | flux schema validate +``` + ## Input Strategies ### Flatten (Default) @@ -265,6 +287,67 @@ Common patterns: - `status.conditions.filter(e, e.type == 'Ready').all(e, e.status == 'True')` — standard Ready check - `status.observedGeneration >= 0` — resource has been reconciled at least once +## Step-Based Reconciliation + +`spec.steps` replaces the flat `resources` list with an **ordered sequence of named steps**. +Each step's resources are applied and health-checked to completion before the next step starts, +giving in-ResourceSet ordering without splitting the work across multiple `dependsOn`-linked +ResourceSets. `steps` is mutually exclusive with `resources` and `resourcesTemplate`, but each +step carries its own `resources` or `resourcesTemplate` (with the same `<< >>` templating and +per-input rendering): + +```yaml +apiVersion: fluxcd.controlplane.io/v1 +kind: ResourceSet +metadata: + name: platform + namespace: flux-system +spec: + inputs: + - tenant: team1 + wait: true + steps: + - name: tenant # step 1: create the tenant namespace + RBAC, wait until applied + timeout: 1m + resources: + - apiVersion: v1 + kind: Namespace + metadata: + name: << inputs.tenant >> + - name: sources # step 2: only after step 1 is healthy + timeout: 3m + resources: + - apiVersion: source.toolkit.fluxcd.io/v1 + kind: OCIRepository + metadata: + name: apps + namespace: << inputs.tenant >> + spec: + interval: 5m + url: "oci://ghcr.io/my-org/apps/<< inputs.tenant >>" + ref: + tag: latest + - name: apps # step 3: only after sources are healthy + timeout: 5m + resources: + - apiVersion: kustomize.toolkit.fluxcd.io/v1 + kind: Kustomization + metadata: + name: apps + namespace: << inputs.tenant >> + spec: + interval: 30m + wait: true + sourceRef: + kind: OCIRepository + name: apps + path: "./" +``` + +Step names must be unique and DNS-label formatted max 63 chars. +Use `steps` when a single tenant/app's resources have internal ordering requirements; +use `dependsOn` across ResourceSets when whole ResourceSets must be ordered relative to each other. + ## Advanced Features ### Conditional Reconciliation @@ -302,24 +385,35 @@ For Secrets, you must also set the `type` field. ### resourcesTemplate Alternative to inline `resources`: a Go template string rendered as multi-document YAML -(documents separated by `---`), useful for `<<- range >>` and `<<- if >>` constructs that -the structured `resources` list cannot express: +(documents separated by `---`). Like `resources`, it is rendered **once per input**, with the +current input exposed as `inputs`. Its value over the structured `resources` list is the +`<<- range >>` and `<<- if >>` constructs — in particular, ranging over an **array field +within an input** to emit a variable number of objects per input (which the fixed `resources` +list cannot express): ```yaml spec: inputs: - tenant: team1 + components: [frontend, backend] - tenant: team2 + components: [api] resourcesTemplate: | - <<- range $input := .inputs >> + <<- range $component := inputs.components >> --- apiVersion: v1 kind: Namespace metadata: - name: << $input.tenant >> + name: << inputs.tenant >>-<< $component >> + labels: + tenant: << inputs.tenant >> <<- end >> ``` +Rendered per input: `team1` yields namespaces `team1-frontend` and `team1-backend`, `team2` +yields `team2-api`. Reference input fields as `<< inputs.field >>` and the loop variable as +`<< $field >>` (no leading dot). + When both `resources` and `resourcesTemplate` are set, the generated objects are merged, with the `resources` entries taking precedence on duplicates. @@ -445,30 +539,65 @@ Dependencies: policies → infra → apps. Each ResourceSet waits for the previo apiVersion: fluxcd.controlplane.io/v1 kind: ResourceSetInputProvider metadata: - name: preview-prs + name: app-previews + namespace: preview # the dedicated preview namespace, not flux-system spec: type: GitHubPullRequest url: https://github.com/org/app secretRef: - name: github-token + name: github-app-auth filter: labels: [deploy-preview] --- apiVersion: fluxcd.controlplane.io/v1 kind: ResourceSet metadata: - name: previews + name: app-previews + namespace: preview spec: + serviceAccountName: flux # scoped: admin within the preview namespace only inputsFrom: - - name: preview-prs + - name: app-previews resources: - - apiVersion: v1 - kind: Namespace + - apiVersion: source.toolkit.fluxcd.io/v1 + kind: GitRepository metadata: - name: "preview-<< inputs.id >>" - # ... deploy app at the PR's commit SHA + name: "app-<< inputs.id >>" + namespace: preview + spec: + interval: 2m + url: https://github.com/org/app.git + ref: + commit: << inputs.sha >> # pin to the PR head commit + provider: github + secretRef: + name: github-app-auth + - apiVersion: kustomize.toolkit.fluxcd.io/v1 + kind: Kustomization + metadata: + name: "app-<< inputs.id >>" + namespace: preview + spec: + serviceAccountName: flux + targetNamespace: preview + nameSuffix: "-pr<< inputs.id >>" # isolate this PR's workloads in the shared namespace + interval: 10m + prune: true + wait: true + timeout: 5m + retryInterval: 2m + sourceRef: + kind: GitRepository + name: "app-<< inputs.id >>" + path: ./deploy/preview + images: + - name: ghcr.io/org/app + newTag: preview-<< inputs.sha >> # the image CI built for this PR commit ``` +Provision the `preview` namespace once with the `flux` ServiceAccount and a RoleBinding +to the built-in `admin` — scoping `flux` to `preview` so PR content can't escalate beyond it. + ### Gitless Image Automation with ResourceSets `ResourceSet` + `ResourceSetInputProvider` of `type: OCIArtifactTag` implements image diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/sources.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/sources.md index 1850a40..cc12c21 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/sources.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/sources.md @@ -4,6 +4,8 @@ All source CRDs are in `apiVersion: source.toolkit.fluxcd.io/v1` (except Artifac which uses `source.extensions.fluxcd.io/v1beta1`). Source-controller polls for changes at the configured interval and produces versioned artifacts consumed by Kustomization and HelmRelease. +**Contents:** [GitRepository](#gitrepository) | [OCIRepository](#ocirepository) | [HelmRepository](#helmrepository) | [HelmChart](#helmchart) | [Bucket](#bucket) | [ExternalArtifact](#externalartifact) | [ArtifactGenerator](#artifactgenerator) + ## GitRepository Fetches manifests from a Git repository, producing a tarball artifact. @@ -41,8 +43,8 @@ spec: | `provider` | string | Keyless auth provider: `generic` (default), `aws` (CodeCommit IAM), `azure` (Azure DevOps Workload Identity), `github` (GitHub App) | | `sparseCheckout` | []string | List of directories to checkout when cloning; only the contents of the listed directories appear in the produced artifact | | `recurseSubmodules` | bool | Include Git submodules (default: false) | -| `verify.mode` | string | Which Git object(s) to verify: `HEAD`, `Tag`, or `TagAndHEAD` | -| `verify.secretRef.name` | string | Secret with PGP public keys (e.g., data keys `author1.asc`) | +| `verify.mode` | string | Which Git object(s) to verify: `HEAD` (default), `Tag`, or `TagAndHEAD` | +| `verify.secretRef.name` | string | Secret with public keys of trusted authors — PGP keys under `.asc` keys and/or SSH keys under `.sshpub` keys (Flux verifies both PGP- and SSH-signed commits/tags) | When no `ref` field is set, source-controller checks out the `master` branch. Ref precedence when multiple are set: `commit` > `name` > `semver` > `tag` > `branch`. There is no @@ -115,6 +117,7 @@ spec: | `layerSelector.operation` | string | `extract` (default) or `copy` | | `verify.provider` | string | Signature verification: `cosign` or `notation` | | `verify.matchOIDCIdentity` | array | OIDC issuer/subject patterns for keyless verification | +| `verify.trustedRootSecretRef.name` | string | Secret with a custom Sigstore `trusted_root.json` — verify against self-hosted Fulcio CA / Rekor (air-gapped keyless) | | `serviceAccountName` | string | Service account for image pull (uses imagePullSecrets) | | `insecure` | bool | Allow HTTP (non-TLS) connections | @@ -350,10 +353,40 @@ spec: name: podinfo-composite ``` +### Path Pattern Discovery (Monorepos) + +Instead of listing every component by hand, `spec.pathPattern` discovers matching directories +in a source and generates one ExternalArtifact per match. The format is `@/`, +where named captures (e.g. `{app}`) become placeholders usable in the generated artifact fields: + +```yaml +apiVersion: source.extensions.fluxcd.io/v1beta1 +kind: ArtifactGenerator +metadata: + name: apps + namespace: flux-system +spec: + sources: + - alias: mono + kind: GitRepository + name: monorepo + pathPattern: "@mono/apps/{app}/kustomize" # matches apps/frontend/kustomize, apps/backend/kustomize, ... + artifacts: + - name: "{app}" # one ExternalArtifact per discovered app + revision: "@mono" + copy: + - from: "@mono/apps/{app}/kustomize/**" + to: "@artifact/" +``` + +This keeps the ArtifactGenerator stable as teams add or remove components — no edit needed +when a new `apps//kustomize` directory appears. + **Key spec fields:** | Field | Type | Description | |-------|------|-------------| +| `pathPattern` | string | `@/` directory-discovery pattern with named captures (e.g. `{app}`) for monorepos | | `sources[].alias` | string | Unique alias for referencing in copy operations | | `sources[].kind` | string | `GitRepository`, `OCIRepository`, `Bucket`, `HelmChart`, or `ExternalArtifact` | | `sources[].name` | string | Source name | diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/terraform-bootstrap.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/terraform-bootstrap.md index c06b694..f71c2bd 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/terraform-bootstrap.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/terraform-bootstrap.md @@ -5,6 +5,8 @@ Terraform module bootstraps Flux Operator and a `FluxInstance` into a Kubernetes using a Kubernetes `Job`. Use this module when the cluster is provisioned with Terraform and Flux should take over GitOps reconciliation afterwards. +**Contents:** [Ownership Model](#ownership-model) | [Repository Layout](#repository-layout) | [Provider Configuration](#provider-configuration) | [GitOps vs Managed Resources](#gitops-vs-managed-resources) | [Revision and Drift](#revision-and-drift) | [Runtime Info and Variable Substitution](#runtime-info-and-variable-substitution) | [Node Scheduling](#node-scheduling) | [Shared Operator Values File](#shared-operator-values-file) | [Sync Source Authentication](#sync-source-authentication) | [Managed Secrets from External Stores](#managed-secrets-from-external-stores) | [Debugging Failed Bootstraps](#debugging-failed-bootstraps) + ## Ownership Model The module solves the bootstrap ownership problem by splitting responsibilities: diff --git a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/web-ui.md b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/web-ui.md index cc6ad2b..b2e9f9d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-knowledge/references/web-ui.md +++ b/plugins/gitops-kubernetes/skills/gitops-knowledge/references/web-ui.md @@ -4,6 +4,8 @@ The Flux Web UI is built into the Flux Operator and provides a browser-based das for viewing Flux resources, triggering reconciliations, and managing workloads. It is served on port `9080` by the `flux-operator` service. +**Contents:** [Enabling the Web UI](#enabling-the-web-ui) | [Accessing the Web UI](#accessing-the-web-ui) | [Authentication](#authentication) | [Role-Based Access Control](#role-based-access-control) | [User Actions](#user-actions) | [Audit](#audit) | [Standalone Deployment](#standalone-deployment) + ## Enabling the Web UI The Web UI is enabled by default in the Flux Operator Helm chart. To explicitly configure it: diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/SKILL.md b/plugins/gitops-kubernetes/skills/gitops-repo-audit/SKILL.md index 8e16770..0a67f71 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/SKILL.md +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/SKILL.md @@ -1,13 +1,13 @@ --- -compatibility: Requires awk, git, kustomize, kubeconform, flux, yq +compatibility: Requires flux, flux-schema, and kustomize or kubectl description: | Audit and validate Flux CD GitOps repositories by scanning local repo files (not live clusters) — runs Kubernetes schema validation, detects deprecated Flux APIs, reviews RBAC/multi-tenancy/secrets management, and produces a prioritized GitOps report. Use when users ask to audit, analyze, validate, review, or security-check a GitOps repo. license: Apache-2.0 metadata: github-path: skills/gitops-repo-audit - github-ref: refs/tags/v0.0.4 + github-ref: refs/tags/v0.1.0 github-repo: https://github.com/fluxcd/agent-skills - github-tree-sha: f1974c94903b38c0756041d9ee9bf84cdfb997f6 + github-tree-sha: 9ba3f6d5e5a59e7b882a2af0086cd745bfcbc025 name: gitops-repo-audit --- # GitOps Repository Auditor @@ -31,19 +31,36 @@ Understand the repository before diving into specifics. ```bash scripts/discover.sh -d ``` - The script scans all YAML files (including multi-document files) and outputs resource counts by kind and by directory. + The script wraps `flux-schema discover` and outputs JSON with a top-level `inventory` + object (alongside `kind`/`apiVersion`/`$schema`) containing: a `summary` (file, resource, + and line counts), a `directories` map classifying each directory as `kubernetes-manifests`, + `kustomize-overlay`, `helm-chart`, or `terraform`, a `resources` map with counts keyed by + `group/version/Kind`, and a `flux` map listing every Flux resource per file. Read the fields + under `.inventory`. Multi-document files are handled. 2. Classify the repository pattern by reading [repo-patterns.md](references/repo-patterns.md) and matching against the heuristics table 3. Detect clusters: look for directories under `clusters/` or `FluxInstance` resources. Read the FluxInstance to understand how the clusters are configured. 4. Check for `gotk-sync.yaml` under `flux-system/` — its presence indicates `flux bootstrap` was used. Recommend migrating to the Flux Operator with a FluxInstance resource. Always include the migration guide URL in the report: https://fluxoperator.dev/docs/guides/migration/ ### Phase 2: Manifest Validation -Run the bundled validation script to check YAML syntax, Kubernetes schemas, and Kustomize builds. +Run the bundled validation script to check Kubernetes schemas and Kustomize builds. +Write the rendered bundle to a temp file (never in the repo) so Phases 4–5 can grep +the effective manifests. Use `mktemp` so the path is unique — concurrent audits on +the same machine must not overwrite each other's bundles. If tmp is not writable, +`mktemp` fails and the script runs without the bundle: ```bash -scripts/validate.sh -d +bundle="$(mktemp "${TMPDIR:-/tmp}/flux-audit-bundle.XXXXXX" 2>/dev/null || true)" +scripts/validate.sh -d ${bundle:+-b "$bundle"} ``` +It validates every manifest and the rendered output of each Kustomize overlay, +exiting non-zero with a count of invalid resources and failed builds. Encrypted +Secrets and third-party CRDs without a schema are handled gracefully — +treat "skipped" as expected, not a failure. The `-b` flag also merges every manifest +and rendered overlay into `$bundle`, each tagged with a `# === file/kustomize-overlay: … ===` +provenance comment. + Use `-e ` to exclude additional directories from validation. ### Phase 3: API Compliance @@ -73,6 +90,7 @@ Focus on the categories most relevant to what you found in discovery: - Has HelmReleases? Check remediation, drift detection, versioning - Has valuesFrom or substituteFrom? Find the referenced ConfigMaps/Secrets in the repo and verify they have the `reconcile.fluxcd.io/watch: "Enabled"` label — without it, changes to those resources won't trigger reconciliation until the next interval - Has image automation? Check ImagePolicy semver ranges, update paths +- Has Kustomize overlays? Grep `$bundle` (if written) to see resources in rendered form — an overlay `patch`/`images` can change the effective manifest; cite line numbers from the raw file Also check for **consistency** across similar resources. For example, if some HelmReleases use the modern `install.strategy` pattern while others use legacy @@ -98,6 +116,7 @@ Focus on the categories most relevant to what you found in discovery: - Multi-tenant? Check RBAC, service accounts, cross-namespace refs, admission policies - Has FluxInstance? Check operator security settings (multitenant, network policies) - Has image automation? Check push credential separation and branch isolation +- Has Kustomize overlays? Grep `$bundle` (if written) for post-render security fields — e.g. a `securityContext` weakened or image retagged by an overlay patch, which the base files won't show ### Phase 6: Report @@ -152,10 +171,10 @@ Load reference files when you need deeper information: - **Not a Flux repo**: If no Flux CRDs are found, say so clearly. The repo might use ArgoCD, plain kubectl, or another tool. Don't force-fit Flux analysis. - **Mixed tooling**: Some repos combine Flux with CI workflows and Terraform. Analyze the Flux parts and note the other tools. -- **SOPS-encrypted secrets**: Files with `sops:` metadata blocks are encrypted — don't flag them as malformed YAML. The validation script already skips Secrets. +- **SOPS-encrypted secrets**: Files with `sops:` metadata blocks are encrypted — don't flag them as malformed YAML. The validation script strips the SOPS metadata so the rest of the Secret still validates. - **Generated manifests**: The `flux-system/gotk-components.yaml` is auto-generated by Flux bootstrap. Don't analyze it for best practices — it's managed by Flux itself. - **Repos without kustomization.yaml**: Some repos use plain YAML directories without Kustomize. Flux can reconcile these directly. Don't flag the absence of kustomization.yaml as an error. - **Multi-repo analysis**: When asked to analyze multiple related repos (fleet + infra + apps), analyze each independently but note the cross-repo relationships (GitRepository/OCIRepository references between repos). - **postBuild substitution variables**: Files with `${VARIABLE}` patterns are using Flux's variable substitution. Don't flag these as broken YAML — they're resolved at reconciliation time. -- **Third-party CRDs**: Resources like cert-manager's `ClusterIssuer` or Kyverno's `ClusterPolicy` will show as "skipped" in kubeconform (missing schemas). This is expected — only Flux CRD schemas are downloaded. Don't flag these as validation failures. +- **Third-party CRDs**: Resources without a catalog schema (e.g. cert-manager `ClusterIssuer`) show as "skipped" — expected, not a validation failure. - **Kustomize build files**: `kustomization.yaml` files with `apiVersion: kustomize.config.k8s.io/v1beta1` are Kustomize build configs, not Flux CRDs. diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/alert-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/alert-notification-v1beta2.json deleted file mode 100644 index 9ddaddb..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/alert-notification-v1beta2.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "description": "Alert is the Schema for the alerts API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "AlertSpec defines an alerting rule for events involving a list of objects.", - "properties": { - "eventMetadata": { - "additionalProperties": { - "type": "string" - }, - "description": "EventMetadata is an optional field for adding metadata to events dispatched by the\ncontroller. This can be used for enhancing the context of the event. If a field\nwould override one already present on the original event as generated by the emitter,\nthen the override doesn't happen, i.e. the original value is preserved, and an info\nlog is printed.", - "type": "object" - }, - "eventSeverity": { - "default": "info", - "description": "EventSeverity specifies how to filter events based on severity.\nIf set to 'info' no events will be filtered.", - "enum": [ - "info", - "error" - ], - "type": "string" - }, - "eventSources": { - "description": "EventSources specifies how to filter events based\non the involved object kind, name and namespace.", - "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", - "properties": { - "apiVersion": { - "description": "API version of the referent", - "type": "string" - }, - "kind": { - "description": "Kind of the referent", - "enum": [ - "Bucket", - "GitRepository", - "Kustomization", - "HelmRelease", - "HelmChart", - "HelmRepository", - "ImageRepository", - "ImagePolicy", - "ImageUpdateAutomation", - "OCIRepository", - "ArtifactGenerator", - "ExternalArtifact" - ], - "type": "string" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\nMatchLabels requires the name to be set to `*`.", - "type": "object" - }, - "name": { - "description": "Name of the referent\nIf multiple resources are targeted `*` may be set.", - "maxLength": 253, - "minLength": 1, - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent", - "maxLength": 253, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "exclusionList": { - "description": "ExclusionList specifies a list of Golang regular expressions\nto be used for excluding messages.", - "items": { - "type": "string" - }, - "type": "array" - }, - "inclusionList": { - "description": "InclusionList specifies a list of Golang regular expressions\nto be used for including messages.", - "items": { - "type": "string" - }, - "type": "array" - }, - "providerRef": { - "description": "ProviderRef specifies which Provider this Alert should use.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "summary": { - "description": "Summary holds a short description of the impact and affected cluster.", - "maxLength": 255, - "type": "string" - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this Alert.", - "type": "boolean" - } - }, - "required": [ - "eventSources", - "providerRef" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "AlertStatus defines the observed state of the Alert.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Alert.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last observed generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/artifactgenerator-source-v1beta1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/artifactgenerator-source-v1beta1.json index 014c6ad..b1c9351 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/artifactgenerator-source-v1beta1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/artifactgenerator-source-v1beta1.json @@ -25,7 +25,7 @@ "items": { "properties": { "exclude": { - "description": "Exclude specifies a list of glob patterns to exclude\nfiles and dirs matched by the 'From' field.", + "description": "Exclude specifies a list of glob patterns to exclude\nfiles and dirs matched by the 'From' field. Patterns are matched\nagainst paths relative to the source alias root or to the non-glob\nprefix of 'From'. Patterns without a separator (e.g. \"*.md\") match\nthe file name at any depth.", "items": { "type": "string" }, @@ -33,8 +33,9 @@ "type": "array" }, "from": { - "description": "From specifies the source (by alias) and the glob pattern to match files.\nThe format is \"@/\".", + "description": "From specifies the source (by alias) and the glob pattern to match files.\nThe format is \"@/\". When pathPattern is set,\nthe path may use capture placeholders such as \"{app}\".", "maxLength": 1024, + "minLength": 1, "pattern": "^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$", "type": "string" }, @@ -48,8 +49,9 @@ "type": "string" }, "to": { - "description": "To specifies the destination path within the artifact.\nThe format is \"@artifact/path\", the alias \"artifact\"\nrefers to the root path of the generated artifact.", + "description": "To specifies the destination path within the artifact.\nThe format is \"@artifact/path\", the alias \"artifact\"\nrefers to the root path of the generated artifact. When pathPattern\nis set, the path may use capture placeholders such as \"{app}\".", "maxLength": 1024, + "minLength": 1, "pattern": "^@(artifact)/(.*)$", "type": "string" } @@ -65,9 +67,9 @@ "type": "array" }, "name": { - "description": "Name is the name of the generated artifact.", + "description": "Name is the name of the generated artifact.\nWhen pathPattern is set, this field may use capture placeholders such as \"{app}\".", "maxLength": 253, - "pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?$", + "minLength": 1, "type": "string" }, "originRevision": { @@ -94,6 +96,33 @@ "minItems": 1, "type": "array" }, + "commonMetadata": { + "description": "CommonMetadata specifies the common labels and annotations that are\napplied to all resources. Any existing label or annotation will be\noverridden if its key matches a common one.", + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "description": "Annotations to be added to the object's metadata.", + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "description": "Labels to be added to the object's metadata.", + "type": "object" + } + }, + "type": "object", + "additionalProperties": false + }, + "pathPattern": { + "description": "PathPattern specifies a directory traversal pattern to match within the sources.\nThe format is \"@/\". Named captures in the pattern (e.g. \"{app}\")\ncan be used as placeholders in OutputArtifacts fields.", + "maxLength": 1024, + "pattern": "^@([a-z0-9]([a-z0-9_-]*[a-z0-9])?)/(.*)$", + "type": "string" + }, "sources": { "description": "Sources is a list of references to the Flux source-controller\nresources that will be used to generate the artifact.", "items": { @@ -148,6 +177,12 @@ "sources" ], "type": "object", + "x-kubernetes-validations": [ + { + "message": "artifact names must be valid Kubernetes object names when pathPattern is not set", + "rule": "has(self.pathPattern) && size(self.pathPattern) > 0 || self.artifacts.all(a, a.name.matches('^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'))" + } + ], "additionalProperties": false }, "status": { diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/fluxinstance-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/fluxinstance-fluxcd-v1.json index 1635135..63206b8 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/fluxinstance-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/fluxinstance-fluxcd-v1.json @@ -249,7 +249,7 @@ "type": "array" }, "storage": { - "description": "Storage defines if the source-controller shards\nshould use an emptyDir or a persistent volume claim for storage.\nAccepted values are 'ephemeral' or 'persistent', defaults to 'ephemeral'.\nFor 'persistent' to take effect, the '.spec.storage' field must be set.", + "description": "Storage defines if the source-controller shards\nshould use an emptyDir or a persistent volume claim for storage.\nAccepted values are 'ephemeral' or 'persistent', defaults to 'ephemeral'.\nWhen set to 'persistent', the '.spec.storage' field must be set.", "enum": [ "ephemeral", "persistent" @@ -366,7 +366,13 @@ "required": [ "distribution" ], - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": ".spec.storage must be set when .spec.sharding.storage is 'persistent'", + "rule": "!has(self.sharding) || !has(self.sharding.storage) || self.sharding.storage != 'persistent' || has(self.storage)" + } + ] }, "status": { "additionalProperties": false, diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/gitrepository-source-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/gitrepository-source-v1.json index 576baae..bcef2e3 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/gitrepository-source-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/gitrepository-source-v1.json @@ -61,9 +61,10 @@ "type": "string" }, "provider": { - "description": "Provider used for authentication, can be 'azure', 'github', 'generic'.\nWhen not specified, defaults to 'generic'.", + "description": "Provider used for authentication, can be 'aws', 'azure', 'github', 'generic'.\nWhen not specified, defaults to 'generic'.", "enum": [ "generic", + "aws", "azure", "github" ], @@ -129,7 +130,7 @@ "additionalProperties": false }, "serviceAccountName": { - "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to\nauthenticate to the GitRepository. This field is only supported for 'azure' provider.", + "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to\nauthenticate to the GitRepository. This field is only supported for 'azure' and 'aws' providers.", "type": "string" }, "sparseCheckout": { @@ -169,7 +170,7 @@ "type": "string" }, "secretRef": { - "description": "SecretRef specifies the Secret containing the public keys of trusted Git\nauthors.", + "description": "SecretRef specifies the Secret containing the public keys of trusted Git\nauthors. PGP public keys must be stored under keys with the .asc suffix,\nand SSH public keys must be stored under keys with the .sshpub suffix.", "properties": { "name": { "description": "Name of the referent.", @@ -197,8 +198,8 @@ "type": "object", "x-kubernetes-validations": [ { - "message": "serviceAccountName can only be set when provider is 'azure'", - "rule": "!has(self.serviceAccountName) || (has(self.provider) && self.provider == 'azure')" + "message": "serviceAccountName can only be set when provider is 'azure' or 'aws'", + "rule": "!has(self.serviceAccountName) || (has(self.provider) && (self.provider == 'azure' || self.provider == 'aws'))" } ], "additionalProperties": false diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/helmrelease-helm-v2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/helmrelease-helm-v2.json index 5cd8695..9401cba 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/helmrelease-helm-v2.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/helmrelease-helm-v2.json @@ -222,14 +222,14 @@ "dependsOn": { "description": "DependsOn may contain a DependencyReference slice with\nreferences to HelmRelease resources that must be ready before this HelmRelease\ncan be reconciled.", "items": { - "description": "DependencyReference defines a HelmRelease dependency on another HelmRelease resource.", + "description": "DependencyReference contains enough information to locate the referenced Kubernetes resource object\nand optional CEL expression to assess its readiness.", "properties": { "name": { "description": "Name of the referent.", "type": "string" }, "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the HelmRelease\nresource object that contains the reference.", + "description": "Namespace of the referent, defaults to the namespace of the resource\nobject that contains the reference.", "type": "string" }, "readyExpr": { @@ -345,8 +345,7 @@ }, "required": [ "apiVersion", - "current", - "kind" + "current" ], "type": "object", "additionalProperties": false @@ -524,6 +523,15 @@ "description": "PersistentClient tells the controller to use a persistent Kubernetes\nclient for this release. When enabled, the client will be reused for the\nduration of the reconciliation, instead of being created and destroyed\nfor each (step of a) Helm action.\n\nThis can improve performance, but may cause issues with some Helm charts\nthat for example do create Custom Resource Definitions during installation\noutside Helm's CRD lifecycle hooks, which are then not observed to be\navailable by e.g. post-install hooks.\n\nIf not set, it defaults to true.", "type": "boolean" }, + "postRenderStrategy": { + "description": "PostRenderStrategy defines the strategy for sending hooks to post-renderers.\nValid values are 'nohooks' (hooks not sent to post-renderers, Helm 3 behavior),\n'combined' (hooks and templates sent together, Helm 4 default), and 'separate'\n(hooks and templates sent in separate streams, Helm 4.2 opt-in).\nDefaults to 'combined', or 'nohooks' when the UseHelm3Defaults feature gate is enabled.", + "enum": [ + "nohooks", + "combined", + "separate" + ], + "type": "string" + }, "postRenderers": { "description": "PostRenderers holds an array of Helm PostRenderers, which will be applied in order\nof their definition.", "items": { @@ -784,6 +792,14 @@ "upgrade": { "description": "Upgrade holds the configuration for Helm upgrade actions for this HelmRelease.", "properties": { + "chartNameChangeStrategy": { + "description": "ChartNameChangeStrategy defines the strategy to use when a Helm chart name changes.\nValid values are 'Reinstall' or 'InPlaceUpdate'. Defaults to 'Reinstall' if omitted.\n\nReinstall: Reinstall the Helm release, uninstalling the existing Helm release.\n\nInPlaceUpdate: Update the Helm release in place.", + "enum": [ + "InPlaceUpdate", + "Reinstall" + ], + "type": "string" + }, "cleanupOnFail": { "description": "CleanupOnFail allows deletion of new resources created during the Helm\nupgrade action when it fails.", "type": "boolean" @@ -920,6 +936,10 @@ ], "type": "string" }, + "literal": { + "description": "Literal marks this ValuesReference as a literal value. When set in\ncombination with TargetPath, the referenced value is merged at the target\npath without interpreting Helm's `--set` syntax (commas, brackets, dots,\nequal signs, etc.), mirroring the behavior of `helm --set-literal`. This\nis the only safe way to inject arbitrary file content (config files, JSON\nblobs, multi-line strings containing special characters) through\n`valuesFrom`. Has no effect when TargetPath is empty: in that mode the\nreferenced value is always YAML-merged at the root.", + "type": "boolean" + }, "name": { "description": "Name of the values referent. Should reside in the same namespace as the\nreferring resource.", "maxLength": 253, diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imagepolicy-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imagepolicy-image-v1beta2.json deleted file mode 100644 index a4aee09..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imagepolicy-image-v1beta2.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "description": "ImagePolicy is the Schema for the imagepolicies API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImagePolicySpec defines the parameters for calculating the\nImagePolicy.", - "properties": { - "digestReflectionPolicy": { - "default": "Never", - "description": "DigestReflectionPolicy governs the setting of the `.status.latestRef.digest` field.\n\nNever: The digest field will always be set to the empty string.\n\nIfNotPresent: The digest field will be set to the digest of the elected\nlatest image if the field is empty and the image did not change.\n\nAlways: The digest field will always be set to the digest of the elected\nlatest image.\n\nDefault: Never.", - "enum": [ - "Always", - "IfNotPresent", - "Never" - ], - "type": "string" - }, - "filterTags": { - "description": "FilterTags enables filtering for only a subset of tags based on a set of\nrules. If no rules are provided, all the tags from the repository will be\nordered and compared.", - "properties": { - "extract": { - "description": "Extract allows a capture group to be extracted from the specified regular\nexpression pattern, useful before tag evaluation.", - "type": "string" - }, - "pattern": { - "description": "Pattern specifies a regular expression pattern used to filter for image\ntags.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "imageRepositoryRef": { - "description": "ImageRepositoryRef points at the object specifying the image\nbeing scanned", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent, when not specified it acts as LocalObjectReference.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "interval": { - "description": "Interval is the length of time to wait between\nrefreshing the digest of the latest tag when the\nreflection policy is set to \"Always\".\n\nDefaults to 10m.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "policy": { - "description": "Policy gives the particulars of the policy to be followed in\nselecting the most recent image", - "properties": { - "alphabetical": { - "description": "Alphabetical set of rules to use for alphabetical ordering of the tags.", - "properties": { - "order": { - "default": "asc", - "description": "Order specifies the sorting order of the tags. Given the letters of the\nalphabet as tags, ascending order would select Z, and descending order\nwould select A.", - "enum": [ - "asc", - "desc" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "numerical": { - "description": "Numerical set of rules to use for numerical ordering of the tags.", - "properties": { - "order": { - "default": "asc", - "description": "Order specifies the sorting order of the tags. Given the integer values\nfrom 0 to 9 as tags, ascending order would select 9, and descending order\nwould select 0.", - "enum": [ - "asc", - "desc" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - }, - "semver": { - "description": "SemVer gives a semantic version range to check against the tags\navailable.", - "properties": { - "range": { - "description": "Range gives a semver range for the image tag; the highest\nversion within the range that's a tag yields the latest image.", - "type": "string" - } - }, - "required": [ - "range" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "This flag tells the controller to suspend subsequent policy reconciliations.\nIt does not apply to already started reconciliations. Defaults to false.", - "type": "boolean" - } - }, - "required": [ - "imageRepositoryRef", - "policy" - ], - "type": "object", - "x-kubernetes-validations": [ - { - "message": "spec.interval is only accepted when spec.digestReflectionPolicy is set to 'Always'", - "rule": "!has(self.interval) || (has(self.digestReflectionPolicy) && self.digestReflectionPolicy == 'Always')" - }, - { - "message": "spec.interval must be set when spec.digestReflectionPolicy is set to 'Always'", - "rule": "has(self.interval) || !has(self.digestReflectionPolicy) || self.digestReflectionPolicy != 'Always'" - } - ], - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImagePolicyStatus defines the observed state of ImagePolicy", - "properties": { - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "latestRef": { - "description": "LatestRef gives the first in the list of images scanned by\nthe image repository, when filtered and ordered according\nto the policy.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - }, - "observedGeneration": { - "format": "int64", - "type": "integer" - }, - "observedPreviousRef": { - "description": "ObservedPreviousRef is the observed previous LatestRef. It is used\nto keep track of the previous and current images.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imagerepository-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imagerepository-image-v1beta2.json deleted file mode 100644 index cfb868d..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imagerepository-image-v1beta2.json +++ /dev/null @@ -1,262 +0,0 @@ -{ - "description": "ImageRepository is the Schema for the imagerepositories API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImageRepositorySpec defines the parameters for scanning an image\nrepository, e.g., `fluxcd/flux`.", - "properties": { - "accessFrom": { - "description": "AccessFrom defines an ACL for allowing cross-namespace references\nto the ImageRepository object based on the caller's namespace labels.", - "properties": { - "namespaceSelectors": { - "description": "NamespaceSelectors is the list of namespace selectors to which this ACL applies.\nItems in this list are evaluated using a logical OR operation.", - "items": { - "description": "NamespaceSelector selects the namespaces to which this ACL applies.\nAn empty map of MatchLabels matches all namespaces in a cluster.", - "properties": { - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object" - } - }, - "type": "object", - "additionalProperties": false - }, - "type": "array" - } - }, - "required": [ - "namespaceSelectors" - ], - "type": "object", - "additionalProperties": false - }, - "certSecretRef": { - "description": "CertSecretRef can be given the name of a Secret containing\neither or both of\n\n- a PEM-encoded client certificate (`tls.crt`) and private\nkey (`tls.key`);\n- a PEM-encoded CA certificate (`ca.crt`)\n\nand whichever are supplied, will be used for connecting to the\nregistry. The client cert and key are useful if you are\nauthenticating with a certificate; the CA cert is useful if\nyou are using a self-signed server certificate. The Secret must\nbe of type `Opaque` or `kubernetes.io/tls`.\n\nNote: Support for the `caFile`, `certFile` and `keyFile` keys has\nbeen deprecated.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "exclusionList": { - "default": [ - "^.*\\.sig$" - ], - "description": "ExclusionList is a list of regex strings used to exclude certain tags\nfrom being stored in the database.", - "items": { - "type": "string" - }, - "maxItems": 25, - "type": "array" - }, - "image": { - "description": "Image is the name of the image repository", - "type": "string" - }, - "insecure": { - "description": "Insecure allows connecting to a non-TLS HTTP container registry.", - "type": "boolean" - }, - "interval": { - "description": "Interval is the length of time to wait between\nscans of the image repository.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "provider": { - "default": "generic", - "description": "The provider used for authentication, can be 'aws', 'azure', 'gcp' or 'generic'.\nWhen not specified, defaults to 'generic'.", - "enum": [ - "generic", - "aws", - "azure", - "gcp" - ], - "type": "string" - }, - "proxySecretRef": { - "description": "ProxySecretRef specifies the Secret containing the proxy configuration\nto use while communicating with the container registry.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "secretRef": { - "description": "SecretRef can be given the name of a secret containing\ncredentials to use for the image registry. The secret should be\ncreated with `kubectl create secret docker-registry`, or the\nequivalent.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "serviceAccountName": { - "description": "ServiceAccountName is the name of the Kubernetes ServiceAccount used to authenticate\nthe image pull if the service account has attached pull secrets.", - "maxLength": 253, - "type": "string" - }, - "suspend": { - "description": "This flag tells the controller to suspend subsequent image scans.\nIt does not apply to already started scans. Defaults to false.", - "type": "boolean" - }, - "timeout": { - "description": "Timeout for image scanning.\nDefaults to 'Interval' duration.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m))+$", - "type": "string" - } - }, - "required": [ - "image", - "interval" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImageRepositoryStatus defines the observed state of ImageRepository", - "properties": { - "canonicalImageName": { - "description": "CanonicalName is the name of the image repository with all the\nimplied bits made explicit; e.g., `docker.io/library/alpine`\nrather than `alpine`.", - "type": "string" - }, - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "lastScanResult": { - "description": "LastScanResult contains the number of fetched tags.", - "properties": { - "latestTags": { - "description": "LatestTags is a small sample of the tags found in the last scan.\nIt's the first 10 tags when sorting all the tags in descending\nalphabetical order.", - "items": { - "type": "string" - }, - "type": "array" - }, - "revision": { - "description": "Revision is a stable hash of the scanned tags.", - "type": "string" - }, - "scanTime": { - "description": "ScanTime is the time when the last scan was performed.", - "format": "date-time", - "type": "string" - }, - "tagCount": { - "description": "TagCount is the number of tags found in the last scan.", - "type": "integer" - } - }, - "required": [ - "tagCount" - ], - "type": "object", - "additionalProperties": false - }, - "observedExclusionList": { - "description": "ObservedExclusionList is a list of observed exclusion list. It reflects\nthe exclusion rules used for the observed scan result in\nspec.lastScanResult.", - "items": { - "type": "string" - }, - "type": "array" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last reconciled generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1.json index 54da093..957d586 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1.json @@ -88,10 +88,10 @@ "type": "object" }, "signingKey": { - "description": "SigningKey provides the option to sign commits with a GPG key", + "description": "SigningKey provides the option to sign commits with an OpenPGP or\nSSH signing key, referenced from a Secret. See SigningKey.", "properties": { "secretRef": { - "description": "SecretRef holds the name to a secret that contains a 'git.asc' key\ncorresponding to the ASCII Armored file containing the GPG signing\nkeypair as the value. It must be in the same namespace as the\nImageUpdateAutomation.", + "description": "SecretRef references a Secret containing the signing key. For type\n'gpg', the Secret must contain a 'git.asc' (ASCII-armored OpenPGP\nkeypair) and may contain a 'passphrase'. For type 'ssh', the Secret\nmust contain an 'identity' (an SSH private key in any format\ngolang.org/x/crypto/ssh.ParsePrivateKey accepts; typically the\nOpenSSH format produced by 'ssh-keygen') and may contain a 'password'\n(the key's passphrase). The SSH conventions match the GitRepository\nSSH transport-auth Secret format, allowing a single Secret to serve\nboth transport and signing when the ImageUpdateAutomation lives in\nthe same namespace as the GitRepository.\n\nThe Secret itself must live in the same namespace as the\nImageUpdateAutomation.\n\nSupported SSH key algorithms: ed25519, ecdsa-sha2-nistp256/384/521,\nand rsa (>= 2048-bit).", "properties": { "name": { "description": "Name of the referent.", @@ -103,6 +103,14 @@ ], "type": "object", "additionalProperties": false + }, + "type": { + "description": "Type selects the signing-key format expected in the referenced\nSecret. When empty, the controller defaults to 'gpg'.", + "enum": [ + "gpg", + "ssh" + ], + "type": "string" } }, "required": [ diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1beta2.json deleted file mode 100644 index 54da093..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/imageupdateautomation-image-v1beta2.json +++ /dev/null @@ -1,387 +0,0 @@ -{ - "description": "ImageUpdateAutomation is the Schema for the imageupdateautomations API", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ImageUpdateAutomationSpec defines the desired state of ImageUpdateAutomation", - "properties": { - "git": { - "description": "GitSpec contains all the git-specific definitions. This is\ntechnically optional, but in practice mandatory until there are\nother kinds of source allowed.", - "properties": { - "checkout": { - "description": "Checkout gives the parameters for cloning the git repository,\nready to make changes. If not present, the `spec.ref` field from the\nreferenced `GitRepository` or its default will be used.", - "properties": { - "ref": { - "description": "Reference gives a branch, tag or commit to clone from the Git\nrepository.", - "properties": { - "branch": { - "description": "Branch to check out, defaults to 'master' if no other field is defined.", - "type": "string" - }, - "commit": { - "description": "Commit SHA to check out, takes precedence over all reference fields.\n\nThis can be combined with Branch to shallow clone the branch, in which\nthe commit is expected to exist.", - "type": "string" - }, - "name": { - "description": "Name of the reference to check out; takes precedence over Branch, Tag and SemVer.\n\nIt must be a valid Git reference: https://git-scm.com/docs/git-check-ref-format#_description\nExamples: \"refs/heads/main\", \"refs/tags/v0.1.0\", \"refs/pull/420/head\", \"refs/merge-requests/1/head\"", - "type": "string" - }, - "semver": { - "description": "SemVer tag expression to check out, takes precedence over Tag.", - "type": "string" - }, - "tag": { - "description": "Tag to check out, takes precedence over Branch.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "ref" - ], - "type": "object", - "additionalProperties": false - }, - "commit": { - "description": "Commit specifies how to commit to the git repository.", - "properties": { - "author": { - "description": "Author gives the email and optionally the name to use as the\nauthor of commits.", - "properties": { - "email": { - "description": "Email gives the email to provide when making a commit.", - "type": "string" - }, - "name": { - "description": "Name gives the name to provide when making a commit.", - "type": "string" - } - }, - "required": [ - "email" - ], - "type": "object", - "additionalProperties": false - }, - "messageTemplate": { - "description": "MessageTemplate provides a template for the commit message,\ninto which will be interpolated the details of the change made.\nNote: The `Updated` template field has been removed. Use `Changed` instead.", - "type": "string" - }, - "messageTemplateValues": { - "additionalProperties": { - "type": "string" - }, - "description": "MessageTemplateValues provides additional values to be available to the\ntemplating rendering.", - "type": "object" - }, - "signingKey": { - "description": "SigningKey provides the option to sign commits with a GPG key", - "properties": { - "secretRef": { - "description": "SecretRef holds the name to a secret that contains a 'git.asc' key\ncorresponding to the ASCII Armored file containing the GPG signing\nkeypair as the value. It must be in the same namespace as the\nImageUpdateAutomation.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "secretRef" - ], - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "author" - ], - "type": "object", - "additionalProperties": false - }, - "push": { - "description": "Push specifies how and where to push commits made by the\nautomation. If missing, commits are pushed (back) to\n`.spec.checkout.branch` or its default.", - "properties": { - "branch": { - "description": "Branch specifies that commits should be pushed to the branch\nnamed. The branch is created using `.spec.checkout.branch` as the\nstarting point, if it doesn't already exist.", - "type": "string" - }, - "options": { - "additionalProperties": { - "type": "string" - }, - "description": "Options specifies the push options that are sent to the Git\nserver when performing a push operation. For details, see:\nhttps://git-scm.com/docs/git-push#Documentation/git-push.txt---push-optionltoptiongt", - "type": "object" - }, - "refspec": { - "description": "Refspec specifies the Git Refspec to use for a push operation.\nIf both Branch and Refspec are provided, then the commit is pushed\nto the branch and also using the specified refspec.\nFor more details about Git Refspecs, see:\nhttps://git-scm.com/book/en/v2/Git-Internals-The-Refspec", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "commit" - ], - "type": "object", - "additionalProperties": false - }, - "interval": { - "description": "Interval gives an lower bound for how often the automation\nrun should be attempted.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "policySelector": { - "description": "PolicySelector allows to filter applied policies based on labels.\nBy default includes all policies in namespace.", - "properties": { - "matchExpressions": { - "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.", - "items": { - "description": "A label selector requirement is a selector that contains values, a key, and an operator that\nrelates the key and values.", - "properties": { - "key": { - "description": "key is the label key that the selector applies to.", - "type": "string" - }, - "operator": { - "description": "operator represents a key's relationship to a set of values.\nValid operators are In, NotIn, Exists and DoesNotExist.", - "type": "string" - }, - "values": { - "description": "values is an array of string values. If the operator is In or NotIn,\nthe values array must be non-empty. If the operator is Exists or DoesNotExist,\nthe values array must be empty. This array is replaced during a strategic\nmerge patch.", - "items": { - "type": "string" - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - } - }, - "required": [ - "key", - "operator" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array", - "x-kubernetes-list-type": "atomic" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", - "type": "object" - } - }, - "type": "object", - "x-kubernetes-map-type": "atomic", - "additionalProperties": false - }, - "sourceRef": { - "description": "SourceRef refers to the resource giving access details\nto a git repository.", - "properties": { - "apiVersion": { - "description": "API version of the referent.", - "type": "string" - }, - "kind": { - "default": "GitRepository", - "description": "Kind of the referent.", - "enum": [ - "GitRepository" - ], - "type": "string" - }, - "name": { - "description": "Name of the referent.", - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the Kubernetes resource object that contains the reference.", - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to not run this automation, until\nit is unset (or set to false). Defaults to false.", - "type": "boolean" - }, - "update": { - "default": { - "strategy": "Setters" - }, - "description": "Update gives the specification for how to update the files in\nthe repository. This can be left empty, to use the default\nvalue.", - "properties": { - "path": { - "description": "Path to the directory containing the manifests to be updated.\nDefaults to 'None', which translates to the root path\nof the GitRepositoryRef.", - "type": "string" - }, - "strategy": { - "default": "Setters", - "description": "Strategy names the strategy to be used.", - "enum": [ - "Setters" - ], - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "required": [ - "interval", - "sourceRef" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ImageUpdateAutomationStatus defines the observed state of ImageUpdateAutomation", - "properties": { - "conditions": { - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastAutomationRunTime": { - "description": "LastAutomationRunTime records the last time the controller ran\nthis automation through to completion (even if no updates were\nmade).", - "format": "date-time", - "type": "string" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "lastPushCommit": { - "description": "LastPushCommit records the SHA1 of the last commit made by the\ncontroller, for this automation object", - "type": "string" - }, - "lastPushTime": { - "description": "LastPushTime records the time of the last pushed change.", - "format": "date-time", - "type": "string" - }, - "observedGeneration": { - "format": "int64", - "type": "integer" - }, - "observedPolicies": { - "additionalProperties": { - "description": "ImageRef represents an image reference.", - "properties": { - "digest": { - "description": "Digest is the image's digest.", - "type": "string" - }, - "name": { - "description": "Name is the bare image's name.", - "type": "string" - }, - "tag": { - "description": "Tag is the image's tag.", - "type": "string" - } - }, - "required": [ - "name", - "tag" - ], - "type": "object", - "additionalProperties": false - }, - "description": "ObservedPolicies is the list of observed ImagePolicies that were\nconsidered by the ImageUpdateAutomation update process.", - "type": "object" - }, - "observedSourceRevision": { - "description": "ObservedPolicies []ObservedPolicy `json:\"observedPolicies,omitempty\"`\nObservedSourceRevision is the last observed source revision. This can be\nused to determine if the source has been updated since last observation.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/kustomization-kustomize-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/kustomization-kustomize-v1.json index fcba8b6..abd3909 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/kustomization-kustomize-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/kustomization-kustomize-v1.json @@ -15,6 +15,18 @@ "spec": { "description": "KustomizationSpec defines the configuration to calculate the desired state\nfrom a Source using Kustomize.", "properties": { + "buildMetadata": { + "description": "BuildMetadata specifies which kustomize build metadata should be added\nto the built resources. The allowed values are 'originAnnotations' to\nannotate resources with their source origin, and 'transformerAnnotations'\nto annotate resources with the transformers that produced them.", + "items": { + "description": "BuildMetadataOption defines the supported buildMetadata options.", + "enum": [ + "originAnnotations", + "transformerAnnotations" + ], + "type": "string" + }, + "type": "array" + }, "commonMetadata": { "description": "CommonMetadata specifies the common labels and annotations that are\napplied to all resources. Any existing label or annotation will be\noverridden if its key matches a common one.", "properties": { @@ -91,14 +103,14 @@ "dependsOn": { "description": "DependsOn may contain a DependencyReference slice\nwith references to Kustomization resources that must be ready before this\nKustomization can be reconciled.", "items": { - "description": "DependencyReference defines a Kustomization dependency on another Kustomization resource.", + "description": "DependencyReference contains enough information to locate the referenced Kubernetes resource object\nand optional CEL expression to assess its readiness.", "properties": { "name": { "description": "Name of the referent.", "type": "string" }, "namespace": { - "description": "Namespace of the referent, defaults to the namespace of the Kustomization\nresource object that contains the reference.", + "description": "Namespace of the referent, defaults to the namespace of the resource\nobject that contains the reference.", "type": "string" }, "readyExpr": { @@ -147,8 +159,7 @@ }, "required": [ "apiVersion", - "current", - "kind" + "current" ], "type": "object", "additionalProperties": false @@ -186,6 +197,62 @@ }, "type": "array" }, + "ignore": { + "description": "Ignore is a list of rules for specifying which changes to ignore\nduring drift detection. These rules are applied to the resources managed\nby the Kustomization and are used to exclude specific JSON pointer paths\nfrom the drift detection and apply process.", + "items": { + "description": "IgnoreRule defines a rule to selectively disregard specific changes during\nthe drift detection process.", + "properties": { + "paths": { + "description": "Paths is a list of JSON Pointer (RFC 6901) paths to be excluded from\nconsideration in a Kubernetes object.", + "items": { + "type": "string" + }, + "type": "array" + }, + "target": { + "description": "Target is a selector for specifying Kubernetes objects to which this\nrule applies.\nIf Target is not set, the Paths will be ignored for all Kubernetes\nobjects within the manifest of the Kustomization.", + "properties": { + "annotationSelector": { + "description": "AnnotationSelector is a string that follows the label selection expression\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api\nIt matches with the resource annotations.", + "type": "string" + }, + "group": { + "description": "Group is the API group to select resources from.\nTogether with Version and Kind it is capable of unambiguously identifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + }, + "kind": { + "description": "Kind of the API Group to select resources from.\nTogether with Group and Version it is capable of unambiguously\nidentifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + }, + "labelSelector": { + "description": "LabelSelector is a string that follows the label selection expression\nhttps://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api\nIt matches with the resource labels.", + "type": "string" + }, + "name": { + "description": "Name to match resources with.", + "type": "string" + }, + "namespace": { + "description": "Namespace to select resources from.", + "type": "string" + }, + "version": { + "description": "Version of the API Group to select resources from.\nTogether with Group and Kind it is capable of unambiguously identifying and/or selecting resources.\nhttps://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md", + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "paths" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + }, "ignoreMissingComponents": { "description": "IgnoreMissingComponents instructs the controller to ignore Components paths\nnot found in source by removing them from the generated kustomization.yaml\nbefore running kustomize build.", "type": "boolean" @@ -386,6 +453,14 @@ "additionalProperties": false }, "type": "array" + }, + "substituteStrategy": { + "description": "SubstituteStrategy defines the strategy for substituting variables in the YAML manifests.\nValid values are:\n\n - WithVariables (the default): require at least one variable to be defined,\n either through the inline map or through the resolved references to ConfigMaps\n and Secrets.\n - Always: perform the substitution even if no variables are defined.", + "enum": [ + "WithVariables", + "Always" + ], + "type": "string" } }, "type": "object", diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/ocirepository-source-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/ocirepository-source-v1.json index a80528c..f7f351d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/ocirepository-source-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/ocirepository-source-v1.json @@ -190,6 +190,20 @@ ], "type": "object", "additionalProperties": false + }, + "trustedRootSecretRef": { + "description": "TrustedRootSecretRef specifies the Kubernetes Secret containing a\nSigstore trusted_root.json file. This enables verification against\nself-hosted Sigstore infrastructure (custom Fulcio CA, self-hosted\nRekor instance). The Secret must contain a key named \"trusted_root.json\".", + "properties": { + "name": { + "description": "Name of the referent.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "additionalProperties": false } }, "required": [ diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/provider-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/provider-notification-v1beta2.json deleted file mode 100644 index ace15b2..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/provider-notification-v1beta2.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "description": "Provider is the Schema for the providers API.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ProviderSpec defines the desired state of the Provider.", - "properties": { - "address": { - "description": "Address specifies the endpoint, in a generic sense, to where alerts are sent.\nWhat kind of endpoint depends on the specific Provider type being used.\nFor the generic Provider, for example, this is an HTTP/S address.\nFor other Provider types this could be a project ID or a namespace.", - "maxLength": 2048, - "type": "string" - }, - "certSecretRef": { - "description": "CertSecretRef specifies the Secret containing\na PEM-encoded CA certificate (in the `ca.crt` key).\n\nNote: Support for the `caFile` key has\nbeen deprecated.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "channel": { - "description": "Channel specifies the destination channel where events should be posted.", - "maxLength": 2048, - "type": "string" - }, - "interval": { - "description": "Interval at which to reconcile the Provider with its Secret references.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "proxy": { - "description": "Proxy the HTTP/S address of the proxy server.", - "maxLength": 2048, - "pattern": "^(http|https)://.*$", - "type": "string" - }, - "secretRef": { - "description": "SecretRef specifies the Secret containing the authentication\ncredentials for this Provider.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this Provider.", - "type": "boolean" - }, - "timeout": { - "description": "Timeout for sending alerts to the Provider.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m))+$", - "type": "string" - }, - "type": { - "description": "Type specifies which Provider implementation to use.", - "enum": [ - "slack", - "discord", - "msteams", - "rocket", - "generic", - "generic-hmac", - "github", - "gitlab", - "gitea", - "bitbucketserver", - "bitbucket", - "azuredevops", - "googlechat", - "googlepubsub", - "webex", - "sentry", - "azureeventhub", - "telegram", - "lark", - "matrix", - "opsgenie", - "alertmanager", - "grafana", - "githubdispatch", - "pagerduty", - "datadog" - ], - "type": "string" - }, - "username": { - "description": "Username specifies the name under which events are posted.", - "maxLength": 2048, - "type": "string" - } - }, - "required": [ - "type" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ProviderStatus defines the observed state of the Provider.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Provider.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last reconciled generation.", - "format": "int64", - "type": "integer" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1.json index 90141f3..c8eda12 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1.json @@ -28,19 +28,98 @@ "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", "type": "string" }, + "oidcProviders": { + "description": "OIDCProviders specifies the OIDC providers used to authenticate incoming\nrequests when Type is 'generic-oidc'. The provider whose IssuerURL matches\nthe token's 'iss' claim is used to verify the token signature, expiration\nand audience, and to evaluate the configured CEL validations against the\ntoken claims.", + "items": { + "description": "OIDCProvider configures an OIDC issuer used to authenticate requests for a\n'generic-oidc' Receiver.", + "properties": { + "audience": { + "description": "Audience is the expected audience ('aud' claim) for tokens issued by\nthis provider. Defaults to 'notification-controller'.", + "type": "string" + }, + "issuerURL": { + "description": "IssuerURL is the OIDC issuer URL used for provider discovery. It must\nmatch the 'iss' claim of tokens issued by this provider.", + "pattern": "^https?://", + "type": "string" + }, + "validations": { + "description": "Validations is the list of CEL boolean expressions evaluated against the\ntoken claims and the variables. The request is accepted only if all of\nthem evaluate to true; the message of each failing expression is returned\nto the caller.\n\nAt least one validation is required. A valid signature alone does not\nauthorize a request: public issuers issue tokens to any caller on the\nplatform, so the validations must constrain the caller's identity claims\n(e.g. 'repository_owner' for GitHub Actions).", + "items": { + "description": "OIDCValidation is a CEL boolean expression evaluated against the OIDC token\nclaims and variables of a 'generic-oidc' Receiver.", + "properties": { + "expression": { + "description": "Expression is the CEL boolean expression to evaluate.", + "type": "string" + }, + "message": { + "description": "Message is returned to the caller when the expression evaluates to false.", + "type": "string" + } + }, + "required": [ + "expression", + "message" + ], + "type": "object", + "additionalProperties": false + }, + "minItems": 1, + "type": "array" + }, + "variables": { + "description": "Variables is an optional list of named CEL expressions, evaluated in order\nand exposed as 'vars.'. Each expression can read the token claims\nvia 'claims' and any variable defined before it. Use it to share\nsub-expressions across validations.", + "items": { + "description": "OIDCVariable is a named CEL expression evaluated against the OIDC token\nclaims of a 'generic-oidc' Receiver.", + "properties": { + "expression": { + "description": "Expression is the CEL expression that defines the variable value.", + "type": "string" + }, + "name": { + "description": "Name is the variable name; it must be a valid CEL identifier.", + "type": "string" + } + }, + "required": [ + "expression", + "name" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array" + } + }, + "required": [ + "issuerURL", + "validations" + ], + "type": "object", + "additionalProperties": false + }, + "type": "array", + "x-kubernetes-list-map-keys": [ + "issuerURL" + ], + "x-kubernetes-list-type": "map" + }, "resourceFilter": { - "description": "ResourceFilter is a CEL expression expected to return a boolean that is\nevaluated for each resource referenced in the Resources field when a\nwebhook is received. If the expression returns false then the controller\nwill not request a reconciliation for the resource.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", + "description": "ResourceFilter is a CEL expression expected to return a boolean that is\nevaluated for each resource referenced in the Resources field when a\nwebhook is received. If the expression returns false then the controller\nwill not request a reconciliation for the resource.\nThe expression can read the resource metadata via 'res' and the webhook\nrequest body via 'req'. For generic-oidc receivers, the verified OIDC\ntoken claims are also available via 'claims'.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", "type": "string" }, "resources": { "description": "A list of resources to be notified about changes.", "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", + "description": "ReceiverResource references a resource to be notified about changes, with an\noptional per-resource CEL filter.", "properties": { "apiVersion": { "description": "API version of the referent", "type": "string" }, + "filter": { + "description": "Filter is a CEL expression expected to return a boolean that is evaluated\nfor each resource matched by this reference when a webhook is received,\nin addition to the top-level resourceFilter. A reconciliation is requested\nonly when both expressions (when set) return true.\nThe expression can read the resource metadata via 'res' and the webhook\nrequest body via 'req'. For generic-oidc receivers, the verified OIDC\ntoken claims are also available via 'claims'.\nWhen the expression is specified the controller will parse it and mark\nthe object as terminally failed if the expression is invalid or does not\nreturn a boolean.", + "type": "string" + }, "kind": { "description": "Kind of the referent", "enum": [ @@ -89,7 +168,7 @@ "type": "array" }, "secretRef": { - "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity. The Secret must contain a 'token'\nkey. For GCR receivers, the Secret must also contain an 'email' key\nwith the IAM service account email configured on the Pub/Sub push\nsubscription, and an 'audience' key with the expected OIDC token audience.", + "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity. The Secret must contain a 'token'\nkey. For GCR receivers, the Secret must also contain an 'email' key\nwith the IAM service account email configured on the Pub/Sub push\nsubscription, and an 'audience' key with the expected OIDC token audience.\n\nRequired for all receiver types except 'generic-oidc', which authenticates\nrequests using the OIDC token instead and must not set this field.", "properties": { "name": { "description": "Name of the referent.", @@ -111,6 +190,7 @@ "enum": [ "generic", "generic-hmac", + "generic-oidc", "github", "gitlab", "bitbucket", @@ -127,10 +207,27 @@ }, "required": [ "resources", - "secretRef", "type" ], "type": "object", + "x-kubernetes-validations": [ + { + "message": "generic-oidc receivers must define at least one oidcProvider", + "rule": "self.type != 'generic-oidc' || (has(self.oidcProviders) && size(self.oidcProviders) > 0)" + }, + { + "message": "oidcProviders can only be set when type is generic-oidc", + "rule": "self.type == 'generic-oidc' || !has(self.oidcProviders) || size(self.oidcProviders) == 0" + }, + { + "message": "secretRef cannot be set when type is generic-oidc", + "rule": "self.type != 'generic-oidc' || !has(self.secretRef)" + }, + { + "message": "secretRef is required when type is not generic-oidc", + "rule": "self.type == 'generic-oidc' || has(self.secretRef)" + } + ], "additionalProperties": false }, "status": { diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1beta2.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1beta2.json deleted file mode 100644 index 4b7cfe6..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/receiver-notification-v1beta2.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "description": "Receiver is the Schema for the receivers API.", - "properties": { - "apiVersion": { - "description": "APIVersion defines the versioned schema of this representation of an object.\nServers should convert recognized schemas to the latest internal value, and\nmay reject unrecognized values.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - "type": "string" - }, - "kind": { - "description": "Kind is a string value representing the REST resource this object represents.\nServers may infer this from the endpoint the client submits requests to.\nCannot be updated.\nIn CamelCase.\nMore info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - "type": "string" - }, - "metadata": { - "type": "object" - }, - "spec": { - "description": "ReceiverSpec defines the desired state of the Receiver.", - "properties": { - "events": { - "description": "Events specifies the list of event types to handle,\ne.g. 'push' for GitHub or 'Push Hook' for GitLab.", - "items": { - "type": "string" - }, - "type": "array" - }, - "interval": { - "description": "Interval at which to reconcile the Receiver with its Secret references.", - "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", - "type": "string" - }, - "resources": { - "description": "A list of resources to be notified about changes.", - "items": { - "description": "CrossNamespaceObjectReference contains enough information to let you locate the\ntyped referenced object at cluster level", - "properties": { - "apiVersion": { - "description": "API version of the referent", - "type": "string" - }, - "kind": { - "description": "Kind of the referent", - "enum": [ - "Bucket", - "GitRepository", - "Kustomization", - "HelmRelease", - "HelmChart", - "HelmRepository", - "ImageRepository", - "ImagePolicy", - "ImageUpdateAutomation", - "OCIRepository", - "ArtifactGenerator", - "ExternalArtifact" - ], - "type": "string" - }, - "matchLabels": { - "additionalProperties": { - "type": "string" - }, - "description": "MatchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels\nmap is equivalent to an element of matchExpressions, whose key field is \"key\", the\noperator is \"In\", and the values array contains only \"value\". The requirements are ANDed.\nMatchLabels requires the name to be set to `*`.", - "type": "object" - }, - "name": { - "description": "Name of the referent\nIf multiple resources are targeted `*` may be set.", - "maxLength": 253, - "minLength": 1, - "type": "string" - }, - "namespace": { - "description": "Namespace of the referent", - "maxLength": 253, - "minLength": 1, - "type": "string" - } - }, - "required": [ - "kind", - "name" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "secretRef": { - "description": "SecretRef specifies the Secret containing the token used\nto validate the payload authenticity.", - "properties": { - "name": { - "description": "Name of the referent.", - "type": "string" - } - }, - "required": [ - "name" - ], - "type": "object", - "additionalProperties": false - }, - "suspend": { - "description": "Suspend tells the controller to suspend subsequent\nevents handling for this receiver.", - "type": "boolean" - }, - "type": { - "description": "Type of webhook sender, used to determine\nthe validation procedure and payload deserialization.", - "enum": [ - "generic", - "generic-hmac", - "github", - "gitlab", - "bitbucket", - "harbor", - "dockerhub", - "quay", - "gcr", - "nexus", - "acr" - ], - "type": "string" - } - }, - "required": [ - "resources", - "secretRef", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "status": { - "default": { - "observedGeneration": -1 - }, - "description": "ReceiverStatus defines the observed state of the Receiver.", - "properties": { - "conditions": { - "description": "Conditions holds the conditions for the Receiver.", - "items": { - "description": "Condition contains details for one aspect of the current state of this API Resource.", - "properties": { - "lastTransitionTime": { - "description": "lastTransitionTime is the last time the condition transitioned from one status to another.\nThis should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", - "format": "date-time", - "type": "string" - }, - "message": { - "description": "message is a human readable message indicating details about the transition.\nThis may be an empty string.", - "maxLength": 32768, - "type": "string" - }, - "observedGeneration": { - "description": "observedGeneration represents the .metadata.generation that the condition was set based upon.\nFor instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date\nwith respect to the current state of the instance.", - "format": "int64", - "minimum": 0, - "type": "integer" - }, - "reason": { - "description": "reason contains a programmatic identifier indicating the reason for the condition's last transition.\nProducers of specific condition types may define expected values and meanings for this field,\nand whether the values are considered a guaranteed API.\nThe value should be a CamelCase string.\nThis field may not be empty.", - "maxLength": 1024, - "minLength": 1, - "pattern": "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$", - "type": "string" - }, - "status": { - "description": "status of the condition, one of True, False, Unknown.", - "enum": [ - "True", - "False", - "Unknown" - ], - "type": "string" - }, - "type": { - "description": "type of condition in CamelCase or in foo.example.com/CamelCase.", - "maxLength": 316, - "pattern": "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$", - "type": "string" - } - }, - "required": [ - "lastTransitionTime", - "message", - "reason", - "status", - "type" - ], - "type": "object", - "additionalProperties": false - }, - "type": "array" - }, - "lastHandledReconcileAt": { - "description": "LastHandledReconcileAt holds the value of the most recent\nreconcile request value, so a change of the annotation value\ncan be detected.", - "type": "string" - }, - "observedGeneration": { - "description": "ObservedGeneration is the last observed generation of the Receiver object.", - "format": "int64", - "type": "integer" - }, - "url": { - "description": "URL is the generated incoming webhook address in the format\nof '/hook/sha256sum(token+name+namespace)'.\nDeprecated: Replaced by WebhookPath.", - "type": "string" - }, - "webhookPath": { - "description": "WebhookPath is the generated incoming webhook address in the format\nof '/hook/sha256sum(token+name+namespace)'.", - "type": "string" - } - }, - "type": "object", - "additionalProperties": false - } - }, - "type": "object" -} \ No newline at end of file diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourceset-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourceset-fluxcd-v1.json index f55e30e..b54ca2d 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourceset-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourceset-fluxcd-v1.json @@ -205,6 +205,7 @@ "resources": { "description": "Resources contains the list of Kubernetes resources to reconcile.", "items": { + "type": "object", "x-kubernetes-preserve-unknown-fields": true }, "type": "array" @@ -217,12 +218,73 @@ "description": "The name of the Kubernetes service account to impersonate\nwhen reconciling the generated resources.", "type": "string" }, + "steps": { + "description": "Steps contains an ordered list of named steps to reconcile in sequence.\nEach step's resources are applied and health-checked before the next\nstep starts. Mutually exclusive with Resources and ResourcesTemplate.", + "items": { + "additionalProperties": false, + "description": "ResourceSetStep defines a named step in the ResourceSet reconciliation\nsequence. The step's resources are applied and health-checked before\nthe next step starts.", + "properties": { + "name": { + "description": "Name of the step, must be unique within the ResourceSet.", + "maxLength": 63, + "pattern": "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", + "type": "string" + }, + "resources": { + "description": "Resources contains the list of Kubernetes resources to reconcile.", + "items": { + "type": "object", + "x-kubernetes-preserve-unknown-fields": true + }, + "type": "array" + }, + "resourcesTemplate": { + "description": "ResourcesTemplate is a Go template that generates the list of\nKubernetes resources to reconcile. The template is rendered\nas multi-document YAML, the resources should be separated by '---'.\nWhen both Resources and ResourcesTemplate are set, the resulting\nobjects are merged and deduplicated, with the ones from Resources taking precedence.", + "type": "string" + }, + "timeout": { + "description": "Timeout is the maximum time to wait for the step's resources to\nbecome ready. When not set, the ResourceSet reconciliation\ntimeout is used.", + "pattern": "^([0-9]+(\\.[0-9]+)?(ms|s|m|h))+$", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object", + "x-kubernetes-validations": [ + { + "message": "at least one of resources or resourcesTemplate must be set", + "rule": "has(self.resources) || has(self.resourcesTemplate)" + } + ] + }, + "maxItems": 20, + "minItems": 1, + "type": "array", + "x-kubernetes-validations": [ + { + "message": "step names must be unique", + "rule": "self.all(s, self.exists_one(t, t.name == s.name))" + } + ] + }, "wait": { "description": "Wait instructs the controller to check the health\nof all the reconciled resources.", "type": "boolean" } }, - "type": "object" + "type": "object", + "x-kubernetes-validations": [ + { + "message": "steps is mutually exclusive with resources and resourcesTemplate", + "rule": "!has(self.steps) || (!has(self.resources) && !has(self.resourcesTemplate))" + }, + { + "message": "at least one of steps, resources or resourcesTemplate must be set", + "rule": "has(self.steps) || has(self.resources) || has(self.resourcesTemplate)" + } + ] }, "status": { "additionalProperties": false, diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourcesetinputprovider-fluxcd-v1.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourcesetinputprovider-fluxcd-v1.json index 5e98e35..5e713f1 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourcesetinputprovider-fluxcd-v1.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/assets/schemas/resourcesetinputprovider-fluxcd-v1.json @@ -230,11 +230,11 @@ }, { "message": "cannot specify spec.certSecretRef when spec.type is one of Static, AzureDevOps*, AWSCodeCommit*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag", - "rule": "!has(self.certSecretRef) || !(self.url == 'Static' || self.type.startsWith('AzureDevOps') || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" + "rule": "!has(self.certSecretRef) || !(self.type == 'Static' || self.type.startsWith('AzureDevOps') || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" }, { "message": "cannot specify spec.secretRef when spec.type is one of Static, AWSCodeCommit*, ACRArtifactTag, ECRArtifactTag or GARArtifactTag", - "rule": "!has(self.secretRef) || !(self.url == 'Static' || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" + "rule": "!has(self.secretRef) || !(self.type == 'Static' || self.type.startsWith('AWSCodeCommit') || (self.type.endsWith('ArtifactTag') && self.type != 'OCIArtifactTag'))" } ] }, diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/evals/evals.json b/plugins/gitops-kubernetes/skills/gitops-repo-audit/evals/evals.json index 30de0a7..5a4a0cb 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/evals/evals.json +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/evals/evals.json @@ -97,6 +97,33 @@ "Clearly conveys the repository has critical or severe issues requiring immediate attention (deprecated APIs, plaintext secrets, insecure sources)", "Produces a structured report with API compliance, security, best practices, and recommendations sections" ] + }, + { + "id": 5, + "prompt": "Analyze the tests/gitops-repo-audit/overlay-effects/ directory as a GitOps repository and provide a complete analysis report focusing on security and operational best practices.", + "expected_output": "A report catching that the hardened base Deployment (apps/base/webapp) is subverted by the production Kustomize overlay (apps/production/webapp): the rendered manifest runs privileged: true, hostNetwork: true, readOnlyRootFilesystem: false, and a mutable :latest image tag. These issues are visible only in the effective/rendered output, not in the raw base files — the auditor must inspect the rendered overlay (via the validation bundle or kustomize build) to detect them.", + "files": [], + "expectations": [ + "Flags the production webapp container image using a mutable ':latest' tag, set by the overlay's images transformer (raw base pins :1.4.2)", + "Flags the webapp container running as privileged (privileged: true) in production, set by the overlay patch (raw base has privileged: false)", + "Flags hostNetwork: true / host network namespace sharing on the webapp pod, added by the overlay patch (absent in raw base)", + "Flags readOnlyRootFilesystem disabled in production, overridden by the overlay patch (raw base has it enabled)", + "Demonstrates it inspected the effective/rendered manifests rather than only the raw base files, and does not incorrectly certify the deployment as secure" + ] + }, + { + "id": 6, + "prompt": "Analyze the tests/gitops-repo-audit/overlay-stress/ directory as a GitOps repository and provide a complete analysis report focusing on security and operational best practices.", + "expected_output": "A report identifying that among six production app overlays (frontend, backend, cache, payments, analytics, notifications) sharing a hardened base, only the payments overlay is insecure: its rendered Deployment runs privileged: true, hostNetwork: true, readOnlyRootFilesystem: false, and a mutable :latest image tag. The five other overlays are benign. Detecting the payments needle requires rendering every overlay (via the validation bundle or kustomize build), not eyeballing raw base files — which are all hardened and would wrongly read as a clean, secure repo.", + "files": [], + "expectations": [ + "Identifies payments specifically as the one insecure app among the six production overlays", + "Flags the payments production image using a mutable ':latest' tag (overlay images transformer)", + "Flags the payments container running as privileged (privileged: true) in production (overlay patch)", + "Flags hostNetwork: true on the payments pod (overlay patch)", + "Flags readOnlyRootFilesystem disabled on payments in production (overlay patch)", + "Does not certify the repository as secure/PASS on the basis of the hardened raw base files — inspects the rendered overlays for effective state" + ] } ] } diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/best-practices.md b/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/best-practices.md index 557f36a..c225f17 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/best-practices.md +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/best-practices.md @@ -68,7 +68,7 @@ network policies, and image automation security. - [ ] **Alerts configured**: At minimum, error-severity Alerts with a Provider (Slack, Teams, etc.) for production clusters - [ ] **Receivers for webhooks**: Configure Receivers to trigger immediate reconciliation on Git push instead of waiting for polling interval - [ ] **Appropriate intervals**: Sources polled frequently (5m-15m), reconciliation intervals longer (30m-1h), drift detection at reconciliation interval -- [ ] **CI validation pipeline**: Run `validate.sh` (YAML syntax + kubeconform + kustomize build) in CI before merging +- [ ] **CI validation pipeline**: Run `validate.sh` (flux-schema strict + CEL validation + kustomize build) in CI before merging - [ ] **`prune: true` on all Kustomizations**: Enables garbage collection of resources removed from source - [ ] **Image automation**: For container images that need automatic updates, configure ImageRepository + ImagePolicy + ImageUpdateAutomation - [ ] **Monitoring**: Deploy kube-prometheus-stack or similar with ServiceMonitors/PodMonitors for Flux controllers diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-api-summary.md b/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-api-summary.md index 8f4ed02..06d04ee 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-api-summary.md +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-api-summary.md @@ -2,6 +2,8 @@ Condensed reference for the Flux CRDs. +**Contents:** [Source API](#source-api) | [Appliers API](#appliers-api) | [Notification API](#notification-api) | [Image Automation API](#image-automation-api) | [Common Validation Rules](#common-validation-rules) | [Deep Dive API Specs](#deep-dive-api-specs) + ## Source API ### GitRepository (`source.toolkit.fluxcd.io/v1`) diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-operator-api-summary.md b/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-operator-api-summary.md index 03abf08..77063d6 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-operator-api-summary.md +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/references/flux-operator-api-summary.md @@ -61,7 +61,7 @@ OpenAPI schema: assets/schemas/resourcesetinputprovider-fluxcd-v1.json Fetches input values from external services for ResourceSet consumption. **Key fields:** -- `.spec.type` — Provider type (see patterns below) +- `.spec.type` — Provider type. Full list: `Static` (inline inputs), `GitHubBranch`/`GitHubTag`/`GitHubPullRequest`, `GitLabBranch`/`GitLabTag`/`GitLabMergeRequest`/`GitLabEnvironment`, `AzureDevOpsBranch`/`AzureDevOpsTag`/`AzureDevOpsPullRequest`, `AWSCodeCommitBranch`/`AWSCodeCommitTag`/`AWSCodeCommitPullRequest`, `GiteaBranch`/`GiteaTag`/`GiteaPullRequest`, `OCIArtifactTag`/`ACRArtifactTag`/`ECRArtifactTag`/`GARArtifactTag`, `ExternalService` — do not flag a type as invalid without checking this list or the schema - `.spec.url` — Repository or registry URL - `.spec.filter.labels[]` — Label filter for PRs/MRs - `.spec.filter.limit` — Maximum number of inputs to fetch diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.sh b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.sh index 8d6ba25..96b3979 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.sh +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.sh @@ -3,55 +3,85 @@ # Copyright 2026 The Flux authors. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# This script checks for deprecated Flux API versions in a directory -# using the flux CLI's migrate command in dry-run mode. -# Requires: flux CLI (https://fluxcd.io/flux/installation/) +# This script checks for deprecated Flux API versions in a directory using the +# flux CLI's migrate command in dry-run mode. It reports the exact resources and +# the versions they should migrate to; exit code 1 means deprecated APIs found. + +# Prerequisites +# - flux (https://fluxcd.io/flux/installation/) set -o errexit set -o pipefail +root_dir="." + usage() { - echo "Usage: $(basename "$0") -d " + echo "Usage: $0 [-d ] [-h]" + echo "" + echo "Check a directory for deprecated Flux API versions." echo "" echo "Options:" - echo " -d Directory to scan for deprecated Flux APIs" - echo " -h Show this help message" - exit 1 + echo " -d, --dir Root directory to scan (default: current directory)" + echo " -h, --help Show this help message" } -dir="" - -while getopts "d:h" opt; do - case $opt in - d) dir="$OPTARG" ;; - h) usage ;; - *) usage ;; - esac -done - -if [[ -z "$dir" ]]; then - echo "Error: directory is required" - usage -fi +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + -d|--dir) + if [[ -z "${2:-}" ]]; then + echo "ERROR - --dir requires a directory argument" >&2 + exit 1 + fi + root_dir="${2%/}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR - Unknown argument: $1" >&2 + usage >&2 + exit 1 + ;; + esac + done +} -if [[ ! -d "$dir" ]]; then - echo "Error: $dir is not a directory" - exit 1 -fi +check_prerequisites() { + if ! command -v flux &> /dev/null; then + echo "ERROR - flux is not installed" >&2 + echo "ERROR - Install it from https://fluxcd.io/flux/installation/" >&2 + exit 1 + fi +} -if ! command -v flux &> /dev/null; then - echo "Error: flux CLI is required but not found" - echo "Install it from https://fluxcd.io/flux/installation/" - exit 1 -fi +check_deprecated() { + echo "INFO - Checking for deprecated Flux API versions in ${root_dir}" + echo "INFO - Using $(flux version --client 2>&1 | head -1)" -echo "Checking for deprecated Flux API versions in $dir" -echo "Using $(flux version --client 2>&1 | head -1)" -echo "" + # flux migrate dry-run prints one line per resource that references a + # deprecated API version, e.g.: + # ✚ infrastructure/nginx.yaml:11: HelmRelease v2beta2 -> v2 + # Match the versioned-arrow shape ('vX... -> vY...') rather than any bare + # '✚' or '->' so unrelated arrows in flux output can't cause a false positive. + local output + output="$(cd "$root_dir" && flux migrate -f . --dry-run 2>&1)" || true + echo "$output" -output=$(cd "$dir" && flux migrate -f . --dry-run 2>&1) || true -echo "$output" + if echo "$output" | grep -qE " v[0-9][a-zA-Z0-9]* -> v[0-9]"; then + echo "ERROR - deprecated Flux API versions found" >&2 + return 1 + fi + echo "INFO - No deprecated Flux API versions found" +} -if echo "$output" | grep -qE "✚|->"; then +# Main +parse_args "$@" +check_prerequisites +if [[ ! -d "$root_dir" ]]; then + echo "ERROR - directory not found: $root_dir" >&2 exit 1 fi +check_deprecated diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.test.sh b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.test.sh deleted file mode 100755 index df28039..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/check-deprecated.test.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -# Self-test for check-deprecated.sh. -# -# Proves the script's argument validation, prerequisite checks, and -# deprecation-detection exit-code contract behave as documented — so a refactor -# that breaks an error path or silently swallows a deprecated-API finding is -# caught here, not by a stale audit reaching a user. Self-contained: stubs the -# `flux` CLI on PATH (no real flux, no cluster, no network) and asserts exit -# code + the specific message for every branch. -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SCRIPT="$SCRIPT_DIR/check-deprecated.sh" - -pass=0 -fail=0 - -# A minimal PATH that has coreutils (basename/grep/head) but NOT flux, used to -# exercise the "flux CLI is required" branch deterministically regardless of -# whether flux happens to be installed on the host. -NOFLUX_PATH="/usr/bin:/bin" - -# Write a stub `flux` into a throwaway bin dir and echo that dir. -# $1 = output that `flux migrate` should print (its presence of ✚/-> drives -# the script's exit code). -make_flux_stub() { - local out="$1" bindir - bindir="$(mktemp -d)" - cat > "$bindir/flux" < -- -# Runs the command, capturing combined output + exit code without tripping the -# test's own errexit-free flow, then asserts both. -check_exit() { - local desc="$1" want_rc="$2" pat="$3" - shift 3 - [ "$1" = "--" ] && shift - local out rc - out=$("$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq "$want_rc" ] && { [ -z "$pat" ] || printf '%s' "$out" | grep -qF "$pat"; }; then - echo " ✓ $desc" - pass=$((pass + 1)) - else - echo " ✗ $desc — expected exit $want_rc + message containing '$pat'; got exit $rc" - printf '%s\n' "$out" | sed 's/^/ /' - fail=$((fail + 1)) - fi -} - -# --- argument / prerequisite validation (flux never reached) --- - -check_exit "no directory flag fails with usage" 1 "directory is required" \ - -- bash "$SCRIPT" - -check_exit "-h prints usage and exits non-zero" 1 "Usage:" \ - -- bash "$SCRIPT" -h - -check_exit "non-existent directory fails" 1 "is not a directory" \ - -- bash "$SCRIPT" -d "/no/such/dir/$$" - -valid_dir="$(mktemp -d)" -check_exit "missing flux CLI is reported" 1 "flux CLI is required" \ - -- env PATH="$NOFLUX_PATH" bash "$SCRIPT" -d "$valid_dir" - -# --- deprecation detection (flux stubbed) --- - -clean_bin="$(make_flux_stub "All Flux resources are up to date.")" -check_exit "clean repo exits 0" 0 "" \ - -- env PATH="$clean_bin:$PATH" bash "$SCRIPT" -d "$valid_dir" - -# `->` marks an API migration in `flux migrate` dry-run output. -arrow_bin="$(make_flux_stub "HelmRelease/app helm.toolkit.fluxcd.io/v2beta1 -> helm.toolkit.fluxcd.io/v2")" -check_exit "deprecated API (-> marker) exits 1" 1 "" \ - -- env PATH="$arrow_bin:$PATH" bash "$SCRIPT" -d "$valid_dir" - -# `✚` is the other marker the script greps for. -plus_bin="$(make_flux_stub "✚ Kustomization/app migrated to kustomize.toolkit.fluxcd.io/v1")" -check_exit "deprecated API (✚ marker) exits 1" 1 "" \ - -- env PATH="$plus_bin:$PATH" bash "$SCRIPT" -d "$valid_dir" - -echo "-----------------------------------------" -echo "check-deprecated.sh self-test: $pass passed, $fail failed" -[ "$fail" -eq 0 ] diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.sh b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.sh index 0f39901..9c3eaa3 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.sh +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.sh @@ -3,27 +3,33 @@ # Copyright 2026 The Flux authors. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# This script scans a directory for Kubernetes and Flux resources -# and outputs an inventory report with counts by kind and directory. +# This script scans a directory for Kubernetes and Flux resources using the +# flux-schema plugin and outputs a structured JSON inventory: directory +# classification (manifests, kustomize-overlay, helm-chart, terraform), +# Flux resources listed per file, and Kubernetes resource counts by kind. # Prerequisites -# - awk +# - flux-schema, available either as a standalone binary on PATH or as a plugin +# (install with: flux plugin install schema) set -o errexit set -o pipefail root_dir="." -exclude_dirs=() +skip_files=() + +# command used to invoke flux-schema, resolved by check_prerequisites +flux_schema=() usage() { - echo "Usage: $0 [-d ] [-e ]... [-h]" + echo "Usage: $0 [-d ] [-e ]... [-h]" echo "" - echo "Discover Kubernetes resources and output an inventory report." + echo "Discover Kubernetes resources and output a JSON inventory report." echo "" echo "Options:" - echo " -d, --dir Root directory to scan (default: current directory)" - echo " -e, --exclude Directory to exclude from scanning (can be repeated)" - echo " -h, --help Show this help message" + echo " -d, --dir Root directory to scan (default: current directory)" + echo " -e, --exclude File or directory basename glob to skip (can be repeated)" + echo " -h, --help Show this help message" } parse_args() { @@ -39,10 +45,10 @@ parse_args() { ;; -e|--exclude) if [[ -z "${2:-}" ]]; then - echo "ERROR - --exclude requires a directory argument" >&2 + echo "ERROR - --exclude requires an argument" >&2 exit 1 fi - exclude_dirs+=("${2%/}") + skip_files+=("$(basename "${2%/}")") shift 2 ;; -h|--help) @@ -58,140 +64,29 @@ parse_args() { done } +# Resolve how to invoke flux-schema: prefer the 'flux schema' plugin dispatch, +# otherwise fall back to a standalone flux-schema binary on PATH. check_prerequisites() { - if ! command -v awk &> /dev/null; then - echo "ERROR - awk is not installed" >&2 - exit 1 - fi -} - -# List files matching glob patterns under root_dir, respecting .gitignore. -# Outputs null-terminated paths. Falls back to find for non-git directories. -# Usage: find_files '*.yaml' or find_files '*.tf' 'Chart.yaml' -find_files() { - if git -C "$root_dir" rev-parse --is-inside-work-tree &>/dev/null; then - # Literal filenames need **/ prefix for recursive matching in git pathspec; - # glob patterns (containing * ? [) already match recursively. - local git_patterns=() - for pattern in "$@"; do - if [[ "$pattern" == *'*'* || "$pattern" == *'?'* || "$pattern" == *'['* ]]; then - git_patterns+=("$pattern") - else - git_patterns+=("**/$pattern") - fi - done - git -C "$root_dir" ls-files -z --cached --others --exclude-standard -- "${git_patterns[@]}" | \ - while IFS= read -r -d '' f; do - [[ "$f" == .* || "$f" == */.* ]] && continue - printf '%s\0' "$root_dir/$f" - done + if command -v flux &> /dev/null && flux schema version &> /dev/null; then + flux_schema=(flux schema) + elif command -v flux-schema &> /dev/null; then + flux_schema=(flux-schema) else - local name_args=() - local first=true - for pattern in "$@"; do - if $first; then - name_args+=(-name "$pattern") - first=false - else - name_args+=(-o -name "$pattern") - fi - done - find "$root_dir" -path '*/.*' -prune -o -type f \( "${name_args[@]}" \) -print0 + echo "ERROR - flux-schema is not installed" >&2 + echo "ERROR - Install it with: flux plugin install schema" >&2 + exit 1 fi } -declare -A auto_skip_dirs=() - -detect_excluded_dirs() { - while IFS= read -r -d $'\0' file; do - auto_skip_dirs["$(dirname "$file")"]=1 - done < <(find_files '*.tf' 'Chart.yaml') -} - -is_excluded() { - local path="$1" - for dir in "${exclude_dirs[@]}" "${!auto_skip_dirs[@]}"; do - if [[ "$path" == "$dir"/* || "$path" == "$dir" ]]; then - return 0 - fi - done - return 1 -} - discover() { - # Collect non-excluded YAML files - local files=() - while IFS= read -r -d $'\0' file; do - dir="$(dirname "$file")" - if is_excluded "$dir"; then - continue - fi - files+=("$file") - done < <(find_files '*.yaml') - - if [[ ${#files[@]} -eq 0 ]]; then - echo '{}' - return - fi - - # Single-pass extraction using awk: reads top-level kind/apiVersion from - # each YAML document across all files, categorizes, counts, and outputs JSON. - awk -v root_dir="$root_dir" ' - FNR == 1 || /^---[[:space:]]*$/ { kind = ""; api = "" } - /^kind:[[:space:]]/ { - val = $0; sub(/^kind:[[:space:]]+/, "", val); gsub(/["\047\r]/, "", val); kind = val - } - /^apiVersion:[[:space:]]/ { - val = $0; sub(/^apiVersion:[[:space:]]+/, "", val); gsub(/["\047\r]/, "", val); api = val - } - kind != "" && api != "" { - dir = FILENAME - if (index(dir, "/") > 0) sub(/\/[^\/]*$/, "", dir); else dir = "." - rd_len = length(root_dir) - if (rd_len > 0 && substr(dir, 1, rd_len) == root_dir) { - dir = substr(dir, rd_len + 1) - sub(/^\//, "", dir) - } - if (dir == "") dir = "." - - if (api ~ /kustomize\.config\.k8s\.io/) { - kust_dirs[dir]++ - } else if (api ~ /fluxcd/) { - flux_kinds[kind]++ - flux_dirs[dir]++ - } else { - k8s_kinds[kind]++ - k8s_dirs[dir]++ - } - kind = ""; api = "" - } - function print_obj(arr, s, k) { - printf "{" - s = "" - for (k in arr) { printf "%s\n \"%s\": %d", s, k, arr[k]; s = "," } - if (s != "") printf "\n " - printf "}" - } - END { - printf "{\n" - printf " \"fluxResources\": {\n" - printf " \"byKind\": "; print_obj(flux_kinds) - printf ",\n \"byDirectory\": "; print_obj(flux_dirs) - printf "\n },\n" - printf " \"kubernetesResources\": {\n" - printf " \"byKind\": "; print_obj(k8s_kinds) - printf ",\n \"byDirectory\": "; print_obj(k8s_dirs) - printf "\n },\n" - printf " \"kustomizeOverlays\": {\n" - printf " \"byDirectory\": "; print_obj(kust_dirs) - printf "\n }\n" - printf "}\n" - } - ' "${files[@]}" + local args=("$root_dir" "-o" "json") + for pattern in "${skip_files[@]}"; do + args+=("--skip-file" "$pattern") + done + "${flux_schema[@]}" discover "${args[@]}" } # Main parse_args "$@" check_prerequisites -detect_excluded_dirs discover diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.test.sh b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.test.sh deleted file mode 100755 index 65980eb..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/discover.test.sh +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/env bash -# Self-test for discover.sh. -# -# Proves the inventory script's argument validation, awk prerequisite check, and -# resource-classification contract behave as documented — so a refactor that -# breaks an error path, misfiles a resource (Flux vs Kubernetes vs kustomize), -# or stops honouring an exclusion is caught here, not by a wrong audit reaching a -# user. Self-contained: builds throwaway fixture dirs and uses only awk/git/ -# coreutils — no network, no cluster. -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SCRIPT="$SCRIPT_DIR/discover.sh" - -# discover.sh needs bash 4+ (associative arrays); invoke it via an absolute bash -# path captured here so the restricted PATHs below control only what the *script* -# sees (awk presence), never which interpreter runs it. -BASH_BIN="$(command -v bash)" - -pass=0 -fail=0 - -# check_exit -- -# Runs the command, capturing combined output + exit code without tripping the -# test's own flow, then asserts both. An empty substring asserts only the code. -check_exit() { - local desc="$1" want_rc="$2" pat="$3" - shift 3 - [ "$1" = "--" ] && shift - local out rc - out=$("$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq "$want_rc" ] && { [ -z "$pat" ] || printf '%s' "$out" | grep -qF -- "$pat"; }; then - echo " ✓ $desc" - pass=$((pass + 1)) - else - echo " ✗ $desc — expected exit $want_rc + message containing '$pat'; got exit $rc" - printf '%s\n' "$out" | sed 's/^/ /' - fail=$((fail + 1)) - fi -} - -# refute_substr -- -# Asserts the command exits 0 and the substring is ABSENT from its output — used -# to prove an excluded / auto-skipped resource is NOT counted in the inventory. -refute_substr() { - local desc="$1" pat="$2" - shift 2 - [ "$1" = "--" ] && shift - local out rc - out=$("$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ] && ! printf '%s' "$out" | grep -qF -- "$pat"; then - echo " ✓ $desc" - pass=$((pass + 1)) - else - echo " ✗ $desc — expected exit 0 with '$pat' absent; got exit $rc" - printf '%s\n' "$out" | sed 's/^/ /' - fail=$((fail + 1)) - fi -} - -# check_output_eq -- -# Asserts exit 0 and that combined output equals exactly the expected string -# (trailing newline ignored). Used for the empty-inventory `{}` contract, where a -# substring match would be ambiguous (the empty `byKind: {}` appears regardless). -check_output_eq() { - local desc="$1" want="$2" - shift 2 - [ "$1" = "--" ] && shift - local out rc - out=$("$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq 0 ] && [ "$out" = "$want" ]; then - echo " ✓ $desc" - pass=$((pass + 1)) - else - echo " ✗ $desc — expected exit 0 and output '$want'; got exit $rc" - printf '%s\n' "$out" | sed 's/^/ /' - fail=$((fail + 1)) - fi -} - -# section_block : reads the full discover JSON on stdin and prints -# only the lines of that top-level section (header through the line before the -# next 2-space-indented "key" — i.e. the next section, or EOF for the last one). -section_block() { - awk -v s="\"$1\"" ' - $0 ~ "^ " s ":" { inblock = 1; print; next } - inblock && /^ "[a-zA-Z]/ { inblock = 0 } - inblock { print } - ' -} - -# check_in_section
-- -# Asserts exit 0 AND that `"": 1` appears *within* the named section — -# so a resource filed under the wrong top-level bucket (Flux vs Kubernetes vs -# kustomize) is caught, which a whole-output substring match would miss. -check_in_section() { - local desc="$1" section="$2" key="$3" - shift 3 - [ "$1" = "--" ] && shift - local out rc block - out=$("$@" 2>&1) && rc=0 || rc=$? - block=$(printf '%s\n' "$out" | section_block "$section") - if [ "$rc" -eq 0 ] && printf '%s' "$block" | grep -qF -- "\"$key\": 1"; then - echo " ✓ $desc" - pass=$((pass + 1)) - else - echo " ✗ $desc — expected exit 0 and '\"$key\": 1' inside section '$section'; got exit $rc" - printf '%s\n' "$out" | sed 's/^/ /' - fail=$((fail + 1)) - fi -} - -# A throwaway git work tree — exercises discover.sh's primary git-pathspec branch -# (untracked files are surfaced via `git ls-files --others`, no commit needed). -new_git_dir() { - local d - d="$(mktemp -d)" - git -C "$d" init -q - echo "$d" -} - -# --- argument validation (prerequisites / discovery never reached) --- - -check_exit "-h prints usage and exits 0" 0 "Usage:" \ - -- "$BASH_BIN" "$SCRIPT" -h - -check_exit "unknown argument fails with usage" 1 "Unknown argument" \ - -- "$BASH_BIN" "$SCRIPT" --bogus - -check_exit "-d without a value fails" 1 "--dir requires" \ - -- "$BASH_BIN" "$SCRIPT" -d - -check_exit "-e without a value fails" 1 "--exclude requires" \ - -- "$BASH_BIN" "$SCRIPT" -e - -# --- prerequisite check (awk required) --- - -# An empty PATH dir hides awk regardless of where it lives on the host, so the -# "awk is not installed" branch is exercised deterministically on any machine. -NOAWK_PATH="$(mktemp -d)" -empty_dir="$(mktemp -d)" -check_exit "missing awk is reported" 1 "awk is not installed" \ - -- env PATH="$NOAWK_PATH" "$BASH_BIN" "$SCRIPT" -d "$empty_dir" - -# --- empty inventory (non-git dir → find fallback branch) --- - -check_output_eq "empty directory yields an empty JSON object" "{}" \ - -- "$BASH_BIN" "$SCRIPT" -d "$empty_dir" - -# --- resource classification --- - -flux_dir="$(new_git_dir)" -cat > "$flux_dir/hr.yaml" <<'EOF' -apiVersion: helm.toolkit.fluxcd.io/v2 -kind: HelmRelease -metadata: - name: app -EOF -check_in_section "Flux resource is filed under fluxResources" fluxResources HelmRelease \ - -- "$BASH_BIN" "$SCRIPT" -d "$flux_dir" - -k8s_dir="$(new_git_dir)" -cat > "$k8s_dir/deploy.yaml" <<'EOF' -apiVersion: apps/v1 -kind: Deployment -metadata: - name: web -EOF -check_in_section "vanilla Kubernetes resource is filed under kubernetesResources" kubernetesResources Deployment \ - -- "$BASH_BIN" "$SCRIPT" -d "$k8s_dir" - -kust_dir="$(new_git_dir)" -mkdir -p "$kust_dir/overlays" -cat > "$kust_dir/overlays/kustomization.yaml" <<'EOF' -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - deploy.yaml -EOF -check_in_section "kustomize overlay is filed under kustomizeOverlays.byDirectory" kustomizeOverlays overlays \ - -- "$BASH_BIN" "$SCRIPT" -d "$kust_dir" - -# --- exclusion semantics --- - -# -e takes a path resolved the same way as the scanned dirs (under -d), so an -# absolute child path excludes it. The only manifest lives under it → empty set. -ex_dir="$(new_git_dir)" -mkdir -p "$ex_dir/skipme" -cat > "$ex_dir/skipme/deploy.yaml" <<'EOF' -apiVersion: apps/v1 -kind: Deployment -metadata: - name: x -EOF -refute_substr "resource under an -e excluded directory is not counted" "Deployment" \ - -- "$BASH_BIN" "$SCRIPT" -d "$ex_dir" -e "$ex_dir/skipme" - -# A dir holding a Chart.yaml is auto-detected as a Helm chart and skipped, so a -# manifest nested beneath it must not appear in the inventory. -chart_dir="$(new_git_dir)" -mkdir -p "$chart_dir/mychart/templates" -cat > "$chart_dir/mychart/Chart.yaml" <<'EOF' -apiVersion: v2 -name: mychart -version: 0.1.0 -EOF -cat > "$chart_dir/mychart/templates/deploy.yaml" <<'EOF' -apiVersion: apps/v1 -kind: Deployment -metadata: - name: x -EOF -refute_substr "resource under an auto-skipped Helm chart directory is not counted" "Deployment" \ - -- "$BASH_BIN" "$SCRIPT" -d "$chart_dir" - -echo "-----------------------------------------" -echo "discover.sh self-test: $pass passed, $fail failed" -[ "$fail" -eq 0 ] diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.sh b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.sh index f80a43d..08de431 100644 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.sh +++ b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.sh @@ -1,56 +1,137 @@ #!/usr/bin/env bash -# Copyright 2023-2026 The Flux authors. All rights reserved. +# DO NOT EDIT: this file is vendored from the flux-schema action (single source of truth). +# Source: https://raw.githubusercontent.com/fluxcd/flux-schema/main/actions/validate/validate.sh + +# Copyright 2026 The Flux authors. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# This script validates the Flux custom resources and the kustomize -# overlays using kubeconform against the Flux OpenAPI schemas bundled -# in the assets directory. +# This script validates Kubernetes manifests using the Flux Schema CLI. +# It builds kustomize overlays and validating the output against the +# default schema catalog or a user-provided config file. +# Arguments after '--' are passed verbatim to 'flux-schema validate' and +# take precedence over the config file, so callers (e.g. AI agents) can +# set validation options inline without writing a config file to disk. +# The script auto-detects and excludes non-Kubernetes directories such as +# dotfiles, Terraform modules and Helm charts. +# With --helm-charts, Helm charts are rendered with 'helm template' using +# their default values and the output is validated as well. +# A build or validation failure does not stop the run; the script keeps +# going and exits non-zero at the end with the total error count. +# With --output-bundle, all standalone manifests and rendered kustomize +# overlays are merged into a single YAML file where each unit is preceded +# by a provenance comment ('# === file: ===' or +# '# === kustomize-overlay: ==='), so tools and AI agents can grep +# one file instead of crawling the repository. +# This script is meant to be run locally and in CI before the changes +# are merged on the main branch that's synced by Flux. # Prerequisites -# - yq >= 4.50 -# - kustomize >= 5.8 -# - kubeconform >= 0.7 +# - flux-schema >= 0.6 (standalone binary, or the 'flux schema' plugin) +# - kustomize, or kubectl (uses its embedded kustomize via 'kubectl kustomize') +# - helm >= 4.0 (only with --helm-charts) + +# Usage examples: +# validate.sh \ +# -d ./manifests \ +# -c ./.fluxschema.yml \ +# -b ./.bundle.yaml +# +# validate.sh -d ./manifests -- \ +# --skip-json-path=Secret:/sops \ +# --skip-missing-schemas \ +# --output=json +# +# Name the bundle with a leading dot (e.g. '.bundle.yaml') so that +# validation and 'flux-schema discover' ignore it on subsequent runs, +# as dotfiles are excluded by default. +set -o errexit set -o pipefail -# track validation failures +# track validation and build failures errors=0 -# resolve the skill root directory (parent of scripts/) -script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -skill_dir="$(cd "$script_dir/.." && pwd)" -assets_schemas_dir="$skill_dir/assets/schemas" +# running tally of resources across every flux-schema invocation, parsed +# in-memory from each run's "Summary:" line. +# summaries_parsed stays 0 when output is non-text (e.g. a '--' or config +# override to json/yaml), so the report can omit a misleading "0 valid". +valid_count=0 +invalid_count=0 +skipped_count=0 +summaries_parsed=0 # mirror kustomize-controller build options kustomize_flags=("--load-restrictor=LoadRestrictionsNone") kustomize_config="kustomization.yaml" -# skip Kubernetes Secrets due to SOPS fields failing validation -kubeconform_flags=("-skip=Secret") -kubeconform_config=("-strict" "-ignore-missing-schemas" "-schema-location" "default" "-verbose") +# mirror helm-controller install options (CRDs are installed by default) +helm_flags=("--include-crds") +helm_config="Chart.yaml" + +# Default flags used when no config file is found. Strip SOPS-encrypted +# fields before validation (Flux removes these at apply time), skip documents +# whose schema is not in the catalog, and pin text output so the per-resource +# summary tally is parsed reliably (a config or '--' override owns the format). +default_flux_schema_flags=("--skip-json-path=/sops" "--skip-missing-schemas" "--verbose" "--output=text") + +# Effective flags passed to flux-schema, populated by resolve_config. +flux_schema_flags=() + +# Flags given after '--', passed verbatim to 'flux-schema validate'. +# When set, they take precedence over the config file and default flags. +flux_schema_args=() + +# Effective flux-schema invocation, populated by resolve_flux_schema. +# Either ("flux" "schema") for the Flux CLI plugin or ("flux-schema") for the +# standalone CLI. +flux_schema_cmd=() + +# Effective kustomize invocation, populated by resolve_kustomize. +# Either ("kustomize" "build") or ("kubectl" "kustomize"). +kustomize_cmd=() # root directory to validate root_dir="." +# path to the flux-schema config file +config_file=".fluxschema.yml" + +# path to the merged YAML bundle (empty disables bundling) +bundle_file="" + +# when true, render Helm charts with 'helm template' and validate the output +build_helm_charts=false + # directories to exclude from validation exclude_dirs=() # directories auto-detected as non-Kubernetes (terraform, helm charts) -declare -A auto_skip_dirs=() +declare -a auto_skip_dirs=() + +# directories that are Helm charts +declare -a helm_chart_dirs=() # directories that are kustomize overlays -declare -A kustomize_dirs=() +declare -a kustomize_dirs=() usage() { - echo "Usage: $0 [-d ] [-e ]... [-h]" + echo "Usage: $0 [-d ] [-c ] [-e ]... [-b ] [-H] [-h] [-- ]" echo "" - echo "Validate Flux custom resources and kustomize overlays using kubeconform." + echo "Validate Flux custom resources and kustomize overlays using flux-schema." echo "" echo "Options:" - echo " -d, --dir Root directory to validate (default: current directory)" - echo " -e, --exclude Directory to exclude from validation (can be repeated)" - echo " -h, --help Show this help message" + echo " -d, --dir Root directory to validate (default: current directory)" + echo " -c, --config Path to a flux-schema config file (default: .fluxschema.yml)." + echo " When the file does not exist, sensible defaults are used." + echo " -e, --exclude Directory to exclude from validation and the bundle (can be repeated)" + echo " -b, --output-bundle Write all standalone manifests and rendered kustomize" + echo " overlays to a single YAML file with provenance comments" + echo " -H, --helm-charts Render Helm charts with 'helm template' using their" + echo " default values and validate the output (requires helm)" + echo " -h, --help Show this help message" + echo " -- Pass the remaining arguments verbatim to 'flux-schema validate'," + echo " taking precedence over the config file and default flags" } parse_args() { @@ -64,6 +145,14 @@ parse_args() { root_dir="${2%/}" shift 2 ;; + -c|--config) + if [[ -z "${2:-}" ]]; then + echo "ERROR - --config requires a file path argument" >&2 + exit 1 + fi + config_file="$2" + shift 2 + ;; -e|--exclude) if [[ -z "${2:-}" ]]; then echo "ERROR - --exclude requires a directory argument" >&2 @@ -72,10 +161,27 @@ parse_args() { exclude_dirs+=("./${2#./}") shift 2 ;; + -b|--output-bundle) + if [[ -z "${2:-}" ]]; then + echo "ERROR - --output-bundle requires a file path argument" >&2 + exit 1 + fi + bundle_file="$2" + shift 2 + ;; + -H|--helm-charts) + build_helm_charts=true + shift + ;; -h|--help) usage exit 0 ;; + --) + shift + flux_schema_args=("$@") + break + ;; *) echo "ERROR - Unknown argument: $1" >&2 usage >&2 @@ -86,59 +192,56 @@ parse_args() { } check_prerequisites() { - local missing=0 - for cmd in yq kustomize kubeconform; do - if ! command -v "$cmd" &> /dev/null; then - echo "ERROR - $cmd is not installed" >&2 - missing=1 - fi - done - if [[ ! -d "$assets_schemas_dir" ]]; then - echo "ERROR - Flux OpenAPI schemas not found in $assets_schemas_dir" >&2 - echo "ERROR - Run 'make download-schemas' to fetch them" >&2 - missing=1 + if [[ ! -d "$root_dir" ]]; then + echo "ERROR - directory not found: $root_dir" >&2 + exit 1 fi - if [[ $missing -ne 0 ]]; then + if [[ "$build_helm_charts" == true ]] && ! command -v helm &> /dev/null; then + echo "ERROR - helm is not installed (required by --helm-charts)" >&2 exit 1 fi } -setup_schemas() { - kubeconform_config+=("-schema-location" "$assets_schemas_dir/{{.ResourceKind}}{{.KindSuffix}}.json") +# Pick the flux-schema invocation. Prefer the 'flux schema' plugin dispatch +# (the documented 'flux plugin install schema' path); fall back to a standalone +# flux-schema binary on PATH. +resolve_flux_schema() { + if command -v flux &> /dev/null && flux schema --help &> /dev/null; then + flux_schema_cmd=("flux" "schema") + elif command -v flux-schema &> /dev/null; then + flux_schema_cmd=("flux-schema") + else + echo "ERROR - flux-schema is not installed (tried 'flux schema' plugin and 'flux-schema')" >&2 + exit 1 + fi } -# List files matching glob patterns under root_dir, respecting .gitignore. -# Outputs null-terminated paths. Falls back to find for non-git directories. -# Usage: find_files '*.yaml' or find_files '*.tf' 'Chart.yaml' -find_files() { - if git -C "$root_dir" rev-parse --is-inside-work-tree &>/dev/null; then - # Literal filenames need **/ prefix for recursive matching in git pathspec; - # glob patterns (containing * ? [) already match recursively. - local git_patterns=() - for pattern in "$@"; do - if [[ "$pattern" == *'*'* || "$pattern" == *'?'* || "$pattern" == *'['* ]]; then - git_patterns+=("$pattern") - else - git_patterns+=("**/$pattern") - fi - done - git -C "$root_dir" ls-files -z --cached --others --exclude-standard -- "${git_patterns[@]}" | \ - while IFS= read -r -d '' f; do - [[ "$f" == .* || "$f" == */.* ]] && continue - printf '%s\0' "$root_dir/$f" - done +# Pick the kustomize invocation. Prefer the standalone CLI (independently +# updatable); fall back to kubectl's embedded kustomize ('kubectl kustomize'). +resolve_kustomize() { + if command -v kustomize &> /dev/null; then + kustomize_cmd=("kustomize" "build") + elif command -v kubectl &> /dev/null; then + kustomize_cmd=("kubectl" "kustomize") else - local name_args=() - local first=true - for pattern in "$@"; do - if $first; then - name_args+=(-name "$pattern") - first=false - else - name_args+=(-o -name "$pattern") - fi - done - find "$root_dir" -path '*/.*' -prune -o -type f \( "${name_args[@]}" \) -print0 + echo "ERROR - neither kustomize nor kubectl is installed" >&2 + exit 1 + fi +} + +# Pick the flags to pass to flux-schema. Flags given after '--' win; when +# the config file exists, defer all validation options to it; otherwise +# fall back to the built-in defaults. +resolve_config() { + if [[ ${#flux_schema_args[@]} -gt 0 ]]; then + echo "INFO - Using flux-schema flags from the command line" + flux_schema_flags=("${flux_schema_args[@]}") + elif [[ -f "$config_file" ]]; then + echo "INFO - Using flux-schema config: $config_file" + flux_schema_flags=("--config=$config_file") + else + echo "INFO - Config file '$config_file' not found, using default flags" + flux_schema_flags=("${default_flux_schema_flags[@]}") fi } @@ -148,6 +251,46 @@ normalize_path() { echo "${p%/}" } +# Create the parent directory and truncate the bundle file when +# --output-bundle is set +init_bundle() { + if [[ -z "$bundle_file" ]]; then + return 0 + fi + if ! mkdir -p "$(dirname "$bundle_file")" 2>/dev/null || \ + ! : 2>/dev/null > "$bundle_file"; then + echo "ERROR - Cannot write bundle file: $bundle_file" >&2 + exit 1 + fi +} + +# Path relative to root_dir, used in bundle provenance comments to match +# the root-relative paths emitted by 'flux-schema discover' +rel_path() { + local p r + p="$(normalize_path "$1")" + r="$(normalize_path "$root_dir")" + if [[ "$r" != "." && "$p" == "$r"/* ]]; then + p="${p#"$r"/}" + fi + echo "$p" +} + +# Append a unit to the bundle file: a document separator, a provenance +# comment, and the YAML content read from stdin. No-op when --output-bundle +# is not set (stdin is drained so writers never see a broken pipe). +bundle_append() { + if [[ -z "$bundle_file" ]]; then + cat > /dev/null + return 0 + fi + { + echo "---" + echo "# === $1 ===" + cat + } >> "$bundle_file" +} + # Check if a path is under a user-excluded, auto-skipped, or kustomize directory is_excluded_dir() { local path @@ -159,14 +302,28 @@ is_excluded_dir() { return 0 fi done - for dir in "${!auto_skip_dirs[@]}"; do + for dir in "${auto_skip_dirs[@]}"; do + local d + d="$(normalize_path "$dir")" + if [[ "$path" == "$d"/* || "$path" == "$d" ]]; then + return 0 + fi + done + for dir in "${kustomize_dirs[@]}"; do local d d="$(normalize_path "$dir")" if [[ "$path" == "$d"/* || "$path" == "$d" ]]; then return 0 fi done - for dir in "${!kustomize_dirs[@]}"; do + return 1 +} + +# Check if a path is under a user-excluded directory only +is_user_excluded_dir() { + local path + path="$(normalize_path "$1")" + for dir in "${exclude_dirs[@]}"; do local d d="$(normalize_path "$dir")" if [[ "$path" == "$d"/* || "$path" == "$d" ]]; then @@ -176,11 +333,27 @@ is_excluded_dir() { return 1 } +# Check if a chart directory is vendored inside another chart +# (e.g. a dependency under the parent's charts/ directory); such charts +# are rendered as part of their parent and must not be templated standalone +is_nested_chart_dir() { + local path + path="$(normalize_path "$1")" + for dir in "${helm_chart_dirs[@]}"; do + local d + d="$(normalize_path "$dir")" + if [[ "$path" != "$d" && "$path" == "$d"/* ]]; then + return 0 + fi + done + return 1 +} + # Check if a path is under a user-excluded or auto-skipped directory (but not kustomize dirs) is_non_kustomize_excluded_dir() { local path path="$(normalize_path "$1")" - for dir in "${exclude_dirs[@]}" "${!auto_skip_dirs[@]}"; do + for dir in "${exclude_dirs[@]}" "${auto_skip_dirs[@]}"; do local d d="$(normalize_path "$dir")" if [[ "$path" == "$d"/* || "$path" == "$d" ]]; then @@ -193,66 +366,145 @@ is_non_kustomize_excluded_dir() { # Detect directories containing Terraform files, Helm charts, or kustomize overlays detect_excluded_dirs() { while IFS= read -r -d $'\0' file; do - auto_skip_dirs["$(dirname "$file")"]=1 - done < <(find_files '*.tf' 'Chart.yaml') + auto_skip_dirs+=("$(dirname "$file")") + done < <(find "$root_dir" -mindepth 1 -name '.*' -prune -o -type f -name '*.tf' -print0) while IFS= read -r -d $'\0' file; do - kustomize_dirs["$(dirname "$file")"]=1 - done < <(find_files "$kustomize_config") -} + auto_skip_dirs+=("$(dirname "$file")") + helm_chart_dirs+=("$(dirname "$file")") + done < <(find "$root_dir" -mindepth 1 -name '.*' -prune -o -type f -name "$helm_config" -print0) -validate_yaml_syntax() { - echo "INFO - Validating YAML syntax" while IFS= read -r -d $'\0' file; do - dir="$(dirname "$file")" - if is_excluded_dir "$dir"; then - continue - fi - if ! yq e 'true' "$file" > /dev/null 2>&1; then - echo "ERROR - Invalid YAML syntax in $file" >&2 - errors=$((errors + 1)) - fi - done < <(find_files '*.yaml') + kustomize_dirs+=("$(dirname "$file")") + done < <(find "$root_dir" -mindepth 1 -name '.*' -prune -o -type f -name "$kustomize_config" -print0) +} + +# Add a captured flux-schema run's "Summary:" counts to the running tally. +accumulate_summary() { + if [[ "$1" =~ Valid:\ ([0-9]+),\ Invalid:\ ([0-9]+),\ Skipped:\ ([0-9]+) ]]; then + valid_count=$((valid_count + BASH_REMATCH[1])) + invalid_count=$((invalid_count + BASH_REMATCH[2])) + skipped_count=$((skipped_count + BASH_REMATCH[3])) + summaries_parsed=$((summaries_parsed + 1)) + fi } validate_kubernetes_manifests() { echo "INFO - Validating Kubernetes manifests" + local files=() output dir while IFS= read -r -d $'\0' file; do dir="$(dirname "$file")" if is_excluded_dir "$dir"; then continue fi - if ! kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" "${file}"; then + if [[ -n "$bundle_file" && "$file" -ef "$bundle_file" ]]; then + continue + fi + files+=("$file") + done < <(find "$root_dir" -mindepth 1 -name '.*' -prune -o -type f \( -name '*.yaml' -o -name '*.yml' \) -print0) + if [[ ${#files[@]} -gt 0 ]]; then + if [[ -n "$bundle_file" ]]; then + for file in "${files[@]}"; do + # SC2094: $file is never the bundle file (filtered above via -ef) + # shellcheck disable=SC2094 + bundle_append "file: $(rel_path "$file")" < "$file" + done + fi + # Capture stdout+stderr in memory so the run can be both printed and + # tallied; the assignment lives in the 'if' so errexit does not fire. + if ! output="$("${flux_schema_cmd[@]}" validate "${flux_schema_flags[@]}" "${files[@]}" 2>&1)"; then errors=$((errors + 1)) fi - done < <(find_files '*.yaml') + printf '%s\n' "$output" + accumulate_summary "$output" + fi } validate_kustomize_overlays() { + local overlay build_output output dir while IFS= read -r -d $'\0' file; do dir="$(dirname "$file")" if is_non_kustomize_excluded_dir "$dir"; then continue fi - echo "INFO - Validating kustomize overlay ${file/%$kustomize_config}" - kustomize build "${file/%$kustomize_config}" "${kustomize_flags[@]}" | \ - kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" - if [[ ${PIPESTATUS[0]} != 0 || ${PIPESTATUS[1]} != 0 ]]; then + overlay="${file/%$kustomize_config}" + echo "INFO - Validating kustomize overlay $overlay" + if ! build_output=$("${kustomize_cmd[@]}" "$overlay" "${kustomize_flags[@]}"); then + echo "ERROR - kustomize build failed for $overlay" >&2 + bundle_append "kustomize-overlay: $(rel_path "$overlay") (build failed)" < /dev/null + errors=$((errors + 1)) + continue + fi + [[ -n "$bundle_file" ]] && bundle_append "kustomize-overlay: $(rel_path "$overlay")" <<< "$build_output" + if ! output="$(printf '%s\n' "$build_output" | \ + "${flux_schema_cmd[@]}" validate "${flux_schema_flags[@]}" 2>&1)"; then + errors=$((errors + 1)) + fi + printf '%s\n' "$output" + accumulate_summary "$output" + done < <(find "$root_dir" -mindepth 1 -name '.*' -prune -o -type f -name "$kustomize_config" -print0) +} + +validate_helm_charts() { + if [[ "$build_helm_charts" != true ]]; then + return 0 + fi + local chart build_output output + for chart in "${helm_chart_dirs[@]}"; do + if is_user_excluded_dir "$chart" || is_nested_chart_dir "$chart"; then + continue + fi + echo "INFO - Validating helm chart $chart" + if ! build_output=$(helm template "$chart" "${helm_flags[@]}"); then + echo "ERROR - helm template failed for $chart" >&2 + bundle_append "helm-chart: $(rel_path "$chart") (build failed)" < /dev/null errors=$((errors + 1)) + continue fi - done < <(find_files "$kustomize_config") + [[ -n "$bundle_file" ]] && bundle_append "helm-chart: $(rel_path "$chart")" <<< "$build_output" + if ! output="$(printf '%s\n' "$build_output" | \ + "${flux_schema_cmd[@]}" validate "${flux_schema_flags[@]}" 2>&1)"; then + errors=$((errors + 1)) + fi + printf '%s\n' "$output" + accumulate_summary "$output" + done +} + +# Print the final outcome and exit non-zero when any build or validation failed. +# The exit decision comes from the per-invocation error count (robust even if +# output is reformatted); the valid/invalid/skipped tally is parsed from the +# collected "Summary:" lines for a precise, agent-readable breakdown. +report_results() { + if [[ -n "$bundle_file" ]]; then + echo "INFO - Bundle written to $bundle_file" + fi + # Only append the resource breakdown when text Summary lines were parsed; + # a non-text output override leaves the tally at zero, which would mislead. + local tally="" + if [[ $summaries_parsed -gt 0 ]]; then + tally=" (${valid_count} valid, ${skipped_count} skipped)" + fi + if [[ $errors -gt 0 ]]; then + if [[ $summaries_parsed -gt 0 ]]; then + echo "ERROR - Validation failed: ${errors} error(s); ${invalid_count} invalid resource(s) (${valid_count} valid, ${skipped_count} skipped)" >&2 + else + echo "ERROR - Validation failed with ${errors} error(s)" >&2 + fi + exit 1 + fi + echo "INFO - All validations passed${tally}" } # Main parse_args "$@" check_prerequisites -setup_schemas +resolve_flux_schema +resolve_kustomize +resolve_config +init_bundle detect_excluded_dirs -validate_yaml_syntax validate_kubernetes_manifests validate_kustomize_overlays -if [[ $errors -gt 0 ]]; then - echo "ERROR - Validation failed with $errors error(s)" >&2 - exit 1 -fi -echo "INFO - All validations passed" +validate_helm_charts +report_results diff --git a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.test.sh b/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.test.sh deleted file mode 100755 index 8e5a790..0000000 --- a/plugins/gitops-kubernetes/skills/gitops-repo-audit/scripts/validate.test.sh +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env bash -# Self-test for validate.sh. -# -# Proves the manifest-validation script's argument validation, prerequisite -# checks (each of yq / kustomize / kubeconform), and exit-code contract behave as -# documented — so a refactor that swallows a validation failure or breaks an -# error path is caught here, not by a green audit hiding an invalid manifest. -# Self-contained: stubs the three external CLIs on PATH (configurable exit codes) -# and runs against throwaway fixtures — no network, no cluster, no real tools. -set -uo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SCRIPT="$SCRIPT_DIR/validate.sh" - -# validate.sh needs bash 4+ (associative arrays); invoke it via an absolute bash -# path captured here so the restricted PATHs below control only which external -# tools the *script* sees, never which interpreter runs it. -BASH_BIN="$(command -v bash)" - -pass=0 -fail=0 - -# check_exit -- -check_exit() { - local desc="$1" want_rc="$2" pat="$3" - shift 3 - [ "$1" = "--" ] && shift - local out rc - out=$("$@" 2>&1) && rc=0 || rc=$? - if [ "$rc" -eq "$want_rc" ] && { [ -z "$pat" ] || printf '%s' "$out" | grep -qF -- "$pat"; }; then - echo " ✓ $desc" - pass=$((pass + 1)) - else - echo " ✗ $desc — expected exit $want_rc + message containing '$pat'; got exit $rc" - printf '%s\n' "$out" | sed 's/^/ /' - fail=$((fail + 1)) - fi -} - -# make_tools -> bindir -# Writes the three CLIs as stubs exiting with the given codes (kustomize also -# prints a trivial manifest so the `kustomize build | kubeconform` pipe has -# data). Pass "x" for a tool's rc to leave it ABSENT — driving the matching -# " is not installed" prerequisite branch. -make_tools() { - local yqrc="$1" kzrc="$2" kcrc="$3" bindir - bindir="$(mktemp -d)" - if [ "$yqrc" != x ]; then - printf '#!/usr/bin/env bash\nexit %s\n' "$yqrc" > "$bindir/yq" - chmod +x "$bindir/yq" - fi - if [ "$kzrc" != x ]; then - printf '#!/usr/bin/env bash\necho "apiVersion: v1"\necho "kind: ConfigMap"\nexit %s\n' "$kzrc" > "$bindir/kustomize" - chmod +x "$bindir/kustomize" - fi - if [ "$kcrc" != x ]; then - printf '#!/usr/bin/env bash\nexit %s\n' "$kcrc" > "$bindir/kubeconform" - chmod +x "$bindir/kubeconform" - fi - echo "$bindir" -} - -# A fixture with a plain manifest (exercises validate_yaml_syntax + -# validate_kubernetes_manifests) and a kustomize overlay in its own dir -# (auto-detected → skipped by the first two passes, handled by -# validate_kustomize_overlays). Non-git → find fallback for file discovery. -make_fixture() { - local d - d="$(mktemp -d)" - mkdir -p "$d/app" "$d/overlay" - printf 'apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name: web\n' > "$d/app/deploy.yaml" - printf 'apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:\n - ../app/deploy.yaml\n' > "$d/overlay/kustomization.yaml" - echo "$d" -} - -# A minimal PATH carrying coreutils (find/dirname/env) but none of the three -# validated tools, so the stub bindir is the sole source of yq/kustomize/ -# kubeconform. git is intentionally absent → find_files takes its find fallback. -BASE_PATH="/usr/bin:/bin" - -# --- argument validation (prerequisites never reached) --- - -check_exit "-h prints usage and exits 0" 0 "Usage:" \ - -- "$BASH_BIN" "$SCRIPT" -h - -check_exit "unknown argument fails with usage" 1 "Unknown argument" \ - -- "$BASH_BIN" "$SCRIPT" --bogus - -check_exit "-d without a value fails" 1 "--dir requires" \ - -- "$BASH_BIN" "$SCRIPT" -d - -check_exit "-e without a value fails" 1 "--exclude requires" \ - -- "$BASH_BIN" "$SCRIPT" -e - -# --- prerequisite checks (each tool reported independently) --- - -# These run on a stub-ONLY PATH (no BASE_PATH): the script reaches only shell -# builtins before it exits, so it needs no coreutils — and excluding the real -# bin dirs is what makes " is not installed" deterministic on CI runners -# that ship a real yq/kustomize/kubeconform in /usr/bin (which would otherwise -# shadow the intentionally-absent stub). -empty_fix="$(mktemp -d)" - -no_yq="$(make_tools x 0 0)" -check_exit "missing yq is reported" 1 "yq is not installed" \ - -- env PATH="$no_yq" "$BASH_BIN" "$SCRIPT" -d "$empty_fix" - -no_kustomize="$(make_tools 0 x 0)" -check_exit "missing kustomize is reported" 1 "kustomize is not installed" \ - -- env PATH="$no_kustomize" "$BASH_BIN" "$SCRIPT" -d "$empty_fix" - -no_kubeconform="$(make_tools 0 0 x)" -check_exit "missing kubeconform is reported" 1 "kubeconform is not installed" \ - -- env PATH="$no_kubeconform" "$BASH_BIN" "$SCRIPT" -d "$empty_fix" - -# --- exit-code contract (tools stubbed; real schemas dir present in the repo) --- - -good_fix="$(make_fixture)" -all_pass="$(make_tools 0 0 0)" -check_exit "all validations passing → exit 0" 0 "All validations passed" \ - -- env PATH="$all_pass:$BASE_PATH" "$BASH_BIN" "$SCRIPT" -d "$good_fix" - -yaml_fix="$(make_fixture)" -bad_yq="$(make_tools 1 0 0)" -check_exit "invalid YAML syntax (yq fails) → exit 1" 1 "Invalid YAML syntax" \ - -- env PATH="$bad_yq:$BASE_PATH" "$BASH_BIN" "$SCRIPT" -d "$yaml_fix" - -manifest_fix="$(make_fixture)" -bad_kc="$(make_tools 0 0 1)" -check_exit "manifest validation failure (kubeconform fails) → exit 1" 1 "Validation failed with" \ - -- env PATH="$bad_kc:$BASE_PATH" "$BASH_BIN" "$SCRIPT" -d "$manifest_fix" - -echo "-----------------------------------------" -echo "validate.sh self-test: $pass passed, $fail failed" -[ "$fail" -eq 0 ]