Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.3.1] - 2026-05-10

### Added

- **Release-time bench workflow** (`.github/workflows/bench.yml`). Manually triggered (`workflow_dispatch`) against a release tag, branch, or SHA; runs the full 8-profile bench matrix on a self-hosted runner (label `bench-runner`) and persists the resulting JSON to the `bench-history` orphan branch under `bench-history/<label>.json`. The orphan branch is self-bootstrapped on first run. A markdown table of source-update / self-heal / ns-flip p99 latencies by profile is rendered into the workflow's step summary, and the bench JSON is uploaded as an artifact. `pull_request` triggers are deliberately omitted — self-hosted runners on public repos are exposed to fork-PR malicious code, so this workflow is operator-driven only. Per-PR shape-break smoke coverage continues to run on `ubuntu-22.04` via `bench-smoke.yml`.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ Need to mirror into many namespaces from one source? Use `ClusterProjection` (cl

```bash
helm install projection oci://ghcr.io/projection-operator/charts/projection \
--version 0.3.0 \
--version 0.3.1 \
--namespace projection-system --create-namespace
```

### `kubectl apply`

```bash
kubectl apply -f https://github.com/projection-operator/projection/releases/download/v0.3.0/install.yaml
kubectl apply -f https://github.com/projection-operator/projection/releases/download/v0.3.1/install.yaml
```

Then create your first `Projection`:
Expand Down
8 changes: 2 additions & 6 deletions charts/projection/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ apiVersion: v2
name: projection
description: The Kubernetes CRD for declarative resource mirroring across namespaces.
type: application
version: 0.3.0
appVersion: "0.3.0"
version: 0.3.1
appVersion: "0.3.1"
kubeVersion: ">=1.32.0-0"
home: https://docs.projection.sh
sources:
Expand Down Expand Up @@ -60,8 +60,6 @@ annotations:
namespace: tenant-a
spec:
source:
group: ""
version: v1
kind: ConfigMap
namespace: default
name: app-config
Expand All @@ -71,8 +69,6 @@ annotations:
name: app-config-fanout
spec:
source:
group: ""
version: v1
kind: ConfigMap
namespace: default
name: app-config
Expand Down
2 changes: 1 addition & 1 deletion charts/projection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Override the image for local / air-gapped deployments:
helm install projection charts/projection \
--namespace projection-system --create-namespace \
--set image.repository=my-registry/projection \
--set image.tag=v0.3.0
--set image.tag=v0.3.1
```

## Upgrade
Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ This walks through installing the `projection` operator and creating your first

```bash
helm install projection oci://ghcr.io/projection-operator/charts/projection \
--version 0.3.0 \
--version 0.3.1 \
--namespace projection-system --create-namespace
```

### Option 2 — `kubectl apply`

```bash
kubectl apply -f https://github.com/projection-operator/projection/releases/download/v0.3.0/install.yaml
kubectl apply -f https://github.com/projection-operator/projection/releases/download/v0.3.1/install.yaml
```

Either way, verify the operator is healthy:
Expand Down
4 changes: 2 additions & 2 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,15 +347,15 @@ This is the same design point as the [non-aggregation choice](#why-projection-cl
Release images are pushed to `ghcr.io/projection-operator/projection` and **cosign-signed** with GitHub's OIDC keyless workflow. Verify before pulling:

```bash
cosign verify ghcr.io/projection-operator/projection:v0.3.0 \
cosign verify ghcr.io/projection-operator/projection:v0.3.1 \
--certificate-identity-regexp "https://github.com/projection-operator/projection/.github/workflows/.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
```

The Helm chart is published to `oci://ghcr.io/projection-operator/charts/projection` and signed with the same workflow:

```bash
cosign verify ghcr.io/projection-operator/charts/projection:0.3.0 \
cosign verify ghcr.io/projection-operator/charts/projection:0.3.1 \
--certificate-identity-regexp "https://github.com/projection-operator/projection/.github/workflows/.*" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
```
Expand Down
4 changes: 2 additions & 2 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Things that can cause it:

#### Sub-cause: unpinned version with multiple served versions

The v0.3.0 SourceRef permits omitting `source.version` for any group, including core (`kind: ConfigMap` alone is valid; the operator resolves to `v1` via the RESTMapper). The RESTMapper resolves the omitted version through `RESTMapping(GroupKind)`, which returns the *preferred* version. For core resources the preferred version is always `v1`, so the resolved GVR is stable in practice. The pitfall is on **CRDs with multiple served versions**: if the source CRD has more than one served version and either declares no preferred or its preferred isn't the one that holds your data, you'll see `SourceResolutionFailed` (no mapping for the picked version) or, more confusingly, a successful resolve onto the wrong version with subsequent `SourceFetchFailed` because the data lives on a different served version.
The SourceRef admission permits omitting `source.version` for any group, including core (`kind: ConfigMap` alone is valid; the operator resolves to `v1` via the RESTMapper). The RESTMapper resolves the omitted version through `RESTMapping(GroupKind)`, which returns the *preferred* version. For core resources the preferred version is always `v1`, so the resolved GVR is stable in practice. The pitfall is on **CRDs with multiple served versions**: if the source CRD has more than one served version and either declares no preferred or its preferred isn't the one that holds your data, you'll see `SourceResolutionFailed` (no mapping for the picked version) or, more confusingly, a successful resolve onto the wrong version with subsequent `SourceFetchFailed` because the data lives on a different served version.

Diagnose:

Expand Down Expand Up @@ -159,7 +159,7 @@ If you see `SourceNotResolved`, the real failure is on the `SourceResolved` cond

The apiserver rejected the spec via CEL validation rules on the CRD. The CR either never created (you'll see this as a `kubectl apply` error) or, in the rare case where CEL validation is bypassed, the controller surfaces it as a runtime `DestinationWritten=False reason=InvalidSpec` event. Either way, the cause is one of two structural mistakes:

> Pre-v0.3 SourceRef carried a CEL rule `size(self.group) != 0 || size(self.version) != 0` that rejected `kubectl apply` for any Projection or ClusterProjection with both `source.group` and `source.version` empty. v0.3 drops that rule — `source.version` is now optional for any group, including core. Manifests with explicit `version: v1` continue to validate; `kind: ConfigMap` alone now works too. Old runbooks mentioning an `InvalidSpec` admission error from `source must specify version when group is empty` apply only to pre-v0.3.
> Pre-v0.3.1 SourceRef carried a CEL rule `size(self.group) != 0 || size(self.version) != 0` that rejected `kubectl apply` for any Projection or ClusterProjection with both `source.group` and `source.version` empty. v0.3.1 drops that rule — `source.version` is now optional for any group, including core. Manifests with explicit `version: v1` continue to validate; `kind: ConfigMap` alone now works too. Old runbooks mentioning an `InvalidSpec` admission error from `source must specify version when group is empty` apply only to v0.3.0 and earlier.

#### 1. ClusterProjection.destination with both `namespaces` AND `namespaceSelector` set

Expand Down