Skip to content
Open
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
9 changes: 5 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion agent/.devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
6 changes: 5 additions & 1 deletion agent/Dockerfile
Original file line number Diff line number Diff line change
@@ -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.
# <mirror>/library/golang:1.27@sha256:<digest>.
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
Expand Down
6 changes: 3 additions & 3 deletions agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion agent/go.mod
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 1 addition & 2 deletions agent/internal/controller/fswatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand Down
6 changes: 2 additions & 4 deletions agent/internal/controller/imagecache_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions agent/internal/controller/node_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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").
Expand All @@ -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
}))

Expand Down
61 changes: 27 additions & 34 deletions agent/internal/controller/node_reconciler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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")
Expand All @@ -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",
Expand Down Expand Up @@ -228,15 +227,15 @@ 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",
},
}
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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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",
Expand All @@ -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())
Expand All @@ -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())
Expand All @@ -559,15 +556,15 @@ 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())
})

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())
Expand All @@ -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())
})
Expand All @@ -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() {
Expand All @@ -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",
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
7 changes: 2 additions & 5 deletions agent/internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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())

Expand Down
18 changes: 6 additions & 12 deletions agent/internal/puller/puller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
},
)

Expand Down Expand Up @@ -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},
},
)

Expand Down
Loading