package image import ( "fmt" "regexp" "runtime" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/common" "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options" ) func Test_GetHelmOptions(t *testing.T) { t.Run("Get Helm parameter for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.Prefixed("", common.HelmParamImageNameAnnotationSuffix), "dummy"): "release.name", fmt.Sprintf(common.Prefixed("", common.HelmParamImageTagAnnotationSuffix), "dummy"): "release.tag", fmt.Sprintf(common.Prefixed("", common.HelmParamImageSpecAnnotationSuffix), "dummy"): "release.image", } img := NewFromIdentifier("dummy=foo/bar:1.12") paramName := img.GetParameterHelmImageName(annotations, "") paramTag := img.GetParameterHelmImageTag(annotations, "") paramSpec := img.GetParameterHelmImageSpec(annotations, "") assert.Equal(t, "release.name", paramName) assert.Equal(t, "release.tag", paramTag) assert.Equal(t, "release.image", paramSpec) }) t.Run("Get Helm parameter for non-configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.Prefixed("", common.HelmParamImageNameAnnotationSuffix), "dummy"): "release.name", fmt.Sprintf(common.Prefixed("", common.HelmParamImageTagAnnotationSuffix), "dummy"): "release.tag", fmt.Sprintf(common.Prefixed("", common.HelmParamImageSpecAnnotationSuffix), "dummy"): "release.image", } img := NewFromIdentifier("foo=foo/bar:1.12") paramName := img.GetParameterHelmImageName(annotations, "") paramTag := img.GetParameterHelmImageTag(annotations, "") paramSpec := img.GetParameterHelmImageSpec(annotations, "") assert.Equal(t, "", paramName) assert.Equal(t, "", paramTag) assert.Equal(t, "", paramSpec) }) t.Run("Get Helm parameter for configured application with normalized name", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.Prefixed("", common.HelmParamImageNameAnnotationSuffix), "foo_dummy"): "release.name", fmt.Sprintf(common.Prefixed("", common.HelmParamImageTagAnnotationSuffix), "foo_dummy"): "release.tag", fmt.Sprintf(common.Prefixed("", common.HelmParamImageSpecAnnotationSuffix), "foo_dummy"): "release.image", } img := NewFromIdentifier("foo/dummy=foo/bar:1.12") paramName := img.GetParameterHelmImageName(annotations, "") paramTag := img.GetParameterHelmImageTag(annotations, "") paramSpec := img.GetParameterHelmImageSpec(annotations, "") assert.Equal(t, "release.name", paramName) assert.Equal(t, "release.tag", paramTag) assert.Equal(t, "release.image", paramSpec) }) } func Test_GetKustomizeOptions(t *testing.T) { t.Run("Get Kustomize parameter for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.Prefixed("", common.KustomizeApplicationNameAnnotationSuffix), "dummy"): "argoproj/argo-cd", } img := NewFromIdentifier("dummy=foo/bar:1.12") paramName := img.GetParameterKustomizeImageName(annotations, "") assert.Equal(t, "argoproj/argo-cd", paramName) img = NewFromIdentifier("dummy2=foo2/bar2:1.12") paramName = img.GetParameterKustomizeImageName(annotations, "") assert.Equal(t, "", paramName) }) } func Test_GetSortOption(t *testing.T) { t.Run("Get update strategy semver for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "semver", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategySemVer, sortMode) }) t.Run("Use update strategy newest-build for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "newest-build", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyNewestBuild, sortMode) }) t.Run("Get update strategy date for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "latest", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyNewestBuild, sortMode) }) t.Run("Get update strategy name for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "name", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyAlphabetical, sortMode) }) t.Run("Use update strategy alphabetical for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "alphabetical", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyAlphabetical, sortMode) }) t.Run("Get update strategy option configured application because of invalid option", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "invalid", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategySemVer, sortMode) }) t.Run("Get update strategy option configured application because of option not set", func(t *testing.T) { annotations := map[string]string{} img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategySemVer, sortMode) }) t.Run("Prefer update strategy option from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.UpdateStrategyAnnotationSuffix, "dummy"): "alphabetical", common.ApplicationWideUpdateStrategyAnnotationSuffix: "newest-build", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyAlphabetical, sortMode) }) t.Run("Get update strategy option from application-wide annotation", func(t *testing.T) { annotations := map[string]string{ common.ApplicationWideUpdateStrategyAnnotationSuffix: "newest-build", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyNewestBuild, sortMode) }) t.Run("Get update strategy option digest from application-wide annotation", func(t *testing.T) { annotations := map[string]string{ common.ApplicationWideUpdateStrategyAnnotationSuffix: "digest", } img := NewFromIdentifier("dummy=foo/bar:1.12") sortMode := img.GetParameterUpdateStrategy(annotations, "") assert.Equal(t, StrategyDigest, sortMode) }) } func Test_GetMatchOption(t *testing.T) { t.Run("Get regexp match option for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.AllowTagsOptionAnnotationSuffix, "dummy"): "regexp:a-z", } img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, matchArgs := img.GetParameterMatch(annotations, "") require.NotNil(t, matchFunc) require.NotNil(t, matchArgs) assert.IsType(t, ®exp.Regexp{}, matchArgs) }) t.Run("Get regexp match option for configured application with invalid expression", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.AllowTagsOptionAnnotationSuffix, "dummy"): `regexp:/foo\`, } img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, matchArgs := img.GetParameterMatch(annotations, "") require.NotNil(t, matchFunc) require.Nil(t, matchArgs) }) t.Run("Get invalid match option for configured application", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.AllowTagsOptionAnnotationSuffix, "dummy"): "invalid", } img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, matchArgs := img.GetParameterMatch(annotations, "") require.NotNil(t, matchFunc) require.Equal(t, false, matchFunc("", nil)) assert.Nil(t, matchArgs) }) t.Run("No match option for configured application", func(t *testing.T) { annotations := map[string]string{} img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, matchArgs := img.GetParameterMatch(annotations, "") require.NotNil(t, matchFunc) require.Equal(t, true, matchFunc("", nil)) assert.Equal(t, "", matchArgs) }) t.Run("Prefer match option from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.AllowTagsOptionAnnotationSuffix, "dummy"): "regexp:^[0-9]", common.ApplicationWideAllowTagsOptionAnnotationSuffix: "regexp:^v", } img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, matchArgs := img.GetParameterMatch(annotations, "") require.NotNil(t, matchFunc) require.NotNil(t, matchArgs) assert.IsType(t, ®exp.Regexp{}, matchArgs) assert.True(t, matchFunc("0.0.1", matchArgs)) assert.False(t, matchFunc("v0.0.1", matchArgs)) }) t.Run("Get match option from application-wide annotation", func(t *testing.T) { annotations := map[string]string{ common.ApplicationWideAllowTagsOptionAnnotationSuffix: "regexp:^v", } img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, matchArgs := img.GetParameterMatch(annotations, "") require.NotNil(t, matchFunc) require.NotNil(t, matchArgs) assert.IsType(t, ®exp.Regexp{}, matchArgs) assert.False(t, matchFunc("0.0.1", matchArgs)) assert.True(t, matchFunc("v0.0.1", matchArgs)) }) } func Test_GetSecretOption(t *testing.T) { t.Run("Get cred source from annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.PullSecretAnnotationSuffix, "dummy"): "pullsecret:foo/bar", } img := NewFromIdentifier("dummy=foo/bar:1.12") credSrc := img.GetParameterPullSecret(annotations, "") require.NotNil(t, credSrc) assert.Equal(t, CredentialSourcePullSecret, credSrc.Type) assert.Equal(t, "foo", credSrc.SecretNamespace) assert.Equal(t, "bar", credSrc.SecretName) assert.Equal(t, ".dockerconfigjson", credSrc.SecretField) }) t.Run("Invalid reference in annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.PullSecretAnnotationSuffix, "dummy"): "foo/bar", } img := NewFromIdentifier("dummy=foo/bar:1.12") credSrc := img.GetParameterPullSecret(annotations, "") require.Nil(t, credSrc) }) t.Run("Missing pull secret in annotation", func(t *testing.T) { annotations := map[string]string{} img := NewFromIdentifier("dummy=foo/bar:1.12") credSrc := img.GetParameterPullSecret(annotations, "") require.Nil(t, credSrc) }) t.Run("Prefer cred source from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.PullSecretAnnotationSuffix, "dummy"): "pullsecret:image/specific", common.ApplicationWidePullSecretAnnotationSuffix: "pullsecret:app/wide", } img := NewFromIdentifier("dummy=foo/bar:1.12") credSrc := img.GetParameterPullSecret(annotations, "") require.NotNil(t, credSrc) assert.Equal(t, CredentialSourcePullSecret, credSrc.Type) assert.Equal(t, "image", credSrc.SecretNamespace) assert.Equal(t, "specific", credSrc.SecretName) assert.Equal(t, ".dockerconfigjson", credSrc.SecretField) }) t.Run("Get cred source from application-wide annotation", func(t *testing.T) { annotations := map[string]string{ common.ApplicationWidePullSecretAnnotationSuffix: "pullsecret:app/wide", } img := NewFromIdentifier("dummy=foo/bar:1.12") credSrc := img.GetParameterPullSecret(annotations, "") require.NotNil(t, credSrc) assert.Equal(t, CredentialSourcePullSecret, credSrc.Type) assert.Equal(t, "app", credSrc.SecretNamespace) assert.Equal(t, "wide", credSrc.SecretName) assert.Equal(t, ".dockerconfigjson", credSrc.SecretField) }) } func Test_GetIgnoreTags(t *testing.T) { t.Run("Get list of tags to ignore from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.IgnoreTagsOptionAnnotationSuffix, "dummy"): "tag1, ,tag2, tag3 , tag4", } img := NewFromIdentifier("dummy=foo/bar:1.12") tags := img.GetParameterIgnoreTags(annotations, "") require.Len(t, tags, 4) assert.Equal(t, "tag1", tags[0]) assert.Equal(t, "tag2", tags[1]) assert.Equal(t, "tag3", tags[2]) assert.Equal(t, "tag4", tags[3]) }) t.Run("No tags to ignore from image-specific annotation", func(t *testing.T) { annotations := map[string]string{} img := NewFromIdentifier("dummy=foo/bar:1.12") tags := img.GetParameterIgnoreTags(annotations, "") require.Nil(t, tags) }) t.Run("Prefer list of tags to ignore from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.IgnoreTagsOptionAnnotationSuffix, "dummy"): "tag1, tag2", common.ApplicationWideIgnoreTagsOptionAnnotationSuffix: "tag3, tag4", } img := NewFromIdentifier("dummy=foo/bar:1.12") tags := img.GetParameterIgnoreTags(annotations, "") require.Len(t, tags, 2) assert.Equal(t, "tag1", tags[0]) assert.Equal(t, "tag2", tags[1]) }) t.Run("Get list of tags to ignore from application-wide annotation", func(t *testing.T) { annotations := map[string]string{ common.ApplicationWideIgnoreTagsOptionAnnotationSuffix: "tag3, tag4", } img := NewFromIdentifier("dummy=foo/bar:1.12") tags := img.GetParameterIgnoreTags(annotations, "") require.Len(t, tags, 2) assert.Equal(t, "tag3", tags[0]) assert.Equal(t, "tag4", tags[1]) }) } func Test_HasForceUpdateOptionAnnotation(t *testing.T) { t.Run("Get force-update option from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.ForceUpdateOptionAnnotationSuffix, "dummy"): "true", } img := NewFromIdentifier("dummy=foo/bar:1.12") forceUpdate := img.HasForceUpdateOptionAnnotation(annotations, "") assert.True(t, forceUpdate) }) t.Run("Prefer force-update option from image-specific annotation", func(t *testing.T) { annotations := map[string]string{ fmt.Sprintf(common.ForceUpdateOptionAnnotationSuffix, "dummy"): "true", common.ApplicationWideForceUpdateOptionAnnotationSuffix: "false", } img := NewFromIdentifier("dummy=foo/bar:1.12") forceUpdate := img.HasForceUpdateOptionAnnotation(annotations, "") assert.True(t, forceUpdate) }) t.Run("Get force-update option from application-wide annotation", func(t *testing.T) { annotations := map[string]string{ common.ApplicationWideForceUpdateOptionAnnotationSuffix: "false", } img := NewFromIdentifier("dummy=foo/bar:1.12") forceUpdate := img.HasForceUpdateOptionAnnotation(annotations, "") assert.False(t, forceUpdate) }) } func Test_GetPlatformOptions(t *testing.T) { t.Run("Empty platform options with restriction", func(t *testing.T) { annotations := map[string]string{} img := NewFromIdentifier("dummy=foo/bar:1.12") opts := img.GetPlatformOptions(annotations, false, "") os := runtime.GOOS arch := runtime.GOARCH platform := opts.Platforms()[0] slashCount := strings.Count(platform, "/") if slashCount == 1 { assert.True(t, opts.WantsPlatform(os, arch, "")) assert.True(t, opts.WantsPlatform(os, arch, "invalid")) } else if slashCount == 2 { assert.False(t, opts.WantsPlatform(os, arch, "")) assert.False(t, opts.WantsPlatform(os, arch, "invalid")) } else { t.Fatal("invalid platform options ", platform) } }) t.Run("Empty platform options without restriction", func(t *testing.T) { annotations := map[string]string{} img := NewFromIdentifier("dummy=foo/bar:1.12") opts := img.GetPlatformOptions(annotations, true, "") os := runtime.GOOS arch := runtime.GOARCH assert.True(t, opts.WantsPlatform(os, arch, "")) assert.True(t, opts.WantsPlatform(os, arch, "invalid")) assert.True(t, opts.WantsPlatform("windows", "amd64", "")) }) t.Run("Single platform without variant requested", func(t *testing.T) { os := "linux" arch := "arm64" variant := "v8" annotations := map[string]string{ fmt.Sprintf(common.PlatformsAnnotationSuffix, "dummy"): options.PlatformKey(os, arch, variant), } img := NewFromIdentifier("dummy=foo/bar:1.12") opts := img.GetPlatformOptions(annotations, false, "") assert.True(t, opts.WantsPlatform(os, arch, variant)) assert.False(t, opts.WantsPlatform(os, arch, "invalid")) }) t.Run("Single platform with variant requested", func(t *testing.T) { os := "linux" arch := "arm" variant := "v6" annotations := map[string]string{ fmt.Sprintf(common.PlatformsAnnotationSuffix, "dummy"): options.PlatformKey(os, arch, variant), } img := NewFromIdentifier("dummy=foo/bar:1.12") opts := img.GetPlatformOptions(annotations, false, "") assert.True(t, opts.WantsPlatform(os, arch, variant)) assert.False(t, opts.WantsPlatform(os, arch, "")) assert.False(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, "")) assert.False(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, variant)) }) t.Run("Multiple platforms requested", func(t *testing.T) { os := "linux" arch := "arm" variant := "v6" annotations := map[string]string{ fmt.Sprintf(common.PlatformsAnnotationSuffix, "dummy"): options.PlatformKey(os, arch, variant) + ", " + options.PlatformKey(runtime.GOOS, runtime.GOARCH, ""), } img := NewFromIdentifier("dummy=foo/bar:1.12") opts := img.GetPlatformOptions(annotations, false, "") assert.True(t, opts.WantsPlatform(os, arch, variant)) assert.True(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, "")) assert.False(t, opts.WantsPlatform(os, arch, "")) assert.True(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, variant)) }) t.Run("Invalid platform requested", func(t *testing.T) { os := "linux" arch := "arm" variant := "v6" annotations := map[string]string{ fmt.Sprintf(common.PlatformsAnnotationSuffix, "dummy"): "invalid", } img := NewFromIdentifier("dummy=foo/bar:1.12") opts := img.GetPlatformOptions(annotations, false, "") assert.False(t, opts.WantsPlatform(os, arch, variant)) assert.False(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, "")) assert.False(t, opts.WantsPlatform(os, arch, "")) assert.False(t, opts.WantsPlatform(runtime.GOOS, runtime.GOARCH, variant)) }) } func Test_ContainerImage_ParseMatchfunc(t *testing.T) { img := NewFromIdentifier("dummy=foo/bar:1.12") matchFunc, pattern := img.ParseMatchfunc("any") assert.True(t, matchFunc("MatchFuncAny any tag name", pattern)) assert.Nil(t, pattern) matchFunc, pattern = img.ParseMatchfunc("ANY") assert.True(t, matchFunc("MatchFuncAny any tag name", pattern)) assert.Nil(t, pattern) matchFunc, pattern = img.ParseMatchfunc("other") assert.False(t, matchFunc("MatchFuncNone any tag name", pattern)) assert.Nil(t, pattern) matchFunc, pattern = img.ParseMatchfunc("not-regexp:a-z") assert.False(t, matchFunc("MatchFuncNone any tag name", pattern)) assert.Nil(t, pattern) matchFunc, pattern = img.ParseMatchfunc("regexp:[aA-zZ]") assert.True(t, matchFunc("MatchFuncRegexp-tag-name", pattern)) compiledRegexp, _ := regexp.Compile("[aA-zZ]") assert.Equal(t, compiledRegexp, pattern) matchFunc, pattern = img.ParseMatchfunc("RegExp:[aA-zZ]") assert.True(t, matchFunc("MatchFuncRegexp-tag-name", pattern)) compiledRegexp, _ = regexp.Compile("[aA-zZ]") assert.Equal(t, compiledRegexp, pattern) matchFunc, pattern = img.ParseMatchfunc("regexp:[aA-zZ") //invalid regexp: missing end ] assert.False(t, matchFunc("MatchFuncNone-tag-name", pattern)) assert.Nil(t, pattern) }