A transient proxy.golang.org HTTP/2 stream reset fails an image build
outright: Go's default GOPROXY falls back to direct only on a 404 or
410. Image builds now get GOPROXY=https://proxy.golang.org|direct as a
build arg from hack/common-envs.mk, declared by every Go builder stage,
so any proxy error falls back to fetching from the origin. The value
comes from IMAGE_GOPROXY rather than the host's GOPROXY, which may
name a mirror that buildkit cannot reach. The alpine golang images
carry no git, so in their stages a module hosted on GitHub cannot be
fetched directly and the retry is the cover.
Explicit `go mod download` steps retry three times with a growing
pause. The loop is inline, in one form the contract test pins: the
images build from different contexts, so no shared script can be
copied in, and a named build context in the shared buildx arguments
makes buildx refuse every Dockerfile pinned to a frontend older than
dockerfile:1.4.
The two alpine stages that left the download to `go build` get the
same download step, because without git neither the fallback nor a
retry covered them.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Upstream publishes the Harbor v2.15 component images for amd64 only,
so every Harbor pod crash-loops with "exec format error" on an arm64
node. The multi-arch build upstream exists on main but is in no
release yet, so the package now rebuilds the pinned v2.15.1 release
itself: the Go components cross-compile on the build platform, the
registry and the Trivy adapter build from the tags upstream's Makefile
pins, the Trivy binary is fetched per architecture against its release
checksum, and the runtime images keep upstream's photon base, users
and entrypoints. The build fails when those registry and Trivy pins,
the swagger generator or the golang and node build images no longer
match upstream's Makefile at the pinned release, so a version bump
cannot pair a new core with an old registry, scanner or toolchain.
The portal compiles to static JS, so it lives in its own Dockerfile
listed as architecture-neutral in the cross-compile check, and takes
the release pin from the main one so both files build the same
commit.
Because values.yaml now pins the images, `make update` pins the chart
release that ships the same Harbor version and fails when its
appVersion differs, instead of pulling the newest chart onto old
images. Renovate stops proposing tag bumps for the rebuild's build
bases, which follow upstream's toolchain for that release.
The committed refs stay on the upstream digests until CI stamps the
rebuilt ones.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The redis, valkey and harbor Redis failovers pin
oliver006/redis_exporter:v1.55.0-alpine, which upstream publishes for
amd64 only. On an arm64 node the exporter sidecar exits with an exec
format error, so the Redis pod never becomes fully ready.
The unsuffixed v1.55.0 tag is a multi-arch index (amd64, arm, arm64)
of the same exporter release, with the same entrypoint, user and
port, and none of these charts run a shell in that container. Pin it
by digest, so the reference is reproducible.
Assisted-by: LLM
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The freshworks-oss operator's compiled-in default image moved from
redis:6.2.6-alpine to 7.2.4; Harbor pinned no image at all and apps/redis left
its sentinels unpinned, so an upgrade would move them silently. 7.2.4 rewrites
dump.rdb into a format 6.2.6 refuses to load, so a platform rollback would leave
Harbor's Redis crash-looping on its own data. Pin redis+sentinel (Harbor) and
the sentinel (apps/redis) at the version they run today; bump deliberately and
separately.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
Harbor provisions its own RedisFailover; the freshworks-oss operator
filters by the redis-failover.freshworks.com/operator-group label, so without
it Harbor's Redis is never created and harbor-core CrashLoops. Stamp the same
group (cozystack) as the operator and the redis app.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
The inline RedisFailover ran its three sentinels at 50m CPU request and
a 100m limit. Sentinels only become Ready after the redis-operator
elects a master and rewrites their monitored-master config, and on a
CPU-contended node CFS shares cycles proportionally to the request, so
underprovisioned sentinels converge slowly and delay harbor readiness
behind them. Raise the sentinels to Guaranteed QoS (requests equal
limits, 250m CPU / 128Mi) so election is never starved or throttled
under load. The redis data container is unchanged.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Bump the vendored Harbor Helm chart from 1.18.2 to 1.19.1, which
upgrades Harbor itself from appVersion 2.14.2 to 2.15.1.
The bump adds PodDisruptionBudget templates for the core, exporter,
jobservice, nginx, portal, registry and trivy components, improving
availability during voluntary disruptions (node drains/upgrades).
Signed-off-by: Алексей Артамонов <alexeyartamonov1987@gmail.com>
`hack/helm-unit-tests.sh` auto-discovers any package whose Makefile has
a `test` target. `packages/system/harbor` had none, so the conditional
rendering of `bucket-secret.yaml` (the path this PR actively reshapes)
was un-asserted in CI and the bucket-secret guard would not be pinned
against future refactors.
Add the canonical `test: helm unittest .` target, matching every other
`packages/system/*` package that ships a `tests/` directory
(linstor-scheduler, linstor-gui, cozystack-scheduler, nfs-driver).
Add `tests/bucket_secret_test.yaml` with seven cases covering: spec
absent, spec empty, secretS3 absent, secretS3 empty, secretS3 partial
(only one inner field), bucketName missing, and fully populated. The
first six assert no Secret is rendered; the last asserts the four
expected `stringData` entries plus the static region. 7/7 pass locally
(`bash hack/helm-unit-tests.sh`).
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The COSI BucketAccess controller is expected to populate `BucketInfo`
atomically, but the prior guard (`with .Values.spec` / `with .secretS3`)
only short-circuits on missing or empty inputs. A partially populated
`secretS3` would still emit the registry-s3 Secret with empty
`accessSecretKey`/`endpoint` values, and a missing `bucketName` was
not guarded at all — silently breaking Harbor's S3 storage backend.
Tighten the inner guard to require all four required fields
(accessKeyID, accessSecretKey, endpoint, bucketName) before the Secret
is emitted, so partial input falls into the no-render path alongside
unset/empty. The body now uses dot-access on `.secretS3` for
consistency with the new gate. Behaviour for fully populated input is
unchanged.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The previous fix (a21c18f9) sourced BucketInfo via Flux valuesFrom with
`targetPath: bucket.bucketInfo`. Flux runs values with `targetPath`
through Helm's `strvals.ParseInto`, which splits the value on commas as
list separators. The COSI BucketInfo JSON is comma-rich, so values
resolution bailed:
could not resolve Secret chart values reference
'tenant-X/harbor-X-registry-bucket' with key 'BucketInfo':
key "\"spec\":{\"bucketName\":\"bucket-1785...\""
has no value (cannot end with ,)
Drop `targetPath`. With only `valuesKey: BucketInfo`, helm-controller
unmarshals the value as YAML and merges at the chart's values root, so
JSON commas stay nested instead of being split. The system chart's
bucket-secret.yaml now reads `.Values.spec.bucketName` /
`.Values.spec.secretS3.{accessKeyID,accessSecretKey,endpoint}`,
guarded by nested `with` blocks so the registry-s3 Secret renders only
when secretS3 has been populated.
Gating semantics from the previous fix are preserved: with the default
`optional: false`, helm-controller still refuses to compose values
until the COSI BucketAccess controller writes `BucketInfo` into the
Secret, and the value's content still drives the HR config-digest.
Verified via `helm template` against the system chart with three
scenarios: missing `spec` and `spec` without `secretS3` render
nothing; full BucketInfo renders all five S3 env keys correctly.
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
Two bugs caused the harbor E2E to fail (CI #25049445125):
1. cozy-harbor's bucket-secret.yaml called `index $existingSecret.data
"BucketInfo"` unconditionally. During first-time install the COSI
BucketAccess controller may create the credentials Secret as a
placeholder before populating it, so `.data` is nil and the chart
render crashes with `index of untyped nil`.
2. The downstream `<release>-system` HelmRelease starts reconciling
immediately, in parallel with bucket provisioning, hitting bug #1
on every retry within the test's 5-minute window.
The previous shape relied on `lookup` to read the Secret at render
time. helm-controller's upgrade trigger is digest-based over composed
values + chart artifact, so a `lookup` returning new data on a later
reconcile is not enough on its own to force an upgrade — the rendered
output may diverge but the digest does not.
Switch to a values-driven shape:
- `bucket-secret.yaml` now reads `.Values.bucket.bucketInfo` (a JSON
string) and uses `dig` for safe access; if the value is empty or the
expected nested fields are missing, no `*-registry-s3` Secret is
rendered.
- The `<release>-system` HR sources `BucketInfo` via `valuesFrom` with
`targetPath: bucket.bucketInfo`. With the default `optional: false`,
helm-controller will refuse to compose values until the key exists,
which both gates initial reconciliation on the BucketAccess Secret
being populated and forces a config-digest change (and thus a helm
upgrade) when its contents change.
- `bucket.secretName` is removed from the system chart's values and
from the apps chart's `values:` block; the only consumer (the
`lookup` call) is gone.
Verified four scenarios via `helm template` against the system chart:
unset bucketInfo, empty string, empty JSON object `{}`, and a
fully-populated BucketInfo — only the last renders the registry-s3
Secret; the others render nothing without erroring.
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Add explicit PostgreSQL 17.7 image to Harbor, SeaweedFS, and Keycloak
databases to ensure consistent version across all system components.
Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
Add encoding, localeCollate, and localeCType to initdb bootstrap
configuration to ensure fulltext search (ilike) works correctly.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Specify initdb bootstrap with database and owner names explicitly
instead of relying on CNPG defaults which may change between versions.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Replace PVC-based registry storage with S3 via COSI BucketClaim/BucketAccess.
The system chart parses BucketInfo secret and creates a registry-s3 Secret
with REGISTRY_STORAGE_S3_* env vars that override Harbor's ConfigMap values.
- Add bucket-secret.yaml to system chart (BucketInfo parser)
- Remove storageType/size from registry config (S3 is now the only option)
- Use Harbor's existingSecret support for S3 credentials injection
- Add objectstorage-controller to PackageSource dependencies
- Update E2E test with COSI bucket provisioning waits and diagnostics
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The vendored Harbor chart does an unsafe `lookup` of the Redis auth
Secret at template rendering time to extract the password. On first
install, the Secret doesn't exist yet (created by the same chart),
causing a nil pointer error. Failed installs are rolled back, deleting
the Secret, so retries also fail — creating an infinite failure loop.
Fix by generating the Redis password in the wrapper chart (same
pattern as admin password), storing it in the credentials Secret,
and injecting it via HelmRelease valuesFrom with targetPath. This
bypasses the vendored chart's lookup entirely — it uses the password
value directly instead of looking up the Secret.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Replace Harbor's internal PostgreSQL with CloudNativePG operator and
internal Redis with redis-operator (RedisFailover), following established
Cozystack patterns from seaweedfs and redis apps.
Additional fixes from code review:
- Fix registry resources nesting level (registry.registry/controller)
- Persist token CA across upgrades to prevent JWT invalidation
- Update values schema and ApplicationDefinition
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Add Harbor v2.14.2 as a tenant-level managed service with per-component
resource configuration, ingress with TLS termination, and internal
PostgreSQL/Redis.
Includes:
- extra/harbor wrapper chart with HelmRelease, WorkloadMonitors, Ingress
- system/harbor with vendored upstream chart (helm.goharbor.io v1.18.2)
- harbor-rd ApplicationDefinition for dynamic CRD registration
- PackageSource and system.yaml bundle entry
- E2E test with Secret and Service verification
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>