diff --git a/.github/workflows/ci-tests.yaml b/.github/workflows/ci-tests.yaml index 445b0ba2..57d0ea4d 100644 --- a/.github/workflows/ci-tests.yaml +++ b/.github/workflows/ci-tests.yaml @@ -57,7 +57,7 @@ jobs: - name: Run golangci-lint uses: golangci/golangci-lint-action@v2 with: - version: v1.46.2 + version: v1.52.2 args: --timeout 5m test: name: Ensure unit tests are passing diff --git a/cmd/run.go b/cmd/run.go index 305863db..c6fda00a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -14,6 +14,7 @@ import ( "github.com/argoproj-labs/argocd-image-updater/pkg/common" "github.com/argoproj-labs/argocd-image-updater/pkg/env" "github.com/argoproj-labs/argocd-image-updater/pkg/health" + "github.com/argoproj-labs/argocd-image-updater/pkg/image" "github.com/argoproj-labs/argocd-image-updater/pkg/log" "github.com/argoproj-labs/argocd-image-updater/pkg/metrics" "github.com/argoproj-labs/argocd-image-updater/pkg/registry" @@ -343,7 +344,7 @@ func warmupImageCache(cfg *ImageUpdaterConfig) error { entries := 0 eps := registry.ConfiguredEndpoints() for _, ep := range eps { - r, err := registry.GetRegistryEndpoint(ep) + r, err := registry.GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ep}) if err == nil { entries += r.Cache.NumEntries() } diff --git a/cmd/test.go b/cmd/test.go index afc9cfbe..bfad7431 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -113,7 +113,7 @@ argocd-image-updater test nginx --allow-tags '^1.19.\d+(\-.*)*$' --update-strate } } - ep, err := registry.GetRegistryEndpoint(img.RegistryURL) + ep, err := registry.GetRegistryEndpoint(img) if err != nil { logCtx.Fatalf("could not get registry endpoint: %v", err) } diff --git a/ext/git/client.go b/ext/git/client.go index fa470bcc..11a9a758 100644 --- a/ext/git/client.go +++ b/ext/git/client.go @@ -155,12 +155,12 @@ func NewClientExt(rawRepoURL string, root string, creds Creds, insecure bool, en // Returns a HTTP client object suitable for go-git to use using the following // pattern: -// - If insecure is true, always returns a client with certificate verification -// turned off. -// - If one or more custom certificates are stored for the repository, returns -// a client with those certificates in the list of root CAs used to verify -// the server's certificate. -// - Otherwise (and on non-fatal errors), a default HTTP client is returned. +// - If insecure is true, always returns a client with certificate verification +// turned off. +// - If one or more custom certificates are stored for the repository, returns +// a client with those certificates in the list of root CAs used to verify +// the server's certificate. +// - Otherwise (and on non-fatal errors), a default HTTP client is returned. func GetRepoHTTPClient(repoURL string, insecure bool, creds Creds, proxyURL string) *http.Client { // Default HTTP client var customHTTPClient = &http.Client{ diff --git a/pkg/argocd/update.go b/pkg/argocd/update.go index c92730dc..a88cf06b 100644 --- a/pkg/argocd/update.go +++ b/pkg/argocd/update.go @@ -180,7 +180,7 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat imgCtx.Debugf("Considering this image for update") - rep, err := registry.GetRegistryEndpoint(applicationImage.RegistryURL) + rep, err := registry.GetRegistryEndpoint(applicationImage) if err != nil { imgCtx.Errorf("Could not get registry endpoint from configuration: %v", err) result.NumErrors += 1 diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index 8bf2a8ec..86640d71 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -3,6 +3,7 @@ package registry import ( "testing" + "github.com/argoproj-labs/argocd-image-updater/pkg/image" "github.com/argoproj-labs/argocd-image-updater/pkg/options" "github.com/distribution/distribution/v3/manifest/schema1" @@ -16,7 +17,7 @@ func Test_TagMetadata(t *testing.T) { History: []schema1.History{}, }, } - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) client, err := NewClient(ep, "", "") require.NoError(t, err) @@ -35,7 +36,7 @@ func Test_TagMetadata(t *testing.T) { }, } - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) client, err := NewClient(ep, "", "") require.NoError(t, err) @@ -54,7 +55,7 @@ func Test_TagMetadata(t *testing.T) { }, } - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) client, err := NewClient(ep, "", "") require.NoError(t, err) @@ -74,7 +75,7 @@ func Test_TagMetadata(t *testing.T) { }, }, } - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) client, err := NewClient(ep, "", "") require.NoError(t, err) diff --git a/pkg/registry/config_test.go b/pkg/registry/config_test.go index 080dd004..5009e7ac 100644 --- a/pkg/registry/config_test.go +++ b/pkg/registry/config_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/argoproj-labs/argocd-image-updater/pkg/image" "github.com/argoproj-labs/argocd-image-updater/test/fixture" "github.com/stretchr/testify/assert" @@ -81,15 +82,15 @@ func Test_LoadRegistryConfiguration(t *testing.T) { err := LoadRegistryConfiguration("../../config/example-config.yaml", true) require.NoError(t, err) assert.Len(t, registries, 4) - reg, err := GetRegistryEndpoint("gcr.io") + reg, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "gcr.io"}) require.NoError(t, err) assert.Equal(t, "pullsecret:foo/bar", reg.Credentials) - reg, err = GetRegistryEndpoint("ghcr.io") + reg, err = GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "ghcr.io"}) require.NoError(t, err) assert.Equal(t, "ext:/some/script", reg.Credentials) assert.Equal(t, 5*time.Hour, reg.CredsExpire) RestoreDefaultRegistryConfiguration() - reg, err = GetRegistryEndpoint("gcr.io") + reg, err = GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "gcr.io"}) require.NoError(t, err) assert.Equal(t, "", reg.Credentials) }) diff --git a/pkg/registry/endpoints.go b/pkg/registry/endpoints.go index a04ff3b2..242acf49 100644 --- a/pkg/registry/endpoints.go +++ b/pkg/registry/endpoints.go @@ -10,6 +10,7 @@ import ( "time" "github.com/argoproj-labs/argocd-image-updater/pkg/cache" + "github.com/argoproj-labs/argocd-image-updater/pkg/image" "github.com/argoproj-labs/argocd-image-updater/pkg/log" "go.uber.org/ratelimit" @@ -179,8 +180,27 @@ func inferRegistryEndpointFromPrefix(prefix string) *RegistryEndpoint { return NewRegistryEndpoint(prefix, prefix, apiURL, "", "", false, TagListSortUnsorted, 20, 0) } -// GetRegistryEndpoint retrieves the endpoint information for the given prefix -func GetRegistryEndpoint(prefix string) (*RegistryEndpoint, error) { +// find registry by prefix based on full image name +func findRegistryEndpointByImage(img *image.ContainerImage) (ep *RegistryEndpoint) { + log.Debugf("Try to find endpoint by image: %s/%s", img.RegistryURL, img.ImageName) + registryLock.RLock() + imgName := fmt.Sprintf("%s/%s", img.RegistryURL, img.ImageName) + + for _, registry := range registries { + if (ep == nil && strings.HasPrefix(imgName, registry.RegistryPrefix)) || (strings.HasPrefix(imgName, registry.RegistryPrefix) && len(registry.RegistryPrefix) > len(ep.RegistryPrefix)) { + log.Debugf("Selected registry: '%s' (last selection in log - final)", registry.RegistryName) + ep = registry + } + } + + registryLock.RUnlock() + return +} + +// GetRegistryEndpoint retrieves the endpoint information for the given image +func GetRegistryEndpoint(img *image.ContainerImage) (*RegistryEndpoint, error) { + prefix := img.RegistryURL + if prefix == "" { if defaultRegistry == nil { return nil, fmt.Errorf("no default endpoint configured") @@ -197,7 +217,12 @@ func GetRegistryEndpoint(prefix string) (*RegistryEndpoint, error) { return registry, nil } else { var err error - ep := inferRegistryEndpointFromPrefix(prefix) + ep := findRegistryEndpointByImage(img) + if ep != nil { + return ep, err + } + + ep = inferRegistryEndpointFromPrefix(prefix) if ep != nil { err = AddRegistryEndpoint(ep) } else { @@ -235,7 +260,7 @@ func GetDefaultRegistry() *RegistryEndpoint { // SetRegistryEndpointCredentials allows to change the credentials used for // endpoint access for existing RegistryEndpoint configuration func SetRegistryEndpointCredentials(prefix, credentials string) error { - registry, err := GetRegistryEndpoint(prefix) + registry, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: prefix}) if err != nil { return err } diff --git a/pkg/registry/endpoints_test.go b/pkg/registry/endpoints_test.go index 9a309b6f..1bf4c0b4 100644 --- a/pkg/registry/endpoints_test.go +++ b/pkg/registry/endpoints_test.go @@ -5,6 +5,8 @@ import ( "sync" "testing" + "github.com/argoproj-labs/argocd-image-updater/pkg/image" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -13,21 +15,21 @@ func Test_GetEndpoints(t *testing.T) { RestoreDefaultRegistryConfiguration() t.Run("Get default endpoint", func(t *testing.T) { - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, "docker.io", ep.RegistryPrefix) }) t.Run("Get GCR endpoint", func(t *testing.T) { - ep, err := GetRegistryEndpoint("gcr.io") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "gcr.io"}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, ep.RegistryPrefix, "gcr.io") }) t.Run("Infer endpoint", func(t *testing.T) { - ep, err := GetRegistryEndpoint("foobar.com") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "foobar.com"}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, "foobar.com", ep.RegistryPrefix) @@ -43,7 +45,7 @@ func Test_AddEndpoint(t *testing.T) { require.NoError(t, err) }) t.Run("Get example.com endpoint", func(t *testing.T) { - ep, err := GetRegistryEndpoint("example.com") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "example.com"}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, ep.RegistryPrefix, "example.com") @@ -56,7 +58,7 @@ func Test_AddEndpoint(t *testing.T) { t.Run("Change existing endpoint", func(t *testing.T) { err := AddRegistryEndpoint(NewRegistryEndpoint("example.com", "Example", "https://example.com", "", "library", true, TagListSortLatestFirst, 5, 0)) require.NoError(t, err) - ep, err := GetRegistryEndpoint("example.com") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "example.com"}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, ep.Insecure, true) @@ -71,7 +73,7 @@ func Test_SetEndpointCredentials(t *testing.T) { t.Run("Set credentials on default registry", func(t *testing.T) { err := SetRegistryEndpointCredentials("", "env:FOOBAR") require.NoError(t, err) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, ep.Credentials, "env:FOOBAR") @@ -80,13 +82,31 @@ func Test_SetEndpointCredentials(t *testing.T) { t.Run("Unset credentials on default registry", func(t *testing.T) { err := SetRegistryEndpointCredentials("", "") require.NoError(t, err) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) require.NotNil(t, ep) assert.Equal(t, ep.Credentials, "") }) } +func Test_SelectRegistryBasedOnMaxPrefixContains(t *testing.T) { + RestoreDefaultRegistryConfiguration() + + t.Run("Set credentials on default registry", func(t *testing.T) { + err := SetRegistryEndpointCredentials("foo.bar/prefix1", "env:FOOBAR_1") + require.NoError(t, err) + err = SetRegistryEndpointCredentials("foo.bar/prefix2", "env:FOOBAR_2") + require.NoError(t, err) + err = SetRegistryEndpointCredentials("foo.bar/prefix1/sub-prefix", "env:FOOBAR_SUB_1") + require.NoError(t, err) + + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "foo.bar", ImageName: "prefix1/sub-prefix/image"}) + require.NoError(t, err) + require.NotNil(t, ep) + assert.Equal(t, ep.Credentials, "env:FOOBAR_SUB_1") + }) +} + func Test_EndpointConcurrentAccess(t *testing.T) { RestoreDefaultRegistryConfiguration() const numRuns = 50 @@ -96,7 +116,7 @@ func Test_EndpointConcurrentAccess(t *testing.T) { wg.Add(numRuns) for i := 0; i < numRuns; i++ { go func() { - ep, err := GetRegistryEndpoint("gcr.io") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "gcr.io"}) require.NoError(t, err) require.NotNil(t, ep) wg.Done() @@ -114,7 +134,7 @@ func Test_EndpointConcurrentAccess(t *testing.T) { creds := fmt.Sprintf("secret:foo/secret-%d", i) err := SetRegistryEndpointCredentials("", creds) require.NoError(t, err) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) require.NotNil(t, ep) wg.Done() @@ -132,7 +152,7 @@ func Test_SetDefault(t *testing.T) { assert.Equal(t, "docker.io", dep.RegistryPrefix) assert.True(t, dep.IsDefault) - ep, err := GetRegistryEndpoint("ghcr.io") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "ghcr.io"}) require.NoError(t, err) require.NotNil(t, ep) require.False(t, ep.IsDefault) @@ -146,7 +166,7 @@ func Test_SetDefault(t *testing.T) { func Test_DeepCopy(t *testing.T) { t.Run("DeepCopy endpoint object", func(t *testing.T) { - ep, err := GetRegistryEndpoint("docker.pkg.github.com") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "docker.pkg.github.com"}) require.NoError(t, err) require.NotNil(t, ep) newEp := ep.DeepCopy() diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go index d558944b..5082b85a 100644 --- a/pkg/registry/registry_test.go +++ b/pkg/registry/registry_test.go @@ -23,7 +23,7 @@ func Test_GetTags(t *testing.T) { regClient.On("NewRepository", mock.Anything).Return(nil) regClient.On("Tags", mock.Anything).Return([]string{"1.2.0", "1.2.1", "1.2.2"}, nil) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) img := image.NewFromIdentifier("foo/bar:1.2.0") @@ -42,7 +42,7 @@ func Test_GetTags(t *testing.T) { regClient.On("NewRepository", mock.Anything).Return(nil) regClient.On("Tags", mock.Anything).Return([]string{"1.2.0", "1.2.1", "1.2.2"}, nil) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) img := image.NewFromIdentifier("foo/bar:1.2.0") @@ -65,7 +65,7 @@ func Test_GetTags(t *testing.T) { regClient.On("NewRepository", mock.Anything).Return(nil) regClient.On("Tags", mock.Anything).Return([]string{"1.2.0", "1.2.1", "1.2.2"}, nil) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) img := image.NewFromIdentifier("foo/bar:1.2.0") @@ -97,7 +97,7 @@ func Test_GetTags(t *testing.T) { regClient.On("ManifestForTag", mock.Anything, mock.Anything).Return(meta1, nil) regClient.On("TagMetadata", mock.Anything, mock.Anything).Return(&tag.TagInfo{}, nil) - ep, err := GetRegistryEndpoint("") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: ""}) require.NoError(t, err) ep.Cache.ClearCache() @@ -132,7 +132,7 @@ registries: // New registry configuration err = AddRegistryEndpointFromConfig(epl.Items[0]) require.NoError(t, err) - ep, err := GetRegistryEndpoint("ghcr.io") + ep, err := GetRegistryEndpoint(&image.ContainerImage{RegistryURL: "ghcr.io"}) require.NoError(t, err) require.NotEqual(t, 0, ep.CredsExpire)