summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorIshita Sequeira <46771830+ishitasequeira@users.noreply.github.com>2025-01-07 15:08:15 -0500
committerGitHub <noreply@github.com>2025-01-07 15:08:15 -0500
commit1a998b1415f937fde94b8912e4eec9235e542e93 (patch)
treeae4b38dc27f27b981a55bc57e188868e52949ef9 /pkg
parent9a20452b7fbe84d13a941a19a29d931481c61d88 (diff)
Move references for kube, image, registry to registry-scanner (#998)
Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>
Diffstat (limited to 'pkg')
-rw-r--r--pkg/argocd/argocd.go6
-rw-r--r--pkg/argocd/argocd_test.go21
-rw-r--r--pkg/argocd/git.go2
-rw-r--r--pkg/argocd/git_test.go2
-rw-r--r--pkg/argocd/gitcreds.go12
-rw-r--r--pkg/argocd/gitcreds_test.go9
-rw-r--r--pkg/argocd/update.go18
-rw-r--r--pkg/argocd/update_test.go253
-rw-r--r--pkg/image/credentials.go261
-rw-r--r--pkg/image/credentials_test.go410
-rw-r--r--pkg/image/image.go275
-rw-r--r--pkg/image/image_test.go226
-rw-r--r--pkg/image/kustomize.go39
-rw-r--r--pkg/image/kustomize_test.go26
-rw-r--r--pkg/image/matchfunc.go27
-rw-r--r--pkg/image/matchfunc_test.go27
-rw-r--r--pkg/image/options.go296
-rw-r--r--pkg/image/options_test.go493
-rw-r--r--pkg/image/version.go220
-rw-r--r--pkg/image/version_test.go196
-rw-r--r--pkg/kube/kubernetes.go83
-rw-r--r--pkg/kube/kubernetes_test.go23
-rw-r--r--pkg/registry/client.go449
-rw-r--r--pkg/registry/client_test.go609
-rw-r--r--pkg/registry/config.go139
-rw-r--r--pkg/registry/config_test.go110
-rw-r--r--pkg/registry/endpoints.go305
-rw-r--r--pkg/registry/endpoints_test.go354
-rw-r--r--pkg/registry/mocks/Limiter.go46
-rw-r--r--pkg/registry/mocks/Manager.go80
-rw-r--r--pkg/registry/mocks/Manifest.go84
-rw-r--r--pkg/registry/mocks/ManifestService.go149
-rw-r--r--pkg/registry/mocks/RegistryClient.go125
-rw-r--r--pkg/registry/mocks/Repository.go128
-rw-r--r--pkg/registry/mocks/RoundTripper.go58
-rw-r--r--pkg/registry/mocks/TagService.go153
-rw-r--r--pkg/registry/registry.go222
-rw-r--r--pkg/registry/registry_test.go162
38 files changed, 225 insertions, 5873 deletions
diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go
index 6ba85d5..fdcc69c 100644
--- a/pkg/argocd/argocd.go
+++ b/pkg/argocd/argocd.go
@@ -9,10 +9,10 @@ import (
"time"
"github.com/argoproj-labs/argocd-image-updater/pkg/common"
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
"github.com/argoproj-labs/argocd-image-updater/pkg/metrics"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/env"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
argocdclient "github.com/argoproj/argo-cd/v2/pkg/apiclient"
@@ -24,7 +24,7 @@ import (
// Kubernetes based client
type k8sClient struct {
- kubeClient *kube.KubernetesClient
+ kubeClient *kube.ImageUpdaterKubernetesClient
}
// GetApplication retrieves an application by name across all namespaces.
@@ -99,7 +99,7 @@ func (client *k8sClient) UpdateSpec(ctx context.Context, spec *application.Appli
}
// NewK8SClient creates a new kubernetes client to interact with kubernetes api-server.
-func NewK8SClient(kubeClient *kube.KubernetesClient) (ArgoCD, error) {
+func NewK8SClient(kubeClient *kube.ImageUpdaterKubernetesClient) (ArgoCD, error) {
return &k8sClient{kubeClient: kubeClient}, nil
}
diff --git a/pkg/argocd/argocd_test.go b/pkg/argocd/argocd_test.go
index 28124f0..00660fb 100644
--- a/pkg/argocd/argocd_test.go
+++ b/pkg/argocd/argocd_test.go
@@ -7,8 +7,9 @@ import (
"testing"
"github.com/argoproj-labs/argocd-image-updater/pkg/common"
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
+ registryKube "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/kube"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -1015,8 +1016,10 @@ func TestKubernetesClient(t *testing.T) {
ObjectMeta: v1.ObjectMeta{Name: "test-app2", Namespace: "testns2"},
}
- client, err := NewK8SClient(&kube.KubernetesClient{
- Namespace: "testns1",
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Namespace: "testns1",
+ },
ApplicationsClientset: fake.NewSimpleClientset(app1, app2),
})
@@ -1057,7 +1060,7 @@ func TestKubernetesClient(t *testing.T) {
})
// Create the Kubernetes client
- client, err := NewK8SClient(&kube.KubernetesClient{
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
ApplicationsClientset: clientset,
})
require.NoError(t, err)
@@ -1087,7 +1090,7 @@ func TestKubernetesClient(t *testing.T) {
)
// Create the Kubernetes client
- client, err := NewK8SClient(&kube.KubernetesClient{
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
ApplicationsClientset: clientset,
})
require.NoError(t, err)
@@ -1117,7 +1120,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) {
}
})
- client, err := NewK8SClient(&kube.KubernetesClient{
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
ApplicationsClientset: clientset,
})
require.NoError(t, err)
@@ -1138,7 +1141,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) {
// Create a fake empty clientset
clientset := fake.NewSimpleClientset()
- client, err := NewK8SClient(&kube.KubernetesClient{
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
ApplicationsClientset: clientset,
})
require.NoError(t, err)
@@ -1162,7 +1165,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{},
})
- client, err := NewK8SClient(&kube.KubernetesClient{
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
ApplicationsClientset: clientset,
})
require.NoError(t, err)
@@ -1191,7 +1194,7 @@ func TestKubernetesClientUpdateSpec(t *testing.T) {
Spec: v1alpha1.ApplicationSpec{},
})
- client, err := NewK8SClient(&kube.KubernetesClient{
+ client, err := NewK8SClient(&kube.ImageUpdaterKubernetesClient{
ApplicationsClientset: clientset,
})
require.NoError(t, err)
diff --git a/pkg/argocd/git.go b/pkg/argocd/git.go
index 25b2599..8ec2274 100644
--- a/pkg/argocd/git.go
+++ b/pkg/argocd/git.go
@@ -15,7 +15,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/order"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/ext/git"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
diff --git a/pkg/argocd/git_test.go b/pkg/argocd/git_test.go
index 4600c0f..cfd7914 100644
--- a/pkg/argocd/git_test.go
+++ b/pkg/argocd/git_test.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/argoproj-labs/argocd-image-updater/pkg/common"
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
"sigs.k8s.io/kustomize/api/types"
diff --git a/pkg/argocd/gitcreds.go b/pkg/argocd/gitcreds.go
index cfd5706..1aa3897 100644
--- a/pkg/argocd/gitcreds.go
+++ b/pkg/argocd/gitcreds.go
@@ -18,7 +18,7 @@ import (
)
// getGitCredsSource returns git credentials source that loads credentials from the secret or from Argo CD settings
-func getGitCredsSource(creds string, kubeClient *kube.KubernetesClient, wbc *WriteBackConfig) (GitCredsSource, error) {
+func getGitCredsSource(creds string, kubeClient *kube.ImageUpdaterKubernetesClient, wbc *WriteBackConfig) (GitCredsSource, error) {
switch {
case creds == "repocreds":
return func(app *v1alpha1.Application) (git.Creds, error) {
@@ -33,12 +33,12 @@ func getGitCredsSource(creds string, kubeClient *kube.KubernetesClient, wbc *Wri
}
// getCredsFromArgoCD loads repository credentials from Argo CD settings
-func getCredsFromArgoCD(wbc *WriteBackConfig, kubeClient *kube.KubernetesClient, project string) (git.Creds, error) {
+func getCredsFromArgoCD(wbc *WriteBackConfig, kubeClient *kube.ImageUpdaterKubernetesClient, project string) (git.Creds, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- settingsMgr := settings.NewSettingsManager(ctx, kubeClient.Clientset, kubeClient.Namespace)
- argocdDB := db.NewDB(kubeClient.Namespace, settingsMgr, kubeClient.Clientset)
+ settingsMgr := settings.NewSettingsManager(ctx, kubeClient.KubeClient.Clientset, kubeClient.KubeClient.Namespace)
+ argocdDB := db.NewDB(kubeClient.KubeClient.Namespace, settingsMgr, kubeClient.KubeClient.Clientset)
repo, err := argocdDB.GetRepository(ctx, wbc.GitRepo, project)
if err != nil {
return nil, err
@@ -112,12 +112,12 @@ func getCAPath(repoURL string) string {
}
// getCredsFromSecret loads repository credentials from secret
-func getCredsFromSecret(wbc *WriteBackConfig, credentialsSecret string, kubeClient *kube.KubernetesClient) (git.Creds, error) {
+func getCredsFromSecret(wbc *WriteBackConfig, credentialsSecret string, kubeClient *kube.ImageUpdaterKubernetesClient) (git.Creds, error) {
var credentials map[string][]byte
var err error
s := strings.SplitN(credentialsSecret, "/", 2)
if len(s) == 2 {
- credentials, err = kubeClient.GetSecretData(s[0], s[1])
+ credentials, err = kubeClient.KubeClient.GetSecretData(s[0], s[1])
if err != nil {
return nil, err
}
diff --git a/pkg/argocd/gitcreds_test.go b/pkg/argocd/gitcreds_test.go
index 0343cbb..d909141 100644
--- a/pkg/argocd/gitcreds_test.go
+++ b/pkg/argocd/gitcreds_test.go
@@ -5,6 +5,7 @@ import (
"github.com/argoproj-labs/argocd-image-updater/ext/git"
"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
+ registryKube "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/kube"
"github.com/argoproj-labs/argocd-image-updater/test/fake"
"github.com/argoproj-labs/argocd-image-updater/test/fixture"
@@ -21,8 +22,10 @@ func TestGetCredsFromSecret(t *testing.T) {
secret1 := fixture.NewSecret("foo", "bar", map[string][]byte{"username": []byte("myuser"), "password": []byte("mypass")})
secret2 := fixture.NewSecret("foo1", "bar1", map[string][]byte{"username": []byte("myuser")})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret1, secret2),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret1, secret2),
+ },
}
// Test case 1: Valid secret reference
@@ -53,7 +56,7 @@ func TestGetCredsFromSecret(t *testing.T) {
}
func TestGetGitCredsSource(t *testing.T) {
- kubeClient := &kube.KubernetesClient{}
+ kubeClient := &kube.ImageUpdaterKubernetesClient{}
wbc := &WriteBackConfig{
GitRepo: "https://github.com/example/repo.git",
GitCreds: git.NoopCredsStore{},
diff --git a/pkg/argocd/update.go b/pkg/argocd/update.go
index 0971fdd..872886b 100644
--- a/pkg/argocd/update.go
+++ b/pkg/argocd/update.go
@@ -13,10 +13,10 @@ import (
"github.com/argoproj-labs/argocd-image-updater/ext/git"
"github.com/argoproj-labs/argocd-image-updater/pkg/common"
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
- "github.com/argoproj-labs/argocd-image-updater/pkg/registry"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/registry"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
@@ -37,7 +37,7 @@ type ImageUpdaterResult struct {
type UpdateConfiguration struct {
NewRegFN registry.NewRegistryClient
ArgoClient ArgoCD
- KubeClient *kube.KubernetesClient
+ KubeClient *kube.ImageUpdaterKubernetesClient
UpdateApp *ApplicationImages
DryRun bool
GitCommitUser string
@@ -221,7 +221,7 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat
}
// The endpoint can provide default credentials for pulling images
- err = rep.SetEndpointCredentials(updateConf.KubeClient)
+ err = rep.SetEndpointCredentials(updateConf.KubeClient.KubeClient)
if err != nil {
imgCtx.Errorf("Could not set registry endpoint credentials: %v", err)
result.NumErrors += 1
@@ -231,7 +231,7 @@ func UpdateApplication(updateConf *UpdateConfiguration, state *SyncIterationStat
imgCredSrc := applicationImage.GetParameterPullSecret(updateConf.UpdateApp.Application.Annotations)
var creds *image.Credential = &image.Credential{}
if imgCredSrc != nil {
- creds, err = imgCredSrc.FetchCredentials(rep.RegistryAPI, updateConf.KubeClient)
+ creds, err = imgCredSrc.FetchCredentials(rep.RegistryAPI, updateConf.KubeClient.KubeClient)
if err != nil {
imgCtx.Warnf("Could not fetch credentials: %v", err)
result.NumErrors += 1
@@ -633,7 +633,7 @@ func setHelmValue(currentValues *yaml.MapSlice, key string, value interface{}) e
return err
}
-func getWriteBackConfig(app *v1alpha1.Application, kubeClient *kube.KubernetesClient, argoClient ArgoCD) (*WriteBackConfig, error) {
+func getWriteBackConfig(app *v1alpha1.Application, kubeClient *kube.ImageUpdaterKubernetesClient, argoClient ArgoCD) (*WriteBackConfig, error) {
wbc := &WriteBackConfig{}
// Default write-back is to use Argo CD API
wbc.Method = WriteBackApplication
@@ -675,10 +675,10 @@ func getWriteBackConfig(app *v1alpha1.Application, kubeClient *kube.KubernetesCl
return wbc, nil
}
-func parseDefaultTarget(appNamespace string, appName string, path string, kubeClient *kube.KubernetesClient) string {
+func parseDefaultTarget(appNamespace string, appName string, path string, kubeClient *kube.ImageUpdaterKubernetesClient) string {
// when running from command line and argocd-namespace is not set, e.g., via --argocd-namespace option,
// kubeClient.Namespace may be resolved to "default". In this case, also use the file name without namespace
- if appNamespace == kubeClient.Namespace || kubeClient.Namespace == "default" || appNamespace == "" {
+ if appNamespace == kubeClient.KubeClient.Namespace || kubeClient.KubeClient.Namespace == "default" || appNamespace == "" {
defaultTargetFile := fmt.Sprintf(common.DefaultTargetFilePatternWithoutNamespace, appName)
return filepath.Join(path, defaultTargetFile)
} else {
@@ -708,7 +708,7 @@ func parseTarget(writeBackTarget string, sourcePath string) string {
}
}
-func parseGitConfig(app *v1alpha1.Application, kubeClient *kube.KubernetesClient, wbc *WriteBackConfig, creds string) error {
+func parseGitConfig(app *v1alpha1.Application, kubeClient *kube.ImageUpdaterKubernetesClient, wbc *WriteBackConfig, creds string) error {
branch, ok := app.Annotations[common.GitBranchAnnotation]
if ok {
branches := strings.Split(strings.TrimSpace(branch), ":")
diff --git a/pkg/argocd/update_test.go b/pkg/argocd/update_test.go
index a6e87a1..480f994 100644
--- a/pkg/argocd/update_test.go
+++ b/pkg/argocd/update_test.go
@@ -15,10 +15,11 @@ import (
gitmock "github.com/argoproj-labs/argocd-image-updater/ext/git/mocks"
argomock "github.com/argoproj-labs/argocd-image-updater/pkg/argocd/mocks"
"github.com/argoproj-labs/argocd-image-updater/pkg/common"
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
"github.com/argoproj-labs/argocd-image-updater/pkg/kube"
- "github.com/argoproj-labs/argocd-image-updater/pkg/registry"
- regmock "github.com/argoproj-labs/argocd-image-updater/pkg/registry/mocks"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/image"
+ registryKube "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/kube"
+ "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/registry"
+ regmock "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/registry/mocks"
"github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
"github.com/argoproj-labs/argocd-image-updater/test/fake"
"github.com/argoproj-labs/argocd-image-updater/test/fixture"
@@ -44,8 +45,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
annotations := map[string]string{
common.ImageUpdaterAnnotation: "foobar=gcr.io/jannfis/foobar:>=1.0.1,foobar=gcr.io/jannfis/barbar:>=1.0.1",
@@ -111,8 +114,10 @@ func Test_UpdateApplication(t *testing.T) {
"githubAppInstallationID": []byte("87654321"),
"githubAppPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
annotations := map[string]string{
@@ -174,8 +179,10 @@ func Test_UpdateApplication(t *testing.T) {
return &regMock, nil
}
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -240,8 +247,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -302,8 +311,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -366,8 +377,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -427,8 +440,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -485,8 +500,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(fixture.NewSecret("foo", "bar", map[string][]byte{"creds": []byte("myuser:mypass")})),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(fixture.NewSecret("foo", "bar", map[string][]byte{"creds": []byte("myuser:mypass")})),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -543,8 +560,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -599,8 +618,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -655,8 +676,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
annotations := map[string]string{
common.ImageUpdaterAnnotation: "foobar=gcr.io/jannfis/foobar:>=1.0.1",
@@ -714,8 +737,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
annotations := map[string]string{
common.ImageUpdaterAnnotation: "foobar=gcr.io/jannfis/foobar:>=1.0.1",
@@ -789,8 +814,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -865,8 +892,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -925,8 +954,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -978,8 +1009,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -1034,8 +1067,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -1090,8 +1125,10 @@ func Test_UpdateApplication(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
appImages := &ApplicationImages{
Application: v1alpha1.Application{
@@ -2262,8 +2299,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2294,8 +2333,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2325,8 +2366,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2359,8 +2402,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2394,8 +2439,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2430,8 +2477,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2466,8 +2515,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2502,8 +2553,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2538,8 +2591,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2573,8 +2628,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
_, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2603,8 +2660,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2636,8 +2695,10 @@ func Test_GetWriteBackConfig(t *testing.T) {
argoClient := argomock.ArgoCD{}
argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeKubeClient(),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeKubeClient(),
+ },
}
wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient)
@@ -2655,8 +2716,10 @@ func Test_GetGitCreds(t *testing.T) {
"username": []byte("foo"),
"password": []byte("bar"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2696,8 +2759,10 @@ func Test_GetGitCreds(t *testing.T) {
"githubAppInstallationID": []byte("87654321"),
"githubAppPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2751,8 +2816,10 @@ func Test_GetGitCreds(t *testing.T) {
}
for _, secretEntry := range invalidSecretEntries {
secret = fixture.NewSecret("argocd-image-updater", "git-creds", secretEntry)
- kubeClient = kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient = kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
wbc, err = getWriteBackConfig(&app, &kubeClient, &argoClient)
require.NoError(t, err)
@@ -2767,8 +2834,10 @@ func Test_GetGitCreds(t *testing.T) {
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
"sshPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2818,9 +2887,11 @@ func Test_GetGitCreds(t *testing.T) {
})
fixture.AddPartOfArgoCDLabel(secret, configMap)
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret, configMap),
- Namespace: "argocd",
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret, configMap),
+ Namespace: "argocd",
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2857,8 +2928,10 @@ func Test_GetGitCreds(t *testing.T) {
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
"sshPrivateKex": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2892,8 +2965,10 @@ func Test_GetGitCreds(t *testing.T) {
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
"sshPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2928,8 +3003,10 @@ func Test_GetGitCreds(t *testing.T) {
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
"sshPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -2964,8 +3041,10 @@ func Test_GetGitCreds(t *testing.T) {
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
"sshPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
@@ -3007,8 +3086,10 @@ func Test_CommitUpdates(t *testing.T) {
secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{
"sshPrivateKey": []byte("foo"),
})
- kubeClient := kube.KubernetesClient{
- Clientset: fake.NewFakeClientsetWithResources(secret),
+ kubeClient := kube.ImageUpdaterKubernetesClient{
+ KubeClient: &registryKube.KubernetesClient{
+ Clientset: fake.NewFakeClientsetWithResources(secret),
+ },
}
app := v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{
@@ -3167,7 +3248,7 @@ helm:
})
t.Run("Good commit to helm override with argocd namespace", func(t *testing.T) {
- kubeClient.Namespace = "argocd"
+ kubeClient.KubeClient.Namespace = "argocd"
app := app.DeepCopy()
app.Status.SourceType = "Helm"
app.ObjectMeta.Namespace = "argocd"
@@ -3219,7 +3300,7 @@ helm:
})
t.Run("Good commit to helm override with another namespace", func(t *testing.T) {
- kubeClient.Namespace = "argocd"
+ kubeClient.KubeClient.Namespace = "argocd"
app := app.DeepCopy()
app.Status.SourceType = "Helm"
app.ObjectMeta.Namespace = "testNS"
diff --git a/pkg/image/credentials.go b/pkg/image/credentials.go
deleted file mode 100644
index 4a5559c..0000000
--- a/pkg/image/credentials.go
+++ /dev/null
@@ -1,261 +0,0 @@
-package image
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "os"
- "os/exec"
- "strings"
- "time"
-
- argoexec "github.com/argoproj/pkg/exec"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/kube"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
-)
-
-type CredentialSourceType int
-
-const (
- CredentialSourceUnknown CredentialSourceType = 0
- CredentialSourcePullSecret CredentialSourceType = 1
- CredentialSourceSecret CredentialSourceType = 2
- CredentialSourceEnv CredentialSourceType = 3
- CredentialSourceExt CredentialSourceType = 4
-)
-
-type CredentialSource struct {
- Type CredentialSourceType
- Registry string
- SecretNamespace string
- SecretName string
- SecretField string
- EnvName string
- ScriptPath string
-}
-
-type Credential struct {
- Username string
- Password string
-}
-
-const pullSecretField = ".dockerconfigjson"
-
-// gcr.io=secret:foo/bar#baz
-// gcr.io=pullsecret:foo/bar
-// gcr.io=env:FOOBAR
-
-func ParseCredentialSource(credentialSource string, requirePrefix bool) (*CredentialSource, error) {
- src := CredentialSource{}
- var secretDef string
- tokens := strings.SplitN(credentialSource, "=", 2)
- if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
- if requirePrefix {
- return nil, fmt.Errorf("invalid credential spec: %s", credentialSource)
- }
- secretDef = credentialSource
- } else {
- src.Registry = tokens[0]
- secretDef = tokens[1]
- }
-
- tokens = strings.Split(secretDef, ":")
- if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
- return nil, fmt.Errorf("invalid credential spec: %s", credentialSource)
- }
-
- var err error
- switch strings.ToLower(tokens[0]) {
- case "secret":
- err = src.parseSecretDefinition(tokens[1])
- src.Type = CredentialSourceSecret
- case "pullsecret":
- err = src.parsePullSecretDefinition(tokens[1])
- src.Type = CredentialSourcePullSecret
- case "env":
- err = src.parseEnvDefinition(tokens[1])
- src.Type = CredentialSourceEnv
- case "ext":
- err = src.parseExtDefinition(tokens[1])
- src.Type = CredentialSourceExt
- default:
- err = fmt.Errorf("unknown credential source: %s", tokens[0])
- }
-
- if err != nil {
- return nil, err
- }
-
- return &src, nil
-}
-
-// FetchCredentials fetches the credentials for a given registry according to
-// the credential source.
-func (src *CredentialSource) FetchCredentials(registryURL string, kubeclient *kube.KubernetesClient) (*Credential, error) {
- var creds Credential
- log.Tracef("Fetching credentials for registry %s", registryURL)
- switch src.Type {
- case CredentialSourceEnv:
- credEnv := os.Getenv(src.EnvName)
- if credEnv == "" {
- return nil, fmt.Errorf("could not fetch credentials: env '%s' is not set", src.EnvName)
- }
- tokens := strings.SplitN(credEnv, ":", 2)
- if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
- return nil, fmt.Errorf("could not fetch credentials: value of %s is malformed", src.EnvName)
- }
- creds.Username = tokens[0]
- creds.Password = tokens[1]
- return &creds, nil
- case CredentialSourceSecret:
- if kubeclient == nil {
- return nil, fmt.Errorf("could not fetch credentials: no Kubernetes client given")
- }
- data, err := kubeclient.GetSecretField(src.SecretNamespace, src.SecretName, src.SecretField)
- if err != nil {
- return nil, fmt.Errorf("could not fetch secret '%s' from namespace '%s' (field: '%s'): %v", src.SecretName, src.SecretNamespace, src.SecretField, err)
- }
- tokens := strings.SplitN(data, ":", 2)
- if len(tokens) != 2 {
- return nil, fmt.Errorf("invalid credentials in secret '%s' from namespace '%s' (field '%s')", src.SecretName, src.SecretNamespace, src.SecretField)
- }
- creds.Username = tokens[0]
- creds.Password = tokens[1]
- return &creds, nil
- case CredentialSourcePullSecret:
- if kubeclient == nil {
- return nil, fmt.Errorf("could not fetch credentials: no Kubernetes client given")
- }
- src.SecretField = pullSecretField
- data, err := kubeclient.GetSecretField(src.SecretNamespace, src.SecretName, src.SecretField)
- if err != nil {
- return nil, fmt.Errorf("could not fetch secret '%s' from namespace '%s' (field: '%s'): %v", src.SecretName, src.SecretNamespace, src.SecretField, err)
- }
- creds.Username, creds.Password, err = parseDockerConfigJson(registryURL, data)
- if err != nil {
- return nil, err
- }
- return &creds, nil
- case CredentialSourceExt:
- if !strings.HasPrefix(src.ScriptPath, "/") {
- return nil, fmt.Errorf("path to script must be absolute, but is '%s'", src.ScriptPath)
- }
- _, err := os.Stat(src.ScriptPath)
- if err != nil {
- return nil, fmt.Errorf("could not stat %s: %v", src.ScriptPath, err)
- }
- cmd := exec.Command(src.ScriptPath)
- out, err := argoexec.RunCommandExt(cmd, argoexec.CmdOpts{Timeout: 10 * time.Second})
- if err != nil {
- return nil, fmt.Errorf("error executing %s: %v", src.ScriptPath, err)
- }
- tokens := strings.SplitN(out, ":", 2)
- if len(tokens) != 2 {
- return nil, fmt.Errorf("invalid script output, must be single line with syntax <username>:<password>")
- }
- creds.Username = tokens[0]
- creds.Password = tokens[1]
- return &creds, nil
-
- default:
- return nil, fmt.Errorf("unknown credential type")
- }
-}
-
-// Parse a secret definition in form of 'namespace/name#field'
-func (src *CredentialSource) parseSecretDefinition(definition string) error {
- tokens := strings.Split(definition, "#")
- if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
- return fmt.Errorf("invalid secret definition: %s", definition)
- }
- src.SecretField = tokens[1]
- tokens = strings.Split(tokens[0], "/")
- if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
- return fmt.Errorf("invalid secret definition: %s", definition)
- }
- src.SecretNamespace = tokens[0]
- src.SecretName = tokens[1]
-
- return nil
-}
-
-// Parse an image pull secret definition in form of 'namespace/name'
-func (src *CredentialSource) parsePullSecretDefinition(definition string) error {
- tokens := strings.Split(definition, "/")
- if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" {
- return fmt.Errorf("invalid secret definition: %s", definition)
- }
-
- src.SecretNamespace = tokens[0]
- src.SecretName = tokens[1]
- src.SecretField = pullSecretField
-
- return nil
-}
-
-// Parse an environment definition
-// nolint:unparam
-func (src *CredentialSource) parseEnvDefinition(definition string) error {
- src.EnvName = definition
- return nil
-}
-
-// Parse an external script definition
-// nolint:unparam
-func (src *CredentialSource) parseExtDefinition(definition string) error {
- src.ScriptPath = definition
- return nil
-}
-
-// This unmarshals & parses Docker's config.json file, returning username and
-// password for given registry URL
-func parseDockerConfigJson(registryURL string, jsonSource string) (string, string, error) {
- var dockerConf map[string]interface{}
- err := json.Unmarshal([]byte(jsonSource), &dockerConf)
- if err != nil {
- return "", "", err
- }
- auths, ok := dockerConf["auths"].(map[string]interface{})
- if !ok {
- return "", "", fmt.Errorf("no credentials in image pull secret")
- }
-
- var regPrefix string
- if strings.HasPrefix(registryURL, "http://") {
- regPrefix = strings.TrimPrefix(registryURL, "http://")
- } else if strings.HasPrefix(registryURL, "https://") {
- regPrefix = strings.TrimPrefix(registryURL, "https://")
- } else {
- regPrefix = registryURL
- }
-
- regPrefix = strings.TrimSuffix(regPrefix, "/")
-
- for registry, authConf := range auths {
- if !strings.HasPrefix(registry, registryURL) && !strings.HasPrefix(registry, regPrefix) {
- log.Tracef("found registry %s in image pull secret, but we want %s (%s) - skipping", registry, registryURL, regPrefix)
- continue
- }
- authEntry, ok := authConf.(map[string]interface{})
- if !ok {
- return "", "", fmt.Errorf("invalid auth entry for registry entry %s ('auths' entry should be map)", registry)
- }
- authString, ok := authEntry["auth"].(string)
- if !ok {
- return "", "", fmt.Errorf("invalid auth token for registry entry %s ('auth' should be string')", registry)
- }
- authToken, err := base64.StdEncoding.DecodeString(authString)
- if err != nil {
- return "", "", fmt.Errorf("could not base64-decode auth data for registry entry %s: %v", registry, err)
- }
- tokens := strings.SplitN(string(authToken), ":", 2)
- if len(tokens) != 2 {
- return "", "", fmt.Errorf("invalid data after base64 decoding auth entry for registry entry %s", registry)
- }
-
- return tokens[0], tokens[1], nil
- }
-
- return "", "", fmt.Errorf("no valid auth entry for registry %s found in image pull secret", registryURL)
-}
diff --git a/pkg/image/credentials_test.go b/pkg/image/credentials_test.go
deleted file mode 100644
index 4c5b767..0000000
--- a/pkg/image/credentials_test.go
+++ /dev/null
@@ -1,410 +0,0 @@
-package image
-
-import (
- "fmt"
- "os"
- "path"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/kube"
- "github.com/argoproj-labs/argocd-image-updater/test/fake"
- "github.com/argoproj-labs/argocd-image-updater/test/fixture"
-)
-
-func Test_ParseCredentialAnnotation(t *testing.T) {
- t.Run("Parse valid credentials definition of type secret", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=secret:mynamespace/mysecret#anyfield", true)
- assert.NoError(t, err)
- assert.Equal(t, "gcr.io", src.Registry)
- assert.Equal(t, "mynamespace", src.SecretNamespace)
- assert.Equal(t, "mysecret", src.SecretName)
- assert.Equal(t, "anyfield", src.SecretField)
- })
-
- t.Run("Parse valid credentials definition of type pullsecret", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=pullsecret:mynamespace/mysecret", true)
- assert.NoError(t, err)
- assert.Equal(t, "gcr.io", src.Registry)
- assert.Equal(t, "mynamespace", src.SecretNamespace)
- assert.Equal(t, "mysecret", src.SecretName)
- assert.Equal(t, ".dockerconfigjson", src.SecretField)
- })
-
- t.Run("Parse invalid secret definition - missing registry", func(t *testing.T) {
- src, err := ParseCredentialSource("secret:mynamespace/mysecret#anyfield", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid secret definition - empty registry", func(t *testing.T) {
- src, err := ParseCredentialSource("=secret:mynamespace/mysecret#anyfield", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid secret definition - unknown credential type", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=secrets:mynamespace/mysecret#anyfield", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid secret definition - missing field", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=secret:mynamespace/mysecret#", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid secret definition - missing namespace", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=secret:/mysecret#anyfield", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid credential definition - missing name", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=secret:mynamespace/#anyfield", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid credential definition - missing most", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=secret:", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid pullsecret definition - missing namespace", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=pullsecret:/mysecret", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse invalid credential definition - missing name", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=pullsecret:mynamespace", true)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
- t.Run("Parse valid credentials definition from environment", func(t *testing.T) {
- src, err := ParseCredentialSource("env:DUMMY_SECRET", false)
- require.NoError(t, err)
- require.NotNil(t, src)
- assert.Equal(t, "DUMMY_SECRET", src.EnvName)
- })
-
- t.Run("Parse valid credentials definition from environment", func(t *testing.T) {
- src, err := ParseCredentialSource("env:DUMMY_SECRET", false)
- require.NoError(t, err)
- require.NotNil(t, src)
- assert.Equal(t, "DUMMY_SECRET", src.EnvName)
- })
-
- t.Run("Parse external script credentials", func(t *testing.T) {
- src, err := ParseCredentialSource("ext:/tmp/a.sh", false)
- require.NoError(t, err)
- assert.Equal(t, CredentialSourceExt, src.Type)
- assert.Equal(t, "/tmp/a.sh", src.ScriptPath)
- })
-}
-
-func Test_ParseCredentialReference(t *testing.T) {
- t.Run("Parse valid credentials definition of type secret", func(t *testing.T) {
- src, err := ParseCredentialSource("secret:mynamespace/mysecret#anyfield", false)
- assert.NoError(t, err)
- assert.Equal(t, "", src.Registry)
- assert.Equal(t, "mynamespace", src.SecretNamespace)
- assert.Equal(t, "mysecret", src.SecretName)
- assert.Equal(t, "anyfield", src.SecretField)
- })
-
- t.Run("Parse valid credentials definition of type pullsecret", func(t *testing.T) {
- src, err := ParseCredentialSource("gcr.io=pullsecret:mynamespace/mysecret", false)
- assert.NoError(t, err)
- assert.Equal(t, "gcr.io", src.Registry)
- assert.Equal(t, "mynamespace", src.SecretNamespace)
- assert.Equal(t, "mysecret", src.SecretName)
- assert.Equal(t, ".dockerconfigjson", src.SecretField)
- })
-
- t.Run("Parse invalid secret definition - empty registry", func(t *testing.T) {
- src, err := ParseCredentialSource("=secret:mynamespace/mysecret#anyfield", false)
- assert.Error(t, err)
- assert.Nil(t, src)
- })
-
-}
-
-func Test_FetchCredentialsFromSecret(t *testing.T) {
- t.Run("Fetch credentials from secret", func(t *testing.T) {
- secretData := make(map[string][]byte)
- secretData["username_password"] = []byte(fmt.Sprintf("%s:%s", "foo", "bar"))
- secret := fixture.NewSecret("test", "test", secretData)
- clientset := fake.NewFakeClientsetWithResources(secret)
- credSrc := &CredentialSource{
- Type: CredentialSourceSecret,
- SecretNamespace: "test",
- SecretName: "test",
- SecretField: "username_password",
- }
- creds, err := credSrc.FetchCredentials("NA", &kube.KubernetesClient{Clientset: clientset})
- require.NoError(t, err)
- require.NotNil(t, creds)
- assert.Equal(t, "foo", creds.Username)
- assert.Equal(t, "bar", creds.Password)
-
- credSrc.SecretNamespace = "test1" // test with a wrong SecretNamespace
- creds, err = credSrc.FetchCredentials("NA", &kube.KubernetesClient{Clientset: clientset})
- require.Error(t, err)
- require.Nil(t, creds)
- })
-
- t.Run("Fetch credentials from secret with invalid config", func(t *testing.T) {
- secretData := make(map[string][]byte)
- secretData["username_password"] = []byte(fmt.Sprintf("%s:%s", "foo", "bar"))
- secret := fixture.NewSecret("test", "test", secretData)
- clientset := fake.NewFakeClientsetWithResources(secret)
- credSrc := &CredentialSource{
- Type: CredentialSourceSecret,
- SecretNamespace: "test",
- SecretName: "test",
- SecretField: "username_password",
- }
- creds, err := credSrc.FetchCredentials("NA", nil)
- require.Error(t, err) // should fail with "could not fetch credentials: no Kubernetes client given"
- require.Nil(t, creds)
-
- credSrc.SecretField = "BAD" // test with a wrong SecretField
- creds, err = credSrc.FetchCredentials("NA", &kube.KubernetesClient{Clientset: clientset})
- require.Error(t, err)
- require.Nil(t, creds)
-
- })
-}
-
-func Test_FetchCredentialsFromPullSecret(t *testing.T) {
- t.Run("Fetch credentials from pull secret", func(t *testing.T) {
- dockerJson := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
- secretData := make(map[string][]byte)
- secretData[pullSecretField] = []byte(dockerJson)
- pullSecret := fixture.NewSecret("test", "test", secretData)
- clientset := fake.NewFakeClientsetWithResources(pullSecret)
- credSrc := &CredentialSource{
- Type: CredentialSourcePullSecret,
- Registry: "https://registry-1.docker.io/v2",
- SecretNamespace: "test",
- SecretName: "test",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
- require.NoError(t, err)
- require.NotNil(t, creds)
- assert.Equal(t, "foo", creds.Username)
- assert.Equal(t, "bar", creds.Password)
-
- credSrc.SecretNamespace = "test1" // test with a wrong SecretNamespace
- creds, err = credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
- require.Error(t, err)
- require.Nil(t, creds)
- })
-
- t.Run("Fetch credentials from pull secret with invalid config", func(t *testing.T) {
- dockerJson := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
- dockerJson = strings.ReplaceAll(dockerJson, "auths", "BAD-KEY")
- secretData := make(map[string][]byte)
- secretData[pullSecretField] = []byte(dockerJson)
- pullSecret := fixture.NewSecret("test", "test", secretData)
- clientset := fake.NewFakeClientsetWithResources(pullSecret)
- credSrc := &CredentialSource{
- Type: CredentialSourcePullSecret,
- Registry: "https://registry-1.docker.io/v2",
- SecretNamespace: "test",
- SecretName: "test",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
- require.Error(t, err) // should fail with "no credentials in image pull secret"
- require.Nil(t, creds)
-
- creds, err = credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Error(t, err) // should fail with "could not fetch credentials: no Kubernetes client given"
- require.Nil(t, creds)
- })
-
- t.Run("Fetch credentials from pull secret with protocol stripped", func(t *testing.T) {
- dockerJson := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
- secretData := make(map[string][]byte)
- secretData[pullSecretField] = []byte(dockerJson)
- pullSecret := fixture.NewSecret("test", "test", secretData)
- clientset := fake.NewFakeClientsetWithResources(pullSecret)
- credSrc := &CredentialSource{
- Type: CredentialSourcePullSecret,
- Registry: "https://registry-1.docker.io/v2",
- SecretNamespace: "test",
- SecretName: "test",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", &kube.KubernetesClient{Clientset: clientset})
- require.NoError(t, err)
- require.NotNil(t, creds)
- assert.Equal(t, "foo", creds.Username)
- assert.Equal(t, "bar", creds.Password)
- })
-}
-
-func Test_FetchCredentialsFromEnv(t *testing.T) {
- t.Run("Fetch credentials from environment", func(t *testing.T) {
- err := os.Setenv("MY_SECRET_ENV", "foo:bar")
- require.NoError(t, err)
- credSrc := &CredentialSource{
- Type: CredentialSourceEnv,
- Registry: "https://registry-1.docker.io/v2",
- EnvName: "MY_SECRET_ENV",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.NoError(t, err)
- require.NotNil(t, creds)
- assert.Equal(t, "foo", creds.Username)
- assert.Equal(t, "bar", creds.Password)
- })
-
- t.Run("Fetch credentials from environment with missing env var", func(t *testing.T) {
- err := os.Setenv("MY_SECRET_ENV", "")
- require.NoError(t, err)
- credSrc := &CredentialSource{
- Type: CredentialSourceEnv,
- Registry: "https://registry-1.docker.io/v2",
- EnvName: "MY_SECRET_ENV",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Error(t, err)
- require.Nil(t, creds)
- })
-
- t.Run("Fetch credentials from environment with invalid value in env var", func(t *testing.T) {
- for _, value := range []string{"babayaga", "foo:", "bar:", ":"} {
- err := os.Setenv("MY_SECRET_ENV", value)
- require.NoError(t, err)
- credSrc := &CredentialSource{
- Type: CredentialSourceEnv,
- Registry: "https://registry-1.docker.io/v2",
- EnvName: "MY_SECRET_ENV",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Error(t, err)
- require.Nil(t, creds)
- }
- })
-}
-
-func Test_FetchCredentialsFromExt(t *testing.T) {
- t.Run("Fetch credentials from external script - valid output", func(t *testing.T) {
- pwd, err := os.Getwd()
- require.NoError(t, err)
- credSrc := &CredentialSource{
- Type: CredentialSourceExt,
- Registry: "https://registry-1.docker.io/v2",
- ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-valid.sh"),
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.NoError(t, err)
- require.NotNil(t, creds)
- assert.Equal(t, "username", creds.Username)
- assert.Equal(t, "password", creds.Password)
- })
- t.Run("Fetch credentials from external script - invalid script output", func(t *testing.T) {
- pwd, err := os.Getwd()
- require.NoError(t, err)
- credSrc := &CredentialSource{
- Type: CredentialSourceExt,
- Registry: "https://registry-1.docker.io/v2",
- ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-invalid.sh"),
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Errorf(t, err, "invalid script output")
- require.Nil(t, creds)
- })
- t.Run("Fetch credentials from external script - script does not exist", func(t *testing.T) {
- pwd, err := os.Getwd()
- require.NoError(t, err)
- credSrc := &CredentialSource{
- Type: CredentialSourceExt,
- Registry: "https://registry-1.docker.io/v2",
- ScriptPath: path.Join(pwd, "..", "..", "test", "testdata", "scripts", "get-credentials-notexist.sh"),
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Errorf(t, err, "no such file or directory")
- require.Nil(t, creds)
- })
- t.Run("Fetch credentials from external script - relative path", func(t *testing.T) {
- credSrc := &CredentialSource{
- Type: CredentialSourceExt,
- Registry: "https://registry-1.docker.io/v2",
- ScriptPath: "get-credentials-notexist.sh",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Errorf(t, err, "path to script must be absolute")
- require.Nil(t, creds)
- })
-}
-
-func Test_FetchCredentialsFromUnknown(t *testing.T) {
- t.Run("Fetch credentials from unknown type", func(t *testing.T) {
- credSrc := &CredentialSource{
- Type: CredentialSourceType(-1),
- Registry: "https://registry-1.docker.io/v2",
- }
- creds, err := credSrc.FetchCredentials("https://registry-1.docker.io", nil)
- require.Error(t, err) // should fail with "unknown credential type"
- require.Nil(t, creds)
- })
-}
-
-func Test_ParseDockerConfig(t *testing.T) {
- t.Run("Parse valid Docker configuration with matching registry", func(t *testing.T) {
- config := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
- username, password, err := parseDockerConfigJson("https://registry-1.docker.io", config)
- require.NoError(t, err)
- assert.Equal(t, "foo", username)
- assert.Equal(t, "bar", password)
- })
-
- t.Run("Parse valid Docker configuration with matching registry as prefix", func(t *testing.T) {
- config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
- username, password, err := parseDockerConfigJson("https://registry-1.docker.io", config)
- require.NoError(t, err)
- assert.Equal(t, "foo", username)
- assert.Equal(t, "bar", password)
- })
-
- t.Run("Parse valid Docker configuration with matching http registry as prefix", func(t *testing.T) {
- config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
- username, password, err := parseDockerConfigJson("http://registry-1.docker.io", config)
- require.NoError(t, err)
- assert.Equal(t, "foo", username)
- assert.Equal(t, "bar", password)
- })
-
- t.Run("Parse valid Docker configuration with matching no-protocol registry as prefix", func(t *testing.T) {
- config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
- username, password, err := parseDockerConfigJson("registry-1.docker.io", config)
- require.NoError(t, err)
- assert.Equal(t, "foo", username)
- assert.Equal(t, "bar", password)
- })
-
- t.Run("Parse valid Docker configuration with matching registry as prefix with / in the end", func(t *testing.T) {
- config := fixture.MustReadFile("../../test/testdata/docker/valid-config-noproto.json")
- username, password, err := parseDockerConfigJson("https://registry-1.docker.io/", config)
- require.NoError(t, err)
- assert.Equal(t, "foo", username)
- assert.Equal(t, "bar", password)
- })
-
- t.Run("Parse valid Docker configuration without matching registry", func(t *testing.T) {
- config := fixture.MustReadFile("../../test/testdata/docker/valid-config.json")
- username, password, err := parseDockerConfigJson("https://gcr.io", config)
- assert.Error(t, err)
- assert.Empty(t, username)
- assert.Empty(t, password)
- })
-}
diff --git a/pkg/image/image.go b/pkg/image/image.go
deleted file mode 100644
index 01261be..0000000
--- a/pkg/image/image.go
+++ /dev/null
@@ -1,275 +0,0 @@
-package image
-
-import (
- "strings"
- "time"
-
- "github.com/distribution/distribution/v3/reference"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-)
-
-type ContainerImage struct {
- RegistryURL string
- ImageName string
- ImageTag *tag.ImageTag
- ImageAlias string
- HelmParamImageName string
- HelmParamImageVersion string
- KustomizeImage *ContainerImage
- original string
-}
-
-type ContainerImageList []*ContainerImage
-
-// NewFromIdentifier parses an image identifier and returns a populated ContainerImage
-func NewFromIdentifier(identifier string) *ContainerImage {
- imgRef := identifier
- alias := ""
- if strings.Contains(identifier, "=") {
- n := strings.SplitN(identifier, "=", 2)
- imgRef = n[1]
- alias = n[0]
- }
- if parsed, err := reference.ParseNormalizedNamed(imgRef); err == nil {
- img := ContainerImage{}
- img.RegistryURL = reference.Domain(parsed)
- // remove default registry for backwards-compatibility
- if img.RegistryURL == "docker.io" && !strings.HasPrefix(imgRef, "docker.io") {
- img.RegistryURL = ""
- }
- img.ImageAlias = alias
- img.ImageName = reference.Path(parsed)
- // if library/ was added to the image name, remove it
- if !strings.HasPrefix(imgRef, "library/") {
- img.ImageName = strings.TrimPrefix(img.ImageName, "library/")
- }
- if digested, ok := parsed.(reference.Digested); ok {
- img.ImageTag = &tag.ImageTag{
- TagDigest: string(digested.Digest()),
- }
- } else if tagged, ok := parsed.(reference.Tagged); ok {
- img.ImageTag = &tag.ImageTag{
- TagName: tagged.Tag(),
- }
- }
- img.original = identifier
- return &img
- }
-
- // if distribution couldn't parse it, fall back to the legacy parsing logic
- img := ContainerImage{}
- img.RegistryURL = getRegistryFromIdentifier(identifier)
- img.ImageAlias, img.ImageName, img.ImageTag = getImageTagFromIdentifier(identifier)
- img.original = identifier
- return &img
-}
-
-// String returns the string representation of given ContainerImage
-func (img *ContainerImage) String() string {
- str := ""
- if img.ImageAlias != "" {
- str += img.ImageAlias
- str += "="
- }
- str += img.GetFullNameWithTag()
- return str
-}
-
-func (img *ContainerImage) GetFullNameWithoutTag() string {
- str := ""
- if img.RegistryURL != "" {
- str += img.RegistryURL + "/"
- }
- str += img.ImageName
- return str
-}
-
-// GetFullNameWithTag returns the complete image slug, including the registry
-// and any tag digest or tag name set for the image.
-func (img *ContainerImage) GetFullNameWithTag() string {
- str := ""
- if img.RegistryURL != "" {
- str += img.RegistryURL + "/"
- }
- str += img.ImageName
- if img.ImageTag != nil {
- if img.ImageTag.TagName != "" {
- str += ":"
- str += img.ImageTag.TagName
- }
- if img.ImageTag.TagDigest != "" {
- str += "@"
- str += img.ImageTag.TagDigest
- }
- }
- return str
-}
-
-// GetTagWithDigest returns tag name along with any tag digest set for the image
-func (img *ContainerImage) GetTagWithDigest() string {
- str := ""
- if img.ImageTag != nil {
- if img.ImageTag.TagName != "" {
- str += img.ImageTag.TagName
- }
- if img.ImageTag.TagDigest != "" {
- if str == "" {
- str += "latest"
- }
- str += "@"
- str += img.ImageTag.TagDigest
- }
- }
- return str
-}
-
-func (img *ContainerImage) Original() string {
- return img.original
-}
-
-// IsUpdatable checks whether the given image can be updated with newTag while
-// taking tagSpec into account. tagSpec must be given as a semver compatible
-// version spec, i.e. ^1.0 or ~2.1
-func (img *ContainerImage) IsUpdatable(newTag, tagSpec string) bool {
- return false
-}
-
-// WithTag returns a copy of img with new tag information set
-func (img *ContainerImage) WithTag(newTag *tag.ImageTag) *ContainerImage {
- nimg := &ContainerImage{}
- nimg.RegistryURL = img.RegistryURL
- nimg.ImageName = img.ImageName
- nimg.ImageTag = newTag
- nimg.ImageAlias = img.ImageAlias
- nimg.HelmParamImageName = img.HelmParamImageName
- nimg.HelmParamImageVersion = img.HelmParamImageVersion
- return nimg
-}
-
-func (img *ContainerImage) DiffersFrom(other *ContainerImage, checkVersion bool) bool {
- return img.RegistryURL != other.RegistryURL || img.ImageName != other.ImageName || (checkVersion && img.ImageTag.TagName != other.ImageTag.TagName)
-}
-
-// ContainsImage checks whether img is contained in a list of images
-func (list *ContainerImageList) ContainsImage(img *ContainerImage, checkVersion bool) *ContainerImage {
- // if there is a KustomizeImage override, check it for a match first
- if img.KustomizeImage != nil {
- if kustomizeMatch := list.ContainsImage(img.KustomizeImage, checkVersion); kustomizeMatch != nil {
- return kustomizeMatch
- }
- }
- for _, image := range *list {
- if img.ImageName == image.ImageName && image.RegistryURL == img.RegistryURL {
- if !checkVersion || image.ImageTag.TagName == img.ImageTag.TagName {
- return image
- }
- }
- }
- return nil
-}
-
-func (list *ContainerImageList) Originals() []string {
- results := make([]string, len(*list))
- for i, img := range *list {
- results[i] = img.Original()
- }
- return results
-}
-
-// String Returns the name of all images as a string, separated using comma
-func (list *ContainerImageList) String() string {
- imgNameList := make([]string, 0)
- for _, image := range *list {
- imgNameList = append(imgNameList, image.String())
- }
- return strings.Join(imgNameList, ",")
-}
-
-// Gets the registry URL from an image identifier
-func getRegistryFromIdentifier(identifier string) string {
- var imageString string
- comp := strings.Split(identifier, "=")
- if len(comp) > 1 {
- imageString = comp[1]
- } else {
- imageString = identifier
- }
- comp = strings.Split(imageString, "/")
- if len(comp) > 1 && strings.Contains(comp[0], ".") {
- return comp[0]
- } else {
- return ""
- }
-}
-
-// Gets the image name and tag from an image identifier
-func getImageTagFromIdentifier(identifier string) (string, string, *tag.ImageTag) {
- var imageString string
- var sourceName string
-
- // The original name is prepended to the image name, separated by =
- comp := strings.SplitN(identifier, "=", 2)
- if len(comp) == 2 {
- sourceName = comp[0]
- imageString = comp[1]
- } else {
- imageString = identifier
- }
-
- // Strip any repository identifier from the string
- comp = strings.Split(imageString, "/")
- if len(comp) > 1 && strings.Contains(comp[0], ".") {
- imageString = strings.Join(comp[1:], "/")
- }
-
- // We can either have a tag name or a digest reference, or both
- // jannfis/test-image:0.1
- // gcr.io/jannfis/test-image:0.1
- // gcr.io/jannfis/test-image@sha256:abcde
- // gcr.io/jannfis/test-image:test-tag@sha256:abcde
- if strings.Contains(imageString, "@") {
- comp = strings.SplitN(imageString, "@", 2)
- colonPos := strings.LastIndex(comp[0], ":")
- slashPos := strings.LastIndex(comp[0], "/")
- if colonPos > slashPos {
- // first half (before @) contains image and tag name
- return sourceName, comp[0][:colonPos], tag.NewImageTag(comp[0][colonPos+1:], time.Unix(0, 0), comp[1])
- } else {
- // first half contains image name without tag name
- return sourceName, comp[0], tag.NewImageTag("", time.Unix(0, 0), comp[1])
- }
- } else {
- comp = strings.SplitN(imageString, ":", 2)
- if len(comp) != 2 {
- return sourceName, imageString, nil
- } else {
- tagName, tagDigest := getImageDigestFromTag(comp[1])
- return sourceName, comp[0], tag.NewImageTag(tagName, time.Unix(0, 0), tagDigest)
- }
- }
-}
-
-func getImageDigestFromTag(tagStr string) (string, string) {
- a := strings.Split(tagStr, "@")
- if len(a) != 2 {
- return tagStr, ""
- } else {
- return a[0], a[1]
- }
-}
-
-// LogContext returns a log context for the given image, with required fields
-// set to the image's information.
-func (img *ContainerImage) LogContext() *log.LogContext {
- logCtx := log.WithContext()
- logCtx.AddField("image_name", img.GetFullNameWithoutTag())
- logCtx.AddField("image_alias", img.ImageAlias)
- logCtx.AddField("registry_url", img.RegistryURL)
- if img.ImageTag != nil {
- logCtx.AddField("image_tag", img.ImageTag.TagName)
- logCtx.AddField("image_digest", img.ImageTag.TagDigest)
- }
- return logCtx
-}
diff --git a/pkg/image/image_test.go b/pkg/image/image_test.go
deleted file mode 100644
index 1709461..0000000
--- a/pkg/image/image_test.go
+++ /dev/null
@@ -1,226 +0,0 @@
-package image
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "golang.org/x/exp/slices"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-)
-
-func Test_ParseImageTags(t *testing.T) {
- t.Run("Parse valid image name without registry info", func(t *testing.T) {
- image := NewFromIdentifier("jannfis/test-image:0.1")
- assert.Empty(t, image.RegistryURL)
- assert.Empty(t, image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- require.NotNil(t, image.ImageTag)
- assert.Equal(t, "0.1", image.ImageTag.TagName)
- assert.Equal(t, "jannfis/test-image:0.1", image.GetFullNameWithTag())
- assert.Equal(t, "jannfis/test-image", image.GetFullNameWithoutTag())
- })
-
- t.Run("Single element image name is unmodified", func(t *testing.T) {
- image := NewFromIdentifier("test-image")
- assert.Empty(t, image.RegistryURL)
- assert.Empty(t, image.ImageAlias)
- assert.Equal(t, "test-image", image.ImageName)
- require.Nil(t, image.ImageTag)
- assert.Equal(t, "test-image", image.GetFullNameWithTag())
- assert.Equal(t, "test-image", image.GetFullNameWithoutTag())
- })
-
- t.Run("library image name is unmodified", func(t *testing.T) {
- image := NewFromIdentifier("library/test-image")
- assert.Empty(t, image.RegistryURL)
- assert.Empty(t, image.ImageAlias)
- assert.Equal(t, "library/test-image", image.ImageName)
- require.Nil(t, image.ImageTag)
- assert.Equal(t, "library/test-image", image.GetFullNameWithTag())
- assert.Equal(t, "library/test-image", image.GetFullNameWithoutTag())
- })
-
- t.Run("Parse valid image name with registry info", func(t *testing.T) {
- image := NewFromIdentifier("gcr.io/jannfis/test-image:0.1")
- assert.Equal(t, "gcr.io", image.RegistryURL)
- assert.Empty(t, image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- require.NotNil(t, image.ImageTag)
- assert.Equal(t, "0.1", image.ImageTag.TagName)
- assert.Equal(t, "gcr.io/jannfis/test-image:0.1", image.GetFullNameWithTag())
- assert.Equal(t, "gcr.io/jannfis/test-image", image.GetFullNameWithoutTag())
- })
-
- t.Run("Parse valid image name with default registry info", func(t *testing.T) {
- image := NewFromIdentifier("docker.io/jannfis/test-image:0.1")
- assert.Equal(t, "docker.io", image.RegistryURL)
- assert.Empty(t, image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- require.NotNil(t, image.ImageTag)
- assert.Equal(t, "0.1", image.ImageTag.TagName)
- assert.Equal(t, "docker.io/jannfis/test-image:0.1", image.GetFullNameWithTag())
- assert.Equal(t, "docker.io/jannfis/test-image", image.GetFullNameWithoutTag())
- })
-
- t.Run("Parse valid image name with digest tag", func(t *testing.T) {
- image := NewFromIdentifier("gcr.io/jannfis/test-image@sha256:abcde")
- assert.Equal(t, "gcr.io", image.RegistryURL)
- assert.Empty(t, image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- require.NotNil(t, image.ImageTag)
- assert.Empty(t, image.ImageTag.TagName)
- assert.Equal(t, "sha256:abcde", image.ImageTag.TagDigest)
- assert.Equal(t, "latest@sha256:abcde", image.GetTagWithDigest())
- assert.Equal(t, "gcr.io/jannfis/test-image@sha256:abcde", image.GetFullNameWithTag())
- assert.Equal(t, "gcr.io/jannfis/test-image", image.GetFullNameWithoutTag())
- })
-
- t.Run("Parse valid image name with tag and digest", func(t *testing.T) {
- image := NewFromIdentifier("gcr.io/jannfis/test-image:test-tag@sha256:abcde")
- require.NotNil(t, image.ImageTag)
- assert.Equal(t, "test-tag", image.ImageTag.TagName)
- assert.Equal(t, "sha256:abcde", image.ImageTag.TagDigest)
- assert.Equal(t, "test-tag@sha256:abcde", image.GetTagWithDigest())
- assert.Equal(t, "gcr.io/jannfis/test-image", image.GetFullNameWithoutTag())
- assert.Equal(t, "gcr.io/jannfis/test-image:test-tag@sha256:abcde", image.GetFullNameWithTag())
- })
-
- t.Run("Parse valid image name with source name and registry info", func(t *testing.T) {
- image := NewFromIdentifier("jannfis/orig-image=gcr.io/jannfis/test-image:0.1")
- assert.Equal(t, "gcr.io", image.RegistryURL)
- assert.Equal(t, "jannfis/orig-image", image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- require.NotNil(t, image.ImageTag)
- assert.Equal(t, "0.1", image.ImageTag.TagName)
- })
-
- t.Run("Parse valid image name with source name and registry info with port", func(t *testing.T) {
- image := NewFromIdentifier("ghcr.io:4567/jannfis/orig-image=gcr.io:1234/jannfis/test-image:0.1")
- assert.Equal(t, "gcr.io:1234", image.RegistryURL)
- assert.Equal(t, "ghcr.io:4567/jannfis/orig-image", image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- require.NotNil(t, image.ImageTag)
- assert.Equal(t, "0.1", image.ImageTag.TagName)
- })
-
- t.Run("Parse image without version source name and registry info", func(t *testing.T) {
- image := NewFromIdentifier("jannfis/orig-image=gcr.io/jannfis/test-image")
- assert.Equal(t, "gcr.io", image.RegistryURL)
- assert.Equal(t, "jannfis/orig-image", image.ImageAlias)
- assert.Equal(t, "jannfis/test-image", image.ImageName)
- assert.Nil(t, image.ImageTag)
- })
- t.Run("#273 classic-web=registry:5000/classic-web", func(t *testing.T) {
- image := NewFromIdentifier("classic-web=registry:5000/classic-web")
- assert.Equal(t, "registry:5000", image.RegistryURL)
- assert.Equal(t, "classic-web", image.ImageAlias)
- assert.Equal(t, "classic-web", image.ImageName)
- assert.Nil(t, image.ImageTag)
- })
-}
-
-func Test_ImageToString(t *testing.T) {
- t.Run("Get string representation of full-qualified image name", func(t *testing.T) {
- imageName := "jannfis/argocd=jannfis/orig-image:0.1"
- img := NewFromIdentifier(imageName)
- assert.Equal(t, imageName, img.String())
- })
- t.Run("Get string representation of full-qualified image name with registry", func(t *testing.T) {
- imageName := "jannfis/argocd=gcr.io/jannfis/orig-image:0.1"
- img := NewFromIdentifier(imageName)
- assert.Equal(t, imageName, img.String())
- })
- t.Run("Get string representation of full-qualified image name with registry", func(t *testing.T) {
- imageName := "jannfis/argocd=gcr.io/jannfis/orig-image"
- img := NewFromIdentifier(imageName)
- assert.Equal(t, imageName, img.String())
- })
- t.Run("Get original value", func(t *testing.T) {
- imageName := "invalid==foo"
- img := NewFromIdentifier(imageName)
- assert.Equal(t, imageName, img.Original())
- })
-}
-
-func Test_WithTag(t *testing.T) {
- t.Run("Get string representation of full-qualified image name", func(t *testing.T) {
- imageName := "jannfis/argocd=jannfis/orig-image:0.1"
- nimageName := "jannfis/argocd=jannfis/orig-image:0.2"
- oImg := NewFromIdentifier(imageName)
- nImg := oImg.WithTag(tag.NewImageTag("0.2", time.Unix(0, 0), ""))
- assert.Equal(t, nimageName, nImg.String())
- })
-}
-
-func Test_ContainerList(t *testing.T) {
- t.Run("Test whether image is contained in list", func(t *testing.T) {
- images := make(ContainerImageList, 0)
- image_names := []string{"a/a:0.1", "a/b:1.2", "x/y=foo.bar/a/c:0.23"}
- for _, n := range image_names {
- images = append(images, NewFromIdentifier(n))
- }
- withKustomizeOverride := NewFromIdentifier("k1/k2:k3")
- withKustomizeOverride.KustomizeImage = images[0]
- images = append(images, withKustomizeOverride)
-
- assert.NotNil(t, images.ContainsImage(NewFromIdentifier(image_names[0]), false))
- assert.NotNil(t, images.ContainsImage(NewFromIdentifier(image_names[1]), false))
- assert.NotNil(t, images.ContainsImage(NewFromIdentifier(image_names[2]), false))
- assert.Nil(t, images.ContainsImage(NewFromIdentifier("foo/bar"), false))
-
- imageMatch := images.ContainsImage(withKustomizeOverride, false)
- assert.Equal(t, images[0], imageMatch)
- })
-}
-
-func Test_getImageDigestFromTag(t *testing.T) {
- tagAndDigest := "test-tag@sha256:abcde"
- tagName, tagDigest := getImageDigestFromTag(tagAndDigest)
- assert.Equal(t, "test-tag", tagName)
- assert.Equal(t, "sha256:abcde", tagDigest)
-
- tagAndDigest = "test-tag"
- tagName, tagDigest = getImageDigestFromTag(tagAndDigest)
- assert.Equal(t, "test-tag", tagName)
- assert.Empty(t, tagDigest)
-}
-
-func Test_ContainerImageList_String_Originals(t *testing.T) {
- images := make(ContainerImageList, 0)
- originals := []string{}
-
- assert.Equal(t, "", images.String())
- assert.True(t, slices.Equal(originals, images.Originals()))
-
- images = append(images, NewFromIdentifier("foo/bar:0.1"))
- originals = append(originals, "foo/bar:0.1")
- assert.Equal(t, "foo/bar:0.1", images.String())
- assert.True(t, slices.Equal(originals, images.Originals()))
-
- images = append(images, NewFromIdentifier("alias=foo/bar:0.2"))
- originals = append(originals, "alias=foo/bar:0.2")
- assert.Equal(t, "foo/bar:0.1,alias=foo/bar:0.2", images.String())
- assert.True(t, slices.Equal(originals, images.Originals()))
-}
-
-func TestContainerImage_DiffersFrom(t *testing.T) {
- foo1 := NewFromIdentifier("x/foo:1")
- foo2 := NewFromIdentifier("x/foo:2")
- bar1 := NewFromIdentifier("x/bar:1")
- bar1WithRegistry := NewFromIdentifier("docker.io/x/bar:1")
-
- assert.False(t, foo1.DiffersFrom(foo1, true))
- assert.False(t, foo1.DiffersFrom(foo2, false))
- assert.True(t, foo1.DiffersFrom(foo2, true))
-
- assert.True(t, foo1.DiffersFrom(bar1, false))
- assert.True(t, bar1.DiffersFrom(foo1, false))
- assert.True(t, foo1.DiffersFrom(bar1, true))
- assert.True(t, bar1.DiffersFrom(foo1, true))
- assert.True(t, bar1.DiffersFrom(bar1WithRegistry, false))
-
- assert.False(t, foo1.IsUpdatable("0.1", "^1.0"))
-}
diff --git a/pkg/image/kustomize.go b/pkg/image/kustomize.go
deleted file mode 100644
index ef7c88b..0000000
--- a/pkg/image/kustomize.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package image
-
-import (
- "strings"
-)
-
-// Shamelessly ripped from ArgoCD CLI code
-
-type KustomizeImage string
-
-func (i KustomizeImage) delim() string {
- for _, d := range []string{"=", ":", "@"} {
- if strings.Contains(string(i), d) {
- return d
- }
- }
- return ":"
-}
-
-// if the image name matches (i.e. up to the first delimiter)
-func (i KustomizeImage) Match(j KustomizeImage) bool {
- delim := j.delim()
- if !strings.Contains(string(j), delim) {
- return false
- }
- return strings.HasPrefix(string(i), strings.Split(string(j), delim)[0])
-}
-
-type KustomizeImages []KustomizeImage
-
-// find the image or -1
-func (images KustomizeImages) Find(image KustomizeImage) int {
- for i, a := range images {
- if a.Match(image) {
- return i
- }
- }
- return -1
-}
diff --git a/pkg/image/kustomize_test.go b/pkg/image/kustomize_test.go
deleted file mode 100644
index 98dede9..0000000
--- a/pkg/image/kustomize_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package image
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func Test_KustomizeImages_Find(t *testing.T) {
- images := KustomizeImages{
- "a/b:1.0",
- "a/b@sha256:aabb",
- "a/b:latest@sha256:aabb",
- "x/y=busybox",
- "x/y=foo.bar/a/c:0.23",
- }
- for _, image := range images {
- assert.True(t, images.Find(image) >= 0)
- }
- for _, image := range []string{"a/b:2", "x/y=foo.bar"} {
- assert.True(t, images.Find(KustomizeImage(image)) >= 0)
- }
- for _, image := range []string{"a/b", "x", "x/y"} {
- assert.Equal(t, -1, images.Find(KustomizeImage(image)))
- }
-}
diff --git a/pkg/image/matchfunc.go b/pkg/image/matchfunc.go
deleted file mode 100644
index 036e6fb..0000000
--- a/pkg/image/matchfunc.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package image
-
-import (
- "regexp"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
-)
-
-// MatchFuncAny matches any pattern, i.e. always returns true
-func MatchFuncAny(tagName string, args interface{}) bool {
- return true
-}
-
-// MatchFuncNone matches no pattern, i.e. always returns false
-func MatchFuncNone(tagName string, args interface{}) bool {
- return false
-}
-
-// MatchFuncRegexp matches the tagName against regexp pattern and returns the result
-func MatchFuncRegexp(tagName string, args interface{}) bool {
- pattern, ok := args.(*regexp.Regexp)
- if !ok {
- log.Errorf("args is not a RegExp")
- return false
- }
- return pattern.Match([]byte(tagName))
-}
diff --git a/pkg/image/matchfunc_test.go b/pkg/image/matchfunc_test.go
deleted file mode 100644
index 11929b1..0000000
--- a/pkg/image/matchfunc_test.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package image
-
-import (
- "regexp"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func Test_MatchFuncAny(t *testing.T) {
- assert.True(t, MatchFuncAny("whatever", nil))
-}
-
-func Test_MatchFuncNone(t *testing.T) {
- assert.False(t, MatchFuncNone("whatever", nil))
-}
-
-func Test_MatchFuncRegexp(t *testing.T) {
- t.Run("Test with valid expression", func(t *testing.T) {
- re := regexp.MustCompile("[a-z]+")
- assert.True(t, MatchFuncRegexp("lemon", re))
- assert.False(t, MatchFuncRegexp("31337", re))
- })
- t.Run("Test with invalid type", func(t *testing.T) {
- assert.False(t, MatchFuncRegexp("lemon", "[a-z]+"))
- })
-}
diff --git a/pkg/image/options.go b/pkg/image/options.go
deleted file mode 100644
index ce2f5ca..0000000
--- a/pkg/image/options.go
+++ /dev/null
@@ -1,296 +0,0 @@
-package image
-
-import (
- "fmt"
- "regexp"
- "runtime"
- "strings"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/common"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
-)
-
-// GetParameterHelmImageName gets the value for image-name option for the image
-// from a set of annotations
-func (img *ContainerImage) GetParameterHelmImageName(annotations map[string]string) string {
- key := fmt.Sprintf(common.HelmParamImageNameAnnotation, img.normalizedSymbolicName())
- val, ok := annotations[key]
- if !ok {
- return ""
- }
- return val
-}
-
-// GetParameterHelmImageTag gets the value for image-tag option for the image
-// from a set of annotations
-func (img *ContainerImage) GetParameterHelmImageTag(annotations map[string]string) string {
- key := fmt.Sprintf(common.HelmParamImageTagAnnotation, img.normalizedSymbolicName())
- val, ok := annotations[key]
- if !ok {
- return ""
- }
- return val
-}
-
-// GetParameterHelmImageSpec gets the value for image-spec option for the image
-// from a set of annotations
-func (img *ContainerImage) GetParameterHelmImageSpec(annotations map[string]string) string {
- key := fmt.Sprintf(common.HelmParamImageSpecAnnotation, img.normalizedSymbolicName())
- val, ok := annotations[key]
- if !ok {
- return ""
- }
- return val
-}
-
-// GetParameterKustomizeImageName gets the value for image-spec option for the
-// image from a set of annotations
-func (img *ContainerImage) GetParameterKustomizeImageName(annotations map[string]string) string {
- key := fmt.Sprintf(common.KustomizeApplicationNameAnnotation, img.normalizedSymbolicName())
- val, ok := annotations[key]
- if !ok {
- return ""
- }
- return val
-}
-
-// HasForceUpdateOptionAnnotation gets the value for force-update option for the
-// image from a set of annotations
-func (img *ContainerImage) HasForceUpdateOptionAnnotation(annotations map[string]string) bool {
- forceUpdateAnnotations := []string{
- fmt.Sprintf(common.ForceUpdateOptionAnnotation, img.normalizedSymbolicName()),
- common.ApplicationWideForceUpdateOptionAnnotation,
- }
- var forceUpdateVal = ""
- for _, key := range forceUpdateAnnotations {
- if val, ok := annotations[key]; ok {
- forceUpdateVal = val
- break
- }
- }
- return forceUpdateVal == "true"
-}
-
-// GetParameterSort gets and validates the value for the sort option for the
-// image from a set of annotations
-func (img *ContainerImage) GetParameterUpdateStrategy(annotations map[string]string) UpdateStrategy {
- updateStrategyAnnotations := []string{
- fmt.Sprintf(common.UpdateStrategyAnnotation, img.normalizedSymbolicName()),
- common.ApplicationWideUpdateStrategyAnnotation,
- }
- var updateStrategyVal = ""
- for _, key := range updateStrategyAnnotations {
- if val, ok := annotations[key]; ok {
- updateStrategyVal = val
- break
- }
- }
- logCtx := img.LogContext()
- if updateStrategyVal == "" {
- logCtx.Tracef("No sort option found")
- // Default is sort by version
- return StrategySemVer
- }
- logCtx.Tracef("Found update strategy %s", updateStrategyVal)
- return img.ParseUpdateStrategy(updateStrategyVal)
-}
-
-func (img *ContainerImage) ParseUpdateStrategy(val string) UpdateStrategy {
- logCtx := img.LogContext()
- switch strings.ToLower(val) {
- case "semver":
- return StrategySemVer
- case "latest":
- logCtx.Warnf("\"latest\" strategy has been renamed to \"newest-build\". Please switch to the new convention as support for the old naming convention will be removed in future versions.")
- fallthrough
- case "newest-build":
- return StrategyNewestBuild
- case "name":
- logCtx.Warnf("\"name\" strategy has been renamed to \"alphabetical\". Please switch to the new convention as support for the old naming convention will be removed in future versions.")
- fallthrough
- case "alphabetical":
- return StrategyAlphabetical
- case "digest":
- return StrategyDigest
- default:
- logCtx.Warnf("Unknown sort option %s -- using semver", val)
- return StrategySemVer
- }
-}
-
-// GetParameterMatch returns the match function and pattern to use for matching
-// tag names. If an invalid option is found, it returns MatchFuncNone as the
-// default, to prevent accidental matches.
-func (img *ContainerImage) GetParameterMatch(annotations map[string]string) (MatchFuncFn, interface{}) {
- allowTagsAnnotations := []string{
- fmt.Sprintf(common.AllowTagsOptionAnnotation, img.normalizedSymbolicName()),
- common.ApplicationWideAllowTagsOptionAnnotation,
- }
- var allowTagsVal = ""
- for _, key := range allowTagsAnnotations {
- if val, ok := annotations[key]; ok {
- allowTagsVal = val
- break
- }
- }
- logCtx := img.LogContext()
- if allowTagsVal == "" {
- // The old match-tag annotation is deprecated and will be subject to removal
- // in a future version.
- key := fmt.Sprintf(common.OldMatchOptionAnnotation, img.normalizedSymbolicName())
- val, ok := annotations[key]
- if ok {
- logCtx.Warnf("The 'tag-match' annotation is deprecated and subject to removal. Please use 'allow-tags' annotation instead.")
- allowTagsVal = val
- }
- }
- if allowTagsVal == "" {
- logCtx.Tracef("No match annotation found")
- return MatchFuncAny, ""
- }
- return img.ParseMatchfunc(allowTagsVal)
-}
-
-// ParseMatchfunc returns a matcher function and its argument from given value
-func (img *ContainerImage) ParseMatchfunc(val string) (MatchFuncFn, interface{}) {
- logCtx := img.LogContext()
-
- // The special value "any" doesn't take any parameter
- if strings.ToLower(val) == "any" {
- return MatchFuncAny, nil
- }
-
- opt := strings.SplitN(val, ":", 2)
- if len(opt) != 2 {
- logCtx.Warnf("Invalid match option syntax '%s', ignoring", val)
- return MatchFuncNone, nil
- }
- switch strings.ToLower(opt[0]) {
- case "regexp":
- re, err := regexp.Compile(opt[1])
- if err != nil {
- logCtx.Warnf("Could not compile regexp '%s'", opt[1])
- return MatchFuncNone, nil
- }
- return MatchFuncRegexp, re
- default:
- logCtx.Warnf("Unknown match function: %s", opt[0])
- return MatchFuncNone, nil
- }
-}
-
-// GetParameterPullSecret retrieves an image's pull secret credentials
-func (img *ContainerImage) GetParameterPullSecret(annotations map[string]string) *CredentialSource {
- pullSecretAnnotations := []string{
- fmt.Sprintf(common.PullSecretAnnotation, img.normalizedSymbolicName()),
- common.ApplicationWidePullSecretAnnotation,
- }
- var pullSecretVal = ""
- for _, key := range pullSecretAnnotations {
- if val, ok := annotations[key]; ok {
- pullSecretVal = val
- break
- }
- }
- logCtx := img.LogContext()
- if pullSecretVal == "" {
- logCtx.Tracef("No pull-secret annotation found")
- return nil
- }
- credSrc, err := ParseCredentialSource(pullSecretVal, false)
- if err != nil {
- logCtx.Warnf("Invalid credential reference specified: %s", pullSecretVal)
- return nil
- }
- return credSrc
-}
-
-// GetParameterIgnoreTags retrieves a list of tags to ignore from a comma-separated string
-func (img *ContainerImage) GetParameterIgnoreTags(annotations map[string]string) []string {
- ignoreTagsAnnotations := []string{
- fmt.Sprintf(common.IgnoreTagsOptionAnnotation, img.normalizedSymbolicName()),
- common.ApplicationWideIgnoreTagsOptionAnnotation,
- }
- var ignoreTagsVal = ""
- for _, key := range ignoreTagsAnnotations {
- if val, ok := annotations[key]; ok {
- ignoreTagsVal = val
- break
- }
- }
- logCtx := img.LogContext()
- if ignoreTagsVal == "" {
- logCtx.Tracef("No ignore-tags annotation found")
- return nil
- }
- ignoreList := make([]string, 0)
- tags := strings.Split(strings.TrimSpace(ignoreTagsVal), ",")
- for _, tag := range tags {
- // We ignore empty tags
- trimmed := strings.TrimSpace(tag)
- if trimmed != "" {
- ignoreList = append(ignoreList, trimmed)
- }
- }
- return ignoreList
-}
-
-// GetPlatformOptions sets up platform constraints for an image. If no platform
-// is specified in the annotations, we restrict the platform for images to the
-// platform we're executed on unless unrestricted is set to true, in which case
-// we do not setup a platform restriction if no platform annotation is found.
-func (img *ContainerImage) GetPlatformOptions(annotations map[string]string, unrestricted bool) *options.ManifestOptions {
- logCtx := img.LogContext()
- var opts *options.ManifestOptions = options.NewManifestOptions()
- key := fmt.Sprintf(common.PlatformsAnnotation, img.normalizedSymbolicName())
- val, ok := annotations[key]
- if !ok {
- if !unrestricted {
- os := runtime.GOOS
- arch := runtime.GOARCH
- variant := ""
- if strings.Contains(runtime.GOARCH, "/") {
- a := strings.SplitN(runtime.GOARCH, "/", 2)
- arch = a[0]
- variant = a[1]
- }
- logCtx.Tracef("Using runtime platform constraint %s", options.PlatformKey(os, arch, variant))
- opts = opts.WithPlatform(os, arch, variant)
- }
- } else {
- platforms := strings.Split(val, ",")
- for _, ps := range platforms {
- pt := strings.TrimSpace(ps)
- os, arch, variant, err := ParsePlatform(pt)
- if err != nil {
- // If the platform identifier could not be parsed, we set the
- // constraint intentionally to the invalid value so we don't
- // end up updating to the wrong architecture possibly.
- os = ps
- logCtx.Warnf("could not parse platform identifier '%v': invalid format", pt)
- }
- logCtx.Tracef("Adding platform constraint %s", options.PlatformKey(os, arch, variant))
- opts = opts.WithPlatform(os, arch, variant)
- }
- }
-
- return opts
-}
-
-func ParsePlatform(platformID string) (string, string, string, error) {
- p := strings.SplitN(platformID, "/", 3)
- if len(p) < 2 {
- return "", "", "", fmt.Errorf("could not parse platform constraint '%s'", platformID)
- }
- os := p[0]
- arch := p[1]
- variant := ""
- if len(p) == 3 {
- variant = p[2]
- }
- return os, arch, variant, nil
-}
-
-func (img *ContainerImage) normalizedSymbolicName() string {
- return strings.ReplaceAll(img.ImageAlias, "/", "_")
-}
diff --git a/pkg/image/options_test.go b/pkg/image/options_test.go
deleted file mode 100644
index 7f32244..0000000
--- a/pkg/image/options_test.go
+++ /dev/null
@@ -1,493 +0,0 @@
-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/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.HelmParamImageNameAnnotation, "dummy"): "release.name",
- fmt.Sprintf(common.HelmParamImageTagAnnotation, "dummy"): "release.tag",
- fmt.Sprintf(common.HelmParamImageSpecAnnotation, "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.HelmParamImageNameAnnotation, "dummy"): "release.name",
- fmt.Sprintf(common.HelmParamImageTagAnnotation, "dummy"): "release.tag",
- fmt.Sprintf(common.HelmParamImageSpecAnnotation, "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.HelmParamImageNameAnnotation, "foo_dummy"): "release.name",
- fmt.Sprintf(common.HelmParamImageTagAnnotation, "foo_dummy"): "release.tag",
- fmt.Sprintf(common.HelmParamImageSpecAnnotation, "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.KustomizeApplicationNameAnnotation, "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.UpdateStrategyAnnotation, "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.UpdateStrategyAnnotation, "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.UpdateStrategyAnnotation, "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.UpdateStrategyAnnotation, "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.UpdateStrategyAnnotation, "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.UpdateStrategyAnnotation, "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.UpdateStrategyAnnotation, "dummy"): "alphabetical",
- common.ApplicationWideUpdateStrategyAnnotation: "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.ApplicationWideUpdateStrategyAnnotation: "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.ApplicationWideUpdateStrategyAnnotation: "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.AllowTagsOptionAnnotation, "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, &regexp.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.AllowTagsOptionAnnotation, "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.AllowTagsOptionAnnotation, "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.AllowTagsOptionAnnotation, "dummy"): "regexp:^[0-9]",
- common.ApplicationWideAllowTagsOptionAnnotation: "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, &regexp.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.ApplicationWideAllowTagsOptionAnnotation: "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, &regexp.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.PullSecretAnnotation, "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.PullSecretAnnotation, "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.PullSecretAnnotation, "dummy"): "pullsecret:image/specific",
- common.ApplicationWidePullSecretAnnotation: "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.ApplicationWidePullSecretAnnotation: "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.IgnoreTagsOptionAnnotation, "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.IgnoreTagsOptionAnnotation, "dummy"): "tag1, tag2",
- common.ApplicationWideIgnoreTagsOptionAnnotation: "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.ApplicationWideIgnoreTagsOptionAnnotation: "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.ForceUpdateOptionAnnotation, "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.ForceUpdateOptionAnnotation, "dummy"): "true",
- common.ApplicationWideForceUpdateOptionAnnotation: "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.ApplicationWideForceUpdateOptionAnnotation: "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.PlatformsAnnotation, "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.PlatformsAnnotation, "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.PlatformsAnnotation, "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.PlatformsAnnotation, "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)
-}
diff --git a/pkg/image/version.go b/pkg/image/version.go
deleted file mode 100644
index 97437bd..0000000
--- a/pkg/image/version.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package image
-
-import (
- "path/filepath"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-
- "github.com/Masterminds/semver/v3"
-)
-
-// VersionSortMode defines the method to sort a list of tags
-type UpdateStrategy int
-
-const (
- // VersionSortSemVer sorts tags using semver sorting (the default)
- StrategySemVer UpdateStrategy = 0
- // VersionSortLatest sorts tags after their creation date
- StrategyNewestBuild UpdateStrategy = 1
- // VersionSortName sorts tags alphabetically by name
- StrategyAlphabetical UpdateStrategy = 2
- // VersionSortDigest uses latest digest of an image
- StrategyDigest UpdateStrategy = 3
-)
-
-func (us UpdateStrategy) String() string {
- switch us {
- case StrategySemVer:
- return "semver"
- case StrategyNewestBuild:
- return "newest-build"
- case StrategyAlphabetical:
- return "alphabetical"
- case StrategyDigest:
- return "digest"
- }
-
- return "unknown"
-}
-
-// ConstraintMatchMode defines how the constraint should be matched
-type ConstraintMatchMode int
-
-const (
- // ConstraintMatchSemVer uses semver to match a constraint
- ConstraintMatchSemver ConstraintMatchMode = 0
- // ConstraintMatchRegExp uses regexp to match a constraint
- ConstraintMatchRegExp ConstraintMatchMode = 1
- // ConstraintMatchNone does not enforce a constraint
- ConstraintMatchNone ConstraintMatchMode = 2
-)
-
-// VersionConstraint defines a constraint for comparing versions
-type VersionConstraint struct {
- Constraint string
- MatchFunc MatchFuncFn
- MatchArgs interface{}
- IgnoreList []string
- Strategy UpdateStrategy
- Options *options.ManifestOptions
-}
-
-type MatchFuncFn func(tagName string, pattern interface{}) bool
-
-// String returns the string representation of VersionConstraint
-func (vc *VersionConstraint) String() string {
- return vc.Constraint
-}
-
-func NewVersionConstraint() *VersionConstraint {
- return &VersionConstraint{
- MatchFunc: MatchFuncNone,
- Strategy: StrategySemVer,
- Options: options.NewManifestOptions(),
- }
-}
-
-// GetNewestVersionFromTags returns the latest available version from a list of
-// tags while optionally taking a semver constraint into account. Returns the
-// original version if no new version could be found from the list of tags.
-func (img *ContainerImage) GetNewestVersionFromTags(vc *VersionConstraint, tagList *tag.ImageTagList) (*tag.ImageTag, error) {
- logCtx := log.NewContext()
- logCtx.AddField("image", img.String())
-
- var availableTags tag.SortableImageTagList
- switch vc.Strategy {
- case StrategySemVer:
- availableTags = tagList.SortBySemVer()
- case StrategyAlphabetical:
- availableTags = tagList.SortAlphabetically()
- case StrategyNewestBuild:
- availableTags = tagList.SortByDate()
- case StrategyDigest:
- availableTags = tagList.SortAlphabetically()
- }
-
- considerTags := tag.SortableImageTagList{}
-
- // It makes no sense to proceed if we have no available tags
- if len(availableTags) == 0 {
- return img.ImageTag, nil
- }
-
- // The given constraint MUST match a semver constraint
- var semverConstraint *semver.Constraints
- var err error
- if vc.Strategy == StrategySemVer {
- // TODO: Shall we really ensure a valid semver on the current tag?
- // This prevents updating from a non-semver tag currently.
- if img.ImageTag != nil && img.ImageTag.TagName != "" {
- _, err := semver.NewVersion(img.ImageTag.TagName)
- if err != nil {
- return nil, err
- }
- }
-
- if vc.Constraint != "" {
- if vc.Strategy == StrategySemVer {
- semverConstraint, err = semver.NewConstraint(vc.Constraint)
- if err != nil {
- logCtx.Errorf("invalid constraint '%s' given: '%v'", vc, err)
- return nil, err
- }
- }
- }
- }
-
- // Loop through all tags to check whether it's an update candidate.
- for _, tag := range availableTags {
- logCtx.Tracef("Finding out whether to consider %s for being updateable", tag.TagName)
-
- if vc.Strategy == StrategySemVer {
- // Non-parseable tag does not mean error - just skip it
- ver, err := semver.NewVersion(tag.TagName)
- if err != nil {
- logCtx.Tracef("Not a valid version: %s", tag.TagName)
- continue
- }
-
- // If we have a version constraint, check image tag against it. If the
- // constraint is not satisfied, skip tag.
- if semverConstraint != nil {
- if !semverConstraint.Check(ver) {
- logCtx.Tracef("%s did not match constraint %s", ver.Original(), vc.Constraint)
- continue
- }
- }
- } else if vc.Strategy == StrategyDigest {
- if tag.TagName != vc.Constraint {
- logCtx.Tracef("%s did not match contraint %s", tag.TagName, vc.Constraint)
- continue
- }
- }
-
- // Append tag as update candidate
- considerTags = append(considerTags, tag)
- }
-
- logCtx.Debugf("found %d from %d tags eligible for consideration", len(considerTags), len(availableTags))
-
- // If we found tags to consider, return the most recent tag found according
- // to the update strategy.
- if len(considerTags) > 0 {
- return considerTags[len(considerTags)-1], nil
- }
-
- return nil, nil
-}
-
-// IsTagIgnored matches tag against the patterns in IgnoreList and returns true if one of them matches
-func (vc *VersionConstraint) IsTagIgnored(tag string) bool {
- for _, t := range vc.IgnoreList {
- if match, err := filepath.Match(t, tag); err == nil && match {
- log.Tracef("tag %s is ignored by pattern %s", tag, t)
- return true
- }
- }
- return false
-}
-
-// IsCacheable returns true if we can safely cache tags for strategy s
-func (s UpdateStrategy) IsCacheable() bool {
- switch s {
- case StrategyDigest:
- return false
- default:
- return true
- }
-}
-
-// NeedsMetadata returns true if strategy s requires image metadata to work correctly
-func (s UpdateStrategy) NeedsMetadata() bool {
- switch s {
- case StrategyNewestBuild:
- return true
- default:
- return false
- }
-}
-
-// NeedsVersionConstraint returns true if strategy s requires a version constraint to be defined
-func (s UpdateStrategy) NeedsVersionConstraint() bool {
- switch s {
- case StrategyDigest:
- return true
- default:
- return false
- }
-}
-
-// WantsOnlyConstraintTag returns true if strategy s only wants to inspect the tag specified by the constraint
-func (s UpdateStrategy) WantsOnlyConstraintTag() bool {
- switch s {
- case StrategyDigest:
- return true
- default:
- return false
- }
-}
diff --git a/pkg/image/version_test.go b/pkg/image/version_test.go
deleted file mode 100644
index 4c1fe2c..0000000
--- a/pkg/image/version_test.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package image
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-)
-
-func newImageTagList(tagNames []string) *tag.ImageTagList {
- tagList := tag.NewImageTagList()
- for _, tagName := range tagNames {
- tagList.Add(tag.NewImageTag(tagName, time.Unix(0, 0), ""))
- }
- return tagList
-}
-
-func newImageTagListWithDate(tagNames []string) *tag.ImageTagList {
- tagList := tag.NewImageTagList()
- for i, t := range tagNames {
- tagList.Add(tag.NewImageTag(t, time.Unix(int64(i*5), 0), ""))
- }
- return tagList
-}
-
-func Test_LatestVersion(t *testing.T) {
- t.Run("Find the latest version without any constraint", func(t *testing.T) {
- tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "1.0", "1.0.1", "1.1.2", "2.0.3"})
- img := NewFromIdentifier("jannfis/test:1.0")
- vc := VersionConstraint{}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "2.0.3", newTag.TagName)
- })
-
- t.Run("Find the latest version with a semver constraint on major", func(t *testing.T) {
- tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "1.0", "1.0.1", "1.1.2", "2.0.3"})
- img := NewFromIdentifier("jannfis/test:1.0")
- vc := VersionConstraint{Constraint: "^1.0"}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "1.1.2", newTag.TagName)
- })
-
- t.Run("Find the latest version with a semver constraint on patch", func(t *testing.T) {
- tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "1.0", "1.0.1", "1.1.2", "2.0.3"})
- img := NewFromIdentifier("jannfis/test:1.0")
- vc := VersionConstraint{Constraint: "~1.0"}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "1.0.1", newTag.TagName)
- })
-
- t.Run("Find the latest version with a semver constraint that has no match", func(t *testing.T) {
- tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "2.0.3"})
- img := NewFromIdentifier("jannfis/test:1.0")
- vc := VersionConstraint{Constraint: "~1.0"}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.Nil(t, newTag)
- })
-
- t.Run("Find the latest version with a semver constraint that is invalid", func(t *testing.T) {
- tagList := newImageTagList([]string{"0.1", "0.5.1", "0.9", "2.0.3"})
- img := NewFromIdentifier("jannfis/test:1.0")
- vc := VersionConstraint{Constraint: "latest"}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- assert.Error(t, err)
- assert.Nil(t, newTag)
- })
-
- t.Run("Find the latest version with no tags", func(t *testing.T) {
- tagList := newImageTagList([]string{})
- img := NewFromIdentifier("jannfis/test:1.0")
- vc := VersionConstraint{Constraint: "~1.0"}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "1.0", newTag.TagName)
- })
-
- t.Run("Find the latest version using latest sortmode", func(t *testing.T) {
- tagList := newImageTagListWithDate([]string{"zz", "bb", "yy", "cc", "yy", "aa", "ll"})
- img := NewFromIdentifier("jannfis/test:bb")
- vc := VersionConstraint{Strategy: StrategyNewestBuild}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "ll", newTag.TagName)
- })
-
- t.Run("Find the latest version using latest sortmode, invalid tags", func(t *testing.T) {
- tagList := newImageTagListWithDate([]string{"zz", "bb", "yy", "cc", "yy", "aa", "ll"})
- img := NewFromIdentifier("jannfis/test:bb")
- vc := VersionConstraint{Strategy: StrategySemVer}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "bb", newTag.TagName)
- })
-
- t.Run("Find the latest version using VersionConstraint StrategyAlphabetical", func(t *testing.T) {
- tagList := newImageTagListWithDate([]string{"zz", "bb", "yy", "cc", "yy", "aa", "ll"})
- img := NewFromIdentifier("jannfis/test:bb")
- vc := VersionConstraint{Strategy: StrategyAlphabetical}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- require.NotNil(t, newTag)
- assert.Equal(t, "zz", newTag.TagName)
- })
-
- t.Run("Find the latest version using VersionConstraint StrategyDigest", func(t *testing.T) {
- tagList := tag.NewImageTagList()
- newDigest := "latest@sha:abcdefg"
- tagList.Add(tag.NewImageTag("latest", time.Unix(int64(6), 0), newDigest))
- img := NewFromIdentifier("jannfis/test:latest@sha:1234567")
- vc := VersionConstraint{Strategy: StrategyDigest, Constraint: "latest"}
- newTag, err := img.GetNewestVersionFromTags(&vc, tagList)
- require.NoError(t, err)
- assert.Equal(t, "latest", newTag.TagName)
- assert.Equal(t, newDigest, newTag.TagDigest)
- })
-
-}
-
-func Test_UpdateStrategy_String(t *testing.T) {
- tests := []struct {
- name string
- us UpdateStrategy
- want string
- }{
- {"StrategySemVer", StrategySemVer, "semver"},
- {"StrategyNewestBuild", StrategyNewestBuild, "newest-build"},
- {"StrategyAlphabetical", StrategyAlphabetical, "alphabetical"},
- {"StrategyDigest", StrategyDigest, "digest"},
- {"unknown", UpdateStrategy(-1), "unknown"},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, tt.want, tt.us.String())
- })
- }
-}
-
-func Test_NewVersionConstraint(t *testing.T) {
- constraint := NewVersionConstraint()
- assert.Equal(t, StrategySemVer, constraint.Strategy)
- assert.Equal(t, options.NewManifestOptions(), constraint.Options)
- assert.False(t, constraint.MatchFunc("", ""))
-}
-
-func Test_VersionConstraint_IsTagIgnored(t *testing.T) {
- versionConstraint := VersionConstraint{IgnoreList: []string{"tag1", "tag2"}}
- assert.True(t, versionConstraint.IsTagIgnored("tag1"))
- assert.True(t, versionConstraint.IsTagIgnored("tag2"))
- assert.False(t, versionConstraint.IsTagIgnored("tag3"))
- versionConstraint.IgnoreList = []string{"tag?", "foo"}
- assert.True(t, versionConstraint.IsTagIgnored("tag1"))
- assert.True(t, versionConstraint.IsTagIgnored("foo"))
- assert.False(t, versionConstraint.IsTagIgnored("tag10"))
-}
-
-func Test_UpdateStrategy_IsCacheable(t *testing.T) {
- assert.True(t, StrategySemVer.IsCacheable())
- assert.True(t, StrategyNewestBuild.IsCacheable())
- assert.True(t, StrategyAlphabetical.IsCacheable())
- assert.False(t, StrategyDigest.IsCacheable())
-}
-
-func Test_UpdateStrategy_NeedsMetadata(t *testing.T) {
- assert.False(t, StrategySemVer.NeedsMetadata())
- assert.True(t, StrategyNewestBuild.NeedsMetadata())
- assert.False(t, StrategyAlphabetical.NeedsMetadata())
- assert.False(t, StrategyDigest.NeedsMetadata())
-}
-
-func Test_UpdateStrategy_NeedsVersionConstraint(t *testing.T) {
- assert.False(t, StrategySemVer.NeedsVersionConstraint())
- assert.False(t, StrategyNewestBuild.NeedsVersionConstraint())
- assert.False(t, StrategyAlphabetical.NeedsVersionConstraint())
- assert.True(t, StrategyDigest.NeedsVersionConstraint())
-}
-
-func Test_UpdateStrategy_WantsOnlyConstraintTag(t *testing.T) {
- assert.False(t, StrategySemVer.WantsOnlyConstraintTag())
- assert.False(t, StrategyNewestBuild.WantsOnlyConstraintTag())
- assert.False(t, StrategyAlphabetical.WantsOnlyConstraintTag())
- assert.True(t, StrategyDigest.WantsOnlyConstraintTag())
-}
diff --git a/pkg/kube/kubernetes.go b/pkg/kube/kubernetes.go
index 1158331..cb8c7f8 100644
--- a/pkg/kube/kubernetes.go
+++ b/pkg/kube/kubernetes.go
@@ -5,10 +5,9 @@ package kube
import (
"context"
"fmt"
- "os"
"time"
- "github.com/argoproj-labs/argocd-image-updater/pkg/metrics"
+ kube "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/kube"
appv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
@@ -16,88 +15,22 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth"
- "k8s.io/client-go/tools/clientcmd"
)
-type KubernetesClient struct {
- Clientset kubernetes.Interface
+type ImageUpdaterKubernetesClient struct {
ApplicationsClientset versioned.Interface
- Context context.Context
- Namespace string
+ KubeClient *kube.KubernetesClient
}
-func NewKubernetesClient(ctx context.Context, client kubernetes.Interface, applicationsClientset versioned.Interface, namespace string) *KubernetesClient {
- kc := &KubernetesClient{}
- kc.Context = ctx
- kc.Clientset = client
+func NewKubernetesClient(ctx context.Context, client kubernetes.Interface, applicationsClientset versioned.Interface, namespace string) *ImageUpdaterKubernetesClient {
+ kc := &ImageUpdaterKubernetesClient{}
+ kc.KubeClient = kube.NewKubernetesClient(ctx, client, namespace)
kc.ApplicationsClientset = applicationsClientset
- kc.Namespace = namespace
return kc
}
-// NewKubernetesClient creates a new Kubernetes client object from given
-// configuration file. If configuration file is the empty string, in-cluster
-// client will be created.
-func NewKubernetesClientFromConfig(ctx context.Context, namespace string, kubeconfig string) (*KubernetesClient, error) {
- loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
- loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig
- loadingRules.ExplicitPath = kubeconfig
- overrides := clientcmd.ConfigOverrides{}
- clientConfig := clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, &overrides, os.Stdin)
-
- config, err := clientConfig.ClientConfig()
- if err != nil {
- return nil, err
- }
-
- if namespace == "" {
- namespace, _, err = clientConfig.Namespace()
- if err != nil {
- return nil, err
- }
- }
-
- clientset, err := kubernetes.NewForConfig(config)
- if err != nil {
- return nil, err
- }
-
- applicationsClientset, err := versioned.NewForConfig(config)
- if err != nil {
- return nil, err
- }
-
- return NewKubernetesClient(ctx, clientset, applicationsClientset, namespace), nil
-}
-
-// GetSecretData returns the raw data from named K8s secret in given namespace
-func (client *KubernetesClient) GetSecretData(namespace string, secretName string) (map[string][]byte, error) {
- secret, err := client.Clientset.CoreV1().Secrets(namespace).Get(client.Context, secretName, metav1.GetOptions{})
- metrics.Clients().IncreaseK8sClientRequest(1)
- if err != nil {
- metrics.Clients().IncreaseK8sClientRequest(1)
- return nil, err
- }
- return secret.Data, nil
-}
-
-// GetSecretField returns the value of a field from named K8s secret in given namespace
-func (client *KubernetesClient) GetSecretField(namespace string, secretName string, field string) (string, error) {
- secret, err := client.GetSecretData(namespace, secretName)
- metrics.Clients().IncreaseK8sClientRequest(1)
- if err != nil {
- metrics.Clients().IncreaseK8sClientRequest(1)
- return "", err
- }
- if data, ok := secret[field]; !ok {
- return "", fmt.Errorf("secret '%s/%s' does not have a field '%s'", namespace, secretName, field)
- } else {
- return string(data), nil
- }
-}
-
// CreateApplicationEvent creates a kubernetes event with a custom reason and message for an application.
-func (client *KubernetesClient) CreateApplicationEvent(app *appv1alpha1.Application, reason string, message string, annotations map[string]string) (*v1.Event, error) {
+func (client *ImageUpdaterKubernetesClient) CreateApplicationEvent(app *appv1alpha1.Application, reason string, message string, annotations map[string]string) (*v1.Event, error) {
t := metav1.Time{Time: time.Now()}
event := v1.Event{
@@ -125,7 +58,7 @@ func (client *KubernetesClient) CreateApplicationEvent(app *appv1alpha1.Applicat
Reason: reason,
}
- result, err := client.Clientset.CoreV1().Events(app.ObjectMeta.Namespace).Create(client.Context, &event, metav1.CreateOptions{})
+ result, err := client.KubeClient.Clientset.CoreV1().Events(app.ObjectMeta.Namespace).Create(client.KubeClient.Context, &event, metav1.CreateOptions{})
if err != nil {
return nil, err
}
diff --git a/pkg/kube/kubernetes_test.go b/pkg/kube/kubernetes_test.go
index 35d6922..d1b2fae 100644
--- a/pkg/kube/kubernetes_test.go
+++ b/pkg/kube/kubernetes_test.go
@@ -7,6 +7,7 @@ import (
appv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ registryKube "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/kube"
"github.com/argoproj-labs/argocd-image-updater/test/fake"
"github.com/argoproj-labs/argocd-image-updater/test/fixture"
@@ -16,14 +17,14 @@ import (
func Test_NewKubernetesClient(t *testing.T) {
t.Run("Get new K8s client for remote cluster instance", func(t *testing.T) {
- client, err := NewKubernetesClientFromConfig(context.TODO(), "", "../../test/testdata/kubernetes/config")
+ client, err := registryKube.NewKubernetesClientFromConfig(context.TODO(), "", "../../test/testdata/kubernetes/config")
require.NoError(t, err)
assert.NotNil(t, client)
assert.Equal(t, "default", client.Namespace)
})
t.Run("Get new K8s client for remote cluster instance specified namespace", func(t *testing.T) {
- client, err := NewKubernetesClientFromConfig(context.TODO(), "argocd", "../../test/testdata/kubernetes/config")
+ client, err := registryKube.NewKubernetesClientFromConfig(context.TODO(), "argocd", "../../test/testdata/kubernetes/config")
require.NoError(t, err)
assert.NotNil(t, client)
assert.Equal(t, "argocd", client.Namespace)
@@ -34,8 +35,8 @@ func Test_GetDataFromSecrets(t *testing.T) {
t.Run("Get all data from dummy secret", func(t *testing.T) {
secret := fixture.MustCreateSecretFromFile("../../test/testdata/resources/dummy-secret.json")
clientset := fake.NewFakeClientsetWithResources(secret)
- client := &KubernetesClient{Clientset: clientset}
- data, err := client.GetSecretData("test-namespace", "test-secret")
+ client := &ImageUpdaterKubernetesClient{KubeClient: &registryKube.KubernetesClient{Clientset: clientset}}
+ data, err := client.KubeClient.GetSecretData("test-namespace", "test-secret")
require.NoError(t, err)
require.NotNil(t, data)
assert.Len(t, data, 1)
@@ -45,8 +46,8 @@ func Test_GetDataFromSecrets(t *testing.T) {
t.Run("Get string data from dummy secret existing field", func(t *testing.T) {
secret := fixture.MustCreateSecretFromFile("../../test/testdata/resources/dummy-secret.json")
clientset := fake.NewFakeClientsetWithResources(secret)
- client := &KubernetesClient{Clientset: clientset}
- data, err := client.GetSecretField("test-namespace", "test-secret", "namespace")
+ client := &ImageUpdaterKubernetesClient{KubeClient: &registryKube.KubernetesClient{Clientset: clientset}}
+ data, err := client.KubeClient.GetSecretField("test-namespace", "test-secret", "namespace")
require.NoError(t, err)
assert.Equal(t, "argocd", data)
})
@@ -54,8 +55,8 @@ func Test_GetDataFromSecrets(t *testing.T) {
t.Run("Get string data from dummy secret non-existing field", func(t *testing.T) {
secret := fixture.MustCreateSecretFromFile("../../test/testdata/resources/dummy-secret.json")
clientset := fake.NewFakeClientsetWithResources(secret)
- client := &KubernetesClient{Clientset: clientset}
- data, err := client.GetSecretField("test-namespace", "test-secret", "nonexisting")
+ client := &ImageUpdaterKubernetesClient{KubeClient: &registryKube.KubernetesClient{Clientset: clientset}}
+ data, err := client.KubeClient.GetSecretField("test-namespace", "test-secret", "nonexisting")
require.Error(t, err)
require.Empty(t, data)
})
@@ -63,8 +64,8 @@ func Test_GetDataFromSecrets(t *testing.T) {
t.Run("Get string data from non-existing secret non-existing field", func(t *testing.T) {
secret := fixture.MustCreateSecretFromFile("../../test/testdata/resources/dummy-secret.json")
clientset := fake.NewFakeClientsetWithResources(secret)
- client := &KubernetesClient{Clientset: clientset}
- data, err := client.GetSecretField("test-namespace", "test", "namespace")
+ client := &ImageUpdaterKubernetesClient{KubeClient: &registryKube.KubernetesClient{Clientset: clientset}}
+ data, err := client.KubeClient.GetSecretField("test-namespace", "test", "namespace")
require.Error(t, err)
require.Empty(t, data)
})
@@ -88,7 +89,7 @@ func Test_CreateApplicationEvent(t *testing.T) {
"origin": "nginx:1.12.2",
}
clientset := fake.NewFakeClientsetWithResources()
- client := &KubernetesClient{Clientset: clientset, Namespace: "default"}
+ client := &ImageUpdaterKubernetesClient{KubeClient: &registryKube.KubernetesClient{Clientset: clientset, Namespace: "default"}}
event, err := client.CreateApplicationEvent(application, "TestEvent", "test-message", annotations)
require.NoError(t, err)
require.NotNil(t, event)
diff --git a/pkg/registry/client.go b/pkg/registry/client.go
deleted file mode 100644
index 3bad7ec..0000000
--- a/pkg/registry/client.go
+++ /dev/null
@@ -1,449 +0,0 @@
-package registry
-
-import (
- "context"
- "crypto/sha256"
- "fmt"
-
- "github.com/argoproj/pkg/json"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/metrics"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-
- "github.com/distribution/distribution/v3"
- "github.com/distribution/distribution/v3/manifest/manifestlist"
- "github.com/distribution/distribution/v3/manifest/ocischema"
- "github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck
- "github.com/distribution/distribution/v3/manifest/schema2"
- "github.com/distribution/distribution/v3/reference"
- "github.com/distribution/distribution/v3/registry/client"
- "github.com/distribution/distribution/v3/registry/client/auth"
- "github.com/distribution/distribution/v3/registry/client/auth/challenge"
- "github.com/distribution/distribution/v3/registry/client/transport"
-
- "github.com/opencontainers/go-digest"
- ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
-
- "go.uber.org/ratelimit"
-
- "net/http"
- "net/url"
- "strings"
- "time"
-)
-
-// TODO: Check image's architecture and OS
-
-// knownMediaTypes is the list of media types we can process
-var knownMediaTypes = []string{
- ocischema.SchemaVersion.MediaType,
- schema1.MediaTypeSignedManifest, //nolint:staticcheck
- schema2.SchemaVersion.MediaType,
- manifestlist.SchemaVersion.MediaType,
- ociv1.MediaTypeImageIndex,
-}
-
-// RegistryClient defines the methods we need for querying container registries
-type RegistryClient interface {
- NewRepository(nameInRepository string) error
- Tags() ([]string, error)
- ManifestForTag(tagStr string) (distribution.Manifest, error)
- ManifestForDigest(dgst digest.Digest) (distribution.Manifest, error)
- TagMetadata(manifest distribution.Manifest, opts *options.ManifestOptions) (*tag.TagInfo, error)
-}
-
-type NewRegistryClient func(*RegistryEndpoint, string, string) (RegistryClient, error)
-
-// Helper type for registry clients
-type registryClient struct {
- regClient distribution.Repository
- endpoint *RegistryEndpoint
- creds credentials
-}
-
-// credentials is an implementation of distribution/V3/session struct
-// to manage registry credentials and token
-type credentials struct {
- username string
- password string
- refreshTokens map[string]string
-}
-
-func (c credentials) Basic(url *url.URL) (string, string) {
- return c.username, c.password
-}
-
-func (c credentials) RefreshToken(url *url.URL, service string) string {
- return c.refreshTokens[service]
-}
-
-func (c credentials) SetRefreshToken(realm *url.URL, service, token string) {
- if c.refreshTokens != nil {
- c.refreshTokens[service] = token
- }
-}
-
-// rateLimitTransport encapsulates our custom HTTP round tripper with rate
-// limiter from the endpoint.
-type rateLimitTransport struct {
- limiter ratelimit.Limiter
- transport http.RoundTripper
- endpoint *RegistryEndpoint
-}
-
-// RoundTrip is a custom RoundTrip method with rate-limiter
-func (rlt *rateLimitTransport) RoundTrip(r *http.Request) (*http.Response, error) {
- rlt.limiter.Take()
- log.Tracef("Performing HTTP %s %s", r.Method, r.URL)
- resp, err := rlt.transport.RoundTrip(r)
- metrics.Endpoint().IncreaseRequest(rlt.endpoint.RegistryAPI, err != nil)
- return resp, err
-}
-
-// NewRepository is a wrapper for creating a registry client that is possibly
-// rate-limited by using a custom HTTP round tripper method.
-func (clt *registryClient) NewRepository(nameInRepository string) error {
- urlToCall := strings.TrimSuffix(clt.endpoint.RegistryAPI, "/")
- challengeManager1 := challenge.NewSimpleManager()
- _, err := ping(challengeManager1, clt.endpoint, "")
- if err != nil {
- return err
- }
-
- authTransport := transport.NewTransport(
- clt.endpoint.GetTransport(), auth.NewAuthorizer(
- challengeManager1,
- auth.NewTokenHandler(clt.endpoint.GetTransport(), clt.creds, nameInRepository, "pull"),
- auth.NewBasicHandler(clt.creds)))
-
- rlt := &rateLimitTransport{
- limiter: clt.endpoint.Limiter,
- transport: authTransport,
- endpoint: clt.endpoint,
- }
-
- named, err := reference.WithName(nameInRepository)
- if err != nil {
- return err
- }
- clt.regClient, err = client.NewRepository(named, urlToCall, rlt)
- if err != nil {
- return err
- }
- return nil
-}
-
-// NewClient returns a new RegistryClient for the given endpoint information
-func NewClient(endpoint *RegistryEndpoint, username, password string) (RegistryClient, error) {
- if username == "" && endpoint.Username != "" {
- username = endpoint.Username
- }
- if password == "" && endpoint.Password != "" {
- password = endpoint.Password
- }
- creds := credentials{
- username: username,
- password: password,
- }
- return &registryClient{
- creds: creds,
- endpoint: endpoint,
- }, nil
-}
-
-// Tags returns a list of tags for given name in repository
-func (clt *registryClient) Tags() ([]string, error) {
- tagService := clt.regClient.Tags(context.Background())
- tTags, err := tagService.All(context.Background())
- if err != nil {
- return nil, err
- }
- return tTags, nil
-}
-
-// Manifest returns a Manifest for a given tag in repository
-func (clt *registryClient) ManifestForTag(tagStr string) (distribution.Manifest, error) {
- manService, err := clt.regClient.Manifests(context.Background())
- if err != nil {
- return nil, err
- }
- manifest, err := manService.Get(
- context.Background(),
- digest.FromString(tagStr),
- distribution.WithTag(tagStr), distribution.WithManifestMediaTypes(knownMediaTypes))
- if err != nil {
- return nil, err
- }
- return manifest, nil
-}
-
-// ManifestForDigest returns a Manifest for a given digest in repository
-func (clt *registryClient) ManifestForDigest(dgst digest.Digest) (distribution.Manifest, error) {
- manService, err := clt.regClient.Manifests(context.Background())
- if err != nil {
- return nil, err
- }
- manifest, err := manService.Get(
- context.Background(),
- dgst,
- distribution.WithManifestMediaTypes(knownMediaTypes))
- if err != nil {
- return nil, err
- }
- return manifest, nil
-}
-
-// TagMetadata retrieves metadata for a given manifest of given repository
-func (client *registryClient) TagMetadata(manifest distribution.Manifest, opts *options.ManifestOptions) (*tag.TagInfo, error) {
- ti := &tag.TagInfo{}
- logCtx := opts.Logger()
- var info struct {
- Arch string `json:"architecture"`
- Created string `json:"created"`
- OS string `json:"os"`
- Variant string `json:"variant"`
- }
-
- // We support the following types of manifests as returned by the registry:
- //
- // V1 (legacy, might go away), V2 and OCI
- //
- // Also ManifestLists (e.g. on multi-arch images) are supported.
- //
- switch deserialized := manifest.(type) {
-
- case *schema1.SignedManifest: //nolint:staticcheck
- var man schema1.Manifest = deserialized.Manifest //nolint:staticcheck
- if len(man.History) == 0 {
- return nil, fmt.Errorf("no history information found in schema V1")
- }
-
- _, mBytes, err := manifest.Payload()
- if err != nil {
- return nil, err
- }
- ti.Digest = sha256.Sum256(mBytes)
-
- logCtx.Tracef("v1 SHA digest is %s", ti.EncodedDigest())
- if err := json.Unmarshal([]byte(man.History[0].V1Compatibility), &info); err != nil {
- return nil, err
- }
- if !opts.WantsPlatform(info.OS, info.Arch, "") {
- logCtx.Debugf("ignoring v1 manifest %v. Manifest platform: %s, requested: %s",
- ti.EncodedDigest(), options.PlatformKey(info.OS, info.Arch, info.Variant), strings.Join(opts.Platforms(), ","))
- return nil, nil
- }
- if createdAt, err := time.Parse(time.RFC3339Nano, info.Created); err != nil {
- return nil, err
- } else {
- ti.CreatedAt = createdAt
- }
- return ti, nil
-
- case *manifestlist.DeserializedManifestList:
- var list manifestlist.DeserializedManifestList = *deserialized
-
- // List must contain at least one image manifest
- if len(list.Manifests) == 0 {
- return nil, fmt.Errorf("empty manifestlist not supported")
- }
-
- // We use the SHA from the manifest list to let the container engine
- // decide which image to pull, in case of multi-arch clusters.
- _, mBytes, err := list.Payload()
- if err != nil {
- return nil, fmt.Errorf("could not retrieve manifestlist payload: %v", err)
- }
- ti.Digest = sha256.Sum256(mBytes)
-
- logCtx.Tracef("SHA256 of manifest parent is %v", ti.EncodedDigest())
-
- return TagInfoFromReferences(client, opts, logCtx, ti, list.References())
-
- case *ocischema.DeserializedImageIndex:
- var index ocischema.DeserializedImageIndex = *deserialized
-
- // Index must contain at least one image manifest
- if len(index.Manifests) == 0 {
- return nil, fmt.Errorf("empty index not supported")
- }
-
- // We use the SHA from the manifest index to let the container engine
- // decide which image to pull, in case of multi-arch clusters.
- _, mBytes, err := index.Payload()
- if err != nil {
- return nil, fmt.Errorf("could not retrieve index payload: %v", err)
- }
- ti.Digest = sha256.Sum256(mBytes)
-
- logCtx.Tracef("SHA256 of manifest parent is %v", ti.EncodedDigest())
-
- return TagInfoFromReferences(client, opts, logCtx, ti, index.References())
-
- case *schema2.DeserializedManifest:
- var man schema2.Manifest = deserialized.Manifest
-
- logCtx.Tracef("Manifest digest is %v", man.Config.Digest.Encoded())
-
- _, mBytes, err := manifest.Payload()
- if err != nil {
- return nil, err
- }
- ti.Digest = sha256.Sum256(mBytes)
- logCtx.Tracef("v2 SHA digest is %s", ti.EncodedDigest())
-
- // The data we require from a V2 manifest is in a blob that we need to
- // fetch from the registry.
- blobReader, err := client.regClient.Blobs(context.Background()).Get(context.Background(), man.Config.Digest)
- if err != nil {
- return nil, err
- }
-
- if err := json.Unmarshal(blobReader, &info); err != nil {
- return nil, err
- }
-
- if !opts.WantsPlatform(info.OS, info.Arch, info.Variant) {
- logCtx.Debugf("ignoring v2 manifest %v. Manifest platform: %s, requested: %s",
- ti.EncodedDigest(), options.PlatformKey(info.OS, info.Arch, info.Variant), strings.Join(opts.Platforms(), ","))
- return nil, nil
- }
-
- if ti.CreatedAt, err = time.Parse(time.RFC3339Nano, info.Created); err != nil {
- return nil, err
- }
-
- return ti, nil
- case *ocischema.DeserializedManifest:
- var man ocischema.Manifest = deserialized.Manifest
-
- _, mBytes, err := manifest.Payload()
- if err != nil {
- return nil, err
- }
- ti.Digest = sha256.Sum256(mBytes)
- logCtx.Tracef("OCI SHA digest is %s", ti.EncodedDigest())
-
- // The data we require from a V2 manifest is in a blob that we need to
- // fetch from the registry.
- blobReader, err := client.regClient.Blobs(context.Background()).Get(context.Background(), man.Config.Digest)
- if err != nil {
- return nil, err
- }
-
- if err := json.Unmarshal(blobReader, &info); err != nil {
- return nil, err
- }
-
- if !opts.WantsPlatform(info.OS, info.Arch, info.Variant) {
- logCtx.Debugf("ignoring OCI manifest %v. Manifest platform: %s, requested: %s",
- ti.EncodedDigest(), options.PlatformKey(info.OS, info.Arch, info.Variant), strings.Join(opts.Platforms(), ","))
- return nil, nil
- }
-
- if ti.CreatedAt, err = time.Parse(time.RFC3339Nano, info.Created); err != nil {
- return nil, err
- }
-
- return ti, nil
- default:
- return nil, fmt.Errorf("invalid manifest type %T", manifest)
- }
-}
-
-// TagInfoFromReferences is a helper method to retrieve metadata for a given
-// list of references. It will return the most recent pushed manifest from the
-// list of references.
-func TagInfoFromReferences(client *registryClient, opts *options.ManifestOptions, logCtx *log.LogContext, ti *tag.TagInfo, references []distribution.Descriptor) (*tag.TagInfo, error) {
- var ml []distribution.Descriptor
- platforms := []string{}
-
- for _, ref := range references {
- var refOS, refArch, refVariant string
- if ref.Platform != nil {
- refOS = ref.Platform.OS
- refArch = ref.Platform.Architecture
- refVariant = ref.Platform.Variant
- }
- platform1 := options.PlatformKey(refOS, refArch, refVariant)
- platforms = append(platforms, platform1)
- logCtx.Tracef("Found %s", platform1)
- if !opts.WantsPlatform(refOS, refArch, refVariant) {
- logCtx.Tracef("Ignoring referenced manifest %v because platform %s does not match any of: %s",
- ref.Digest,
- platform1,
- strings.Join(opts.Platforms(), ","))
- continue
- }
- ml = append(ml, ref)
- }
-
- // We need at least one reference that matches requested platforms
- if len(ml) == 0 {
- logCtx.Debugf("Manifest list did not contain any usable reference. Platforms requested: (%s), platforms included: (%s)",
- strings.Join(opts.Platforms(), ","), strings.Join(platforms, ","))
- return nil, nil
- }
-
- // For some strategies, we do not need to fetch metadata for further
- // processing.
- if !opts.WantsMetadata() {
- return ti, nil
- }
-
- // Loop through all referenced manifests to get their metadata. We only
- // consider manifests for platforms we are interested in.
- for _, ref := range ml {
- logCtx.Tracef("Inspecting metadata of reference: %v", ref.Digest)
-
- man, err := client.ManifestForDigest(ref.Digest)
- if err != nil {
- return nil, fmt.Errorf("could not fetch manifest %v: %v", ref.Digest, err)
- }
-
- cti, err := client.TagMetadata(man, opts)
- if err != nil {
- return nil, fmt.Errorf("could not fetch metadata for manifest %v: %v", ref.Digest, err)
- }
-
- // We save the timestamp of the most recent pushed manifest for any
- // given reference, if the metadata for the tag was correctly
- // retrieved. This is important for the latest update strategy to
- // be able to handle multi-arch images. The latest strategy will
- // consider the most recent reference from an image index.
- if cti != nil {
- if cti.CreatedAt.After(ti.CreatedAt) {
- ti.CreatedAt = cti.CreatedAt
- }
- } else {
- logCtx.Warnf("returned metadata for manifest %v is nil, this should not happen.", ref.Digest)
- continue
- }
- }
-
- return ti, nil
-}
-
-// Implementation of ping method to initialize the challenge list
-// Without this, tokenHandler and AuthorizationHandler won't work
-func ping(manager challenge.Manager, endpoint *RegistryEndpoint, versionHeader string) ([]auth.APIVersion, error) {
- httpc := &http.Client{Transport: endpoint.GetTransport()}
- url := endpoint.RegistryAPI + "/v2/"
- resp, err := httpc.Get(url)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- // Let's consider only HTTP 200 and 401 valid responses for the initial request
- if resp.StatusCode != 200 && resp.StatusCode != 401 {
- return nil, fmt.Errorf("endpoint %s does not seem to be a valid v2 Docker Registry API (received HTTP code %d for GET %s)", endpoint.RegistryAPI, resp.StatusCode, url)
- }
-
- if err := manager.AddResponse(resp); err != nil {
- return nil, err
- }
-
- return auth.APIVersions(resp, versionHeader), err
-}
diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go
deleted file mode 100644
index 6d76512..0000000
--- a/pkg/registry/client_test.go
+++ /dev/null
@@ -1,609 +0,0 @@
-package registry
-
-import (
- "errors"
- "fmt"
- "net/http"
- "net/http/httptest"
- "net/url"
- "testing"
- "time"
-
- "github.com/distribution/distribution/v3/manifest"
- "github.com/distribution/distribution/v3/manifest/manifestlist"
- "github.com/distribution/distribution/v3/manifest/ocischema"
- "github.com/distribution/distribution/v3/manifest/schema2"
-
- "github.com/distribution/distribution/v3"
- "github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck
- v1 "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/registry/mocks"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-)
-
-func TestBasic(t *testing.T) {
- creds := credentials{
- username: "testuser",
- password: "testpass",
- }
-
- testURL, _ := url.Parse("https://example.com")
- username, password := creds.Basic(testURL)
-
- if username != "testuser" {
- t.Errorf("Expected username to be 'testuser', got '%s'", username)
- }
- if password != "testpass" {
- t.Errorf("Expected password to be 'testpass', got '%s'", password)
- }
-}
-
-func TestNewRepository(t *testing.T) {
- t.Run("Invalid Reference Format", func(t *testing.T) {
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- err = client.NewRepository("test@test")
- require.Error(t, err)
- assert.Contains(t, "invalid reference format", err.Error())
-
- })
- t.Run("Success Ping", func(t *testing.T) {
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- err = client.NewRepository("test/test")
- require.NoError(t, err)
- })
-
- t.Run("Fail Ping", func(t *testing.T) {
- testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusInternalServerError)
- }))
- ep := &RegistryEndpoint{RegistryAPI: testServer.URL}
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- err = client.NewRepository("")
- require.Error(t, err)
- })
-
-}
-
-func TestRoundTrip_Success(t *testing.T) {
- // Create mocks
- mockLimiter := new(mocks.Limiter)
- mockTransport := new(mocks.RoundTripper)
- endpoint := &RegistryEndpoint{RegistryAPI: "http://example.com"}
- // Create an instance of rateLimitTransport with mocks
- rlt := &rateLimitTransport{
- limiter: mockLimiter,
- transport: mockTransport,
- endpoint: endpoint,
- }
- // Create a sample HTTP request
- req, err := http.NewRequest("GET", "http://example.com", nil)
- assert.NoError(t, err)
- resp := &http.Response{StatusCode: http.StatusOK}
- // Set up expectations
- mockLimiter.On("Take").Return(time.Now())
- mockTransport.On("RoundTrip", req).Return(resp, nil)
- // Call the method under test
- actualResp, err := rlt.RoundTrip(req)
- // Assert the expectations
- mockLimiter.AssertExpectations(t)
- mockTransport.AssertExpectations(t)
- assert.NoError(t, err)
- assert.Equal(t, resp, actualResp)
-}
-func TestRoundTrip_Failure(t *testing.T) {
- // Create mocks
- mockLimiter := new(mocks.Limiter)
- mockTransport := new(mocks.RoundTripper)
- endpoint := &RegistryEndpoint{RegistryAPI: "http://example.com"}
- // Create an instance of rateLimitTransport with mocks
- rlt := &rateLimitTransport{
- limiter: mockLimiter,
- transport: mockTransport,
- endpoint: endpoint,
- }
- // Create a sample HTTP request
- req := httptest.NewRequest("GET", "http://example.com", nil)
- // Set up expectations
- mockLimiter.On("Take").Return(time.Now())
- mockTransport.On("RoundTrip", req).Return(nil, errors.New("Error on caling func RoundTrip"))
- // Call the method under test
- actualResp, err := rlt.RoundTrip(req)
- // Assert the expectations
- mockLimiter.AssertExpectations(t)
- mockTransport.AssertExpectations(t)
- assert.Error(t, err)
- assert.Nil(t, actualResp)
-}
-
-func TestRefreshToken(t *testing.T) {
- creds := credentials{
- refreshTokens: map[string]string{
- "service1": "token1",
- },
- }
- testURL, _ := url.Parse("https://example.com")
- token := creds.RefreshToken(testURL, "service1")
- if token != "token1" {
- t.Errorf("Expected token to be 'token1', got '%s'", token)
- }
-}
-
-func TestSetRefreshToken(t *testing.T) {
- creds := credentials{
- refreshTokens: make(map[string]string),
- }
- testURL, _ := url.Parse("https://example.com")
- creds.SetRefreshToken(testURL, "service1", "token1")
-
- if token, exists := creds.refreshTokens["service1"]; !exists {
- t.Error("Expected token for 'service1' to exist")
- } else if token != "token1" {
- t.Errorf("Expected token to be 'token1', got '%s'", token)
- }
-}
-func TestNewClient(t *testing.T) {
- t.Run("Create client with provided username and password", func(t *testing.T) {
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- _, err = NewClient(ep, "testuser", "pass")
- require.NoError(t, err)
- })
- t.Run("Create client with empty username and password", func(t *testing.T) {
- ep := &RegistryEndpoint{Username: "testuser", Password: "pass"}
- _, err := NewClient(ep, "", "")
- require.NoError(t, err)
- })
-}
-
-func TestTags(t *testing.T) {
- t.Run("success", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- mockTagService := new(mocks.TagService)
- mockTagService.On("All", mock.Anything).Return([]string{"testTag-1", "testTag-2"}, nil)
- mockRegClient.On("Tags", mock.Anything).Return(mockTagService)
- tags, err := client.Tags()
- require.NoError(t, err)
- assert.Contains(t, tags, "testTag-1")
- assert.Contains(t, tags, "testTag-2")
- })
- t.Run("Fail", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- mockTagService := new(mocks.TagService)
- mockTagService.On("All", mock.Anything).Return([]string{}, errors.New("Error on caling func All"))
- mockRegClient.On("Tags", mock.Anything).Return(mockTagService)
- _, err := client.Tags()
- require.Error(t, err)
- })
-}
-
-func TestManifestForTag(t *testing.T) {
- t.Run("Successful retrieval of Manifest", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
- mockRegClient.On("Manifests", mock.Anything).Return(manService, nil)
- _, err := client.ManifestForTag("tagStr")
- require.NoError(t, err)
- })
- t.Run("Error returned from Manifests call", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
- mockRegClient.On("Manifests", mock.Anything).Return(manService, errors.New("Error on caling func Manifests"))
- _, err := client.ManifestForTag("tagStr")
- require.Error(t, err)
- })
-
- t.Run("Error returned from Get call", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("Error on caling func Get"))
- mockRegClient.On("Manifests", mock.Anything).Return(manService, nil)
- _, err := client.ManifestForTag("tagStr")
- require.Error(t, err)
- })
-
-}
-
-func TestManifestForDigest(t *testing.T) {
- t.Run("Successful retrieval of manifest", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
- mockRegClient.On("Manifests", mock.Anything).Return(manService, nil)
- _, err := client.ManifestForDigest("dgst")
- require.NoError(t, err)
- })
- t.Run("Error returned from Manifests call", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
- mockRegClient.On("Manifests", mock.Anything).Return(manService, errors.New("Error on caling func Manifests"))
- _, err := client.ManifestForDigest("dgst")
- require.Error(t, err)
- })
- t.Run("Error returned from Get call", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("Error on caling func Get"))
- mockRegClient.On("Manifests", mock.Anything).Return(manService, nil)
- _, err := client.ManifestForDigest("dgst")
- require.Error(t, err)
- })
-}
-
-func TestTagInfoFromReferences(t *testing.T) {
- t.Run("No usable reference in manifest list", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- tagInfo := &tag.TagInfo{}
- tagInfo.CreatedAt = time.Now()
- tagInfo.Digest = [32]byte{}
- opts := &options.ManifestOptions{}
- opts.WithPlatform("testOS", "testArch", "testVarient")
- opts.WithLogger(log.NewContext())
- opts.WithMetadata(true)
- descriptor := []distribution.Descriptor{
- {
- MediaType: "",
- Digest: "",
- Size: 0,
- Platform: &v1.Platform{
- Architecture: "mTestArch",
- OS: "mTestOS",
- OSVersion: "mTestOSVersion",
- OSFeatures: []string{},
- Variant: "mTestVarient",
- },
- },
- }
- tag, err := TagInfoFromReferences(&client, opts, log.NewContext(), tagInfo, descriptor)
- require.Nil(t, tag)
- require.NoError(t, err)
- })
- t.Run("Return tagInfo when metadata option is false", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- tagInfo := &tag.TagInfo{}
- tagInfo.CreatedAt = time.Now()
- tagInfo.Digest = [32]byte{}
- opts := &options.ManifestOptions{}
- opts.WithMetadata(false)
- opts.WithPlatform("testOS", "testArch", "testVarient")
- opts.WithLogger(log.NewContext())
- descriptor := []distribution.Descriptor{
- {
- MediaType: "",
- Digest: "",
- Size: 0,
- Platform: &v1.Platform{
- Architecture: "testArch",
- OS: "testOS",
- OSVersion: "testOSVersion",
- OSFeatures: []string{},
- Variant: "testVarient",
- },
- },
- }
- tag, err := TagInfoFromReferences(&client, opts, log.NewContext(), tagInfo, descriptor)
- require.NoError(t, err)
- assert.Equal(t, tag, tagInfo)
- require.NoError(t, err)
- })
- t.Run("Return error from ManifestForDigest", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- tagInfo := &tag.TagInfo{}
- tagInfo.CreatedAt = time.Now()
- tagInfo.Digest = [32]byte{}
- opts := &options.ManifestOptions{}
- opts.WithMetadata(true)
- opts.WithPlatform("testOS", "testArch", "testVarient")
- opts.WithLogger(log.NewContext())
- descriptor := []distribution.Descriptor{
- {
- MediaType: "",
- Digest: "",
- Size: 0,
- Platform: &v1.Platform{
- Architecture: "testArch",
- OS: "testOS",
- OSVersion: "testOSVersion",
- OSFeatures: []string{},
- Variant: "testVarient",
- },
- },
- }
- mockRegClient.On("Manifests", mock.Anything).Return(nil, errors.New("Error from Manifests"))
- _, err := TagInfoFromReferences(&client, opts, log.NewContext(), tagInfo, descriptor)
- require.Error(t, err)
- })
- t.Run("Return error from TagMetadata", func(t *testing.T) {
- mockRegClient := new(mocks.Repository)
- client := registryClient{
- regClient: mockRegClient,
- }
- tagInfo := &tag.TagInfo{}
- tagInfo.CreatedAt = time.Now()
- tagInfo.Digest = [32]byte{}
- opts := &options.ManifestOptions{}
- opts.WithMetadata(true)
- opts.WithPlatform("testOS", "testArch", "testVarient")
- opts.WithLogger(log.NewContext())
- descriptor := []distribution.Descriptor{
- {
- MediaType: "",
- Digest: "",
- Size: 0,
- Platform: &v1.Platform{
- Architecture: "testArch",
- OS: "testOS",
- OSVersion: "testOSVersion",
- OSFeatures: []string{},
- Variant: "testVarient",
- },
- },
- }
- manService := new(mocks.ManifestService)
- manService.On("Get", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(new(mocks.Manifest), nil)
- mockRegClient.On("Manifests", mock.Anything).Return(manService, nil)
- _, err := TagInfoFromReferences(&client, opts, log.NewContext(), tagInfo, descriptor)
- require.Error(t, err)
- })
-}
-
-func Test_TagMetadata(t *testing.T) {
- t.Run("Check for correct error handling when manifest contains no history", func(t *testing.T) {
- meta1 := &schema1.SignedManifest{ //nolint:staticcheck
- Manifest: schema1.Manifest{ //nolint:staticcheck
- History: []schema1.History{}, //nolint:staticcheck
- },
- }
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err)
- })
-
- t.Run("Check for correct error handling when manifest contains invalid history", func(t *testing.T) {
- meta1 := &schema1.SignedManifest{ //nolint:staticcheck
- Manifest: schema1.Manifest{ //nolint:staticcheck
- History: []schema1.History{ //nolint:staticcheck
- {
- V1Compatibility: `{"created": {"something": "notastring"}}`,
- },
- },
- },
- }
-
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err)
- })
-
- t.Run("Check for correct error handling when manifest contains invalid history", func(t *testing.T) {
- meta1 := &schema1.SignedManifest{ //nolint:staticcheck
- Manifest: schema1.Manifest{ //nolint:staticcheck
- History: []schema1.History{ //nolint:staticcheck
- {
- V1Compatibility: `{"something": "something"}`,
- },
- },
- },
- }
-
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err)
-
- })
-
- t.Run("Check for invalid/valid timestamp and non-match platforms", func(t *testing.T) {
- ts := "invalid"
- meta1 := &schema1.SignedManifest{ //nolint:staticcheck
- Manifest: schema1.Manifest{ //nolint:staticcheck
- History: []schema1.History{ //nolint:staticcheck
- {
- V1Compatibility: `{"created":"` + ts + `"}`,
- },
- },
- },
- }
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err)
-
- ts = time.Now().Format(time.RFC3339Nano)
- opts := &options.ManifestOptions{}
- meta1.Manifest.History[0].V1Compatibility = `{"created":"` + ts + `"}`
- tagInfo, _ := client.TagMetadata(meta1, opts)
- assert.Equal(t, ts, tagInfo.CreatedAt.Format(time.RFC3339Nano))
-
- opts.WithPlatform("testOS", "testArch", "testVariant")
- tagInfo, err = client.TagMetadata(meta1, opts)
- assert.Nil(t, tagInfo)
- assert.Nil(t, err)
- })
-}
-
-func Test_TagMetadata_2(t *testing.T) {
- t.Run("ocischema DeserializedManifest invalid digest format", func(t *testing.T) {
- meta1 := &ocischema.DeserializedManifest{
- Manifest: ocischema.Manifest{
- Versioned: manifest.Versioned{
- SchemaVersion: 1,
- MediaType: "",
- },
- },
- }
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
-
- require.NoError(t, err)
- err = client.NewRepository("test/test")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err) // invalid digest format
- })
- t.Run("schema2 DeserializedManifest invalid digest format", func(t *testing.T) {
- meta1 := &schema2.DeserializedManifest{
- Manifest: schema2.Manifest{
- Versioned: manifest.Versioned{
- SchemaVersion: 1,
- MediaType: "",
- },
- Config: distribution.Descriptor{
- MediaType: "",
- Digest: "sha256:abc",
- },
- },
- }
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
-
- require.NoError(t, err)
- err = client.NewRepository("test/test")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err) // invalid digest format
- })
- t.Run("ocischema DeserializedImageIndex empty index not supported", func(t *testing.T) {
- meta1 := &ocischema.DeserializedImageIndex{
- ImageIndex: ocischema.ImageIndex{
- Versioned: manifest.Versioned{
- SchemaVersion: 1,
- MediaType: "",
- },
- Manifests: nil,
- Annotations: nil,
- },
- }
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
-
- require.NoError(t, err)
- err = client.NewRepository("test/test")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err) // empty index not supported
- })
- t.Run("ocischema DeserializedImageIndex empty manifestlist not supported", func(t *testing.T) {
- meta1 := &manifestlist.DeserializedManifestList{
- ManifestList: manifestlist.ManifestList{
- Versioned: manifest.Versioned{
- SchemaVersion: 1,
- MediaType: "",
- },
- Manifests: nil,
- },
- }
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- client, err := NewClient(ep, "", "")
-
- require.NoError(t, err)
- err = client.NewRepository("test/test")
- require.NoError(t, err)
- _, err = client.TagMetadata(meta1, &options.ManifestOptions{})
- require.Error(t, err) // empty manifestlist not supported
- })
-}
-
-func TestPing(t *testing.T) {
- t.Run("fail ping", func(t *testing.T) {
- mockManager := new(mocks.Manager)
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- mockManager.On("AddResponse", mock.Anything).Return(fmt.Errorf("fail ping"))
- _, err = ping(mockManager, ep, "")
- require.Error(t, err)
- })
-
- t.Run("success ping", func(t *testing.T) {
- mockManager := new(mocks.Manager)
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- mockManager.On("AddResponse", mock.Anything).Return(nil)
- _, err = ping(mockManager, ep, "")
- require.NoError(t, err)
- })
-
- t.Run("Invalid Docker Registry", func(t *testing.T) {
- testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusInternalServerError)
- }))
- mockManager := new(mocks.Manager)
- ep := &RegistryEndpoint{RegistryAPI: testServer.URL}
- mockManager.On("AddResponse", mock.Anything).Return(nil)
- _, err := ping(mockManager, ep, "")
- require.Error(t, err)
- assert.ErrorContains(t, err, "does not seem to be a valid v2 Docker Registry API")
- })
-
- t.Run("Empty Registry API", func(t *testing.T) {
- mockManager := new(mocks.Manager)
- ep := &RegistryEndpoint{RegistryAPI: ""}
- mockManager.On("AddResponse", mock.Anything).Return(nil)
- _, err := ping(mockManager, ep, "")
- require.Error(t, err)
- assert.ErrorContains(t, err, "unsupported protocol scheme")
- })
-
-}
diff --git a/pkg/registry/config.go b/pkg/registry/config.go
deleted file mode 100644
index 641c598..0000000
--- a/pkg/registry/config.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package registry
-
-import (
- "fmt"
- "os"
- "time"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
-
- "gopkg.in/yaml.v2"
-)
-
-// RegistryConfiguration represents a single repository configuration for being
-// unmarshaled from YAML.
-type RegistryConfiguration struct {
- Name string `yaml:"name"`
- ApiURL string `yaml:"api_url"`
- Ping bool `yaml:"ping,omitempty"`
- Credentials string `yaml:"credentials,omitempty"`
- CredsExpire time.Duration `yaml:"credsexpire,omitempty"`
- TagSortMode string `yaml:"tagsortmode,omitempty"`
- Prefix string `yaml:"prefix,omitempty"`
- Insecure bool `yaml:"insecure,omitempty"`
- DefaultNS string `yaml:"defaultns,omitempty"`
- Limit int `yaml:"limit,omitempty"`
- IsDefault bool `yaml:"default,omitempty"`
-}
-
-// RegistryList contains multiple RegistryConfiguration items
-type RegistryList struct {
- Items []RegistryConfiguration `yaml:"registries"`
-}
-
-func clearRegistries() {
- registryLock.Lock()
- registries = make(map[string]*RegistryEndpoint)
- registryLock.Unlock()
-}
-
-// LoadRegistryConfiguration loads a YAML-formatted registry configuration from
-// a given file at path.
-func LoadRegistryConfiguration(path string, clear bool) error {
- registryBytes, err := os.ReadFile(path)
- if err != nil {
- return err
- }
- registryList, err := ParseRegistryConfiguration(string(registryBytes))
- if err != nil {
- return err
- }
-
- if clear {
- clearRegistries()
- }
-
- haveDefault := false
-
- for _, reg := range registryList.Items {
- tagSortMode := TagListSortFromString(reg.TagSortMode)
- if tagSortMode != TagListSortUnsorted {
- log.Warnf("Registry %s has tag sort mode set to %s, meta data retrieval will be disabled for this registry.", reg.ApiURL, tagSortMode)
- }
- ep := NewRegistryEndpoint(reg.Prefix, reg.Name, reg.ApiURL, reg.Credentials, reg.DefaultNS, reg.Insecure, tagSortMode, reg.Limit, reg.CredsExpire)
- if reg.IsDefault {
- if haveDefault {
- dep := GetDefaultRegistry()
- if dep == nil {
- panic("unexpected: default registry should be set, but is not")
- }
- return fmt.Errorf("cannot set registry %s as default - only one default registry allowed, currently set to %s", ep.RegistryPrefix, dep.RegistryPrefix)
- }
- }
-
- if err := AddRegistryEndpoint(ep); err != nil {
- return err
- }
-
- if reg.IsDefault {
- SetDefaultRegistry(ep)
- haveDefault = true
- }
- }
-
- log.Infof("Loaded %d registry configurations from %s", len(registryList.Items), path)
- return nil
-}
-
-// Parses a registry configuration from a YAML input string and returns a list
-// of registries.
-func ParseRegistryConfiguration(yamlSource string) (RegistryList, error) {
- var regList RegistryList
- var defaultPrefixFound = ""
- err := yaml.UnmarshalStrict([]byte(yamlSource), &regList)
- if err != nil {
- return RegistryList{}, err
- }
-
- // validate the parsed list
- for _, registry := range regList.Items {
- if registry.Name == "" {
- err = fmt.Errorf("registry name is missing for entry %v", registry)
- } else if registry.ApiURL == "" {
- err = fmt.Errorf("API URL must be specified for registry %s", registry.Name)
- } else if registry.Prefix == "" {
- if defaultPrefixFound != "" {
- err = fmt.Errorf("there must be only one default registry (already is %s), %s needs a prefix", defaultPrefixFound, registry.Name)
- } else {
- defaultPrefixFound = registry.Name
- }
- }
-
- if err == nil {
- if tls := TagListSortFromString(registry.TagSortMode); tls == TagListSortUnknown {
- err = fmt.Errorf("unknown tag sort mode for registry %s: %s", registry.Name, registry.TagSortMode)
- }
- }
- }
-
- if err != nil {
- return RegistryList{}, err
- }
-
- return regList, nil
-}
-
-// RestRestoreDefaultRegistryConfiguration restores the registry configuration
-// to the default values.
-func RestoreDefaultRegistryConfiguration() {
- registryLock.Lock()
- defer registryLock.Unlock()
- defaultRegistry = nil
- registries = make(map[string]*RegistryEndpoint)
- for k, v := range registryTweaks {
- registries[k] = v.DeepCopy()
- if v.IsDefault {
- SetDefaultRegistry(registries[k])
- }
- }
-}
diff --git a/pkg/registry/config_test.go b/pkg/registry/config_test.go
deleted file mode 100644
index 080dd00..0000000
--- a/pkg/registry/config_test.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package registry
-
-import (
- "testing"
- "time"
-
- "github.com/argoproj-labs/argocd-image-updater/test/fixture"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func Test_ParseRegistryConfFromYaml(t *testing.T) {
- t.Run("Parse from valid YAML", func(t *testing.T) {
- data := fixture.MustReadFile("../../config/example-config.yaml")
- regList, err := ParseRegistryConfiguration(data)
- require.NoError(t, err)
- assert.Len(t, regList.Items, 4)
- })
-
- t.Run("Parse from invalid YAML: no name found", func(t *testing.T) {
- registries := `
-registries:
-- api_url: https://foo.io
- ping: false
-`
- regList, err := ParseRegistryConfiguration(registries)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "name is missing")
- assert.Len(t, regList.Items, 0)
- })
-
- t.Run("Parse from invalid YAML: no API URL found", func(t *testing.T) {
- registries := `
-registries:
-- name: Foobar Registry
- ping: false
-`
- regList, err := ParseRegistryConfiguration(registries)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "API URL must be")
- assert.Len(t, regList.Items, 0)
- })
-
- t.Run("Parse from invalid YAML: multiple registries without prefix", func(t *testing.T) {
- registries := `
-registries:
-- name: Foobar Registry
- api_url: https://foobar.io
- ping: false
-- name: Barbar Registry
- api_url: https://barbar.io
- ping: false
-`
- regList, err := ParseRegistryConfiguration(registries)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "already is Foobar Registry")
- assert.Len(t, regList.Items, 0)
- })
-
- t.Run("Parse from invalid YAML: invalid tag sort mode", func(t *testing.T) {
- registries := `
-registries:
-- name: Foobar Registry
- api_url: https://foobar.io
- ping: false
- tagsortmode: invalid
-`
- regList, err := ParseRegistryConfiguration(registries)
- require.Error(t, err)
- assert.Contains(t, err.Error(), "unknown tag sort mode")
- assert.Len(t, regList.Items, 0)
- })
-
-}
-
-func Test_LoadRegistryConfiguration(t *testing.T) {
- RestoreDefaultRegistryConfiguration()
-
- t.Run("Load from valid location", func(t *testing.T) {
- err := LoadRegistryConfiguration("../../config/example-config.yaml", true)
- require.NoError(t, err)
- assert.Len(t, registries, 4)
- reg, err := GetRegistryEndpoint("gcr.io")
- require.NoError(t, err)
- assert.Equal(t, "pullsecret:foo/bar", reg.Credentials)
- reg, err = GetRegistryEndpoint("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")
- require.NoError(t, err)
- assert.Equal(t, "", reg.Credentials)
- })
-
- t.Run("Load from invalid location", func(t *testing.T) {
- err := LoadRegistryConfiguration("../../test/testdata/registry/config/does-not-exist.yaml", true)
- require.Error(t, err)
- require.Contains(t, err.Error(), "no such file or directory")
- })
-
- t.Run("Two defaults defined in same config", func(t *testing.T) {
- err := LoadRegistryConfiguration("../../test/testdata/registry/config/two-defaults.yaml", true)
- require.Error(t, err)
- require.Contains(t, err.Error(), "cannot set registry")
- })
-
- RestoreDefaultRegistryConfiguration()
-}
diff --git a/pkg/registry/endpoints.go b/pkg/registry/endpoints.go
deleted file mode 100644
index 3b64fc5..0000000
--- a/pkg/registry/endpoints.go
+++ /dev/null
@@ -1,305 +0,0 @@
-package registry
-
-import (
- "crypto/tls"
- "fmt"
- "math"
- "net/http"
- "strings"
- "sync"
- "time"
-
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/cache"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
-
- "go.uber.org/ratelimit"
-)
-
-// TagListSort defines how the registry returns the list of tags
-type TagListSort int
-
-const (
- TagListSortUnknown TagListSort = -1
- TagListSortUnsorted TagListSort = 0
- TagListSortLatestFirst TagListSort = 1
- TagListSortLatestLast TagListSort = 2
- TagListSortUnsortedString string = "unsorted"
- TagListSortLatestFirstString string = "latest-first"
- TagListSortLatestLastString string = "latest-last"
- TagListSortUnknownString string = "unknown"
-)
-
-const (
- RateLimitNone = math.MaxInt32
- RateLimitDefault = 10
-)
-
-// IsTimeSorted returns whether a tag list is time sorted
-func (tls TagListSort) IsTimeSorted() bool {
- return tls == TagListSortLatestFirst || tls == TagListSortLatestLast
-}
-
-// TagListSortFromString gets the TagListSort value from a given string
-func TagListSortFromString(tls string) TagListSort {
- switch strings.ToLower(tls) {
- case "latest-first":
- return TagListSortLatestFirst
- case "latest-last":
- return TagListSortLatestLast
- case "none", "":
- return TagListSortUnsorted
- default:
- log.Warnf("unknown tag list sort mode: %s", tls)
- return TagListSortUnknown
- }
-}
-
-// String returns the string representation of a TagListSort value
-func (tls TagListSort) String() string {
- switch tls {
- case TagListSortLatestFirst:
- return TagListSortLatestFirstString
- case TagListSortLatestLast:
- return TagListSortLatestLastString
- case TagListSortUnsorted:
- return TagListSortUnsortedString
- }
-
- return TagListSortUnknownString
-}
-
-// RegistryEndpoint holds information on how to access any specific registry API
-// endpoint.
-type RegistryEndpoint struct {
- RegistryName string
- RegistryPrefix string
- RegistryAPI string
- Username string
- Password string
- Ping bool
- Credentials string
- Insecure bool
- DefaultNS string
- CredsExpire time.Duration
- CredsUpdated time.Time
- TagListSort TagListSort
- Cache cache.ImageTagCache
- Limiter ratelimit.Limiter
- IsDefault bool
- lock sync.RWMutex
- limit int
-}
-
-// registryTweaks should contain a list of registries whose settings cannot be
-// inferred by just looking at the image prefix. Prominent example here is the
-// Docker Hub registry, which is referred to as docker.io from the image, but
-// its API endpoint is https://registry-1.docker.io (and not https://docker.io)
-var registryTweaks map[string]*RegistryEndpoint = map[string]*RegistryEndpoint{
- "docker.io": {
- RegistryName: "Docker Hub",
- RegistryPrefix: "docker.io",
- RegistryAPI: "https://registry-1.docker.io",
- Ping: true,
- Insecure: false,
- DefaultNS: "library",
- Cache: cache.NewMemCache(),
- Limiter: ratelimit.New(RateLimitDefault),
- IsDefault: true,
- },
-}
-
-var registries map[string]*RegistryEndpoint = make(map[string]*RegistryEndpoint)
-
-// Default registry points to the registry that is to be used as the default,
-// e.g. when no registry prefix is given for a certain image.
-var defaultRegistry *RegistryEndpoint
-
-// Simple RW mutex for concurrent access to registries map
-var registryLock sync.RWMutex
-
-func AddRegistryEndpointFromConfig(epc RegistryConfiguration) error {
- ep := NewRegistryEndpoint(epc.Prefix, epc.Name, epc.ApiURL, epc.Credentials, epc.DefaultNS, epc.Insecure, TagListSortFromString(epc.TagSortMode), epc.Limit, epc.CredsExpire)
- return AddRegistryEndpoint(ep)
-}
-
-// NewRegistryEndpoint returns an endpoint object with the given configuration
-// pre-populated and a fresh cache.
-func NewRegistryEndpoint(prefix, name, apiUrl, credentials, defaultNS string, insecure bool, tagListSort TagListSort, limit int, credsExpire time.Duration) *RegistryEndpoint {
- if limit <= 0 {
- limit = RateLimitNone
- }
- ep := &RegistryEndpoint{
- RegistryName: name,
- RegistryPrefix: prefix,
- RegistryAPI: strings.TrimSuffix(apiUrl, "/"),
- Credentials: credentials,
- CredsExpire: credsExpire,
- Cache: cache.NewMemCache(),
- Insecure: insecure,
- DefaultNS: defaultNS,
- TagListSort: tagListSort,
- Limiter: ratelimit.New(limit),
- limit: limit,
- }
- return ep
-}
-
-// AddRegistryEndpoint adds registry endpoint information with the given details
-func AddRegistryEndpoint(ep *RegistryEndpoint) error {
- prefix := ep.RegistryPrefix
-
- registryLock.Lock()
- // If the endpoint is supposed to be the default endpoint, make sure that
- // any previously set default endpoint is unset.
- if ep.IsDefault {
- if dep := GetDefaultRegistry(); dep != nil {
- dep.IsDefault = false
- }
- SetDefaultRegistry(ep)
- }
- registries[prefix] = ep
- registryLock.Unlock()
-
- logCtx := log.WithContext()
- logCtx.AddField("registry", ep.RegistryAPI)
- logCtx.AddField("prefix", ep.RegistryPrefix)
- if ep.limit != RateLimitNone {
- logCtx.Debugf("setting rate limit to %d requests per second", ep.limit)
- } else {
- logCtx.Debugf("rate limiting is disabled")
- }
- return nil
-}
-
-// inferRegistryEndpointFromPrefix returns a registry endpoint with the API
-// URL inferred from the prefix and adds it to the list of the configured
-// registries.
-func inferRegistryEndpointFromPrefix(prefix string) *RegistryEndpoint {
- apiURL := "https://" + prefix
- return NewRegistryEndpoint(prefix, prefix, apiURL, "", "", false, TagListSortUnsorted, 20, 0)
-}
-
-// GetRegistryEndpoint retrieves the endpoint information for the given prefix
-func GetRegistryEndpoint(prefix string) (*RegistryEndpoint, error) {
- if prefix == "" {
- if defaultRegistry == nil {
- return nil, fmt.Errorf("no default endpoint configured")
- } else {
- return defaultRegistry, nil
- }
- }
-
- registryLock.RLock()
- registry, ok := registries[prefix]
- registryLock.RUnlock()
-
- if ok {
- return registry, nil
- } else {
- var err error
- ep := inferRegistryEndpointFromPrefix(prefix)
- if ep != nil {
- err = AddRegistryEndpoint(ep)
- } else {
- err = fmt.Errorf("could not infer registry configuration from prefix %s", prefix)
- }
- if err == nil {
- log.Debugf("Inferred registry from prefix %s to use API %s", prefix, ep.RegistryAPI)
- }
- return ep, err
- }
-}
-
-// SetDefaultRegistry sets a given registry endpoint as the default
-func SetDefaultRegistry(ep *RegistryEndpoint) {
- log.Debugf("Setting default registry endpoint to %s", ep.RegistryPrefix)
- ep.IsDefault = true
- if defaultRegistry != nil {
- log.Debugf("Previous default registry was %s", defaultRegistry.RegistryPrefix)
- defaultRegistry.IsDefault = false
- }
- defaultRegistry = ep
-}
-
-// GetDefaultRegistry returns the registry endpoint that is set as default,
-// or nil if no default registry endpoint is set
-func GetDefaultRegistry() *RegistryEndpoint {
- if defaultRegistry != nil {
- log.Debugf("Getting default registry endpoint: %s", defaultRegistry.RegistryPrefix)
- } else {
- log.Debugf("No default registry defined.")
- }
- return defaultRegistry
-}
-
-// SetRegistryEndpointCredentials allows to change the credentials used for
-// endpoint access for existing RegistryEndpoint configuration
-func SetRegistryEndpointCredentials(prefix, credentials string) error {
- registry, err := GetRegistryEndpoint(prefix)
- if err != nil {
- return err
- }
- registry.lock.Lock()
- registry.Credentials = credentials
- registry.lock.Unlock()
- return nil
-}
-
-// ConfiguredEndpoints returns a list of prefixes that are configured
-func ConfiguredEndpoints() []string {
- registryLock.RLock()
- defer registryLock.RUnlock()
- r := make([]string, 0, len(registries))
- for _, v := range registries {
- r = append(r, v.RegistryPrefix)
- }
- return r
-}
-
-// DeepCopy copies the endpoint to a new object, but creating a new Cache
-func (ep *RegistryEndpoint) DeepCopy() *RegistryEndpoint {
- ep.lock.RLock()
- newEp := &RegistryEndpoint{}
- newEp.RegistryAPI = ep.RegistryAPI
- newEp.RegistryName = ep.RegistryName
- newEp.RegistryPrefix = ep.RegistryPrefix
- newEp.Credentials = ep.Credentials
- newEp.Ping = ep.Ping
- newEp.TagListSort = ep.TagListSort
- newEp.Cache = cache.NewMemCache()
- newEp.Insecure = ep.Insecure
- newEp.DefaultNS = ep.DefaultNS
- newEp.Limiter = ep.Limiter
- newEp.CredsExpire = ep.CredsExpire
- newEp.CredsUpdated = ep.CredsUpdated
- newEp.IsDefault = ep.IsDefault
- newEp.limit = ep.limit
- ep.lock.RUnlock()
- return newEp
-}
-
-// GetTransport returns a transport object for this endpoint
-func (ep *RegistryEndpoint) GetTransport() *http.Transport {
- tlsC := &tls.Config{}
- if ep.Insecure {
- tlsC.InsecureSkipVerify = true
- }
- return &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: tlsC,
- }
-}
-
-// init initializes the registry configuration
-func init() {
- for k, v := range registryTweaks {
- registries[k] = v.DeepCopy()
- if v.IsDefault {
- if defaultRegistry == nil {
- defaultRegistry = v
- } else {
- panic("only one default registry can be configured")
- }
- }
- }
-}
diff --git a/pkg/registry/endpoints_test.go b/pkg/registry/endpoints_test.go
deleted file mode 100644
index d2dae1f..0000000
--- a/pkg/registry/endpoints_test.go
+++ /dev/null
@@ -1,354 +0,0 @@
-package registry
-
-import (
- "fmt"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestInferRegistryEndpointFromPrefix(t *testing.T) {
- prefix := "example.com"
- expectedAPIURL := "https://" + prefix
- endpoint := inferRegistryEndpointFromPrefix(prefix)
- assert.NotNil(t, endpoint)
- assert.Equal(t, prefix, endpoint.RegistryName)
- assert.Equal(t, prefix, endpoint.RegistryPrefix)
- assert.Equal(t, expectedAPIURL, endpoint.RegistryAPI)
- assert.Equal(t, TagListSortUnsorted, endpoint.TagListSort)
- assert.Equal(t, 20, endpoint.limit)
- assert.False(t, endpoint.Insecure)
-}
-
-func TestNewRegistryEndpoint(t *testing.T) {
- prefix := "example.com"
- name := "exampleRegistry"
- apiUrl := "https://api.example.com"
- credentials := "user:pass"
- defaultNS := "default"
- insecure := true
- tagListSort := TagListSortLatestFirst
- limit := 10
- credsExpire := time.Minute * 30
- endpoint := NewRegistryEndpoint(prefix, name, apiUrl, credentials, defaultNS, insecure, tagListSort, limit, credsExpire)
- assert.NotNil(t, endpoint)
- assert.Equal(t, name, endpoint.RegistryName)
- assert.Equal(t, prefix, endpoint.RegistryPrefix)
- assert.Equal(t, strings.TrimSuffix(apiUrl, "/"), endpoint.RegistryAPI)
- assert.Equal(t, credentials, endpoint.Credentials)
- assert.Equal(t, credsExpire, endpoint.CredsExpire)
- assert.Equal(t, insecure, endpoint.Insecure)
- assert.Equal(t, defaultNS, endpoint.DefaultNS)
- assert.Equal(t, tagListSort, endpoint.TagListSort)
- assert.Equal(t, limit, endpoint.limit)
-}
-
-func TestTagListSortFromString(t *testing.T) {
- t.Run("returns TagListSortLatestFirst for 'latest-first'", func(t *testing.T) {
- result := TagListSortFromString("latest-first")
- assert.Equal(t, TagListSortLatestFirst, result)
- })
-
- t.Run("returns TagListSortLatestLast for 'latest-last'", func(t *testing.T) {
- result := TagListSortFromString("latest-last")
- assert.Equal(t, TagListSortLatestLast, result)
- })
-
- t.Run("returns TagListSortUnsorted for 'none'", func(t *testing.T) {
- result := TagListSortFromString("none")
- assert.Equal(t, TagListSortUnsorted, result)
- })
-
- t.Run("returns TagListSortUnsorted for an empty string", func(t *testing.T) {
- result := TagListSortFromString("")
- assert.Equal(t, TagListSortUnsorted, result)
- })
-
- t.Run("returns TagListSortUnknown for an unknown value", func(t *testing.T) {
- result := TagListSortFromString("unknown-value")
- assert.Equal(t, TagListSortUnknown, result)
- })
-}
-
-func TestIsTimeSorted(t *testing.T) {
- t.Run("returns true for TagListSortLatestFirst", func(t *testing.T) {
- assert.True(t, TagListSortLatestFirst.IsTimeSorted())
- })
- t.Run("returns true for TagListSortLatestLast", func(t *testing.T) {
- assert.True(t, TagListSortLatestLast.IsTimeSorted())
- })
- t.Run("returns false for TagListSortUnsorted", func(t *testing.T) {
- assert.False(t, TagListSortUnsorted.IsTimeSorted())
- })
- t.Run("returns false for TagListSortUnknown", func(t *testing.T) {
- assert.False(t, TagListSortUnknown.IsTimeSorted())
- })
-}
-
-func TestTagListSort_String(t *testing.T) {
- t.Run("returns 'latest-first' for TagListSortLatestFirst", func(t *testing.T) {
- assert.Equal(t, TagListSortLatestFirstString, TagListSortLatestFirst.String())
- })
-
- t.Run("returns 'latest-last' for TagListSortLatestLast", func(t *testing.T) {
- assert.Equal(t, TagListSortLatestLastString, TagListSortLatestLast.String())
- })
-
- t.Run("returns 'unsorted' for TagListSortUnsorted", func(t *testing.T) {
- assert.Equal(t, TagListSortUnsortedString, TagListSortUnsorted.String())
- })
-
- t.Run("returns 'unknown' for TagListSortUnknown", func(t *testing.T) {
- assert.Equal(t, TagListSortUnknownString, TagListSortUnknown.String())
- })
-
- t.Run("returns 'unknown' for an undefined TagListSort value", func(t *testing.T) {
- var undefined TagListSort = 99
- assert.Equal(t, TagListSortUnknownString, undefined.String())
- })
-}
-
-func Test_GetEndpoints(t *testing.T) {
- RestoreDefaultRegistryConfiguration()
-
- t.Run("Get default endpoint", func(t *testing.T) {
- ep, err := GetRegistryEndpoint("")
- 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")
- 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")
- require.NoError(t, err)
- require.NotNil(t, ep)
- assert.Equal(t, "foobar.com", ep.RegistryPrefix)
- assert.Equal(t, "https://foobar.com", ep.RegistryAPI)
- })
-}
-
-func Test_AddEndpoint(t *testing.T) {
- RestoreDefaultRegistryConfiguration()
-
- t.Run("Add new endpoint", func(t *testing.T) {
- err := AddRegistryEndpoint(NewRegistryEndpoint("example.com", "Example", "https://example.com", "", "", false, TagListSortUnsorted, 5, 0))
- require.NoError(t, err)
- })
- t.Run("Get example.com endpoint", func(t *testing.T) {
- ep, err := GetRegistryEndpoint("example.com")
- require.NoError(t, err)
- require.NotNil(t, ep)
- assert.Equal(t, ep.RegistryPrefix, "example.com")
- assert.Equal(t, ep.RegistryName, "Example")
- assert.Equal(t, ep.RegistryAPI, "https://example.com")
- assert.Equal(t, ep.Insecure, false)
- assert.Equal(t, ep.DefaultNS, "")
- assert.Equal(t, ep.TagListSort, TagListSortUnsorted)
- })
- 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")
- require.NoError(t, err)
- require.NotNil(t, ep)
- assert.Equal(t, ep.Insecure, true)
- assert.Equal(t, ep.DefaultNS, "library")
- assert.Equal(t, ep.TagListSort, TagListSortLatestFirst)
- })
-}
-
-func Test_SetEndpointCredentials(t *testing.T) {
- RestoreDefaultRegistryConfiguration()
-
- t.Run("Set credentials on default registry", func(t *testing.T) {
- err := SetRegistryEndpointCredentials("", "env:FOOBAR")
- require.NoError(t, err)
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- require.NotNil(t, ep)
- assert.Equal(t, ep.Credentials, "env:FOOBAR")
- })
-
- t.Run("Unset credentials on default registry", func(t *testing.T) {
- err := SetRegistryEndpointCredentials("", "")
- require.NoError(t, err)
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- require.NotNil(t, ep)
- assert.Equal(t, ep.Credentials, "")
- })
-}
-
-func Test_EndpointConcurrentAccess(t *testing.T) {
- RestoreDefaultRegistryConfiguration()
- const numRuns = 50
- // Make sure we're not deadlocking on read
- t.Run("Concurrent read access", func(t *testing.T) {
- var wg sync.WaitGroup
- wg.Add(numRuns)
- for i := 0; i < numRuns; i++ {
- go func() {
- ep, err := GetRegistryEndpoint("gcr.io")
- require.NoError(t, err)
- require.NotNil(t, ep)
- wg.Done()
- }()
- }
- wg.Wait()
- })
-
- // Make sure we're not deadlocking on write
- t.Run("Concurrent write access", func(t *testing.T) {
- var wg sync.WaitGroup
- wg.Add(numRuns)
- for i := 0; i < numRuns; i++ {
- go func(i int) {
- creds := fmt.Sprintf("secret:foo/secret-%d", i)
- err := SetRegistryEndpointCredentials("", creds)
- require.NoError(t, err)
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- require.NotNil(t, ep)
- wg.Done()
- }(i)
- }
- wg.Wait()
- })
-}
-
-func Test_SetDefault(t *testing.T) {
- RestoreDefaultRegistryConfiguration()
-
- dep := GetDefaultRegistry()
- require.NotNil(t, dep)
- assert.Equal(t, "docker.io", dep.RegistryPrefix)
- assert.True(t, dep.IsDefault)
-
- ep, err := GetRegistryEndpoint("ghcr.io")
- require.NoError(t, err)
- require.NotNil(t, ep)
- require.False(t, ep.IsDefault)
-
- SetDefaultRegistry(ep)
- assert.True(t, ep.IsDefault)
- assert.False(t, dep.IsDefault)
- require.NotNil(t, GetDefaultRegistry())
- assert.Equal(t, ep.RegistryPrefix, GetDefaultRegistry().RegistryPrefix)
-}
-
-func Test_DeepCopy(t *testing.T) {
- t.Run("DeepCopy endpoint object", func(t *testing.T) {
- ep, err := GetRegistryEndpoint("docker.pkg.github.com")
- require.NoError(t, err)
- require.NotNil(t, ep)
- newEp := ep.DeepCopy()
- assert.Equal(t, ep.RegistryAPI, newEp.RegistryAPI)
- assert.Equal(t, ep.RegistryName, newEp.RegistryName)
- assert.Equal(t, ep.RegistryPrefix, newEp.RegistryPrefix)
- assert.Equal(t, ep.Credentials, newEp.Credentials)
- assert.Equal(t, ep.TagListSort, newEp.TagListSort)
- assert.Equal(t, ep.Username, newEp.Username)
- assert.Equal(t, ep.Ping, newEp.Ping)
- })
-}
-
-func Test_GetTagListSortFromString(t *testing.T) {
- t.Run("Get latest-first sorting", func(t *testing.T) {
- tls := TagListSortFromString("latest-first")
- assert.Equal(t, TagListSortLatestFirst, tls)
- })
- t.Run("Get latest-last sorting", func(t *testing.T) {
- tls := TagListSortFromString("latest-last")
- assert.Equal(t, TagListSortLatestLast, tls)
- })
- t.Run("Get none sorting explicit", func(t *testing.T) {
- tls := TagListSortFromString("none")
- assert.Equal(t, TagListSortUnsorted, tls)
- })
- t.Run("Get none sorting implicit", func(t *testing.T) {
- tls := TagListSortFromString("")
- assert.Equal(t, TagListSortUnsorted, tls)
- })
- t.Run("Get unknown sorting from unknown string", func(t *testing.T) {
- tls := TagListSortFromString("unknown")
- assert.Equal(t, TagListSortUnknown, tls)
- })
-}
-
-func TestGetTransport(t *testing.T) {
- t.Run("returns transport with default TLS config when Insecure is false", func(t *testing.T) {
- endpoint := &RegistryEndpoint{
- Insecure: false,
- }
- transport := endpoint.GetTransport()
-
- assert.NotNil(t, transport)
- assert.NotNil(t, transport.TLSClientConfig)
- assert.False(t, transport.TLSClientConfig.InsecureSkipVerify)
- })
-
- t.Run("returns transport with insecure TLS config when Insecure is true", func(t *testing.T) {
- endpoint := &RegistryEndpoint{
- Insecure: true,
- }
- transport := endpoint.GetTransport()
-
- assert.NotNil(t, transport)
- assert.NotNil(t, transport.TLSClientConfig)
- assert.True(t, transport.TLSClientConfig.InsecureSkipVerify)
- })
-}
-
-func Test_RestoreDefaultRegistryConfiguration(t *testing.T) {
- // Call the function to restore default configuration
- RestoreDefaultRegistryConfiguration()
-
- // Retrieve the default registry endpoint
- defaultEp := GetDefaultRegistry()
-
- // Validate that the default registry endpoint is not nil
- require.NotNil(t, defaultEp)
-
- // Validate that the default registry endpoint has expected properties
- assert.Equal(t, "docker.io", defaultEp.RegistryPrefix)
- assert.True(t, defaultEp.IsDefault)
-}
-
-func TestConfiguredEndpoints(t *testing.T) {
- // Test the function
- endpoints := ConfiguredEndpoints()
- // Validate the output
- expected := []string{"docker.io"}
- require.Len(t, endpoints, len(expected), "The number of endpoints should match the expected number")
- assert.ElementsMatch(t, expected, endpoints, "The endpoints should match the expected values")
-
-}
-
-func TestAddRegistryEndpointFromConfig(t *testing.T) {
- t.Run("successfully adds registry endpoint from config", func(t *testing.T) {
- config := RegistryConfiguration{
- Prefix: "example.com",
- Name: "exampleRegistry",
- ApiURL: "https://api.example.com",
- Credentials: "user:pass",
- DefaultNS: "default",
- Insecure: true,
- TagSortMode: "latest-first",
- Limit: 10,
- CredsExpire: time.Minute * 30,
- }
- err := AddRegistryEndpointFromConfig(config)
- require.NoError(t, err)
- })
-}
diff --git a/pkg/registry/mocks/Limiter.go b/pkg/registry/mocks/Limiter.go
deleted file mode 100644
index 81dbb19..0000000
--- a/pkg/registry/mocks/Limiter.go
+++ /dev/null
@@ -1,46 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- mock "github.com/stretchr/testify/mock"
-
- time "time"
-)
-
-// Limiter is an autogenerated mock type for the Limiter type
-type Limiter struct {
- mock.Mock
-}
-
-// Take provides a mock function with given fields:
-func (_m *Limiter) Take() time.Time {
- ret := _m.Called()
-
- if len(ret) == 0 {
- panic("no return value specified for Take")
- }
-
- var r0 time.Time
- if rf, ok := ret.Get(0).(func() time.Time); ok {
- r0 = rf()
- } else {
- r0 = ret.Get(0).(time.Time)
- }
-
- return r0
-}
-
-// NewLimiter creates a new instance of Limiter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewLimiter(t interface {
- mock.TestingT
- Cleanup(func())
-}) *Limiter {
- mock := &Limiter{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/mocks/Manager.go b/pkg/registry/mocks/Manager.go
deleted file mode 100644
index 02c3776..0000000
--- a/pkg/registry/mocks/Manager.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- http "net/http"
-
- challenge "github.com/distribution/distribution/v3/registry/client/auth/challenge"
-
- mock "github.com/stretchr/testify/mock"
-
- url "net/url"
-)
-
-// Manager is an autogenerated mock type for the Manager type
-type Manager struct {
- mock.Mock
-}
-
-// AddResponse provides a mock function with given fields: resp
-func (_m *Manager) AddResponse(resp *http.Response) error {
- ret := _m.Called(resp)
-
- if len(ret) == 0 {
- panic("no return value specified for AddResponse")
- }
-
- var r0 error
- if rf, ok := ret.Get(0).(func(*http.Response) error); ok {
- r0 = rf(resp)
- } else {
- r0 = ret.Error(0)
- }
-
- return r0
-}
-
-// GetChallenges provides a mock function with given fields: endpoint
-func (_m *Manager) GetChallenges(endpoint url.URL) ([]challenge.Challenge, error) {
- ret := _m.Called(endpoint)
-
- if len(ret) == 0 {
- panic("no return value specified for GetChallenges")
- }
-
- var r0 []challenge.Challenge
- var r1 error
- if rf, ok := ret.Get(0).(func(url.URL) ([]challenge.Challenge, error)); ok {
- return rf(endpoint)
- }
- if rf, ok := ret.Get(0).(func(url.URL) []challenge.Challenge); ok {
- r0 = rf(endpoint)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]challenge.Challenge)
- }
- }
-
- if rf, ok := ret.Get(1).(func(url.URL) error); ok {
- r1 = rf(endpoint)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// NewManager creates a new instance of Manager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewManager(t interface {
- mock.TestingT
- Cleanup(func())
-}) *Manager {
- mock := &Manager{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/mocks/Manifest.go b/pkg/registry/mocks/Manifest.go
deleted file mode 100644
index 8b92f36..0000000
--- a/pkg/registry/mocks/Manifest.go
+++ /dev/null
@@ -1,84 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- distribution "github.com/distribution/distribution/v3"
- mock "github.com/stretchr/testify/mock"
-)
-
-// Manifest is an autogenerated mock type for the Manifest type
-type Manifest struct {
- mock.Mock
-}
-
-// Payload provides a mock function with given fields:
-func (_m *Manifest) Payload() (string, []byte, error) {
- ret := _m.Called()
-
- if len(ret) == 0 {
- panic("no return value specified for Payload")
- }
-
- var r0 string
- var r1 []byte
- var r2 error
- if rf, ok := ret.Get(0).(func() (string, []byte, error)); ok {
- return rf()
- }
- if rf, ok := ret.Get(0).(func() string); ok {
- r0 = rf()
- } else {
- r0 = ret.Get(0).(string)
- }
-
- if rf, ok := ret.Get(1).(func() []byte); ok {
- r1 = rf()
- } else {
- if ret.Get(1) != nil {
- r1 = ret.Get(1).([]byte)
- }
- }
-
- if rf, ok := ret.Get(2).(func() error); ok {
- r2 = rf()
- } else {
- r2 = ret.Error(2)
- }
-
- return r0, r1, r2
-}
-
-// References provides a mock function with given fields:
-func (_m *Manifest) References() []distribution.Descriptor {
- ret := _m.Called()
-
- if len(ret) == 0 {
- panic("no return value specified for References")
- }
-
- var r0 []distribution.Descriptor
- if rf, ok := ret.Get(0).(func() []distribution.Descriptor); ok {
- r0 = rf()
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]distribution.Descriptor)
- }
- }
-
- return r0
-}
-
-// NewManifest creates a new instance of Manifest. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewManifest(t interface {
- mock.TestingT
- Cleanup(func())
-}) *Manifest {
- mock := &Manifest{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/mocks/ManifestService.go b/pkg/registry/mocks/ManifestService.go
deleted file mode 100644
index cb42e32..0000000
--- a/pkg/registry/mocks/ManifestService.go
+++ /dev/null
@@ -1,149 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- context "context"
-
- distribution "github.com/distribution/distribution/v3"
- digest "github.com/opencontainers/go-digest"
-
- mock "github.com/stretchr/testify/mock"
-)
-
-// ManifestService is an autogenerated mock type for the ManifestService type
-type ManifestService struct {
- mock.Mock
-}
-
-// Delete provides a mock function with given fields: ctx, dgst
-func (_m *ManifestService) Delete(ctx context.Context, dgst digest.Digest) error {
- ret := _m.Called(ctx, dgst)
-
- if len(ret) == 0 {
- panic("no return value specified for Delete")
- }
-
- var r0 error
- if rf, ok := ret.Get(0).(func(context.Context, digest.Digest) error); ok {
- r0 = rf(ctx, dgst)
- } else {
- r0 = ret.Error(0)
- }
-
- return r0
-}
-
-// Exists provides a mock function with given fields: ctx, dgst
-func (_m *ManifestService) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
- ret := _m.Called(ctx, dgst)
-
- if len(ret) == 0 {
- panic("no return value specified for Exists")
- }
-
- var r0 bool
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, digest.Digest) (bool, error)); ok {
- return rf(ctx, dgst)
- }
- if rf, ok := ret.Get(0).(func(context.Context, digest.Digest) bool); ok {
- r0 = rf(ctx, dgst)
- } else {
- r0 = ret.Get(0).(bool)
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, digest.Digest) error); ok {
- r1 = rf(ctx, dgst)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Get provides a mock function with given fields: ctx, dgst, options
-func (_m *ManifestService) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
- _va := make([]interface{}, len(options))
- for _i := range options {
- _va[_i] = options[_i]
- }
- var _ca []interface{}
- _ca = append(_ca, ctx, dgst)
- _ca = append(_ca, _va...)
- ret := _m.Called(_ca...)
-
- if len(ret) == 0 {
- panic("no return value specified for Get")
- }
-
- var r0 distribution.Manifest
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, digest.Digest, ...distribution.ManifestServiceOption) (distribution.Manifest, error)); ok {
- return rf(ctx, dgst, options...)
- }
- if rf, ok := ret.Get(0).(func(context.Context, digest.Digest, ...distribution.ManifestServiceOption) distribution.Manifest); ok {
- r0 = rf(ctx, dgst, options...)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(distribution.Manifest)
- }
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, digest.Digest, ...distribution.ManifestServiceOption) error); ok {
- r1 = rf(ctx, dgst, options...)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Put provides a mock function with given fields: ctx, manifest, options
-func (_m *ManifestService) Put(ctx context.Context, manifest distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
- _va := make([]interface{}, len(options))
- for _i := range options {
- _va[_i] = options[_i]
- }
- var _ca []interface{}
- _ca = append(_ca, ctx, manifest)
- _ca = append(_ca, _va...)
- ret := _m.Called(_ca...)
-
- if len(ret) == 0 {
- panic("no return value specified for Put")
- }
-
- var r0 digest.Digest
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, distribution.Manifest, ...distribution.ManifestServiceOption) (digest.Digest, error)); ok {
- return rf(ctx, manifest, options...)
- }
- if rf, ok := ret.Get(0).(func(context.Context, distribution.Manifest, ...distribution.ManifestServiceOption) digest.Digest); ok {
- r0 = rf(ctx, manifest, options...)
- } else {
- r0 = ret.Get(0).(digest.Digest)
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, distribution.Manifest, ...distribution.ManifestServiceOption) error); ok {
- r1 = rf(ctx, manifest, options...)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// NewManifestService creates a new instance of ManifestService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewManifestService(t interface {
- mock.TestingT
- Cleanup(func())
-}) *ManifestService {
- mock := &ManifestService{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/mocks/RegistryClient.go b/pkg/registry/mocks/RegistryClient.go
deleted file mode 100644
index 2943b04..0000000
--- a/pkg/registry/mocks/RegistryClient.go
+++ /dev/null
@@ -1,125 +0,0 @@
-// Code generated by mockery v1.1.2. DO NOT EDIT.
-
-package mocks
-
-import (
- distribution "github.com/distribution/distribution/v3"
- digest "github.com/opencontainers/go-digest"
-
- mock "github.com/stretchr/testify/mock"
-
- options "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
-
- tag "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-)
-
-// RegistryClient is an autogenerated mock type for the RegistryClient type
-type RegistryClient struct {
- mock.Mock
-}
-
-// ManifestForDigest provides a mock function with given fields: dgst
-func (_m *RegistryClient) ManifestForDigest(dgst digest.Digest) (distribution.Manifest, error) {
- ret := _m.Called(dgst)
-
- var r0 distribution.Manifest
- if rf, ok := ret.Get(0).(func(digest.Digest) distribution.Manifest); ok {
- r0 = rf(dgst)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(distribution.Manifest)
- }
- }
-
- var r1 error
- if rf, ok := ret.Get(1).(func(digest.Digest) error); ok {
- r1 = rf(dgst)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// ManifestForTag provides a mock function with given fields: tagStr
-func (_m *RegistryClient) ManifestForTag(tagStr string) (distribution.Manifest, error) {
- ret := _m.Called(tagStr)
-
- var r0 distribution.Manifest
- if rf, ok := ret.Get(0).(func(string) distribution.Manifest); ok {
- r0 = rf(tagStr)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(distribution.Manifest)
- }
- }
-
- var r1 error
- if rf, ok := ret.Get(1).(func(string) error); ok {
- r1 = rf(tagStr)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// NewRepository provides a mock function with given fields: nameInRepository
-func (_m *RegistryClient) NewRepository(nameInRepository string) error {
- ret := _m.Called(nameInRepository)
-
- var r0 error
- if rf, ok := ret.Get(0).(func(string) error); ok {
- r0 = rf(nameInRepository)
- } else {
- r0 = ret.Error(0)
- }
-
- return r0
-}
-
-// TagMetadata provides a mock function with given fields: manifest, opts
-func (_m *RegistryClient) TagMetadata(manifest distribution.Manifest, opts *options.ManifestOptions) (*tag.TagInfo, error) {
- ret := _m.Called(manifest, opts)
-
- var r0 *tag.TagInfo
- if rf, ok := ret.Get(0).(func(distribution.Manifest, *options.ManifestOptions) *tag.TagInfo); ok {
- r0 = rf(manifest, opts)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(*tag.TagInfo)
- }
- }
-
- var r1 error
- if rf, ok := ret.Get(1).(func(distribution.Manifest, *options.ManifestOptions) error); ok {
- r1 = rf(manifest, opts)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Tags provides a mock function with given fields:
-func (_m *RegistryClient) Tags() ([]string, error) {
- ret := _m.Called()
-
- var r0 []string
- if rf, ok := ret.Get(0).(func() []string); ok {
- r0 = rf()
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]string)
- }
- }
-
- var r1 error
- if rf, ok := ret.Get(1).(func() error); ok {
- r1 = rf()
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
diff --git a/pkg/registry/mocks/Repository.go b/pkg/registry/mocks/Repository.go
deleted file mode 100644
index 04d9b7c..0000000
--- a/pkg/registry/mocks/Repository.go
+++ /dev/null
@@ -1,128 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- context "context"
-
- distribution "github.com/distribution/distribution/v3"
- mock "github.com/stretchr/testify/mock"
-
- reference "github.com/distribution/distribution/v3/reference"
-)
-
-// Repository is an autogenerated mock type for the Repository type
-type Repository struct {
- mock.Mock
-}
-
-// Blobs provides a mock function with given fields: ctx
-func (_m *Repository) Blobs(ctx context.Context) distribution.BlobStore {
- ret := _m.Called(ctx)
-
- if len(ret) == 0 {
- panic("no return value specified for Blobs")
- }
-
- var r0 distribution.BlobStore
- if rf, ok := ret.Get(0).(func(context.Context) distribution.BlobStore); ok {
- r0 = rf(ctx)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(distribution.BlobStore)
- }
- }
-
- return r0
-}
-
-// Manifests provides a mock function with given fields: ctx, options
-func (_m *Repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
- _va := make([]interface{}, len(options))
- for _i := range options {
- _va[_i] = options[_i]
- }
- var _ca []interface{}
- _ca = append(_ca, ctx)
- _ca = append(_ca, _va...)
- ret := _m.Called(_ca...)
-
- if len(ret) == 0 {
- panic("no return value specified for Manifests")
- }
-
- var r0 distribution.ManifestService
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, ...distribution.ManifestServiceOption) (distribution.ManifestService, error)); ok {
- return rf(ctx, options...)
- }
- if rf, ok := ret.Get(0).(func(context.Context, ...distribution.ManifestServiceOption) distribution.ManifestService); ok {
- r0 = rf(ctx, options...)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(distribution.ManifestService)
- }
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, ...distribution.ManifestServiceOption) error); ok {
- r1 = rf(ctx, options...)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Named provides a mock function with given fields:
-func (_m *Repository) Named() reference.Named {
- ret := _m.Called()
-
- if len(ret) == 0 {
- panic("no return value specified for Named")
- }
-
- var r0 reference.Named
- if rf, ok := ret.Get(0).(func() reference.Named); ok {
- r0 = rf()
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(reference.Named)
- }
- }
-
- return r0
-}
-
-// Tags provides a mock function with given fields: ctx
-func (_m *Repository) Tags(ctx context.Context) distribution.TagService {
- ret := _m.Called(ctx)
-
- if len(ret) == 0 {
- panic("no return value specified for Tags")
- }
-
- var r0 distribution.TagService
- if rf, ok := ret.Get(0).(func(context.Context) distribution.TagService); ok {
- r0 = rf(ctx)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(distribution.TagService)
- }
- }
-
- return r0
-}
-
-// NewRepository creates a new instance of Repository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewRepository(t interface {
- mock.TestingT
- Cleanup(func())
-}) *Repository {
- mock := &Repository{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/mocks/RoundTripper.go b/pkg/registry/mocks/RoundTripper.go
deleted file mode 100644
index 27e22c2..0000000
--- a/pkg/registry/mocks/RoundTripper.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- http "net/http"
-
- mock "github.com/stretchr/testify/mock"
-)
-
-// RoundTripper is an autogenerated mock type for the RoundTripper type
-type RoundTripper struct {
- mock.Mock
-}
-
-// RoundTrip provides a mock function with given fields: _a0
-func (_m *RoundTripper) RoundTrip(_a0 *http.Request) (*http.Response, error) {
- ret := _m.Called(_a0)
-
- if len(ret) == 0 {
- panic("no return value specified for RoundTrip")
- }
-
- var r0 *http.Response
- var r1 error
- if rf, ok := ret.Get(0).(func(*http.Request) (*http.Response, error)); ok {
- return rf(_a0)
- }
- if rf, ok := ret.Get(0).(func(*http.Request) *http.Response); ok {
- r0 = rf(_a0)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).(*http.Response)
- }
- }
-
- if rf, ok := ret.Get(1).(func(*http.Request) error); ok {
- r1 = rf(_a0)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// NewRoundTripper creates a new instance of RoundTripper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewRoundTripper(t interface {
- mock.TestingT
- Cleanup(func())
-}) *RoundTripper {
- mock := &RoundTripper{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/mocks/TagService.go b/pkg/registry/mocks/TagService.go
deleted file mode 100644
index 5037808..0000000
--- a/pkg/registry/mocks/TagService.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Code generated by mockery v2.43.2. DO NOT EDIT.
-
-package mocks
-
-import (
- context "context"
-
- distribution "github.com/distribution/distribution/v3"
- mock "github.com/stretchr/testify/mock"
-)
-
-// TagService is an autogenerated mock type for the TagService type
-type TagService struct {
- mock.Mock
-}
-
-// All provides a mock function with given fields: ctx
-func (_m *TagService) All(ctx context.Context) ([]string, error) {
- ret := _m.Called(ctx)
-
- if len(ret) == 0 {
- panic("no return value specified for All")
- }
-
- var r0 []string
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context) ([]string, error)); ok {
- return rf(ctx)
- }
- if rf, ok := ret.Get(0).(func(context.Context) []string); ok {
- r0 = rf(ctx)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]string)
- }
- }
-
- if rf, ok := ret.Get(1).(func(context.Context) error); ok {
- r1 = rf(ctx)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Get provides a mock function with given fields: ctx, tag
-func (_m *TagService) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
- ret := _m.Called(ctx, tag)
-
- if len(ret) == 0 {
- panic("no return value specified for Get")
- }
-
- var r0 distribution.Descriptor
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, string) (distribution.Descriptor, error)); ok {
- return rf(ctx, tag)
- }
- if rf, ok := ret.Get(0).(func(context.Context, string) distribution.Descriptor); ok {
- r0 = rf(ctx, tag)
- } else {
- r0 = ret.Get(0).(distribution.Descriptor)
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
- r1 = rf(ctx, tag)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Lookup provides a mock function with given fields: ctx, digest
-func (_m *TagService) Lookup(ctx context.Context, digest distribution.Descriptor) ([]string, error) {
- ret := _m.Called(ctx, digest)
-
- if len(ret) == 0 {
- panic("no return value specified for Lookup")
- }
-
- var r0 []string
- var r1 error
- if rf, ok := ret.Get(0).(func(context.Context, distribution.Descriptor) ([]string, error)); ok {
- return rf(ctx, digest)
- }
- if rf, ok := ret.Get(0).(func(context.Context, distribution.Descriptor) []string); ok {
- r0 = rf(ctx, digest)
- } else {
- if ret.Get(0) != nil {
- r0 = ret.Get(0).([]string)
- }
- }
-
- if rf, ok := ret.Get(1).(func(context.Context, distribution.Descriptor) error); ok {
- r1 = rf(ctx, digest)
- } else {
- r1 = ret.Error(1)
- }
-
- return r0, r1
-}
-
-// Tag provides a mock function with given fields: ctx, tag, desc
-func (_m *TagService) Tag(ctx context.Context, tag string, desc distribution.Descriptor) error {
- ret := _m.Called(ctx, tag, desc)
-
- if len(ret) == 0 {
- panic("no return value specified for Tag")
- }
-
- var r0 error
- if rf, ok := ret.Get(0).(func(context.Context, string, distribution.Descriptor) error); ok {
- r0 = rf(ctx, tag, desc)
- } else {
- r0 = ret.Error(0)
- }
-
- return r0
-}
-
-// Untag provides a mock function with given fields: ctx, tag
-func (_m *TagService) Untag(ctx context.Context, tag string) error {
- ret := _m.Called(ctx, tag)
-
- if len(ret) == 0 {
- panic("no return value specified for Untag")
- }
-
- var r0 error
- if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
- r0 = rf(ctx, tag)
- } else {
- r0 = ret.Error(0)
- }
-
- return r0
-}
-
-// NewTagService creates a new instance of TagService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func NewTagService(t interface {
- mock.TestingT
- Cleanup(func())
-}) *TagService {
- mock := &TagService{}
- mock.Mock.Test(t)
-
- t.Cleanup(func() { mock.AssertExpectations(t) })
-
- return mock
-}
diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go
deleted file mode 100644
index 7300bd3..0000000
--- a/pkg/registry/registry.go
+++ /dev/null
@@ -1,222 +0,0 @@
-package registry
-
-// Package registry implements functions for retrieving data from container
-// registries.
-//
-// TODO: Refactor this package and provide mocks for better testing.
-
-import (
- "context"
- "fmt"
- "strings"
- "sync"
- "time"
-
- "github.com/distribution/distribution/v3"
-
- "golang.org/x/sync/semaphore"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
- "github.com/argoproj-labs/argocd-image-updater/pkg/kube"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/log"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-)
-
-const (
- MaxMetadataConcurrency = 20
-)
-
-// GetTags returns a list of available tags for the given image
-func (endpoint *RegistryEndpoint) GetTags(img *image.ContainerImage, regClient RegistryClient, vc *image.VersionConstraint) (*tag.ImageTagList, error) {
- var tagList *tag.ImageTagList = tag.NewImageTagList()
- var err error
-
- logCtx := vc.Options.Logger()
-
- // Some registries have a default namespace that is used when the image name
- // doesn't specify one. For example at Docker Hub, this is 'library'.
- var nameInRegistry string
- if len := len(strings.Split(img.ImageName, "/")); len == 1 && endpoint.DefaultNS != "" {
- nameInRegistry = endpoint.DefaultNS + "/" + img.ImageName
- logCtx.Debugf("Using canonical image name '%s' for image '%s'", nameInRegistry, img.ImageName)
- } else {
- nameInRegistry = img.ImageName
- }
- err = regClient.NewRepository(nameInRegistry)
- if err != nil {
- return nil, err
- }
- tTags, err := regClient.Tags()
- if err != nil {
- return nil, err
- }
-
- tags := []string{}
-
- // For digest strategy, we do require a version constraint
- if vc.Strategy.NeedsVersionConstraint() && vc.Constraint == "" {
- return nil, fmt.Errorf("cannot use update strategy 'digest' for image '%s' without a version constraint", img.Original())
- }
-
- // Loop through tags, removing those we do not want. If update strategy is
- // digest, all but the constraint tag are ignored.
- if vc.MatchFunc != nil || len(vc.IgnoreList) > 0 || vc.Strategy.WantsOnlyConstraintTag() {
- for _, t := range tTags {
- if (vc.MatchFunc != nil && !vc.MatchFunc(t, vc.MatchArgs)) || vc.IsTagIgnored(t) || (vc.Strategy.WantsOnlyConstraintTag() && t != vc.Constraint) {
- logCtx.Tracef("Removing tag %s because it either didn't match defined pattern or is ignored", t)
- } else {
- tags = append(tags, t)
- }
- }
- } else {
- tags = tTags
- }
-
- // In some cases, we don't need to fetch the metadata to get the creation time
- // stamp of from the image's meta data:
- //
- // - We use an update strategy other than latest or digest
- // - The registry doesn't provide meta data and has tags sorted already
- //
- // We just create a dummy time stamp according to the registry's sort mode, if
- // set.
- if (vc.Strategy != image.StrategyNewestBuild && vc.Strategy != image.StrategyDigest) || endpoint.TagListSort.IsTimeSorted() {
- for i, tagStr := range tags {
- var ts int
- if endpoint.TagListSort == TagListSortLatestFirst {
- ts = len(tags) - i
- } else if endpoint.TagListSort == TagListSortLatestLast {
- ts = i
- }
- imgTag := tag.NewImageTag(tagStr, time.Unix(int64(ts), 0), "")
- tagList.Add(imgTag)
- }
- return tagList, nil
- }
-
- sem := semaphore.NewWeighted(int64(MaxMetadataConcurrency))
- tagListLock := &sync.RWMutex{}
-
- var wg sync.WaitGroup
- wg.Add(len(tags))
-
- // Fetch the manifest for the tag -- we need v1, because it contains history
- // information that we require.
- i := 0
- for _, tagStr := range tags {
- i += 1
- // Look into the cache first and re-use any found item. If GetTag() returns
- // an error, we treat it as a cache miss and just go ahead to invalidate
- // the entry.
- if vc.Strategy.IsCacheable() {
- imgTag, err := endpoint.Cache.GetTag(nameInRegistry, tagStr)
- if err != nil {
- log.Warnf("invalid entry for %s:%s in cache, invalidating.", nameInRegistry, imgTag.TagName)
- } else if imgTag != nil {
- logCtx.Debugf("Cache hit for %s:%s", nameInRegistry, imgTag.TagName)
- tagListLock.Lock()
- tagList.Add(imgTag)
- tagListLock.Unlock()
- wg.Done()
- continue
- }
- }
-
- logCtx.Tracef("Getting manifest for image %s:%s (operation %d/%d)", nameInRegistry, tagStr, i, len(tags))
-
- lockErr := sem.Acquire(context.TODO(), 1)
- if lockErr != nil {
- log.Warnf("could not acquire semaphore: %v", lockErr)
- wg.Done()
- continue
- }
- logCtx.Tracef("acquired metadata semaphore")
-
- go func(tagStr string) {
- defer func() {
- sem.Release(1)
- wg.Done()
- log.Tracef("released semaphore and terminated waitgroup")
- }()
-
- var ml distribution.Manifest
- var err error
-
- // We first try to fetch a V2 manifest, and if that's not available we fall
- // back to fetching V1 manifest. If that fails also, we just skip this tag.
- if ml, err = regClient.ManifestForTag(tagStr); err != nil {
- logCtx.Errorf("Error fetching metadata for %s:%s - neither V1 or V2 or OCI manifest returned by registry: %v", nameInRegistry, tagStr, err)
- return
- }
-
- // Parse required meta data from the manifest. The metadata contains all
- // information needed to decide whether to consider this tag or not.
- ti, err := regClient.TagMetadata(ml, vc.Options)
- if err != nil {
- logCtx.Errorf("error fetching metadata for %s:%s: %v", nameInRegistry, tagStr, err)
- return
- }
- if ti == nil {
- logCtx.Debugf("No metadata found for %s:%s", nameInRegistry, tagStr)
- return
- }
-
- logCtx.Tracef("Found date %s", ti.CreatedAt.String())
- var imgTag *tag.ImageTag
- if vc.Strategy == image.StrategyDigest {
- imgTag = tag.NewImageTag(tagStr, ti.CreatedAt, fmt.Sprintf("sha256:%x", ti.Digest))
- } else {
- imgTag = tag.NewImageTag(tagStr, ti.CreatedAt, "")
- }
- tagListLock.Lock()
- tagList.Add(imgTag)
- tagListLock.Unlock()
- endpoint.Cache.SetTag(nameInRegistry, imgTag)
- }(tagStr)
- }
-
- wg.Wait()
- return tagList, err
-}
-
-func (ep *RegistryEndpoint) expireCredentials() bool {
- if ep.Credentials != "" && !ep.CredsUpdated.IsZero() && ep.CredsExpire > 0 && time.Since(ep.CredsUpdated) >= ep.CredsExpire {
- ep.Username = ""
- ep.Password = ""
- return true
- }
- return false
-}
-
-// Sets endpoint credentials for this registry from a reference to a K8s secret
-func (ep *RegistryEndpoint) SetEndpointCredentials(kubeClient *kube.KubernetesClient) error {
- if ep.expireCredentials() {
- log.Debugf("expired credentials for registry %s (updated:%s, expiry:%0fs)", ep.RegistryAPI, ep.CredsUpdated, ep.CredsExpire.Seconds())
- }
- if ep.Username == "" && ep.Password == "" && ep.Credentials != "" {
- credSrc, err := image.ParseCredentialSource(ep.Credentials, false)
- if err != nil {
- return err
- }
-
- // For fetching credentials, we must have working Kubernetes client.
- if (credSrc.Type == image.CredentialSourcePullSecret || credSrc.Type == image.CredentialSourceSecret) && kubeClient == nil {
- log.WithContext().
- AddField("registry", ep.RegistryAPI).
- Warnf("cannot use K8s credentials without Kubernetes client")
- return fmt.Errorf("could not fetch image tags")
- }
-
- creds, err := credSrc.FetchCredentials(ep.RegistryAPI, kubeClient)
- if err != nil {
- return err
- }
-
- ep.CredsUpdated = time.Now()
-
- ep.Username = creds.Username
- ep.Password = creds.Password
- }
-
- return nil
-}
diff --git a/pkg/registry/registry_test.go b/pkg/registry/registry_test.go
deleted file mode 100644
index 241799f..0000000
--- a/pkg/registry/registry_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package registry
-
-import (
- "os"
- "testing"
- "time"
-
- "github.com/argoproj-labs/argocd-image-updater/pkg/image"
- "github.com/argoproj-labs/argocd-image-updater/pkg/registry/mocks"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/options"
- "github.com/argoproj-labs/argocd-image-updater/registry-scanner/pkg/tag"
-
- "github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "github.com/stretchr/testify/require"
-)
-
-func Test_GetTags(t *testing.T) {
-
- t.Run("Check for correctly returned tags with semver sort", func(t *testing.T) {
- regClient := mocks.RegistryClient{}
- 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("")
- require.NoError(t, err)
-
- img := image.NewFromIdentifier("foo/bar:1.2.0")
-
- tl, err := ep.GetTags(img, &regClient, &image.VersionConstraint{Strategy: image.StrategySemVer, Options: options.NewManifestOptions()})
- require.NoError(t, err)
- assert.NotEmpty(t, tl)
-
- tag, err := ep.Cache.GetTag("foo/bar", "1.2.1")
- require.NoError(t, err)
- assert.Nil(t, tag)
- })
-
- t.Run("Check for correctly returned tags with filter function applied", func(t *testing.T) {
- regClient := mocks.RegistryClient{}
- 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("")
- require.NoError(t, err)
-
- img := image.NewFromIdentifier("foo/bar:1.2.0")
-
- tl, err := ep.GetTags(img, &regClient, &image.VersionConstraint{
- Strategy: image.StrategySemVer,
- MatchFunc: image.MatchFuncNone,
- Options: options.NewManifestOptions()})
- require.NoError(t, err)
- assert.Empty(t, tl.Tags())
-
- tag, err := ep.Cache.GetTag("foo/bar", "1.2.1")
- require.NoError(t, err)
- assert.Nil(t, tag)
- })
-
- t.Run("Check for correctly returned tags with name sort", func(t *testing.T) {
-
- regClient := mocks.RegistryClient{}
- 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("")
- require.NoError(t, err)
-
- img := image.NewFromIdentifier("foo/bar:1.2.0")
-
- tl, err := ep.GetTags(img, &regClient, &image.VersionConstraint{Strategy: image.StrategyAlphabetical, Options: options.NewManifestOptions()})
- require.NoError(t, err)
- assert.NotEmpty(t, tl)
-
- tag, err := ep.Cache.GetTag("foo/bar", "1.2.1")
- require.NoError(t, err)
- assert.Nil(t, tag)
- })
-
- t.Run("Check for correctly returned tags with latest sort", func(t *testing.T) {
- ts := "2006-01-02T15:04:05.999999999Z"
- meta1 := &schema1.SignedManifest{ //nolint:staticcheck
- Manifest: schema1.Manifest{ //nolint:staticcheck
- History: []schema1.History{ //nolint:staticcheck
- {
- V1Compatibility: `{"created":"` + ts + `"}`,
- },
- },
- },
- }
-
- regClient := mocks.RegistryClient{}
- regClient.On("NewRepository", mock.Anything).Return(nil)
- regClient.On("Tags", mock.Anything).Return([]string{"1.2.0", "1.2.1", "1.2.2"}, nil)
- regClient.On("ManifestForTag", mock.Anything, mock.Anything).Return(meta1, nil)
- regClient.On("TagMetadata", mock.Anything, mock.Anything).Return(&tag.TagInfo{}, nil)
-
- ep, err := GetRegistryEndpoint("")
- require.NoError(t, err)
- ep.Cache.ClearCache()
-
- img := image.NewFromIdentifier("foo/bar:1.2.0")
- tl, err := ep.GetTags(img, &regClient, &image.VersionConstraint{Strategy: image.StrategyNewestBuild, Options: options.NewManifestOptions()})
- require.NoError(t, err)
- assert.NotEmpty(t, tl)
-
- tag, err := ep.Cache.GetTag("foo/bar", "1.2.1")
- require.NoError(t, err)
- require.NotNil(t, tag)
- require.Equal(t, "1.2.1", tag.TagName)
- })
-
-}
-
-func Test_ExpireCredentials(t *testing.T) {
- epYAML := `
-registries:
-- name: GitHub Container Registry
- api_url: https://ghcr.io
- ping: no
- prefix: ghcr.io
- credentials: env:TEST_CREDS
- credsexpire: 3s
-`
- t.Run("Expire credentials", func(t *testing.T) {
- epl, err := ParseRegistryConfiguration(epYAML)
- require.NoError(t, err)
- require.Len(t, epl.Items, 1)
-
- // New registry configuration
- err = AddRegistryEndpointFromConfig(epl.Items[0])
- require.NoError(t, err)
- ep, err := GetRegistryEndpoint("ghcr.io")
- require.NoError(t, err)
- require.NotEqual(t, 0, ep.CredsExpire)
-
- // Initial creds
- os.Setenv("TEST_CREDS", "foo:bar")
- err = ep.SetEndpointCredentials(nil)
- assert.NoError(t, err)
- assert.Equal(t, "foo", ep.Username)
- assert.Equal(t, "bar", ep.Password)
- assert.False(t, ep.CredsUpdated.IsZero())
-
- // Creds should still be cached
- os.Setenv("TEST_CREDS", "bar:foo")
- err = ep.SetEndpointCredentials(nil)
- assert.NoError(t, err)
- assert.Equal(t, "foo", ep.Username)
- assert.Equal(t, "bar", ep.Password)
-
- // Pretend 5 minutes have passed - creds have expired and are re-read from env
- ep.CredsUpdated = ep.CredsUpdated.Add(time.Minute * -5)
- err = ep.SetEndpointCredentials(nil)
- assert.NoError(t, err)
- assert.Equal(t, "bar", ep.Username)
- assert.Equal(t, "foo", ep.Password)
- })
-
-}