Skip to content

Commit 155b6c9

Browse files
alexander-demicevk8s-infra-cherrypick-robot
authored andcommitted
Allow non canonical image paths
Signed-off-by: alexander-demicev <[email protected]>
1 parent d21153e commit 155b6c9

File tree

2 files changed

+139
-1
lines changed

2 files changed

+139
-1
lines changed

internal/controller/image_overrides.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"fmt"
21+
"strings"
2122

2223
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2324
"k8s.io/client-go/kubernetes/scheme"
@@ -39,13 +40,41 @@ func imageOverrides(component string, overrides configclient.Client) func(objs [
3940
}
4041

4142
return fixImages(objs, func(image string) (string, error) {
42-
return overrides.ImageMeta().AlterImage(component, image)
43+
return alterImage(component, image, overrides.ImageMeta())
4344
})
4445
}
4546

4647
return imageOverridesWrapper
4748
}
4849

50+
// alterImage accepts images as is, including non canonical formats.
51+
// If image overrides fail due to non canonical format, the original image is returned unchanged.
52+
// Allowing non canonical formats is designed for advanced users who may want to use such formats intentionally.
53+
func alterImage(component, imageString string, imageMeta configclient.ImageMetaClient) (string, error) {
54+
result, err := imageMeta.AlterImage(component, imageString)
55+
if err != nil {
56+
if isCanonicalError(err) {
57+
return imageString, nil
58+
}
59+
60+
return "", err
61+
}
62+
63+
return result, nil
64+
}
65+
66+
// isCanonicalError checks if error is about non nanonical image format.
67+
func isCanonicalError(err error) bool {
68+
if err == nil {
69+
return false
70+
}
71+
72+
msg := err.Error()
73+
74+
return strings.Contains(msg, "repository name must be canonical") ||
75+
strings.Contains(msg, "couldn't parse image name")
76+
}
77+
4978
// fixImages alters images using the give alter func
5079
// NB. The implemented approach is specific for the provider components YAML & for the cert-manager manifest; it is not
5180
// intended to cover all the possible objects used to deploy containers existing in Kubernetes.

internal/controller/image_overrides_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,112 @@ func TestFixImages(t *testing.T) {
169169
})
170170
}
171171
}
172+
173+
// mockImageMetaClient is a test double for configclient.ImageMetaClient.
174+
type mockImageMetaClient struct {
175+
alterFunc func(component, image string) (string, error)
176+
}
177+
178+
func (m *mockImageMetaClient) AlterImage(component, image string) (string, error) {
179+
return m.alterFunc(component, image)
180+
}
181+
182+
func TestAlterImage(t *testing.T) {
183+
tests := []struct {
184+
name string
185+
component string
186+
image string
187+
mockFunc func(component, image string) (string, error)
188+
want string
189+
wantErr bool
190+
}{
191+
{
192+
name: "canonical image with override applies override",
193+
component: "cluster-api",
194+
image: "example.com/controller:v1.0.0",
195+
mockFunc: func(component, image string) (string, error) {
196+
return "example.com/custom:v2.0.0", nil
197+
},
198+
want: "example.com/custom:v2.0.0",
199+
wantErr: false,
200+
},
201+
{
202+
name: "non-canonical image returns original on canonical error",
203+
component: "cluster-api",
204+
image: "example.com/controller:v1.0.0",
205+
mockFunc: func(component, image string) (string, error) {
206+
return "", fmt.Errorf("couldn't parse image name: repository name must be canonical")
207+
},
208+
want: "example.com/controller:v1.0.0",
209+
wantErr: false,
210+
},
211+
{
212+
name: "other errors are propagated",
213+
component: "cluster-api",
214+
image: "example.com/controller:v1.0.0",
215+
mockFunc: func(component, image string) (string, error) {
216+
return "", fmt.Errorf("test")
217+
},
218+
want: "",
219+
wantErr: true,
220+
},
221+
}
222+
223+
for _, tt := range tests {
224+
t.Run(tt.name, func(t *testing.T) {
225+
g := NewWithT(t)
226+
227+
mock := &mockImageMetaClient{alterFunc: tt.mockFunc}
228+
result, err := alterImage(tt.component, tt.image, mock)
229+
230+
if tt.wantErr {
231+
g.Expect(err).To(HaveOccurred())
232+
return
233+
}
234+
235+
g.Expect(err).ToNot(HaveOccurred())
236+
g.Expect(result).To(Equal(tt.want))
237+
})
238+
}
239+
}
240+
241+
func TestIsCanonicalError(t *testing.T) {
242+
tests := []struct {
243+
name string
244+
err error
245+
want bool
246+
}{
247+
{
248+
name: "nil error returns false",
249+
err: nil,
250+
want: false,
251+
},
252+
{
253+
name: "canonical error with 'repository name must be canonical'",
254+
err: fmt.Errorf("repository name must be canonical"),
255+
want: true,
256+
},
257+
{
258+
name: "canonical error with 'couldn't parse image name'",
259+
err: fmt.Errorf("couldn't parse image name: invalid format"),
260+
want: true,
261+
},
262+
{
263+
name: "other error returns false",
264+
err: fmt.Errorf("test"),
265+
want: false,
266+
},
267+
{
268+
name: "empty error message returns false",
269+
err: fmt.Errorf(""),
270+
want: false,
271+
},
272+
}
273+
274+
for _, tt := range tests {
275+
t.Run(tt.name, func(t *testing.T) {
276+
g := NewWithT(t)
277+
g.Expect(isCanonicalError(tt.err)).To(Equal(tt.want))
278+
})
279+
}
280+
}

0 commit comments

Comments
 (0)