Skip to content

Commit d719243

Browse files
authored
feat: initial Kilo cluster-mesh operator POC (#2)
* fix(generate): align manifests target with repo layout Restrict CRD generation to ./api/... so manifests no longer produces config/crd/bases/kilo.squat.ai_peers.yaml for the external Kilo Peer type. Regenerate deepcopy files so the boilerplate header from hack/boilerplate.go.txt is committed, matching what controller-gen produces in CI. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(lint): exclude goconst from test files Test files contain CIDRs, namespace names and similar fixtures repeated across cases; promoting them to constants only obscures intent. Add goconst to the linter exclusion list for _test\.go. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(chart): drop unsupported --namespace and add --metrics-secure flag The operator binary does not define a --namespace flag; passing it caused the manager to exit at startup. Remove the argument from the Deployment and the corresponding unit test. The chart wired --metrics-bind-address=:8080 while the binary defaults metrics-secure to true, which started HTTPS on an HTTP port and made metrics unscrapeable without a TLS setup. Expose metricsSecure in values.yaml (default false) and pass --metrics-secure explicitly. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(ci): pass --verify=false to helm plugin install Helm 4.1 requires plugin source verification by default, which the helm-unittest source does not support. Without the flag the helm CI job aborts with "plugin source does not support verification" before helm lint and unit tests can run. Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(ci): publish container image to ghcr.io on push to main Add an image job that builds the multi-arch Containerfile and pushes to ghcr.io/cozystack/kilo-clustermesh-operator with :main and :sha-<commit> tags. The job runs only on push to main and waits for all checks (lint, test, integration, build, helm, generate) so a broken commit cannot publish an image. Tagged releases are still handled by .github/workflows/release.yml. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(lint): extract Kilo group name to a constant golangci-lint v2.12 flagged "kilo.squat.ai" repeated across register.go and types_test.go via goconst. Define a GroupName constant in the Kilo v1alpha1 package and use it from both call sites; this also matches the convention used by upstream Kubernetes API packages. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(lint): extract Kilo group version to a constant golangci-lint v2.12 picked up another goconst occurrence: the literal "v1alpha1" repeated across register.go and types_test.go. Define a GroupVersion constant alongside GroupName and use it from both call sites. Signed-off-by: Arsolitt <arsolitt@gmail.com> * ci: run CI and publish image on the dev branch Add dev to the push/pull_request trigger lists and to the image job's allow-list. Switch the image tag source from a fixed raw "main" to type=ref,event=branch so pushes to dev publish :dev and pushes to main publish :main. VERSION build-arg follows the ref name. Signed-off-by: Arsolitt <arsolitt@gmail.com> * perf(container): build Go binary on the native runner platform Add --platform=\$BUILDPLATFORM to the builder stage so the Go toolchain runs natively on the GitHub runner (amd64) while cross-compiling for TARGETARCH through GOOS/GOARCH. QEMU emulation is now only used for the final distroless stage, which is just a file copy. ARM64 image builds drop from QEMU-emulated compilation to native compile + cross-link. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(operator): wire startup bootstrap and migrate to events API The previous cmd/main.go constructed the manager with an unset Registry, Log and Recorder on ClusterMeshReconciler, which would have caused a nil pointer panic on the first reconcile. CRD self-install, despite being available in internal/crd, was never invoked, so the manager also failed to start with "no matches for kind ClusterMesh". Startup now: - runs crd.InstallOrUpdate before manager creation - reads its namespace from POD_NAMESPACE (downward API) - lists every ClusterMesh in that namespace, merges cluster entries by name and builds a multicluster.ClusterRegistry up-front - registers each cluster.Cluster with the manager so caches start alongside the leader - wires the reconciler with the real Registry, slog logger and the manager's event recorder - installs a change-watcher that cancels the manager context when cluster fingerprints drift, triggering a kubelet-driven restart The reconciler's Recorder field and the integration test suite are moved off the deprecated record.EventRecorder API onto the events package, fixing the staticcheck SA1019 warning. go mod tidy removes seven now-unused indirect dependencies. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(chart): allow SA token automount, inject POD_NAMESPACE and set seccomp RuntimeDefault Three blocking issues for running the operator on a cluster with PodSecurity restricted enforced: - ServiceAccount.automountServiceAccountToken: false stopped the controller from authenticating to the in-cluster API. Removed; the operator needs the projected token. - The container template did not request POD_NAMESPACE, which the new startup bootstrap reads to scope its ClusterMesh and Secret lookups. Injected via downward API. - securityContext.seccompProfile was unset, triggering a PodSecurity warning under restricted:latest. Set to RuntimeDefault. helm-unittest suites are updated accordingly. All 41 tests pass. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(operator): scope manager cache to operator namespace The controller-runtime manager cache defaulted to cluster-scope list/watch on ClusterMesh and Secret, which required cluster-wide RBAC the chart deliberately does not grant. The operator only watches namespaced resources in its own namespace; cluster-scoped resources (Peers, Nodes, CRDs, Leases) are accessed via the multicluster registry or direct API calls and are unaffected. Restrict Cache.DefaultNamespaces to the operator's own namespace so the existing namespace-scoped Role is sufficient. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(operator): accept cozystack-Kilo wireguard-ip annotation format Upstream Kilo writes the kilo.squat.ai/wireguard-ip annotation as a host route ("10.4.0.1/32"); the cozystack-patched Kilo with cross granularity writes the host IP with the wireguard subnet mask ("100.66.0.3/16"). The operator previously required /32 (or /128) and reused the raw annotation value in the Peer AllowedIPs — both of which fail under cozystack-Kilo: validation skips every node, and even if it did not, every peer would claim the entire wireguard subnet. Add a netutil helper that parses an annotation preserving host bits, and another that emits the host route for a given IP. Validation now checks only that the host IP falls inside the cluster's wireguardCIDR. The peer builder normalises AllowedIPs to a /32 (resp. /128) host route so each peer terminates traffic for exactly one WireGuard IP. Covered by unit tests for both upstream-style and cozystack-style annotations. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(chart): grant operator RBAC on events.k8s.io events The migration to k8s.io/client-go/tools/events.EventRecorder caused events to be written to the events.k8s.io/v1 API group instead of the legacy core "events" resource. The Role only granted access to the latter, so every recorded event was rejected by the apiserver with events.events.k8s.io is forbidden. Grant create/patch on events.k8s.io/events alongside the existing rule for the core group; keep both so the operator stays compatible if any component falls back to the legacy API. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(readme): document force-endpoint annotation, manual reconcile and roadmap The operator only sets a Peer endpoint from kilo.squat.ai/force-endpoint; clusters where nodes are not publicly reachable on that annotation will end up with peers that have no endpoint and silently drop traffic. The manager cache also does not watch Node objects, so annotation changes require a no-op write on the ClusterMesh resource to trigger a reconcile. Capture both of these in the README so users hit fewer surprises. Also refresh the surrounding documentation: - tighten Prerequisites to distinguish apiserver reachability from per-node UDP reachability; - replace the broken Remote Cluster Setup snippet with the working Secret-backed long-lived token procedure that survives 1.24+; - correct the Architecture section, which previously claimed the controller watches Node objects; - add a Possible Improvements list covering a dedicated cross-cluster endpoint annotation, a Node watcher, and an explicit per-node skip annotation. Stop tracking local cluster-specific deployment manifests under deploy/ by adding it to .gitignore; the upstream README now contains the generic procedure, so the per-cluster files belong in a private branch. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(kilonode): document both accepted wireguard-ip annotation formats Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(restart): guard against nil Cancel in ChangeWatcher.Reconcile Signed-off-by: Arsolitt <arsolitt@gmail.com> * test(main): add unit tests for mergeClusterSpecs deduplication Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(containerfile): correct image.source label to cozystack repo Signed-off-by: Arsolitt <arsolitt@gmail.com> * test(peer): cover IPv6 and DNS endpoint parsing Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(main): declare version/revision build vars and log at startup Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(chart): default image.repository to cozystack fork The previous default pointed at the upstream squat image, causing helm install without overrides to pull the wrong image. Switch the default to the cozystack fork, add a helm-unittest assertion that verifies the default, and remove the now-redundant repository override from the README tag-pinning example. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(validation): normalize wireguard-ip before duplicate detection Two nodes with "10.4.0.1/16" and "10.4.0.1/32" resolve to the same WireGuard peer (AllowedIPs = 10.4.0.1/32) and therefore conflict. The old comparison was a raw-string equality check, so it missed this case. Extract the host IP via netutil.ParseHostInCIDR and use that as the dedup key. Invalid annotations fall back to raw-string keying so that identical-invalid values still deduplicate without colliding with any valid IP. Add three new test cases that cover: same host IP / different prefix lengths, different host IPs (sanity), and invalid vs valid annotation. Signed-off-by: Arsolitt <arsolitt@gmail.com> * ci(workflows): include cmd package in unit test job TestMergeClusterSpecs in cmd/main_test.go was never executed in CI because the test step listed explicit package paths that omitted ./cmd/.... Add ./cmd/... to the go test invocation so all unit tests run on every push. Add internal/citest/workflow_test.go to structurally assert the presence of ./cmd/... in the ci.yml test step, preventing future accidental regressions. Signed-off-by: Arsolitt <arsolitt@gmail.com> * fix(peer): strip brackets from DNS fallback in endpoint parser buildDNSOrIP strips brackets from the host before calling net.ParseIP, but the DNS fallback path was returning the unstripped host variable. A bracketed DNS name like [dns.example.com]:51820 would produce DNS: "[dns.example.com]", which is an invalid hostname. Change DNS: host to DNS: cleanHost so the brackets-stripped form is always used for the DNS field. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(api): correct WireguardCIDR comment about accepted annotation formats The previous comment stated that wireguard-ip annotations must be a /32 (or /128) host route. This was accurate before the IsHostRoute validation check was dropped, but has been incorrect since that change. The operator now accepts any prefix length and validates only the host portion of the address against WireguardCIDR. Update the Go doc comment and the generated CRD YAML description to match actual behavior. Also annotate the existing regression test case to document that it guards against inadvertent reintroduction of the /32-only requirement. Signed-off-by: Arsolitt <arsolitt@gmail.com> * test(validation): cover ValidateClusterNetworks and ValidateMeshNetworks Add table-driven unit tests for both public functions in internal/validation/mesh.go, which previously had 0% coverage in CI (integration tests were excluded from the unit test job). TestValidateClusterNetworks covers: - empty cluster list (nil error) - single cluster, multiple disjoint CIDRs (nil error) - single cluster with serviceCIDR overlapping additionalCIDR (error) - two clusters with disjoint CIDRs (nil error) - two clusters with overlapping serviceCIDR (error naming both clusters) - two clusters with overlapping podCIDR (error naming both clusters) - invalid CIDR string triggering a parse error TestValidateMeshNetworks covers: - empty mesh list (nil error) - single mesh with valid clusters (nil error) - two meshes with disjoint network plans (nil error) - two meshes with an overlapping CIDR (error naming both meshes) - intra-mesh overlap detected before cross-mesh check Helpers makeCluster and makeMesh follow the makeNode convention from node_test.go. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(readme): use non-overlapping CIDRs in Quick Start example The original example set identical serviceCIDR (10.96.0.0/12) on both clusters, and both wireguardCIDRs (10.100.x.0/24) fell inside that /12 range — all three overlaps would have caused ValidateClusterNetworks to return an error and suppress all Peer creation. Fix by assigning distinct, non-overlapping ranges to every field: - cluster-a wireguardCIDR: 10.200.0.0/24 - cluster-b wireguardCIDR: 10.200.1.0/24 - cluster-b serviceCIDR: 10.112.0.0/12 Add TestREADMEQuickStartManifestIsValid in internal/validation/mesh_test.go as a regression guard: it reads README.md, extracts the ClusterMesh YAML block, unmarshals it, and asserts ValidateClusterNetworks returns nil. The test fails with the original CIDRs and passes after this fix. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(peer): clarify allowedIPs derivation in BuildPeer comment The previous comment said "wireguard-ip annotation" without explaining that only the host IP is extracted and then normalised to a /32 (IPv4) or /128 (IPv6) host route. The updated comment makes the normalisation step explicit, which is important context given that the annotation may carry a wider subnet mask in cozystack-patched Kilo. Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(api): add wireguardPort field to ClusterEntry Introduces an optional WireguardPort field on ClusterEntry, defaulting to 51820. The operator uses this port when synthesising a peer endpoint from Node.Status.Addresses (i.e. neither clustermesh-endpoint nor force-endpoint annotation is set on the node). Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(annotations): add clustermesh-endpoint node annotation Adds AnnotationClustermeshEndpoint constant for an operator-specific node annotation that conveys the cross-cluster WireGuard endpoint independently of Kilo's own kilo.squat.ai/force-endpoint. Decoupling the two prevents the operator's endpoint configuration from affecting intra-cluster Kilo behaviour (notably the "cross" mesh granularity). Signed-off-by: Arsolitt <arsolitt@gmail.com> * test(kilonode): add tests for endpoint resolution chain (Red) 12 tests covering ResolveEndpoint priority order, ExternalIP fallback (IPv4/IPv6 preference, default port), strict error on malformed annotations, and ignoring non-ExternalIP address types. Tests intentionally fail to compile until ResolveEndpoint is implemented. Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(kilonode): resolve node endpoint via fallback chain Implements ResolveEndpoint which determines a node's WireGuard endpoint via a three-tier priority chain: the operator-specific kilo.squat.ai/clustermesh-endpoint annotation wins; otherwise the legacy kilo.squat.ai/force-endpoint annotation; otherwise the first ExternalIP in Node.Status.Addresses (IPv4 preferred over IPv6) combined with the fallback port. A present-but-malformed annotation is a hard error rather than a silent fall-through, so misconfiguration surfaces immediately instead of yielding peers without an endpoint. Signed-off-by: Arsolitt <arsolitt@gmail.com> * refactor(peer): pass ClusterEntry through Build{Peer,AnchorPeer} Replaces the (meshName, sourceCluster string) and (meshName, sourceCluster, entry) signatures with a unified (meshName string, entry *ClusterEntry) shape. The entry carries both the cluster name and the new WireguardPort field, removing the need to plumb individual fields through call sites and preparing the builders to consume kilonode.ResolveEndpoint in the next step. Existing behaviour is preserved; tests pass without modification beyond the signature change. Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(peer): wire endpoint resolution chain into peer builders BuildPeer and BuildAnchorPeer now consume kilonode.ResolveEndpoint instead of reading kilo.squat.ai/force-endpoint directly. The peer endpoint is therefore picked up from the clustermesh-endpoint annotation, the legacy force-endpoint annotation, or Node.Status ExternalIPs in that priority order. Two behaviour changes flow from this: - A node with no endpoint source no longer produces a Peer without an endpoint silently — BuildPeer now returns an error and BuildAnchorPeer returns nil. Validation should already filter such nodes; the new behaviour is a defensive surface for missed cases. - A present-but-malformed endpoint annotation (clustermesh-endpoint or force-endpoint) is a hard error, not a silent fall-through. The affected unit test was rewritten accordingly. baseAnnotations() in builder_test.go now includes a valid force-endpoint so existing tests succeed by default; tests that exercise specific chain layers override or delete the relevant key. Signed-off-by: Arsolitt <arsolitt@gmail.com> * test(validation): add endpoint resolution skip cases (Red) Adds four ValidateNode test cases that the current implementation does not satisfy yet: a node missing every endpoint source must be skipped with ReasonNoEndpoint, a malformed clustermesh-endpoint or force-endpoint annotation must be skipped with ReasonEndpointInvalid, and a node whose only endpoint source is an ExternalIP must be accepted. The pre-existing baseAnnotations() now includes a valid force-endpoint so that all current "valid node" cases continue to pass once endpoint validation lands. Tests fail at compile time because ReasonNoEndpoint and ReasonEndpointInvalid do not exist yet; the Green commit will add them alongside the validateEndpoint helper. Signed-off-by: Arsolitt <arsolitt@gmail.com> * feat(validation): require resolvable endpoint via kilonode.ResolveEndpoint ValidateNode now exercises the endpoint fallback chain after the WireGuard IP and public key checks. A node with no source is skipped with ReasonNoEndpoint; a node whose clustermesh-endpoint or force-endpoint annotation is present but malformed is skipped with ReasonEndpointInvalid. Filtering out such nodes here prevents BuildPeer from later failing on a per-node basis and surfaces the misconfiguration as a clear SkippedNodes entry in ClusterMesh status. Signed-off-by: Arsolitt <arsolitt@gmail.com> * chore(crd): regenerate manifests after wireguardPort addition Regenerates the ClusterMesh CRD via controller-gen to expose the new wireguardPort field with default 51820 and bounds [1, 65535], and mirrors the result into the embedded copy at internal/crd/ which the operator applies on startup. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(readme): document endpoint resolution chain Updates the per-node configuration and reconciliation-flow sections to describe the new three-tier endpoint chain (kilo.squat.ai/clustermesh-endpoint → kilo.squat.ai/force-endpoint → first ExternalIP), the strict treatment of malformed annotations (NodeEndpointInvalid skip reason), and the per-cluster wireguardPort field used by the ExternalIP fallback. Removes the now-implemented "dedicated cross-cluster endpoint annotation" item from the roadmap and updates the prerequisites note about the WireGuard UDP port to reflect that it is configurable. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs: rewrite README and add docs/ tree Restructure README as a landing page (overview, requirements, quick start) and split deep content into a flat English-only docs/ tree: - docs/architecture.md — components, reconciliation flow, anchor peer, manager cache scoping, CRD bootstrap, restart watcher - docs/installation.md — Helm install, embedded CRD bootstrap, remote-cluster kubeconfig setup, RBAC, verification, uninstall - docs/configuration.md — ClusterMesh CRD reference: Spec, Status, ClusterEntry fields, conditions, CIDR validation rules - docs/per-node-setup.md — required Node annotations, three-tier endpoint resolution chain, strict-invalid semantics, migration from force-endpoint - docs/troubleshooting.md — full NodeSkipReason table, mesh-level validation errors, status conditions, common pitfalls Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs: add known-gaps handoff document Add docs/known-gaps.md tracking outstanding work, divergences from the upstream proposal (cozystack/community#7), and settled design decisions that should not be re-litigated. Link from README documentation table and project-status note. Captures one blocker gap (no Node watches), one operational risk (silent anchor-peer suppression), and three proposal text corrections (annotation name, prefix rule, flat-vs-typed CRD shape). Signed-off-by: Arsolitt <arsolitt@gmail.com> * test(integration): provide resolvable endpoint for nodes The endpoint resolution chain introduced in f8e0ab2 now rejects nodes that have neither a clustermesh-endpoint, a force-endpoint, nor an ExternalIP. The existing fixtures created nodes with no endpoint source at all, so ValidateNode skipped them with NodeNoEndpoint and the integration tests that asserted on resulting peer counts started failing in CI. Attach a default ExternalIP to every node built by makeNode so that the ExternalIP fallback succeeds, and persist Status.Addresses via the status subresource (Create does not). The TestHappyPath endpoint assertion is tightened to target the peer for the node that carries the explicit force-endpoint, since other peers now legitimately resolve via the ExternalIP fallback. Signed-off-by: Arsolitt <arsolitt@gmail.com> * ci(workflows): disable checkout credential persistence Add persist-credentials: false to every actions/checkout step so the GITHUB_TOKEN is not retained in the workspace after the action exits. This is defense-in-depth: jobs that need authenticated access (image publish) authenticate explicitly via docker/login-action, and the remaining jobs are read-only consumers of the checked-out tree. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(installation): set yaml language on RBAC fenced block Address markdownlint MD040 on the ClusterRole snippet. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(installation): strip prompt prefixes from no-output examples Remove the leading "$ " from single-command shell examples that show no command output. Addresses markdownlint MD014. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(per-node): set text language on plain fenced blocks Add text language identifier to the four plain code fences (priority list, host:port template, IPv6 example, bracketed DNS example). Addresses markdownlint MD040. Signed-off-by: Arsolitt <arsolitt@gmail.com> * docs(readme): merge adjacent blockquote callouts Replace the blank separator between the CIDR-overlap warning and the CRD self-install note with a quoted blank line so both callouts live inside one continuous blockquote. Addresses markdownlint MD028. Signed-off-by: Arsolitt <arsolitt@gmail.com> --------- Signed-off-by: Arsolitt <arsolitt@gmail.com>
1 parent f0e9b03 commit d719243

52 files changed

Lines changed: 3987 additions & 411 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ name: CI
22

33
on:
44
push:
5-
branches: [main]
5+
branches: [main, dev]
66
pull_request:
7-
branches: [main]
7+
branches: [main, dev]
88

99
jobs:
1010
lint:
1111
runs-on: ubuntu-latest
1212
steps:
1313
- uses: actions/checkout@v4
14+
with:
15+
persist-credentials: false
1416
- uses: actions/setup-go@v5
1517
with:
1618
go-version: "1.26.3"
@@ -22,10 +24,12 @@ jobs:
2224
runs-on: ubuntu-latest
2325
steps:
2426
- uses: actions/checkout@v4
27+
with:
28+
persist-credentials: false
2529
- uses: actions/setup-go@v5
2630
with:
2731
go-version: "1.26.3"
28-
- run: go test ./api/... ./pkg/... ./internal/... -race -coverprofile=coverage.out
32+
- run: go test ./api/... ./cmd/... ./pkg/... ./internal/... -race -coverprofile=coverage.out
2933
- uses: actions/upload-artifact@v4
3034
with:
3135
name: coverage
@@ -35,6 +39,8 @@ jobs:
3539
runs-on: ubuntu-latest
3640
steps:
3741
- uses: actions/checkout@v4
42+
with:
43+
persist-credentials: false
3844
- uses: actions/setup-go@v5
3945
with:
4046
go-version: "1.26.3"
@@ -47,6 +53,8 @@ jobs:
4753
runs-on: ubuntu-latest
4854
steps:
4955
- uses: actions/checkout@v4
56+
with:
57+
persist-credentials: false
5058
- uses: actions/setup-go@v5
5159
with:
5260
go-version: "1.26.3"
@@ -56,17 +64,59 @@ jobs:
5664
runs-on: ubuntu-latest
5765
steps:
5866
- uses: actions/checkout@v4
67+
with:
68+
persist-credentials: false
5969
- uses: azure/setup-helm@v4
60-
- run: helm plugin install https://github.com/helm-unittest/helm-unittest
70+
- run: helm plugin install --verify=false https://github.com/helm-unittest/helm-unittest
6171
- run: helm lint charts/kilo-clustermesh-operator --strict
6272
- run: helm unittest charts/kilo-clustermesh-operator
6373

6474
generate:
6575
runs-on: ubuntu-latest
6676
steps:
6777
- uses: actions/checkout@v4
78+
with:
79+
persist-credentials: false
6880
- uses: actions/setup-go@v5
6981
with:
7082
go-version: "1.26.3"
7183
- run: make manifests generate
7284
- run: git diff --exit-code
85+
86+
image:
87+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev')
88+
needs: [lint, test, build, helm, generate, integration]
89+
runs-on: ubuntu-latest
90+
permissions:
91+
contents: read
92+
packages: write
93+
steps:
94+
- uses: actions/checkout@v4
95+
with:
96+
persist-credentials: false
97+
- uses: docker/login-action@v3
98+
with:
99+
registry: ghcr.io
100+
username: ${{ github.actor }}
101+
password: ${{ secrets.GITHUB_TOKEN }}
102+
- uses: docker/setup-buildx-action@v3
103+
- uses: docker/metadata-action@v5
104+
id: meta
105+
with:
106+
images: ghcr.io/${{ github.repository }}
107+
tags: |
108+
type=ref,event=branch
109+
type=sha,prefix=sha-,format=long
110+
- uses: docker/build-push-action@v6
111+
with:
112+
context: .
113+
file: Containerfile
114+
push: true
115+
tags: ${{ steps.meta.outputs.tags }}
116+
labels: ${{ steps.meta.outputs.labels }}
117+
platforms: linux/amd64,linux/arm64
118+
build-args: |
119+
VERSION=${{ github.ref_name }}
120+
REVISION=${{ github.sha }}
121+
cache-from: type=gha
122+
cache-to: type=gha,mode=max

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ Thumbs.db
4242

4343
# Internal planning docs (not for public history)
4444
PLAN.md
45+
46+
# Cluster-specific deployment manifests (kept local; not part of the project)
47+
deploy/

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ linters:
140140
- revive
141141
- gochecknoglobals
142142
- noinlineerr
143+
- goconst
143144
path: _test\.go
144145
- linters:
145146
- err113

Containerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM docker.io/library/golang:1.26@sha256:313faae491b410a35402c05d35e7518ae99103d957308e940e1ae2cfa0aac29b AS builder
1+
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26@sha256:313faae491b410a35402c05d35e7518ae99103d957308e940e1ae2cfa0aac29b AS builder
22
ARG TARGETOS TARGETARCH
33
ARG VERSION=dev
44
ARG REVISION=unknown
@@ -16,7 +16,7 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} \
1616

1717
FROM gcr.io/distroless/static:nonroot@sha256:e3f945647ffb95b5839c07038d64f9811adf17308b9121d8a2b87b6a22a80a39
1818

19-
LABEL org.opencontainers.image.source="https://github.com/squat/kilo-clustermesh-operator"
19+
LABEL org.opencontainers.image.source="https://github.com/cozystack/kilo-clustermesh-operator"
2020
LABEL org.opencontainers.image.description="Kubernetes ClusterMesh operator for Kilo"
2121
LABEL org.opencontainers.image.licenses="Apache-2.0"
2222
LABEL org.opencontainers.image.title="kilo-clustermesh-operator"

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ help: ## Display this help.
4545

4646
.PHONY: manifests
4747
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
48-
"$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
48+
"$(CONTROLLER_GEN)" rbac:roleName=manager-role webhook paths="./..."
49+
"$(CONTROLLER_GEN)" crd paths="./api/..." output:crd:artifacts:config=config/crd/bases
4950

5051
.PHONY: generate
5152
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.

0 commit comments

Comments
 (0)