mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
Signed-off-by: pbhatnagar-oss <pbhatifiwork@gmail.com> Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
This commit is contained in:
@@ -5,9 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -17,7 +15,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/commitserver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v3/common"
|
||||
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/util/git"
|
||||
"github.com/argoproj/argo-cd/v3/util/hydrator"
|
||||
"github.com/argoproj/argo-cd/v3/util/io"
|
||||
)
|
||||
|
||||
@@ -36,25 +34,13 @@ func init() {
|
||||
// WriteForPaths writes the manifests, hydrator.metadata, and README.md files for each path in the provided paths. It
|
||||
// also writes a root-level hydrator.metadata file containing the repo URL and dry SHA.
|
||||
func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *appv1.RevisionMetadata, paths []*apiclient.PathDetails) error { //nolint:revive //FIXME(var-naming)
|
||||
author := ""
|
||||
message := ""
|
||||
date := ""
|
||||
var references []appv1.RevisionReference
|
||||
if dryCommitMetadata != nil {
|
||||
author = dryCommitMetadata.Author
|
||||
message = dryCommitMetadata.Message
|
||||
if dryCommitMetadata.Date != nil {
|
||||
date = dryCommitMetadata.Date.Format(time.RFC3339)
|
||||
}
|
||||
references = dryCommitMetadata.References
|
||||
hydratorMetadata, err := hydrator.GetCommitMetadata(repoUrl, drySha, dryCommitMetadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve hydrator metadata: %w", err)
|
||||
}
|
||||
|
||||
subject, body, _ := strings.Cut(message, "\n\n")
|
||||
|
||||
_, bodyMinusTrailers := git.GetReferences(log.WithFields(log.Fields{"repo": repoUrl, "revision": drySha}), body)
|
||||
|
||||
// Write the top-level readme.
|
||||
err := writeMetadata(root, "", hydratorMetadataFile{DrySHA: drySha, RepoURL: repoUrl, Author: author, Subject: subject, Body: bodyMinusTrailers, Date: date, References: references})
|
||||
err = writeMetadata(root, "", hydratorMetadata)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write top-level hydrator metadata: %w", err)
|
||||
}
|
||||
@@ -86,7 +72,7 @@ func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *app
|
||||
}
|
||||
|
||||
// Write hydrator.metadata containing information about the hydration process.
|
||||
hydratorMetadata := hydratorMetadataFile{
|
||||
hydratorMetadata := hydrator.HydratorCommitMetadata{
|
||||
Commands: p.Commands,
|
||||
DrySHA: drySha,
|
||||
RepoURL: repoUrl,
|
||||
@@ -106,7 +92,7 @@ func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *app
|
||||
}
|
||||
|
||||
// writeMetadata writes the metadata to the hydrator.metadata file.
|
||||
func writeMetadata(root *os.Root, dirPath string, metadata hydratorMetadataFile) error {
|
||||
func writeMetadata(root *os.Root, dirPath string, metadata hydrator.HydratorCommitMetadata) error {
|
||||
hydratorMetadataPath := filepath.Join(dirPath, "hydrator.metadata")
|
||||
f, err := root.Create(hydratorMetadataPath)
|
||||
if err != nil {
|
||||
@@ -125,7 +111,7 @@ func writeMetadata(root *os.Root, dirPath string, metadata hydratorMetadataFile)
|
||||
}
|
||||
|
||||
// writeReadme writes the readme to the README.md file.
|
||||
func writeReadme(root *os.Root, dirPath string, metadata hydratorMetadataFile) error {
|
||||
func writeReadme(root *os.Root, dirPath string, metadata hydrator.HydratorCommitMetadata) error {
|
||||
readmeTemplate, err := template.New("readme").Funcs(sprigFuncMap).Parse(manifestHydrationReadmeTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse readme template: %w", err)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/commitserver/apiclient"
|
||||
appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/util/hydrator"
|
||||
)
|
||||
|
||||
// tempRoot creates a temporary directory and returns an os.Root object for it.
|
||||
@@ -144,7 +145,7 @@ Argocd-reference-commit-sha: abc123
|
||||
func TestWriteMetadata(t *testing.T) {
|
||||
root := tempRoot(t)
|
||||
|
||||
metadata := hydratorMetadataFile{
|
||||
metadata := hydrator.HydratorCommitMetadata{
|
||||
RepoURL: "https://github.com/example/repo",
|
||||
DrySHA: "abc123",
|
||||
}
|
||||
@@ -156,7 +157,7 @@ func TestWriteMetadata(t *testing.T) {
|
||||
metadataBytes, err := os.ReadFile(metadataPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var readMetadata hydratorMetadataFile
|
||||
var readMetadata hydrator.HydratorCommitMetadata
|
||||
err = json.Unmarshal(metadataBytes, &readMetadata)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, metadata, readMetadata)
|
||||
@@ -171,7 +172,7 @@ func TestWriteReadme(t *testing.T) {
|
||||
hash := sha256.Sum256(randomData)
|
||||
sha := hex.EncodeToString(hash[:])
|
||||
|
||||
metadata := hydratorMetadataFile{
|
||||
metadata := hydrator.HydratorCommitMetadata{
|
||||
RepoURL: "https://github.com/example/repo",
|
||||
DrySHA: "abc123",
|
||||
References: []appsv1.RevisionReference{
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
|
||||
applog "github.com/argoproj/argo-cd/v3/util/app/log"
|
||||
"github.com/argoproj/argo-cd/v3/util/git"
|
||||
"github.com/argoproj/argo-cd/v3/util/hydrator"
|
||||
utilio "github.com/argoproj/argo-cd/v3/util/io"
|
||||
)
|
||||
|
||||
@@ -59,6 +60,9 @@ type Dependencies interface {
|
||||
// AddHydrationQueueItem adds a hydration queue item to the queue. This is used to trigger the hydration process for
|
||||
// a group of applications which are hydrating to the same repo and target branch.
|
||||
AddHydrationQueueItem(key types.HydrationQueueKey)
|
||||
|
||||
// GetHydratorCommitMessageTemplate gets the configured template for rendering commit messages.
|
||||
GetHydratorCommitMessageTemplate() (string, error)
|
||||
}
|
||||
|
||||
// Hydrator is the main struct that implements the hydration logic. It uses the Dependencies interface to access the
|
||||
@@ -340,13 +344,22 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application) (string
|
||||
}
|
||||
logCtx.Warn("no credentials found for repo, continuing without credentials")
|
||||
}
|
||||
// get the commit message template
|
||||
commitMessageTemplate, err := h.dependencies.GetHydratorCommitMessageTemplate()
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to get hydrated commit message template: %w", err)
|
||||
}
|
||||
commitMessage, errMsg := getTemplatedCommitMessage(repoURL, targetRevision, commitMessageTemplate, revisionMetadata)
|
||||
if errMsg != nil {
|
||||
return "", "", fmt.Errorf("failed to get hydrator commit templated message: %w", errMsg)
|
||||
}
|
||||
|
||||
manifestsRequest := commitclient.CommitHydratedManifestsRequest{
|
||||
Repo: repo,
|
||||
SyncBranch: syncBranch,
|
||||
TargetBranch: targetBranch,
|
||||
DrySha: targetRevision,
|
||||
CommitMessage: "[Argo CD Bot] hydrate " + targetRevision,
|
||||
CommitMessage: commitMessage,
|
||||
Paths: paths,
|
||||
DryCommitMetadata: revisionMetadata,
|
||||
}
|
||||
@@ -411,3 +424,18 @@ func appNeedsHydration(app *appv1.Application, statusHydrateTimeout time.Duratio
|
||||
|
||||
return false, ""
|
||||
}
|
||||
|
||||
// Gets the multi-line commit message based on the template defined in the configmap. It is a two step process:
|
||||
// 1. Get the metadata template engine would use to render the template
|
||||
// 2. Pass the output of Step 1 and Step 2 to template Render
|
||||
func getTemplatedCommitMessage(repoURL, revision, commitMessageTemplate string, dryCommitMetadata *appv1.RevisionMetadata) (string, error) {
|
||||
hydratorCommitMetadata, err := hydrator.GetCommitMetadata(repoURL, revision, dryCommitMetadata)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get hydrated commit message: %w", err)
|
||||
}
|
||||
templatedCommitMsg, err := hydrator.Render(commitMessageTemplate, hydratorCommitMetadata)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse template %s: %w", commitMessageTemplate, err)
|
||||
}
|
||||
return templatedCommitMsg, nil
|
||||
}
|
||||
|
||||
@@ -13,8 +13,15 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/controller/hydrator/mocks"
|
||||
"github.com/argoproj/argo-cd/v3/controller/hydrator/types"
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/util/settings"
|
||||
)
|
||||
|
||||
var message = `testn
|
||||
Argocd-reference-commit-repourl: https://github.com/test/argocd-example-apps
|
||||
Argocd-reference-commit-author: Argocd-reference-commit-author
|
||||
Argocd-reference-commit-subject: testhydratormd
|
||||
Signed-off-by: testUser <test@gmail.com>`
|
||||
|
||||
func Test_appNeedsHydration(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -167,3 +174,80 @@ func Test_getRelevantAppsForHydration_RepoURLNormalization(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, relevantApps, 2, "Expected both apps to be considered relevant despite URL differences")
|
||||
}
|
||||
|
||||
func TestHydrator_getTemplatedCommitMessage(t *testing.T) {
|
||||
references := make([]v1alpha1.RevisionReference, 0)
|
||||
revReference := v1alpha1.RevisionReference{
|
||||
Commit: &v1alpha1.CommitMetadata{
|
||||
Author: "testAuthor",
|
||||
Subject: "test",
|
||||
RepoURL: "https://github.com/test/argocd-example-apps",
|
||||
SHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97c",
|
||||
},
|
||||
}
|
||||
references = append(references, revReference)
|
||||
type args struct {
|
||||
repoURL string
|
||||
revision string
|
||||
dryCommitMetadata *v1alpha1.RevisionMetadata
|
||||
template string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "test template",
|
||||
args: args{
|
||||
repoURL: "https://github.com/test/argocd-example-apps",
|
||||
revision: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
|
||||
dryCommitMetadata: &v1alpha1.RevisionMetadata{
|
||||
Author: "test test@test.com",
|
||||
Date: &metav1.Time{
|
||||
Time: metav1.Now().Time,
|
||||
},
|
||||
Message: message,
|
||||
References: references,
|
||||
},
|
||||
template: settings.CommitMessageTemplate,
|
||||
},
|
||||
want: `3ff41cc: testn
|
||||
Argocd-reference-commit-repourl: https://github.com/test/argocd-example-apps
|
||||
Argocd-reference-commit-author: Argocd-reference-commit-author
|
||||
Argocd-reference-commit-subject: testhydratormd
|
||||
Signed-off-by: testUser <test@gmail.com>
|
||||
|
||||
Co-authored-by: testAuthor
|
||||
Co-authored-by: test test@test.com
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "test empty template",
|
||||
args: args{
|
||||
repoURL: "https://github.com/test/argocd-example-apps",
|
||||
revision: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
|
||||
dryCommitMetadata: &v1alpha1.RevisionMetadata{
|
||||
Author: "test test@test.com",
|
||||
Date: &metav1.Time{
|
||||
Time: metav1.Now().Time,
|
||||
},
|
||||
Message: message,
|
||||
References: references,
|
||||
},
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := getTemplatedCommitMessage(tt.args.repoURL, tt.args.revision, tt.args.template, tt.args.dryCommitMetadata)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Hydrator.getHydratorCommitMessage() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
53
controller/hydrator/mocks/Dependencies.go
generated
53
controller/hydrator/mocks/Dependencies.go
generated
@@ -81,6 +81,59 @@ func (_c *Dependencies_AddHydrationQueueItem_Call) RunAndReturn(run func(key typ
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetHydratorCommitMessageTemplate provides a mock function for the type Dependencies
|
||||
func (_mock *Dependencies) GetHydratorCommitMessageTemplate() (string, error) {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetHydratorCommitMessageTemplate")
|
||||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func() (string, error)); ok {
|
||||
return returnFunc()
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = returnFunc()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Dependencies_GetHydratorCommitMessageTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHydratorCommitMessageTemplate'
|
||||
type Dependencies_GetHydratorCommitMessageTemplate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetHydratorCommitMessageTemplate is a helper method to define mock.On call
|
||||
func (_e *Dependencies_Expecter) GetHydratorCommitMessageTemplate() *Dependencies_GetHydratorCommitMessageTemplate_Call {
|
||||
return &Dependencies_GetHydratorCommitMessageTemplate_Call{Call: _e.mock.On("GetHydratorCommitMessageTemplate")}
|
||||
}
|
||||
|
||||
func (_c *Dependencies_GetHydratorCommitMessageTemplate_Call) Run(run func()) *Dependencies_GetHydratorCommitMessageTemplate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Dependencies_GetHydratorCommitMessageTemplate_Call) Return(s string, err error) *Dependencies_GetHydratorCommitMessageTemplate_Call {
|
||||
_c.Call.Return(s, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Dependencies_GetHydratorCommitMessageTemplate_Call) RunAndReturn(run func() (string, error)) *Dependencies_GetHydratorCommitMessageTemplate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetProcessableAppProj provides a mock function for the type Dependencies
|
||||
func (_mock *Dependencies) GetProcessableAppProj(app *v1alpha1.Application) (*v1alpha1.AppProject, error) {
|
||||
ret := _mock.Called(app)
|
||||
|
||||
@@ -97,3 +97,12 @@ func (ctrl *ApplicationController) PersistAppHydratorStatus(orig *appv1.Applicat
|
||||
func (ctrl *ApplicationController) AddHydrationQueueItem(key types.HydrationQueueKey) {
|
||||
ctrl.hydrationQueue.AddRateLimited(key)
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) GetHydratorCommitMessageTemplate() (string, error) {
|
||||
sourceHydratorCommitMessageKey, err := ctrl.settingsMgr.GetSourceHydratorCommitMessageTemplate()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get sourceHydrator commit message template key: %w", err)
|
||||
}
|
||||
|
||||
return sourceHydratorCommitMessageKey, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v3/test"
|
||||
"github.com/argoproj/argo-cd/v3/util/settings"
|
||||
)
|
||||
|
||||
func TestGetRepoObjs(t *testing.T) {
|
||||
@@ -77,3 +78,46 @@ func TestGetRepoObjs(t *testing.T) {
|
||||
|
||||
assert.Equal(t, "ConfigMap", objs[0].GetKind())
|
||||
}
|
||||
|
||||
func TestGetHydratorCommitMessageTemplate_WhenTemplateisNotDefined_FallbackToDefault(t *testing.T) {
|
||||
cm := test.NewConfigMap()
|
||||
cmBytes, _ := json.Marshal(cm)
|
||||
|
||||
data := fakeData{
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{string(cmBytes)},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
}
|
||||
|
||||
ctrl := newFakeControllerWithResync(&data, time.Minute, nil, errors.New("this should not be called"))
|
||||
|
||||
tmpl, err := ctrl.GetHydratorCommitMessageTemplate()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, tmpl) // should fallback to default
|
||||
assert.Equal(t, settings.CommitMessageTemplate, tmpl)
|
||||
}
|
||||
|
||||
func TestGetHydratorCommitMessageTemplate(t *testing.T) {
|
||||
cm := test.NewFakeConfigMap()
|
||||
cm.Data["sourceHydrator.commitMessageTemplate"] = settings.CommitMessageTemplate
|
||||
cmBytes, _ := json.Marshal(cm)
|
||||
|
||||
data := fakeData{
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{string(cmBytes)},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: test.FakeClusterURL,
|
||||
Revision: "abc123",
|
||||
},
|
||||
configMapData: cm.Data,
|
||||
}
|
||||
|
||||
ctrl := newFakeControllerWithResync(&data, time.Minute, nil, errors.New("this should not be called"))
|
||||
|
||||
tmpl, err := ctrl.GetHydratorCommitMessageTemplate()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, tmpl)
|
||||
}
|
||||
|
||||
@@ -439,3 +439,22 @@ data:
|
||||
|
||||
# application.sync.impersonation.enabled enables application sync to use a custom service account, via impersonation. This allows decoupling sync from control-plane service account.
|
||||
application.sync.impersonation.enabled: "false"
|
||||
|
||||
### SourceHydrator commit message template.
|
||||
# This template iterates through the fields in the `.metadata` object,
|
||||
# and formats them based on their type (map, array, or primitive values).
|
||||
# This is the default template and targets specific metadata properties
|
||||
sourceHydrator.commitMessageTemplate: |
|
||||
{{.metadata.drySha | trunc 7}}: {{ .metadata.subject }}
|
||||
{{- if .metadata.body }}
|
||||
|
||||
{{ .metadata.body }}
|
||||
{{- end }}
|
||||
{{ range $ref := .metadata.references }}
|
||||
{{- if and $ref.commit $ref.commit.author }}
|
||||
Co-authored-by: {{ $ref.commit.author }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .metadata.author }}
|
||||
Co-authored-by: {{ .metadata.author }}
|
||||
{{- end }}
|
||||
|
||||
@@ -262,6 +262,34 @@ specified more than once, the last one will be used.
|
||||
|
||||
All trailers are optional. If a trailer is not specified, the corresponding field in the metadata will be omitted.
|
||||
|
||||
## Commit Message Template
|
||||
|
||||
The commit message is generated using a [Go text/template](https://pkg.go.dev/text/template), optionally configured by the user via the argocd-cm ConfigMap. The template is rendered using the values from `hydrator.metadata`. The template can be multi-line, allowing users to define a subject line, body and optional trailers. To define the commit message template, you need to set the `sourceHydrator.commitMessageTemplate` field in argocd-cm ConfigMap.
|
||||
|
||||
The template may functions from the [Sprig function library](https://github.com/Masterminds/sprig).
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
namespace: argocd
|
||||
data:
|
||||
sourceHydrator.commitMessageTemplate: |
|
||||
{{.metadata.drySha | trunc 7}}: {{ .metadata.subject }}
|
||||
{{- if .metadata.body }}
|
||||
|
||||
{{ .metadata.body }}
|
||||
{{- end }}
|
||||
{{ range $ref := .metadata.references }}
|
||||
{{- if and $ref.commit $ref.commit.author }}
|
||||
Co-authored-by: {{ $ref.commit.author }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .metadata.author }}
|
||||
Co-authored-by: {{ .metadata.author }}
|
||||
{{- end }}
|
||||
|
||||
### Credential Templates
|
||||
|
||||
Credential templates allow a single credential to be used for multiple repositories. The source hydrator supports credential templates. For example, if you setup credential templates for the URL prefix `https://github.com/argoproj`, these credentials will be used for all repositories with this URL as prefix (e.g. `https://github.com/argoproj/argocd-example-apps`) that do not have their own credentials configured.
|
||||
|
||||
60
util/hydrator/hydrator.go
Normal file
60
util/hydrator/hydrator.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package hydrator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/util/git"
|
||||
)
|
||||
|
||||
// HydratorCommitMetadata defines the struct used by both Controller and commitServer
|
||||
// to define the templated commit message and the hydrated manifest
|
||||
type HydratorCommitMetadata struct {
|
||||
RepoURL string `json:"repoURL,omitempty"`
|
||||
DrySHA string `json:"drySha,omitempty"`
|
||||
Commands []string `json:"commands,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
// Subject is the subject line of the DRY commit message, i.e. `git show --format=%s`.
|
||||
Subject string `json:"subject,omitempty"`
|
||||
// Body is the body of the DRY commit message, excluding the subject line, i.e. `git show --format=%b`.
|
||||
// Known Argocd- trailers with valid values are removed, but all other trailers are kept.
|
||||
Body string `json:"body,omitempty"`
|
||||
References []appv1.RevisionReference `json:"references,omitempty"`
|
||||
}
|
||||
|
||||
// GetCommitMetadata takes repo, drySha and commitMetadata and returns a HydratorCommitMetadata which is a
|
||||
// common contract controller and commitServer
|
||||
func GetCommitMetadata(repoUrl, drySha string, dryCommitMetadata *appv1.RevisionMetadata) (HydratorCommitMetadata, error) { //nolint:revive //FIXME(var-naming)
|
||||
author := ""
|
||||
message := ""
|
||||
date := ""
|
||||
var references []appv1.RevisionReference
|
||||
if dryCommitMetadata != nil {
|
||||
author = dryCommitMetadata.Author
|
||||
message = dryCommitMetadata.Message
|
||||
if dryCommitMetadata.Date != nil {
|
||||
date = dryCommitMetadata.Date.Format(time.RFC3339)
|
||||
}
|
||||
references = dryCommitMetadata.References
|
||||
}
|
||||
|
||||
subject, body, _ := strings.Cut(message, "\n\n")
|
||||
|
||||
_, bodyMinusTrailers := git.GetReferences(log.WithFields(log.Fields{"repo": repoUrl, "revision": drySha}), body)
|
||||
|
||||
hydratorCommitMetadata := HydratorCommitMetadata{
|
||||
RepoURL: repoUrl,
|
||||
DrySHA: drySha,
|
||||
Author: author,
|
||||
Subject: subject,
|
||||
Body: bodyMinusTrailers,
|
||||
Date: date,
|
||||
References: references,
|
||||
}
|
||||
|
||||
return hydratorCommitMetadata, nil
|
||||
}
|
||||
74
util/hydrator/hydrator_test.go
Normal file
74
util/hydrator/hydrator_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package hydrator
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func TestGetCommitMetadata(t *testing.T) {
|
||||
repoURL := "https://github.com/test/argocd-example-apps"
|
||||
drySHA := "3ff41cc5247197a6caf50216c4c76cc29d78a97d"
|
||||
date := &metav1.Time{Time: metav1.Now().Time}
|
||||
revisionAuthor := "test test@test.com"
|
||||
references := make([]appv1.RevisionReference, 0)
|
||||
revReference := appv1.RevisionReference{
|
||||
Commit: &appv1.CommitMetadata{
|
||||
Author: "testAuthor",
|
||||
Subject: "test",
|
||||
RepoURL: repoURL,
|
||||
SHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97c",
|
||||
},
|
||||
}
|
||||
references = append(references, revReference)
|
||||
hydratedCommitMetadata := HydratorCommitMetadata{
|
||||
RepoURL: repoURL,
|
||||
DrySHA: drySHA,
|
||||
Author: revisionAuthor,
|
||||
Date: date.Format(time.RFC3339),
|
||||
References: references,
|
||||
Subject: "testMessage",
|
||||
}
|
||||
type args struct {
|
||||
repoURL string
|
||||
drySha string
|
||||
dryCommitMetadata *appv1.RevisionMetadata
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want HydratorCommitMetadata
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "test GetHydratorCommitMD",
|
||||
args: args{
|
||||
repoURL: repoURL,
|
||||
drySha: drySHA,
|
||||
dryCommitMetadata: &appv1.RevisionMetadata{
|
||||
Author: revisionAuthor,
|
||||
Date: date,
|
||||
Message: "testMessage",
|
||||
References: references,
|
||||
},
|
||||
},
|
||||
want: hydratedCommitMetadata,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetCommitMetadata(tt.args.repoURL, tt.args.drySha, tt.args.dryCommitMetadata)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetCommitMetadata() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetCommitMetadata() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
61
util/hydrator/template.go
Normal file
61
util/hydrator/template.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package hydrator
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
)
|
||||
|
||||
var sprigFuncMap = sprig.GenericFuncMap() // a singleton for better performance
|
||||
|
||||
func init() {
|
||||
// Avoid allowing the user to learn things about the environment.
|
||||
delete(sprigFuncMap, "env")
|
||||
delete(sprigFuncMap, "expandenv")
|
||||
delete(sprigFuncMap, "getHostByName")
|
||||
}
|
||||
|
||||
// Render use a parsed template and calls the Execute to apply the data.
|
||||
// currently the method supports struct and a map[string]any as data
|
||||
func Render(tmpl string, data HydratorCommitMetadata) (string, error) {
|
||||
var dataMap map[string]any
|
||||
var err error
|
||||
// short-circuit if template is not defined
|
||||
if tmpl == "" {
|
||||
return "", nil
|
||||
}
|
||||
dataMap, err = structToMap(data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("marshaling failed: %w", err)
|
||||
}
|
||||
metadata := map[string]any{
|
||||
"metadata": dataMap,
|
||||
}
|
||||
template, err := template.New("commit-template").Funcs(sprigFuncMap).Parse(tmpl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse template %s: %w", tmpl, err)
|
||||
}
|
||||
var replacedTmplBuffer bytes.Buffer
|
||||
if err = template.Execute(&replacedTmplBuffer, metadata); err != nil {
|
||||
return "", fmt.Errorf("failed to execute go template %s: %w", tmpl, err)
|
||||
}
|
||||
|
||||
return replacedTmplBuffer.String(), nil
|
||||
}
|
||||
|
||||
func structToMap(s any) (map[string]any, error) {
|
||||
jsonOut, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]any
|
||||
err = json.Unmarshal(jsonOut, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
99
util/hydrator/template_test.go
Normal file
99
util/hydrator/template_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package hydrator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v3/util/settings"
|
||||
)
|
||||
|
||||
func TestRender(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata HydratorCommitMetadata
|
||||
want string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "author and multiple references",
|
||||
metadata: HydratorCommitMetadata{
|
||||
RepoURL: "https://github.com/test/argocd-example-apps",
|
||||
DrySHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
|
||||
Author: "test <test@test.com>",
|
||||
Date: metav1.Now().String(),
|
||||
References: []v1alpha1.RevisionReference{
|
||||
{
|
||||
Commit: &v1alpha1.CommitMetadata{
|
||||
Author: "ref test <ref-test@test.com>",
|
||||
Subject: "test",
|
||||
RepoURL: "https://github.com/test/argocd-example-apps",
|
||||
SHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97c",
|
||||
},
|
||||
},
|
||||
{
|
||||
Commit: &v1alpha1.CommitMetadata{
|
||||
Author: "ref test 2 <ref-test-2@test.com>",
|
||||
Subject: "test 2",
|
||||
RepoURL: "https://github.com/test/argocd-example-apps",
|
||||
SHA: "abc12345678912345678912345678912345678912",
|
||||
},
|
||||
},
|
||||
},
|
||||
Body: "testBody",
|
||||
Subject: "testSubject",
|
||||
},
|
||||
want: `3ff41cc: testSubject
|
||||
|
||||
testBody
|
||||
|
||||
Co-authored-by: ref test <ref-test@test.com>
|
||||
Co-authored-by: ref test 2 <ref-test-2@test.com>
|
||||
Co-authored-by: test <test@test.com>
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no references",
|
||||
metadata: HydratorCommitMetadata{
|
||||
RepoURL: "https://github.com/test/argocd-example-apps",
|
||||
DrySHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
|
||||
Author: "test <test@test.com>",
|
||||
Date: metav1.Now().String(),
|
||||
Body: "testBody",
|
||||
Subject: "testSubject",
|
||||
},
|
||||
want: `3ff41cc: testSubject
|
||||
|
||||
testBody
|
||||
|
||||
Co-authored-by: test <test@test.com>
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "no body",
|
||||
metadata: HydratorCommitMetadata{
|
||||
RepoURL: "https://github.com/test/argocd-example-apps",
|
||||
DrySHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
|
||||
Author: "test <test@test.com>",
|
||||
Date: metav1.Now().String(),
|
||||
Subject: "testSubject",
|
||||
},
|
||||
want: `3ff41cc: testSubject
|
||||
|
||||
Co-authored-by: test <test@test.com>
|
||||
`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Render(settings.CommitMessageTemplate, tt.metadata)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Render() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,21 @@ import (
|
||||
tlsutil "github.com/argoproj/argo-cd/v3/util/tls"
|
||||
)
|
||||
|
||||
var CommitMessageTemplate = `{{.metadata.drySha | trunc 7}}: {{ .metadata.subject }}
|
||||
{{- if .metadata.body }}
|
||||
|
||||
{{ .metadata.body }}
|
||||
{{- end }}
|
||||
{{ range $ref := .metadata.references }}
|
||||
{{- if and $ref.commit $ref.commit.author }}
|
||||
Co-authored-by: {{ $ref.commit.author }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .metadata.author }}
|
||||
Co-authored-by: {{ .metadata.author }}
|
||||
{{- end }}
|
||||
`
|
||||
|
||||
// ArgoCDSettings holds in-memory runtime configuration options.
|
||||
type ArgoCDSettings struct {
|
||||
// URL is the externally facing URL users will visit to reach Argo CD.
|
||||
@@ -494,6 +509,8 @@ const (
|
||||
settingUIBannerPositionKey = "ui.bannerposition"
|
||||
// settingsBinaryUrlsKey designates the key for the argocd binary URLs
|
||||
settingsBinaryUrlsKey = "help.download"
|
||||
// settingsApplicationInstanceLabelKey is the key to configure injected app instance label key
|
||||
settingsSourceHydratorCommitMessageTemplateKey = "sourceHydrator.commitMessageTemplate"
|
||||
// globalProjectsKey designates the key for global project settings
|
||||
globalProjectsKey = "globalProjects"
|
||||
// initialPasswordSecretName is the name of the secret that will hold the initial admin password
|
||||
@@ -1005,6 +1022,17 @@ func (mgr *SettingsManager) GetResourceOverrides() (map[string]v1alpha1.Resource
|
||||
return resourceOverrides, nil
|
||||
}
|
||||
|
||||
func (mgr *SettingsManager) GetSourceHydratorCommitMessageTemplate() (string, error) {
|
||||
argoCDCM, err := mgr.getConfigMap()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if argoCDCM.Data[settingsSourceHydratorCommitMessageTemplateKey] == "" {
|
||||
return CommitMessageTemplate, nil // in case template is not defined return default
|
||||
}
|
||||
return argoCDCM.Data[settingsSourceHydratorCommitMessageTemplateKey], nil
|
||||
}
|
||||
|
||||
func addStatusOverrideToGK(resourceOverrides map[string]v1alpha1.ResourceOverride, groupKind string) {
|
||||
if val, ok := resourceOverrides[groupKind]; ok {
|
||||
val.IgnoreDifferences.JSONPointers = append(val.IgnoreDifferences.JSONPointers, "/status")
|
||||
|
||||
Reference in New Issue
Block a user