Files
cozystack/internal/backupcontroller
Andrey Kolkov 3b4db69f8e feat(backups): record the RDB snapshot location and size for Redis backups (#5046)
## What this PR does

Builds on the shared S3 probe from #5223 (MariaDB and CNPG), which is on
`main`. Refs #4316.

Backups made by the Redis strategy carry no `Backup.status.artifact` at
all: no URI and no size. This covers both the Redis and Valkey apps,
which share the driver. The snapshot is a single object per backup,
`<namespace>/<app>/<backup>.rdb` (`redisObjectKey`). The driver already
records that key in `driverMetadata`. The default strategy uploads the
object using the S3 coordinates from the backup credentials Secret,
which the controller projects into the application namespace
(`endpoint`, `bucketName`, `region` and the access keys).

When a backup completes, the driver now:
1. reads that projected Secret (`CredentialsConfig.TargetSecretName`) in
the application namespace. The projector stores its `endpoint` without a
scheme, and the strategy script reaches it over https; an endpoint with
a path prefix is refused by minio-go, which leaves the artifact unset;
2. reads the TLS trust from the pod spec of the completed backup Job, in
the order the script applies it: `ca.crt`, then `tls.crt`, from the
Secret mounted as the `s3-ca` volume
(`backupStorage.endpointCASecretName`). Only when there is no `s3-ca`
volume does it honour `S3_INSECURE_SKIP_VERIFY=true`, and only when
every container of the pod sets it, since the probe cannot tell which
container uploaded. The Secret is read at probe time; the default
strategy mounts `s3-ca` only when an endpoint CA is configured, so when
the volume yields no CA now (the Secret is gone or lacks both keys, or
the volume is not a Secret), the probe leaves the artifact unset rather
than trust the system roots or skip verification where the Job may have
verified. The CA is trusted on its own, as `curl --cacert` does, not on
top of the system roots;
3. runs `StatObject` on the recorded key; a response without
`Content-Length`, which minio-go reports as size -1, counts as
unmeasured;
4. records `status.artifact.uri = s3://<bucket>/<key>` and
`status.artifact.sizeBytes` = the object's size. An empty object is
recorded too, but `sizeBytes` is `omitempty`, so on the Backup it reads
like an unmeasured size; since a valid RDB snapshot always carries a
header, a Warning `EmptySnapshot` Event on the BackupJob flags it as a
broken backup.

The artifact is set **only when the object is found**. A custom strategy
that writes elsewhere would otherwise get a URI pointing at an object
that does not exist. On any failure (Secret missing or incomplete,
endpoint invalid, CA unreadable, object not found, TLS error) the
artifact stays absent and the backup still succeeds. The full error is
logged at info, and once the Backup is created a Normal
`ArtifactNotMeasured` Event on the BackupJob gives a fixed reason, also
when a failed existence check skipped the probe; the Event lives in the
application namespace, so it never carries the raw error, which may name
the platform's endpoint and bucket. The probe shares the 10s timeout of
the MariaDB and CNPG probes. The retry check is shared with those
drivers too: a `Backup` that already exists is returned without probing,
a failed existence check creates the Backup without probing, a `Create`
collision is resolved by reading past the cache, and an existing Backup
that cannot be read back is retried with `Ready=False` instead of
failing the BackupJob.

The size is useful because an RDB snapshot is the compressed dataset. It
shows growth, and a snapshot that is suddenly tiny is a broken backup.

Kafka is deliberately left out. Its backup is a topic-metadata export of
a few kilobytes, so the size would say nothing about the data and might
suggest the data itself was backed up.

Tests:
- the stat uses the projected credentials;
- every path that leaves the artifact unset: object missing, Secret
missing, bucket, endpoint, access key or secret key missing, no target
Secret configured, a size the endpoint did not report;
- trust resolution: `ca.crt` wins over `tls.crt` and skip-verify;
`items` remapping of the volume, and a key the `items` do not project is
never trusted; the Redis CA volume mounted next to `s3-ca` is not taken
as the S3 CA; an `s3-ca` volume that yields no CA (Secret gone, Secret
without the keys, a ConfigMap source) is refused with and without
skip-verify; skip-verify applies only on an exact `true` of that
variable in every container, the last entry of a repeated variable
winning;
- the real minio client against a TLS stub: its certificate is trusted
only through the `s3-ca` volume or skip-verify, a CA that did not sign
it is refused even with skip-verify set on the Job, and the shared
client enforces a configured CA with and without the system roots; the
client sends a signed `HEAD` and parses `Content-Length`;
- the Events: an unmeasured artifact or a failed existence check raises
`ArtifactNotMeasured` without the raw error; an empty snapshot is
recorded and raises a Warning `EmptySnapshot`; a measured artifact, a
rejected `Create` and a `Create` collision raise none;
- a reconcile test pinning that the completed Job's pod spec reaches the
probe; a Backup that already exists is returned without a probe or a
`Create`; the `Create` collision is read back from the API server past
the cache.

The suite fails if any guard of the trust lookup (volume name, Secret
source, variable name, every-container rule, refusal of an unreadable
CA), the CA enforcement in the transport, the negative-size check, the
Events, the reason-only message, their place after `Create`, the
empty-snapshot warning, the existing-Backup check, the wiring, any term
of the Secret check, or the API-server read-back is removed.

### Screenshots

N/A — no UI changes.

### Downstream repositories

I walked the trigger map against the diff. The change is Go-only in
`internal/backupcontroller`, with no API, CRD, chart, value or schema
change. Existing `Backup` fields are now filled for one more driver.

- [x] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

```release-note
feat(backups): Redis and Valkey backups now record `status.artifact` with the `s3://` location and the size of the RDB snapshot. If the snapshot cannot be found with the platform backup credentials, the artifact stays unset and a Normal `ArtifactNotMeasured` Event on the BackupJob says why. An empty snapshot raises a Warning `EmptySnapshot` Event.
```



<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

<!-- coderabbit-ai-generated:v1 -->

## Summary by CodeRabbit

* **New Features**
* Redis backups now include artifact location and size when they can be
measured.
* Backups are still created when artifact measurement fails, with status
reporting that the artifact was not measured.
* **Bug Fixes**
* Improved handling of existing backups and temporary read-back failures
to support reliable reconciliation.
* Improved compatibility with S3 endpoints using custom certificate
authorities or configured TLS verification settings.
  * Zero-size snapshots are now reported with a warning.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-11-11 15:52:54 +04:00
..