Skip to content

Commit a36786e

Browse files
pbhatnagar-osscrenshaw-dev
authored andcommitted
feat(hydrator): Commit message templating (argoproj#23679) (argoproj#24204)
Signed-off-by: pbhatnagar-oss <[email protected]> Signed-off-by: Michael Crenshaw <[email protected]> Co-authored-by: Michael Crenshaw <[email protected]> Signed-off-by: Mangaal <[email protected]>
1 parent ff6c806 commit a36786e

File tree

14 files changed

+600
-26
lines changed

14 files changed

+600
-26
lines changed

commitserver/commit/hydratorhelper.go

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ import (
55
"fmt"
66
"os"
77
"path/filepath"
8-
"strings"
98
"text/template"
10-
"time"
119

1210
"github.com/Masterminds/sprig/v3"
1311
log "github.com/sirupsen/logrus"
@@ -17,7 +15,7 @@ import (
1715
"github.com/argoproj/argo-cd/v3/commitserver/apiclient"
1816
"github.com/argoproj/argo-cd/v3/common"
1917
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
20-
"github.com/argoproj/argo-cd/v3/util/git"
18+
"github.com/argoproj/argo-cd/v3/util/hydrator"
2119
"github.com/argoproj/argo-cd/v3/util/io"
2220
)
2321

@@ -36,25 +34,13 @@ func init() {
3634
// WriteForPaths writes the manifests, hydrator.metadata, and README.md files for each path in the provided paths. It
3735
// also writes a root-level hydrator.metadata file containing the repo URL and dry SHA.
3836
func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *appv1.RevisionMetadata, paths []*apiclient.PathDetails) error { //nolint:revive //FIXME(var-naming)
39-
author := ""
40-
message := ""
41-
date := ""
42-
var references []appv1.RevisionReference
43-
if dryCommitMetadata != nil {
44-
author = dryCommitMetadata.Author
45-
message = dryCommitMetadata.Message
46-
if dryCommitMetadata.Date != nil {
47-
date = dryCommitMetadata.Date.Format(time.RFC3339)
48-
}
49-
references = dryCommitMetadata.References
37+
hydratorMetadata, err := hydrator.GetCommitMetadata(repoUrl, drySha, dryCommitMetadata)
38+
if err != nil {
39+
return fmt.Errorf("failed to retrieve hydrator metadata: %w", err)
5040
}
5141

52-
subject, body, _ := strings.Cut(message, "\n\n")
53-
54-
_, bodyMinusTrailers := git.GetReferences(log.WithFields(log.Fields{"repo": repoUrl, "revision": drySha}), body)
55-
5642
// Write the top-level readme.
57-
err := writeMetadata(root, "", hydratorMetadataFile{DrySHA: drySha, RepoURL: repoUrl, Author: author, Subject: subject, Body: bodyMinusTrailers, Date: date, References: references})
43+
err = writeMetadata(root, "", hydratorMetadata)
5844
if err != nil {
5945
return fmt.Errorf("failed to write top-level hydrator metadata: %w", err)
6046
}
@@ -86,7 +72,7 @@ func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *app
8672
}
8773

8874
// Write hydrator.metadata containing information about the hydration process.
89-
hydratorMetadata := hydratorMetadataFile{
75+
hydratorMetadata := hydrator.HydratorCommitMetadata{
9076
Commands: p.Commands,
9177
DrySHA: drySha,
9278
RepoURL: repoUrl,
@@ -106,7 +92,7 @@ func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *app
10692
}
10793

10894
// writeMetadata writes the metadata to the hydrator.metadata file.
109-
func writeMetadata(root *os.Root, dirPath string, metadata hydratorMetadataFile) error {
95+
func writeMetadata(root *os.Root, dirPath string, metadata hydrator.HydratorCommitMetadata) error {
11096
hydratorMetadataPath := filepath.Join(dirPath, "hydrator.metadata")
11197
f, err := root.Create(hydratorMetadataPath)
11298
if err != nil {
@@ -125,7 +111,7 @@ func writeMetadata(root *os.Root, dirPath string, metadata hydratorMetadataFile)
125111
}
126112

127113
// writeReadme writes the readme to the README.md file.
128-
func writeReadme(root *os.Root, dirPath string, metadata hydratorMetadataFile) error {
114+
func writeReadme(root *os.Root, dirPath string, metadata hydrator.HydratorCommitMetadata) error {
129115
readmeTemplate, err := template.New("readme").Funcs(sprigFuncMap).Parse(manifestHydrationReadmeTemplate)
130116
if err != nil {
131117
return fmt.Errorf("failed to parse readme template: %w", err)

commitserver/commit/hydratorhelper_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/argoproj/argo-cd/v3/commitserver/apiclient"
2020
appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
21+
"github.com/argoproj/argo-cd/v3/util/hydrator"
2122
)
2223

2324
// tempRoot creates a temporary directory and returns an os.Root object for it.
@@ -144,7 +145,7 @@ Argocd-reference-commit-sha: abc123
144145
func TestWriteMetadata(t *testing.T) {
145146
root := tempRoot(t)
146147

147-
metadata := hydratorMetadataFile{
148+
metadata := hydrator.HydratorCommitMetadata{
148149
RepoURL: "https://github.com/example/repo",
149150
DrySHA: "abc123",
150151
}
@@ -156,7 +157,7 @@ func TestWriteMetadata(t *testing.T) {
156157
metadataBytes, err := os.ReadFile(metadataPath)
157158
require.NoError(t, err)
158159

159-
var readMetadata hydratorMetadataFile
160+
var readMetadata hydrator.HydratorCommitMetadata
160161
err = json.Unmarshal(metadataBytes, &readMetadata)
161162
require.NoError(t, err)
162163
assert.Equal(t, metadata, readMetadata)
@@ -171,7 +172,7 @@ func TestWriteReadme(t *testing.T) {
171172
hash := sha256.Sum256(randomData)
172173
sha := hex.EncodeToString(hash[:])
173174

174-
metadata := hydratorMetadataFile{
175+
metadata := hydrator.HydratorCommitMetadata{
175176
RepoURL: "https://github.com/example/repo",
176177
DrySHA: "abc123",
177178
References: []appsv1.RevisionReference{

controller/hydrator/hydrator.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
1717
applog "github.com/argoproj/argo-cd/v3/util/app/log"
1818
"github.com/argoproj/argo-cd/v3/util/git"
19+
"github.com/argoproj/argo-cd/v3/util/hydrator"
1920
utilio "github.com/argoproj/argo-cd/v3/util/io"
2021
)
2122

@@ -59,6 +60,9 @@ type Dependencies interface {
5960
// AddHydrationQueueItem adds a hydration queue item to the queue. This is used to trigger the hydration process for
6061
// a group of applications which are hydrating to the same repo and target branch.
6162
AddHydrationQueueItem(key types.HydrationQueueKey)
63+
64+
// GetHydratorCommitMessageTemplate gets the configured template for rendering commit messages.
65+
GetHydratorCommitMessageTemplate() (string, error)
6266
}
6367

6468
// 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
340344
}
341345
logCtx.Warn("no credentials found for repo, continuing without credentials")
342346
}
347+
// get the commit message template
348+
commitMessageTemplate, err := h.dependencies.GetHydratorCommitMessageTemplate()
349+
if err != nil {
350+
return "", "", fmt.Errorf("failed to get hydrated commit message template: %w", err)
351+
}
352+
commitMessage, errMsg := getTemplatedCommitMessage(repoURL, targetRevision, commitMessageTemplate, revisionMetadata)
353+
if errMsg != nil {
354+
return "", "", fmt.Errorf("failed to get hydrator commit templated message: %w", errMsg)
355+
}
343356

344357
manifestsRequest := commitclient.CommitHydratedManifestsRequest{
345358
Repo: repo,
346359
SyncBranch: syncBranch,
347360
TargetBranch: targetBranch,
348361
DrySha: targetRevision,
349-
CommitMessage: "[Argo CD Bot] hydrate " + targetRevision,
362+
CommitMessage: commitMessage,
350363
Paths: paths,
351364
DryCommitMetadata: revisionMetadata,
352365
}
@@ -411,3 +424,18 @@ func appNeedsHydration(app *appv1.Application, statusHydrateTimeout time.Duratio
411424

412425
return false, ""
413426
}
427+
428+
// Gets the multi-line commit message based on the template defined in the configmap. It is a two step process:
429+
// 1. Get the metadata template engine would use to render the template
430+
// 2. Pass the output of Step 1 and Step 2 to template Render
431+
func getTemplatedCommitMessage(repoURL, revision, commitMessageTemplate string, dryCommitMetadata *appv1.RevisionMetadata) (string, error) {
432+
hydratorCommitMetadata, err := hydrator.GetCommitMetadata(repoURL, revision, dryCommitMetadata)
433+
if err != nil {
434+
return "", fmt.Errorf("failed to get hydrated commit message: %w", err)
435+
}
436+
templatedCommitMsg, err := hydrator.Render(commitMessageTemplate, hydratorCommitMetadata)
437+
if err != nil {
438+
return "", fmt.Errorf("failed to parse template %s: %w", commitMessageTemplate, err)
439+
}
440+
return templatedCommitMsg, nil
441+
}

controller/hydrator/hydrator_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@ import (
1313
"github.com/argoproj/argo-cd/v3/controller/hydrator/mocks"
1414
"github.com/argoproj/argo-cd/v3/controller/hydrator/types"
1515
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
16+
"github.com/argoproj/argo-cd/v3/util/settings"
1617
)
1718

19+
var message = `testn
20+
Argocd-reference-commit-repourl: https://github.com/test/argocd-example-apps
21+
Argocd-reference-commit-author: Argocd-reference-commit-author
22+
Argocd-reference-commit-subject: testhydratormd
23+
Signed-off-by: testUser <[email protected]>`
24+
1825
func Test_appNeedsHydration(t *testing.T) {
1926
t.Parallel()
2027

@@ -167,3 +174,80 @@ func Test_getRelevantAppsForHydration_RepoURLNormalization(t *testing.T) {
167174
require.NoError(t, err)
168175
assert.Len(t, relevantApps, 2, "Expected both apps to be considered relevant despite URL differences")
169176
}
177+
178+
func TestHydrator_getTemplatedCommitMessage(t *testing.T) {
179+
references := make([]v1alpha1.RevisionReference, 0)
180+
revReference := v1alpha1.RevisionReference{
181+
Commit: &v1alpha1.CommitMetadata{
182+
Author: "testAuthor",
183+
Subject: "test",
184+
RepoURL: "https://github.com/test/argocd-example-apps",
185+
SHA: "3ff41cc5247197a6caf50216c4c76cc29d78a97c",
186+
},
187+
}
188+
references = append(references, revReference)
189+
type args struct {
190+
repoURL string
191+
revision string
192+
dryCommitMetadata *v1alpha1.RevisionMetadata
193+
template string
194+
}
195+
tests := []struct {
196+
name string
197+
args args
198+
want string
199+
wantErr bool
200+
}{
201+
{
202+
name: "test template",
203+
args: args{
204+
repoURL: "https://github.com/test/argocd-example-apps",
205+
revision: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
206+
dryCommitMetadata: &v1alpha1.RevisionMetadata{
207+
Author: "test [email protected]",
208+
Date: &metav1.Time{
209+
Time: metav1.Now().Time,
210+
},
211+
Message: message,
212+
References: references,
213+
},
214+
template: settings.CommitMessageTemplate,
215+
},
216+
want: `3ff41cc: testn
217+
Argocd-reference-commit-repourl: https://github.com/test/argocd-example-apps
218+
Argocd-reference-commit-author: Argocd-reference-commit-author
219+
Argocd-reference-commit-subject: testhydratormd
220+
Signed-off-by: testUser <[email protected]>
221+
222+
Co-authored-by: testAuthor
223+
Co-authored-by: test [email protected]
224+
`,
225+
},
226+
{
227+
name: "test empty template",
228+
args: args{
229+
repoURL: "https://github.com/test/argocd-example-apps",
230+
revision: "3ff41cc5247197a6caf50216c4c76cc29d78a97d",
231+
dryCommitMetadata: &v1alpha1.RevisionMetadata{
232+
Author: "test [email protected]",
233+
Date: &metav1.Time{
234+
Time: metav1.Now().Time,
235+
},
236+
Message: message,
237+
References: references,
238+
},
239+
},
240+
want: "",
241+
},
242+
}
243+
for _, tt := range tests {
244+
t.Run(tt.name, func(t *testing.T) {
245+
got, err := getTemplatedCommitMessage(tt.args.repoURL, tt.args.revision, tt.args.template, tt.args.dryCommitMetadata)
246+
if (err != nil) != tt.wantErr {
247+
t.Errorf("Hydrator.getHydratorCommitMessage() error = %v, wantErr %v", err, tt.wantErr)
248+
return
249+
}
250+
assert.Equal(t, tt.want, got)
251+
})
252+
}
253+
}

controller/hydrator/mocks/Dependencies.go

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controller/hydrator_dependencies.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,12 @@ func (ctrl *ApplicationController) PersistAppHydratorStatus(orig *appv1.Applicat
9797
func (ctrl *ApplicationController) AddHydrationQueueItem(key types.HydrationQueueKey) {
9898
ctrl.hydrationQueue.AddRateLimited(key)
9999
}
100+
101+
func (ctrl *ApplicationController) GetHydratorCommitMessageTemplate() (string, error) {
102+
sourceHydratorCommitMessageKey, err := ctrl.settingsMgr.GetSourceHydratorCommitMessageTemplate()
103+
if err != nil {
104+
return "", fmt.Errorf("failed to get sourceHydrator commit message template key: %w", err)
105+
}
106+
107+
return sourceHydratorCommitMessageKey, nil
108+
}

controller/hydrator_dependencies_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
1515
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
1616
"github.com/argoproj/argo-cd/v3/test"
17+
"github.com/argoproj/argo-cd/v3/util/settings"
1718
)
1819

1920
func TestGetRepoObjs(t *testing.T) {
@@ -77,3 +78,46 @@ func TestGetRepoObjs(t *testing.T) {
7778

7879
assert.Equal(t, "ConfigMap", objs[0].GetKind())
7980
}
81+
82+
func TestGetHydratorCommitMessageTemplate_WhenTemplateisNotDefined_FallbackToDefault(t *testing.T) {
83+
cm := test.NewConfigMap()
84+
cmBytes, _ := json.Marshal(cm)
85+
86+
data := fakeData{
87+
manifestResponse: &apiclient.ManifestResponse{
88+
Manifests: []string{string(cmBytes)},
89+
Namespace: test.FakeDestNamespace,
90+
Server: test.FakeClusterURL,
91+
Revision: "abc123",
92+
},
93+
}
94+
95+
ctrl := newFakeControllerWithResync(&data, time.Minute, nil, errors.New("this should not be called"))
96+
97+
tmpl, err := ctrl.GetHydratorCommitMessageTemplate()
98+
require.NoError(t, err)
99+
assert.NotEmpty(t, tmpl) // should fallback to default
100+
assert.Equal(t, settings.CommitMessageTemplate, tmpl)
101+
}
102+
103+
func TestGetHydratorCommitMessageTemplate(t *testing.T) {
104+
cm := test.NewFakeConfigMap()
105+
cm.Data["sourceHydrator.commitMessageTemplate"] = settings.CommitMessageTemplate
106+
cmBytes, _ := json.Marshal(cm)
107+
108+
data := fakeData{
109+
manifestResponse: &apiclient.ManifestResponse{
110+
Manifests: []string{string(cmBytes)},
111+
Namespace: test.FakeDestNamespace,
112+
Server: test.FakeClusterURL,
113+
Revision: "abc123",
114+
},
115+
configMapData: cm.Data,
116+
}
117+
118+
ctrl := newFakeControllerWithResync(&data, time.Minute, nil, errors.New("this should not be called"))
119+
120+
tmpl, err := ctrl.GetHydratorCommitMessageTemplate()
121+
require.NoError(t, err)
122+
assert.NotEmpty(t, tmpl)
123+
}

0 commit comments

Comments
 (0)