Skip to content

Commit 17cfff0

Browse files
sgalsalehclaude
andcommitted
fix(snapshots): don't decide minio availability from the image repository
The kURL minio add-on ships from kurlsh/minio now, which the previous minio/minio match missed, so IsFileSystemMinioDisabled reported minio as disabled and put NFS/host path snapshots into LVP mode. Split the two uses of the add-on lookup, which want opposite biases: - MinioAvailable answers whether minio can be run at all and looks only for the add-on workload, so it no longer breaks when the add-on moves to another repository. - GetMinioImage keeps choosing which image to run and now accepts both repositories the add-on has shipped from, with an optional registry prefix. On kURL installs in the default namespace that image is run as-is, with no registry rewrite or pull secret, so an image from any other repository is left alone and the bundled image is used instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 2ea7f96 commit 17cfff0

3 files changed

Lines changed: 154 additions & 16 deletions

File tree

pkg/image/minio.go

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import (
1111
"k8s.io/client-go/kubernetes"
1212
)
1313

14-
// minioReleaseImageRegexp matches any repository whose image name is "minio" with an
15-
// upstream RELEASE tag, e.g. "minio/minio:RELEASE.2025-10-15T17-29-55Z" and
16-
// "kurlsh/minio:RELEASE.2025-10-15T17-29-55Z".
17-
var minioReleaseImageRegexp = regexp.MustCompile(`(^|/)minio:RELEASE\.`)
14+
// minioAddonImageRegexp matches the repositories the kURL minio add-on has shipped
15+
// from, with an upstream RELEASE tag and an optional registry prefix, e.g.
16+
// "minio/minio:RELEASE.2025-10-15T17-29-55Z", "kurlsh/minio:RELEASE.2025-10-15T17-29-55Z"
17+
// and "registry.example.com/kurlsh/minio:RELEASE.2025-10-15T17-29-55Z".
18+
var minioAddonImageRegexp = regexp.MustCompile(`(^|/)(minio|kurlsh)/minio:RELEASE\.`)
1819

1920
// MinioImage looks through the nodes in the cluster and finds nodes that have already pulled Minio, and then finds the latest image tag listed
2021
func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (string, error) {
@@ -35,7 +36,7 @@ func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (str
3536
}
3637
if err == nil {
3738
for _, container := range deployment.Spec.Template.Spec.Containers {
38-
if isMinioReleaseImage(container.Image) {
39+
if isMinioAddonImage(container.Image) {
3940
return container.Image, nil
4041
}
4142
}
@@ -49,7 +50,7 @@ func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (str
4950
}
5051
if err == nil {
5152
for _, container := range statefulset.Spec.Template.Spec.Containers {
52-
if isMinioReleaseImage(container.Image) {
53+
if isMinioAddonImage(container.Image) {
5354
return container.Image, nil
5455
}
5556
}
@@ -58,10 +59,39 @@ func GetMinioImage(clientset kubernetes.Interface, kotsadmNamespace string) (str
5859
return "", nil
5960
}
6061

61-
// isMinioReleaseImage returns true if the image is a minio image with an upstream
62-
// RELEASE tag, regardless of which repository it was published to. The add-on has
63-
// shipped from more than one repository (minio/minio, kurlsh/minio), so only the
64-
// image name and tag are matched, not the registry or organization.
65-
func isMinioReleaseImage(image string) bool {
66-
return minioReleaseImageRegexp.MatchString(image)
62+
// isMinioAddonImage returns true if the image is one the kURL minio add-on has
63+
// shipped. On kURL installs in the default namespace this image is run as-is, without
64+
// a registry rewrite or pull secret, so images from other repositories are not
65+
// adopted: the bundled image is used instead.
66+
func isMinioAddonImage(image string) bool {
67+
return minioAddonImageRegexp.MatchString(image)
68+
}
69+
70+
// MinioAvailable reports whether a minio image is available to run for this
71+
// installation. Outside kURL installs in the default namespace kots uses the image it
72+
// bundles; there, minio is only available if the kURL add-on provides it.
73+
func MinioAvailable(clientset kubernetes.Interface, kotsadmNamespace string) (bool, error) {
74+
// expected to fail for minimal rbac
75+
isKurl, _ := kurl.IsKurl(clientset)
76+
if !isKurl || kotsadmNamespace != metav1.NamespaceDefault {
77+
return true, nil
78+
}
79+
80+
_, err := clientset.AppsV1().Deployments("minio").Get(context.TODO(), "minio", metav1.GetOptions{})
81+
if err == nil {
82+
return true, nil
83+
}
84+
if !kuberneteserrors.IsNotFound(err) {
85+
return false, errors.Wrap(err, "failed to get minio deployment")
86+
}
87+
88+
_, err = clientset.AppsV1().StatefulSets("minio").Get(context.TODO(), "ha-minio", metav1.GetOptions{})
89+
if err == nil {
90+
return true, nil
91+
}
92+
if !kuberneteserrors.IsNotFound(err) {
93+
return false, errors.Wrap(err, "failed to get ha-minio statefulset")
94+
}
95+
96+
return false, nil
6797
}

pkg/image/minio_test.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,38 @@ func Test_GetMinioImage(t *testing.T) {
135135
wantImage: "registry.example.com:5000/kurlsh/minio:RELEASE.2025-10-15T17-29-55Z",
136136
wantErr: false,
137137
},
138+
{
139+
name: "should ignore a minio image from a repository the add-on does not ship from",
140+
clientset: fake.NewSimpleClientset(
141+
&corev1.ConfigMap{
142+
ObjectMeta: metav1.ObjectMeta{
143+
Name: kurl.ConfigMapName,
144+
Namespace: kurl.ConfigMapNamespace,
145+
},
146+
},
147+
&appsv1.Deployment{
148+
ObjectMeta: metav1.ObjectMeta{
149+
Name: "minio",
150+
Namespace: "minio",
151+
},
152+
Spec: appsv1.DeploymentSpec{
153+
Template: corev1.PodTemplateSpec{
154+
Spec: corev1.PodSpec{
155+
Containers: []corev1.Container{
156+
{
157+
Name: "minio",
158+
Image: "registry.acme.com/acme/minio:RELEASE.2025-10-15T17-29-55Z",
159+
},
160+
},
161+
},
162+
},
163+
},
164+
},
165+
),
166+
kotsadmNamespace: metav1.NamespaceDefault,
167+
wantImage: "",
168+
wantErr: false,
169+
},
138170
{
139171
name: "should return empty image when the minio container is not a RELEASE build",
140172
clientset: fake.NewSimpleClientset(
@@ -225,3 +257,79 @@ func Test_GetMinioImage(t *testing.T) {
225257
})
226258
}
227259
}
260+
261+
func Test_MinioAvailable(t *testing.T) {
262+
kurlConfigMap := &corev1.ConfigMap{
263+
ObjectMeta: metav1.ObjectMeta{
264+
Name: kurl.ConfigMapName,
265+
Namespace: kurl.ConfigMapNamespace,
266+
},
267+
}
268+
minioDeployment := func(image string) *appsv1.Deployment {
269+
return &appsv1.Deployment{
270+
ObjectMeta: metav1.ObjectMeta{Name: "minio", Namespace: "minio"},
271+
Spec: appsv1.DeploymentSpec{
272+
Template: corev1.PodTemplateSpec{
273+
Spec: corev1.PodSpec{
274+
Containers: []corev1.Container{{Name: "minio", Image: image}},
275+
},
276+
},
277+
},
278+
}
279+
}
280+
281+
tests := []struct {
282+
name string
283+
clientset kubernetes.Interface
284+
kotsadmNamespace string
285+
want bool
286+
}{
287+
{
288+
name: "kots bundles an image for non-kurl instances",
289+
clientset: fake.NewSimpleClientset(),
290+
kotsadmNamespace: metav1.NamespaceDefault,
291+
want: true,
292+
},
293+
{
294+
name: "kots bundles an image for kurl instances outside the default namespace",
295+
clientset: fake.NewSimpleClientset(kurlConfigMap),
296+
kotsadmNamespace: "custom-namespace",
297+
want: true,
298+
},
299+
{
300+
name: "kurl add-on deployment is present",
301+
clientset: fake.NewSimpleClientset(kurlConfigMap, minioDeployment("kurlsh/minio:RELEASE.2025-10-15T17-29-55Z")),
302+
kotsadmNamespace: metav1.NamespaceDefault,
303+
want: true,
304+
},
305+
{
306+
name: "add-on image from an unrecognized repository still counts as available",
307+
clientset: fake.NewSimpleClientset(kurlConfigMap, minioDeployment("kurlsh/minio:0.20250101.0")),
308+
kotsadmNamespace: metav1.NamespaceDefault,
309+
want: true,
310+
},
311+
{
312+
name: "kurl add-on statefulset is present",
313+
clientset: fake.NewSimpleClientset(kurlConfigMap, &appsv1.StatefulSet{
314+
ObjectMeta: metav1.ObjectMeta{Name: "ha-minio", Namespace: "minio"},
315+
}),
316+
kotsadmNamespace: metav1.NamespaceDefault,
317+
want: true,
318+
},
319+
{
320+
name: "kurl instance without the add-on",
321+
clientset: fake.NewSimpleClientset(kurlConfigMap),
322+
kotsadmNamespace: metav1.NamespaceDefault,
323+
want: false,
324+
},
325+
}
326+
327+
for _, test := range tests {
328+
t.Run(test.name, func(t *testing.T) {
329+
req := require.New(t)
330+
got, err := MinioAvailable(test.clientset, test.kotsadmNamespace)
331+
req.NoError(err)
332+
req.Equal(test.want, got)
333+
})
334+
}
335+
}

pkg/snapshot/filesystem_lvp.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,13 @@ func IsFileSystemMinioDisabled(kotsadmNamespace string) (bool, error) {
301301
}
302302

303303
//Minio disabled is detected based on two cases
304-
// 1. minio image is not present in the cluster
304+
// 1. minio is not available in the cluster
305305
// 2. disableS3 flag is enabled
306-
minioImage, err := image.GetMinioImage(clientset, kotsadmNamespace)
306+
minioAvailable, err := image.MinioAvailable(clientset, kotsadmNamespace)
307307
if err != nil {
308-
return false, errors.Wrap(err, "failed to check minio image")
308+
return false, errors.Wrap(err, "failed to check if minio is available")
309309
}
310-
if minioImage == "" {
310+
if !minioAvailable {
311311
return true, nil
312312
}
313313

0 commit comments

Comments
 (0)