diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f0ed4b9..65922cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,10 +29,11 @@ in [agent/README.md](agent/README.md#deploying). ## Development environment -The agent needs Go 1.26+. controller-gen, kustomize, envtest, golangci-lint and -kind are downloaded into `agent/bin/` at pinned versions by the `Makefile`, so -those need no manual install. `kubectl` does: the deploy and e2e targets expect -it on your `PATH`. CI pins it in `.github/workflows/agent-ci.yaml`. +The agent needs the Go version set in `agent/go.mod`. controller-gen, +kustomize, envtest, golangci-lint and kind are downloaded into `agent/bin/` at +pinned versions by the `Makefile`, so those need no manual install. `kubectl` +does: the deploy and e2e targets expect it on your `PATH`. CI pins it in +`.github/workflows/agent-ci.yaml`. One version escapes the pinning. The `logcheck` plugin of the custom golangci-lint build resolves to `latest` in `agent/.custom-gcl.yml`, so a new diff --git a/README.md b/README.md index 09b2623..1ba332d 100644 --- a/README.md +++ b/README.md @@ -255,8 +255,8 @@ under the same tag. ## Development -The agent needs Go 1.26+. The RPM tooling needs Docker, and Go for the -`imagecachectl` package. +The agent needs the Go version set in `agent/go.mod`. The RPM tooling needs +Docker, and Go for the `imagecachectl` package. ```console make -C agent test # unit tests and the envtest suite diff --git a/agent/.devcontainer/devcontainer.json b/agent/.devcontainer/devcontainer.json index a96838b..4db8c36 100644 --- a/agent/.devcontainer/devcontainer.json +++ b/agent/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Kubebuilder DevContainer", - "image": "golang:1.26", + "image": "golang:1.27@sha256:f24164268fc1e741bbdc0066dbadcf1c8426c5a8489f0729c02c3905a53032fd", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": { "moby": false, diff --git a/agent/Dockerfile b/agent/Dockerfile index 6312071..e986232 100644 --- a/agent/Dockerfile +++ b/agent/Dockerfile @@ -1,5 +1,9 @@ # Build the manager binary -FROM golang:1.26 AS builder +# Override BASE_IMAGE to build from another registry. Keep a digest and a Go +# version no older than the go directive of go.mod, e.g. +# /library/golang:1.27@sha256:. +ARG BASE_IMAGE=golang:1.27@sha256:f24164268fc1e741bbdc0066dbadcf1c8426c5a8489f0729c02c3905a53032fd +FROM ${BASE_IMAGE} AS builder ARG TARGETOS=linux # The agent only ships for amd64 (see DESIGN.md). ARG TARGETARCH=amd64 diff --git a/agent/README.md b/agent/README.md index 315e51c..3af65ae 100644 --- a/agent/README.md +++ b/agent/README.md @@ -17,9 +17,9 @@ All the commands below run from this directory. ## Prerequisites -- Go 1.26+ for building and testing. controller-gen, kustomize, envtest, - golangci-lint and kind are downloaded into `bin/` at pinned versions by the - `Makefile`. +- The Go version set in `go.mod` for building and testing. controller-gen, + kustomize, envtest, golangci-lint and kind are downloaded into `bin/` at + pinned versions by the `Makefile`. - `kubectl`. It is not downloaded: the targets expect it on your `PATH`. - Docker, or another `CONTAINER_TOOL`, for the image. - A cluster on Kubernetes 1.25+, which is where CEL validation of custom diff --git a/agent/go.mod b/agent/go.mod index d0951f7..8feab54 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -1,6 +1,6 @@ module github.com/scality/image-cache/agent -go 1.26.0 +go 1.27.2 require ( github.com/fsnotify/fsnotify v1.10.1 diff --git a/agent/internal/controller/fswatch.go b/agent/internal/controller/fswatch.go index 6fde894..636cc1f 100644 --- a/agent/internal/controller/fswatch.go +++ b/agent/internal/controller/fswatch.go @@ -8,7 +8,6 @@ import ( "github.com/fsnotify/fsnotify" "github.com/scality/go-errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/event" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -51,7 +50,7 @@ func NewFSWatcher(nodeName string) (*FSWatcher, error) { paths: map[string]bool{}, trigger: event.GenericEvent{ Object: &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: nodeName}, + Name: nodeName, }, }, Events: make(chan event.GenericEvent, 1), diff --git a/agent/internal/controller/imagecache_validation_test.go b/agent/internal/controller/imagecache_validation_test.go index a1941c8..8a987c6 100644 --- a/agent/internal/controller/imagecache_validation_test.go +++ b/agent/internal/controller/imagecache_validation_test.go @@ -4,8 +4,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - imagecachev1alpha1 "github.com/scality/image-cache/agent/api/v1alpha1" ) @@ -14,8 +12,8 @@ import ( var _ = Describe("ImageCache validation", func() { create := func(name, source string) error { ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: name}, - Spec: imagecachev1alpha1.ImageCacheSpec{Source: source}, + Name: name, + Spec: imagecachev1alpha1.ImageCacheSpec{Source: source}, } err := k8sClient.Create(ctx, ic) if err == nil { diff --git a/agent/internal/controller/node_reconciler.go b/agent/internal/controller/node_reconciler.go index 39e9881..fa18dec 100644 --- a/agent/internal/controller/node_reconciler.go +++ b/agent/internal/controller/node_reconciler.go @@ -10,7 +10,6 @@ import ( "github.com/scality/go-errors" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/client-go/tools/events" "k8s.io/client-go/util/workqueue" @@ -225,7 +224,7 @@ func (r *NodeReconciler) patchLabels(ctx context.Context, node *corev1.Node, wan func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { toNode := handler.EnqueueRequestsFromMapFunc( func(context.Context, client.Object) []reconcile.Request { - return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: r.NodeName}}} + return []reconcile.Request{{Name: r.NodeName}} }) b := ctrl.NewControllerManagedBy(mgr). Named("node"). @@ -236,7 +235,7 @@ func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { // are cleaned up without waiting for a resource event. b = b.WatchesRawSource(source.Func( func(_ context.Context, q workqueue.TypedRateLimitingInterface[reconcile.Request]) error { - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{Name: r.NodeName}}) + q.Add(reconcile.Request{Name: r.NodeName}) return nil })) diff --git a/agent/internal/controller/node_reconciler_test.go b/agent/internal/controller/node_reconciler_test.go index dec889b..a18d392 100644 --- a/agent/internal/controller/node_reconciler_test.go +++ b/agent/internal/controller/node_reconciler_test.go @@ -16,7 +16,6 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/events" @@ -130,7 +129,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { It("becomes synced once the reconciler pulls and extracts it", func() { By("creating a matching ImageCache") ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: workerResourceName}, + Name: workerResourceName, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{osLabelKey: osLabelLinux}, Source: "registry.example.com/boot-cache-worker:134.0.0", @@ -158,7 +157,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { It("does not label the node for a resource that does not select it", func() { By("creating a non-matching ImageCache") ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: "other-134-0-0"}, + Name: "other-134-0-0", Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{zoneLabelKey: "mars"}, Source: "registry.example.com/boot-cache-other:134.0.0", @@ -176,7 +175,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { It("removes the label and the directory once the resource is deleted", func() { By("deleting the resource created in the first case") - ic := &imagecachev1alpha1.ImageCache{ObjectMeta: metav1.ObjectMeta{Name: workerResourceName}} + ic := &imagecachev1alpha1.ImageCache{Name: workerResourceName} Expect(k8sClient.Delete(ctx, ic)).To(Succeed()) By("waiting for the label to disappear") @@ -196,7 +195,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { By("creating a matching ImageCache while the puller fails") ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: "broken-134-0-0"}, + Name: "broken-134-0-0", Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{osLabelKey: osLabelLinux}, Source: "registry.example.com/boot-cache-broken:134.0.0", @@ -228,7 +227,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { It("keeps coexisting versions independent (upgrade scenario)", func() { By("creating two coexisting versions of the same boot cache") older := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: worker133ResourceName}, + Name: worker133ResourceName, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{osLabelKey: osLabelLinux}, Source: "registry.example.com/boot-cache-worker:133.0.0", @@ -236,7 +235,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { } Expect(k8sClient.Create(ctx, older)).To(Succeed()) newer := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: workerResourceName}, + Name: workerResourceName, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{osLabelKey: osLabelLinux}, Source: "registry.example.com/boot-cache-worker:134.0.0", @@ -302,7 +301,7 @@ var _ = Describe("NodeReconciler", Ordered, func() { By("triggering a pass over the cache path with a resource that selects nothing here") trigger := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: "trigger-134-0-0"}, + Name: "trigger-134-0-0", Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{zoneLabelKey: "mars"}, Source: "registry.example.com/boot-cache-other:134.0.0", @@ -453,13 +452,11 @@ var _ = Describe("startup pass", func() { By("creating a node carrying a stale synced label from a resource that no longer exists") node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: staleNodeName, - // Deliberately does not match any suite CR selector (those - // use kubernetes.io/os): this node must stay untouched by - // every other test's ImageCache resources. - Labels: map[string]string{zoneLabelKey: "stale", LabelPrefix + "gone-1-0-0": StatusSynced}, - }, + Name: staleNodeName, + // Deliberately does not match any suite CR selector (those + // use kubernetes.io/os): this node must stay untouched by + // every other test's ImageCache resources. + Labels: map[string]string{zoneLabelKey: "stale", LabelPrefix + "gone-1-0-0": StatusSynced}, } Expect(k8sClient.Create(ctx, node)).To(Succeed()) DeferCleanup(func() { @@ -497,16 +494,16 @@ var _ = Describe("lost sentinel", func() { } { It("fills "+what+" again", func() { ctx := context.Background() - node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{ + node := &corev1.Node{ Name: lostNodeName, Labels: map[string]string{lostLabelKey: "yes"}, - }} + } Expect(k8sClient.Create(ctx, node)).To(Succeed()) DeferCleanup(func() { Expect(k8sClient.Delete(context.Background(), node)).To(Succeed()) }) ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: lostResourceName}, + Name: lostResourceName, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{lostLabelKey: "yes"}, Source: "registry.example.com/boot-cache-lost:1.0.0", @@ -528,7 +525,7 @@ var _ = Describe("lost sentinel", func() { Puller: &fakePuller{}, Resync: time.Hour, } - _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: lostNodeName}}) + _, err := r.Reconcile(ctx, ctrl.Request{Name: lostNodeName}) Expect(err).NotTo(HaveOccurred()) Expect(os.ReadFile(filepath.Join(res, etcdTarName))).To(Equal([]byte("etcd"))) Expect(filepath.Join(res, ".image-cache-agent.json")).To(BeARegularFile()) @@ -542,7 +539,7 @@ var _ = Describe("lost sentinel", func() { var _ = Describe("stray cache entries", func() { It("removes them", func() { ctx := context.Background() - node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: strayNodeName}} + node := &corev1.Node{Name: strayNodeName} Expect(k8sClient.Create(ctx, node)).To(Succeed()) DeferCleanup(func() { Expect(k8sClient.Delete(context.Background(), node)).To(Succeed()) @@ -559,7 +556,7 @@ var _ = Describe("stray cache entries", func() { Puller: &fakePuller{}, Resync: time.Hour, } - res, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: strayNodeName}}) + res, err := r.Reconcile(ctx, ctrl.Request{Name: strayNodeName}) Expect(err).NotTo(HaveOccurred()) Expect(res.RequeueAfter).To(Equal(time.Hour)) Expect(stray).NotTo(BeAnExistingFile()) @@ -567,7 +564,7 @@ var _ = Describe("stray cache entries", func() { It("fails the pass when it cannot tell whether a directory is seeded", func() { ctx := context.Background() - node := &corev1.Node{ObjectMeta: metav1.ObjectMeta{Name: strayNodeName}} + node := &corev1.Node{Name: strayNodeName} Expect(k8sClient.Create(ctx, node)).To(Succeed()) DeferCleanup(func() { Expect(k8sClient.Delete(context.Background(), node)).To(Succeed()) @@ -585,7 +582,7 @@ var _ = Describe("stray cache entries", func() { Puller: &fakePuller{}, Resync: time.Hour, } - _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: types.NamespacedName{Name: strayNodeName}}) + _, err := r.Reconcile(ctx, ctrl.Request{Name: strayNodeName}) Expect(err).To(MatchError(cache.ErrGC)) Expect(unreadable).To(BeADirectory()) }) @@ -601,10 +598,8 @@ var _ = Describe("missing cache path", func() { By("creating a node dedicated to this suite") node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: missingNodeName, - Labels: map[string]string{missingPathLabelKey: missingPathLabelYes}, - }, + Name: missingNodeName, + Labels: map[string]string{missingPathLabelKey: missingPathLabelYes}, } Expect(k8sClient.Create(ctx, node)).To(Succeed()) DeferCleanup(func() { @@ -622,7 +617,7 @@ var _ = Describe("missing cache path", func() { By("creating a matching ImageCache") ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: missingResourceName}, + Name: missingResourceName, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{missingPathLabelKey: missingPathLabelYes}, Source: "registry.example.com/boot-cache-missing:134.0.0", @@ -673,10 +668,8 @@ var _ = Describe("filesystem repair", func() { By("creating a node dedicated to this suite") node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: fsNodeName, - Labels: map[string]string{fsRepairLabelKey: fsRepairLabelYes}, - }, + Name: fsNodeName, + Labels: map[string]string{fsRepairLabelKey: fsRepairLabelYes}, } Expect(k8sClient.Create(ctx, node)).To(Succeed()) DeferCleanup(func() { @@ -707,7 +700,7 @@ var _ = Describe("filesystem repair", func() { By("creating a matching ImageCache") ic := &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: fsResourceName}, + Name: fsResourceName, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{fsRepairLabelKey: fsRepairLabelYes}, Source: "registry.example.com/boot-cache-fsrepair:1.0.0", @@ -774,7 +767,7 @@ func placeDirectory(dir, content, sentinel string) { // matchingResource is a resource that selects the suite's node. func matchingResource(name, source string) *imagecachev1alpha1.ImageCache { return &imagecachev1alpha1.ImageCache{ - ObjectMeta: metav1.ObjectMeta{Name: name}, + Name: name, Spec: imagecachev1alpha1.ImageCacheSpec{ NodeSelector: map[string]string{osLabelKey: osLabelLinux}, Source: source, diff --git a/agent/internal/controller/suite_test.go b/agent/internal/controller/suite_test.go index 64d30a1..e675b57 100644 --- a/agent/internal/controller/suite_test.go +++ b/agent/internal/controller/suite_test.go @@ -27,7 +27,6 @@ import ( . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -107,10 +106,8 @@ var _ = BeforeSuite(func() { By("creating the test node") node := &corev1.Node{ - ObjectMeta: metav1.ObjectMeta{ - Name: testNodeName, - Labels: map[string]string{osLabelKey: osLabelLinux}, - }, + Name: testNodeName, + Labels: map[string]string{osLabelKey: osLabelLinux}, } Expect(k8sClient.Create(ctx, node)).To(Succeed()) diff --git a/agent/internal/puller/puller_test.go b/agent/internal/puller/puller_test.go index 70bf30d..a9f49cf 100644 --- a/agent/internal/puller/puller_test.go +++ b/agent/internal/puller/puller_test.go @@ -118,16 +118,12 @@ func TestRemotePullResolvesMultiArchIndex(t *testing.T) { idx := mutate.AppendManifests(empty.Index, mutate.IndexAddendum{ - Add: imgAmd, - Descriptor: v1.Descriptor{ - Platform: &v1.Platform{OS: linuxOS, Architecture: amd64Arch}, - }, + Add: imgAmd, + Platform: &v1.Platform{OS: linuxOS, Architecture: amd64Arch}, }, mutate.IndexAddendum{ - Add: imgArm, - Descriptor: v1.Descriptor{ - Platform: &v1.Platform{OS: linuxOS, Architecture: arm64Arch}, - }, + Add: imgArm, + Platform: &v1.Platform{OS: linuxOS, Architecture: arm64Arch}, }, ) @@ -216,10 +212,8 @@ func TestRemotePullNoMatchingPlatform(t *testing.T) { idx := mutate.AppendManifests(empty.Index, mutate.IndexAddendum{ - Add: imgArm, - Descriptor: v1.Descriptor{ - Platform: &v1.Platform{OS: linuxOS, Architecture: arm64Arch}, - }, + Add: imgArm, + Platform: &v1.Platform{OS: linuxOS, Architecture: arm64Arch}, }, )