From 2ea7f968b89e107a886d40b931b43ec97fca4997 Mon Sep 17 00:00:00 2001 From: Salah Aldeen Al Saleh Date: Fri, 21 Aug 2026 11:10:43 -0700 Subject: [PATCH] fix(snapshots): detect kURL minio add-on from any repository GetMinioImage only recognized the add-on when its image came from minio/minio, so the kurlsh/minio image kURL now ships returned "". That made IsFileSystemMinioDisabled report minio as disabled, which put NFS/host path snapshots into LVP mode and hid both destinations from the Admin Console unless the local-volume-provider plugin was installed. Match on the image name and RELEASE tag instead of the registry and organization. Co-Authored-By: Claude Opus 5 (1M context) --- pkg/image/minio.go | 19 ++++++-- pkg/image/minio_test.go | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/pkg/image/minio.go b/pkg/image/minio.go index 313eb0020f..92ebe329b0 100644 --- a/pkg/image/minio.go +++ b/pkg/image/minio.go @@ -2,7 +2,7 @@ package image import ( "context" - "strings" + "regexp" "github.com/pkg/errors" "github.com/replicatedhq/kots/pkg/kurl" @@ -11,6 +11,11 @@ import ( "k8s.io/client-go/kubernetes" ) +// minioReleaseImageRegexp matches any repository whose image name is "minio" with an +// upstream RELEASE tag, e.g. "minio/minio:RELEASE.2025-10-15T17-29-55Z" and +// "kurlsh/minio:RELEASE.2025-10-15T17-29-55Z". +var minioReleaseImageRegexp = regexp.MustCompile(`(^|/)minio:RELEASE\.`) + // MinioImage looks through the nodes in the cluster and finds nodes that have already pulled Minio, and then finds the latest image tag listed func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (string, error) { /* @@ -30,7 +35,7 @@ func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (str } if err == nil { for _, container := range deployment.Spec.Template.Spec.Containers { - if strings.Contains(container.Image, "minio/minio:RELEASE.") { + if isMinioReleaseImage(container.Image) { return container.Image, nil } } @@ -44,7 +49,7 @@ func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (str } if err == nil { for _, container := range statefulset.Spec.Template.Spec.Containers { - if strings.Contains(container.Image, "minio/minio:RELEASE.") { + if isMinioReleaseImage(container.Image) { return container.Image, nil } } @@ -52,3 +57,11 @@ func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (str return "", nil } + +// isMinioReleaseImage returns true if the image is a minio image with an upstream +// RELEASE tag, regardless of which repository it was published to. The add-on has +// shipped from more than one repository (minio/minio, kurlsh/minio), so only the +// image name and tag are matched, not the registry or organization. +func isMinioReleaseImage(image string) bool { + return minioReleaseImageRegexp.MatchString(image) +} diff --git a/pkg/image/minio_test.go b/pkg/image/minio_test.go index 64208040c6..ea40072f52 100644 --- a/pkg/image/minio_test.go +++ b/pkg/image/minio_test.go @@ -71,6 +71,102 @@ func Test_GetMinioImage(t *testing.T) { wantImage: "minio/minio:RELEASE.2020-10-27T00-54-19Z", wantErr: false, }, + { + name: "should return minio image from deployment when the add-on ships from the kurlsh repository", + clientset: fake.NewSimpleClientset( + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kurl.ConfigMapName, + Namespace: kurl.ConfigMapNamespace, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "minio", + Namespace: "minio", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "minio", + Image: "kurlsh/minio:RELEASE.2025-10-15T17-29-55Z", + }, + }, + }, + }, + }, + }, + ), + kotsadmNamespace: metav1.NamespaceDefault, + wantImage: "kurlsh/minio:RELEASE.2025-10-15T17-29-55Z", + wantErr: false, + }, + { + name: "should return minio image from deployment when the add-on is pulled from a private registry", + clientset: fake.NewSimpleClientset( + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kurl.ConfigMapName, + Namespace: kurl.ConfigMapNamespace, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "minio", + Namespace: "minio", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "minio", + Image: "registry.example.com:5000/kurlsh/minio:RELEASE.2025-10-15T17-29-55Z", + }, + }, + }, + }, + }, + }, + ), + kotsadmNamespace: metav1.NamespaceDefault, + wantImage: "registry.example.com:5000/kurlsh/minio:RELEASE.2025-10-15T17-29-55Z", + wantErr: false, + }, + { + name: "should return empty image when the minio container is not a RELEASE build", + clientset: fake.NewSimpleClientset( + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: kurl.ConfigMapName, + Namespace: kurl.ConfigMapNamespace, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "minio", + Namespace: "minio", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "minio", + Image: "kurlsh/minio:0.20250101.0", + }, + }, + }, + }, + }, + }, + ), + kotsadmNamespace: metav1.NamespaceDefault, + wantImage: "", + wantErr: false, + }, { name: "should return minio image from statefulset for kurl instance with default namespace", clientset: fake.NewSimpleClientset(