diff --git a/pkg/apis/k0s/v1beta1/clusterconfig_types_test.go b/pkg/apis/k0s/v1beta1/clusterconfig_types_test.go index f9b7e4365855..181a6e29153c 100644 --- a/pkg/apis/k0s/v1beta1/clusterconfig_types_test.go +++ b/pkg/apis/k0s/v1beta1/clusterconfig_types_test.go @@ -18,10 +18,12 @@ package v1beta1 import ( "encoding/json" + "fmt" "testing" "github.com/k0sproject/k0s/internal/pkg/iface" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" ) @@ -65,6 +67,31 @@ func TestEmptyClusterSpec(t *testing.T) { assert.Nil(t, errs) } +func TestClusterSpecCustomImages(t *testing.T) { + underTest := ClusterConfig{ + Spec: &ClusterSpec{ + Images: &ClusterImages{ + DefaultPullPolicy: string(corev1.PullIfNotPresent), + Konnectivity: ImageSpec{ + Image: "foo", + Version: "v1", + }, + PushGateway: ImageSpec{ + Image: "bar", + Version: "v2@sha256:0000000000000000000000000000000000000000000000000000000000000000", + }, + MetricsServer: ImageSpec{ + Image: "baz", + Version: "sha256:0000000000000000000000000000000000000000000000000000000000000000", + }, + }, + }, + } + + errs := underTest.Validate() + assert.Nil(t, errs, fmt.Sprintf("%v", errs)) +} + func TestEtcdDefaults(t *testing.T) { yamlData := ` apiVersion: k0s.k0sproject.io/v1beta1 diff --git a/pkg/apis/k0s/v1beta1/images.go b/pkg/apis/k0s/v1beta1/images.go index 9ab42d856473..8e8dbb17efa6 100644 --- a/pkg/apis/k0s/v1beta1/images.go +++ b/pkg/apis/k0s/v1beta1/images.go @@ -48,7 +48,8 @@ func (s *ImageSpec) Validate(path *field.Path) (errs field.ErrorList) { errs = append(errs, field.Invalid(path.Child("image"), s.Image, "must not have leading or trailing whitespace")) } - versionRe := regexp.MustCompile(`^` + docker.TagRegexp.String() + `$`) + // Validate the image contains a tag and optional digest + versionRe := regexp.MustCompile(`^` + docker.TagRegexp.String() + `(?:@` + docker.DigestRegexp.String() + `)?$`) if !versionRe.MatchString(s.Version) { errs = append(errs, field.Invalid(path.Child("version"), s.Version, "must match regular expression: "+versionRe.String())) } diff --git a/pkg/apis/k0s/v1beta1/images_test.go b/pkg/apis/k0s/v1beta1/images_test.go index 780afc6ce867..ceaef67c3afe 100644 --- a/pkg/apis/k0s/v1beta1/images_test.go +++ b/pkg/apis/k0s/v1beta1/images_test.go @@ -23,6 +23,7 @@ import ( "github.com/k0sproject/k0s/pkg/constant" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/yaml" ) @@ -117,3 +118,53 @@ func TestOverrideFunction(t *testing.T) { assert.Equal(t, tc.Output, overrideRepository(repository, tc.Input)) } } + +func TestImageSpec_Validate(t *testing.T) { + validTestCases := []struct { + Image string + Version string + }{ + {"my.registry/repo/image", "v1.0.0"}, + {"my.registry/repo/image", "latest"}, + {"my.registry/repo/image", "v1.0.0-rc1"}, + {"my.registry/repo/image", "v1.0.0@sha256:0000000000000000000000000000000000000000000000000000000000000000"}, + } + for _, tc := range validTestCases { + t.Run(tc.Image+":"+tc.Version+"_valid", func(t *testing.T) { + s := &ImageSpec{ + Image: tc.Image, + Version: tc.Version, + } + errs := s.Validate(field.NewPath("image")) + assert.Empty(t, errs) + }) + } + + errVersionRe := `must match regular expression: ^[\w][\w.-]{0,127}(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,})?$` + + invalidTestCases := []struct { + Image string + Version string + Errs field.ErrorList + }{ + { + "my.registry/repo/image", "", + field.ErrorList{field.Invalid(field.NewPath("image").Child("version"), "", errVersionRe)}, + }, + // digest only is currently not supported + { + "my.registry/repo/image", "sha256:0000000000000000000000000000000000000000000000000000000000000000", + field.ErrorList{field.Invalid(field.NewPath("image").Child("version"), "sha256:0000000000000000000000000000000000000000000000000000000000000000", errVersionRe)}, + }, + } + for _, tc := range invalidTestCases { + t.Run(tc.Image+":"+tc.Version+"_valid", func(t *testing.T) { + s := &ImageSpec{ + Image: tc.Image, + Version: tc.Version, + } + errs := s.Validate(field.NewPath("image")) + assert.Equal(t, tc.Errs, errs) + }) + } +} diff --git a/pkg/apis/k0s/v1beta1/nllb_test.go b/pkg/apis/k0s/v1beta1/nllb_test.go index 65d1067ad1ed..dcf4dd948861 100644 --- a/pkg/apis/k0s/v1beta1/nllb_test.go +++ b/pkg/apis/k0s/v1beta1/nllb_test.go @@ -100,7 +100,7 @@ spec: }, { "version", `"*"`, - `network: nodeLocalLoadBalancing.envoyProxy.image.version: Invalid value: "*": must match regular expression: ^[\w][\w.-]{0,127}$`, + `network: nodeLocalLoadBalancing.envoyProxy.image.version: Invalid value: "*": must match regular expression: ^[\w][\w.-]{0,127}(?:@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,})?$`, }, } { t.Run(test.field+"_invalid", func(t *testing.T) {