From ae23439fb26c1ea7051b0742718da0149feb74f9 Mon Sep 17 00:00:00 2001 From: flozzone Date: Fri, 22 Apr 2022 14:17:22 +0200 Subject: [PATCH] Add possibility to specify write-back GIT repository as annotation. --- docs/basics/update-methods.md | 16 +++++++++++++ pkg/argocd/git.go | 4 ++-- pkg/argocd/gitcreds.go | 18 +++++++-------- pkg/argocd/update.go | 10 +++++++-- pkg/argocd/update_test.go | 42 +++++++++++++++++++++++++++++++++++ pkg/common/constants.go | 1 + 6 files changed, 78 insertions(+), 13 deletions(-) diff --git a/docs/basics/update-methods.md b/docs/basics/update-methods.md index 2a368ea4..ba27ce4f 100644 --- a/docs/basics/update-methods.md +++ b/docs/basics/update-methods.md @@ -144,6 +144,22 @@ kubectl -n argocd-image-updater create secret generic git-creds \ --from-file=sshPrivateKey=~/.ssh/id_rsa ``` +### Specifying a repository when using a Helm repository in repoURL + +By default, Argo CD Image Updater will use the value found in the Application +spec at `.spec.source.repoURL` as Git repository to checkout. But when using +a Helm repository as `.spec.source.repoURL` GIT will simply fail. To manually +specify the repository to push the changes, specify the +annotation `argocd-image-updater.argoproj.io/git-repository` on the Application +manifest. + +The value of this annotation will define the Git repository to use, for example the +following would use a GitHub's repository: + +```yaml +argocd-image-updater.argoproj.io/git-repository: git@github.com:example/example.git +``` + ### Specifying a branch to commit to By default, Argo CD Image Updater will use the value found in the Application diff --git a/pkg/argocd/git.go b/pkg/argocd/git.go index 6aa4b421..ef26fbe3 100644 --- a/pkg/argocd/git.go +++ b/pkg/argocd/git.go @@ -131,7 +131,7 @@ func commitChangesGit(app *v1alpha1.Application, wbc *WriteBackConfig, changeLis logCtx := log.WithContext().AddField("application", app.GetName()) creds, err := wbc.GetCreds(app) if err != nil { - return fmt.Errorf("could not get creds for repo '%s': %v", app.Spec.Source.RepoURL, err) + return fmt.Errorf("could not get creds for repo '%s': %v", wbc.GitRepo, err) } var gitC git.Client if wbc.GitClient == nil { @@ -145,7 +145,7 @@ func commitChangesGit(app *v1alpha1.Application, wbc *WriteBackConfig, changeLis logCtx.Errorf("could not remove temp dir: %v", err) } }() - gitC, err = git.NewClientExt(app.Spec.Source.RepoURL, tempRoot, creds, false, false, "") + gitC, err = git.NewClientExt(wbc.GitRepo, tempRoot, creds, false, false, "") if err != nil { return err } diff --git a/pkg/argocd/gitcreds.go b/pkg/argocd/gitcreds.go index 4d42e837..85988212 100644 --- a/pkg/argocd/gitcreds.go +++ b/pkg/argocd/gitcreds.go @@ -14,39 +14,39 @@ import ( ) // getGitCredsSource returns git credentials source that loads credentials from the secret or from Argo CD settings -func getGitCredsSource(creds string, kubeClient *kube.KubernetesClient) (GitCredsSource, error) { +func getGitCredsSource(creds string, kubeClient *kube.KubernetesClient, wbc *WriteBackConfig) (GitCredsSource, error) { switch { case creds == "repocreds": return func(app *v1alpha1.Application) (git.Creds, error) { - return getCredsFromArgoCD(app, kubeClient) + return getCredsFromArgoCD(wbc, kubeClient) }, nil case strings.HasPrefix(creds, "secret:"): return func(app *v1alpha1.Application) (git.Creds, error) { - return getCredsFromSecret(app, creds[len("secret:"):], kubeClient) + return getCredsFromSecret(wbc, creds[len("secret:"):], kubeClient) }, nil } return nil, fmt.Errorf("unexpected credentials format. Expected 'repocreds' or 'secret:/' but got '%s'", creds) } // getCredsFromArgoCD loads repository credentials from Argo CD settings -func getCredsFromArgoCD(app *v1alpha1.Application, kubeClient *kube.KubernetesClient) (git.Creds, error) { +func getCredsFromArgoCD(wbc *WriteBackConfig, kubeClient *kube.KubernetesClient) (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) - repo, err := argocdDB.GetRepository(ctx, app.Spec.Source.RepoURL) + repo, err := argocdDB.GetRepository(ctx, wbc.GitRepo) if err != nil { return nil, err } if !repo.HasCredentials() { - return nil, fmt.Errorf("credentials for '%s' are not configured in Argo CD settings", app.Spec.Source.RepoURL) + return nil, fmt.Errorf("credentials for '%s' are not configured in Argo CD settings", wbc.GitRepo) } return repo.GetGitCreds(), nil } // getCredsFromSecret loads repository credentials from secret -func getCredsFromSecret(app *v1alpha1.Application, credentialsSecret string, kubeClient *kube.KubernetesClient) (git.Creds, error) { +func getCredsFromSecret(wbc *WriteBackConfig, credentialsSecret string, kubeClient *kube.KubernetesClient) (git.Creds, error) { var credentials map[string][]byte var err error s := strings.SplitN(credentialsSecret, "/", 2) @@ -59,13 +59,13 @@ func getCredsFromSecret(app *v1alpha1.Application, credentialsSecret string, kub return nil, fmt.Errorf("secret ref must be in format 'namespace/name', but is '%s'", credentialsSecret) } - if ok, _ := git.IsSSHURL(app.Spec.Source.RepoURL); ok { + if ok, _ := git.IsSSHURL(wbc.GitRepo); ok { var sshPrivateKey []byte if sshPrivateKey, ok = credentials["sshPrivateKey"]; !ok { return nil, fmt.Errorf("invalid secret %s: does not contain field sshPrivateKey", credentialsSecret) } return git.NewSSHCreds(string(sshPrivateKey), "", true), nil - } else if git.IsHTTPSURL(app.Spec.Source.RepoURL) { + } else if git.IsHTTPSURL(wbc.GitRepo) { var username, password []byte if username, ok = credentials["username"]; !ok { return nil, fmt.Errorf("invalid secret %s: does not contain field username", credentialsSecret) diff --git a/pkg/argocd/update.go b/pkg/argocd/update.go index c92730dc..c7f6cdd6 100644 --- a/pkg/argocd/update.go +++ b/pkg/argocd/update.go @@ -68,6 +68,7 @@ type WriteBackConfig struct { GitCommitMessage string KustomizeBase string Target string + GitRepo string } // The following are helper structs to only marshal the fields we require @@ -476,7 +477,12 @@ func parseGitConfig(app *v1alpha1.Application, kubeClient *kube.KubernetesClient wbc.GitWriteBranch = branches[1] } } - credsSource, err := getGitCredsSource(creds, kubeClient) + wbc.GitRepo = app.Spec.Source.RepoURL + repo, ok := app.Annotations[common.GitRepositoryAnnotation] + if ok { + wbc.GitRepo = repo + } + credsSource, err := getGitCredsSource(creds, kubeClient, wbc) if err != nil { return fmt.Errorf("invalid git credentials source: %v", err) } @@ -486,7 +492,7 @@ func parseGitConfig(app *v1alpha1.Application, kubeClient *kube.KubernetesClient func commitChangesLocked(app *v1alpha1.Application, wbc *WriteBackConfig, state *SyncIterationState, changeList []ChangeEntry) error { if wbc.RequiresLocking() { - lock := state.GetRepositoryLock(app.Spec.Source.RepoURL) + lock := state.GetRepositoryLock(wbc.GitRepo) lock.Lock() defer lock.Unlock() } diff --git a/pkg/argocd/update_test.go b/pkg/argocd/update_test.go index ea5cdccf..c9110e18 100644 --- a/pkg/argocd/update_test.go +++ b/pkg/argocd/update_test.go @@ -1691,6 +1691,48 @@ func Test_GetGitCreds(t *testing.T) { require.Error(t, err) require.Nil(t, creds) }) + + t.Run("SSH creds from Argo CD settings with Helm Chart repoURL", func(t *testing.T) { + argoClient := argomock.ArgoCD{} + argoClient.On("UpdateSpec", mock.Anything, mock.Anything).Return(nil, nil) + secret := fixture.NewSecret("argocd-image-updater", "git-creds", map[string][]byte{ + "sshPrivateKey": []byte("foo"), + }) + kubeClient := kube.KubernetesClient{ + Clientset: fake.NewFakeClientsetWithResources(secret), + } + + app := v1alpha1.Application{ + ObjectMeta: v1.ObjectMeta{ + Name: "testapp", + Annotations: map[string]string{ + "argocd-image-updater.argoproj.io/image-list": "nginx", + "argocd-image-updater.argoproj.io/write-back-method": "git:secret:argocd-image-updater/git-creds", + "argocd-image-updater.argoproj.io/git-repository": "git@github.com:example/example.git", + }, + }, + Spec: v1alpha1.ApplicationSpec{ + Source: v1alpha1.ApplicationSource{ + RepoURL: "https://example-helm-repo.com/example", + TargetRevision: "main", + }, + }, + Status: v1alpha1.ApplicationStatus{ + SourceType: v1alpha1.ApplicationSourceTypeKustomize, + }, + } + + wbc, err := getWriteBackConfig(&app, &kubeClient, &argoClient) + require.NoError(t, err) + require.Equal(t, wbc.GitRepo, "git@github.com:example/example.git") + + creds, err := wbc.GetCreds(&app) + require.NoError(t, err) + require.NotNil(t, creds) + // Must have SSH creds + _, ok := creds.(git.SSHCreds) + require.True(t, ok) + }) } func Test_CommitUpdates(t *testing.T) { diff --git a/pkg/common/constants.go b/pkg/common/constants.go index 527b2009..5abf6da6 100644 --- a/pkg/common/constants.go +++ b/pkg/common/constants.go @@ -50,6 +50,7 @@ const ( const ( WriteBackMethodAnnotation = ImageUpdaterAnnotationPrefix + "/write-back-method" GitBranchAnnotation = ImageUpdaterAnnotationPrefix + "/git-branch" + GitRepositoryAnnotation = ImageUpdaterAnnotationPrefix + "/git-repository" WriteBackTargetAnnotation = ImageUpdaterAnnotationPrefix + "/write-back-target" KustomizationPrefix = "kustomization" )