Skip to content

Commit

Permalink
feat: Add possibility to specify write-back GIT repository as annotat…
Browse files Browse the repository at this point in the history
…ion (argoproj-labs#424)

* Add possibility to specify write-back GIT repository as annotation.

Signed-off-by: flozzone <flozzone@gmail.com>

* Update golangci-lint to 1.52.2.

Signed-off-by: flozzone <flozzone@gmail.com>

* Replace deprecated golangci linters with 'unused' linter.

Signed-off-by: flozzone <flozzone@gmail.com>

* Fix Goimport issues.

Signed-off-by: flozzone <flozzone@gmail.com>

---------

Signed-off-by: flozzone <flozzone@gmail.com>
  • Loading branch information
flozzone authored and dlactin committed May 9, 2024
1 parent 530880a commit 18ecb91
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 15 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ linters-settings:
goimports:
local-prefixes: github.com/argoproj-labs/argocd-image-updater
service:
golangci-lint-version: 1.26.0
golangci-lint-version: 1.52.2
16 changes: 16 additions & 0 deletions docs/basics/update-methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,22 @@ kubectl -n argocd-image-updater create secret generic git-creds \
--from-file=sshPrivateKey=~/.ssh/id_rsa
```

### <a name="method-git-repository"></a>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
```

### <a name="method-git-branch"></a>Specifying a branch to commit to

By default, Argo CD Image Updater will use the value found in the Application
Expand Down
3 changes: 2 additions & 1 deletion ext/git/mocks/Client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions pkg/argocd/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand Down
18 changes: 9 additions & 9 deletions pkg/argocd/gitcreds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:<namespace>/<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), 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)
Expand All @@ -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)
Expand Down
10 changes: 8 additions & 2 deletions pkg/argocd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type WriteBackConfig struct {
GitCommitSignOff bool
KustomizeBase string
Target string
GitRepo string
}

// The following are helper structs to only marshal the fields we require
Expand Down Expand Up @@ -533,7 +534,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)
}
Expand All @@ -543,7 +549,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()
}
Expand Down
42 changes: 42 additions & 0 deletions pkg/argocd/update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1718,6 +1718,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) {
Expand Down
1 change: 1 addition & 0 deletions pkg/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down

0 comments on commit 18ecb91

Please sign in to comment.