diff --git a/internal/notifier/bitbucket.go b/internal/notifier/bitbucket.go index 09fbc61ac..89e23be89 100644 --- a/internal/notifier/bitbucket.go +++ b/internal/notifier/bitbucket.go @@ -109,9 +109,9 @@ func (b Bitbucket) Post(ctx context.Context, event eventv1.Event) error { return err } - name, desc := formatNameAndDescription(event) + // Use CommitStatus as Name (and its SHA-1 as Key) so commitStatusExpr is visible. + _, desc := formatNameAndDescription(event) id := b.CommitStatus - // key has a limitation of 40 characters in bitbucket api key := sha1String(id) cmo := &bitbucket.CommitsOptions{ @@ -122,7 +122,7 @@ func (b Bitbucket) Post(ctx context.Context, event eventv1.Event) error { cso := &bitbucket.CommitStatusOptions{ State: state, Key: key, - Name: name, + Name: id, Description: desc, Url: "https://bitbucket.org", } diff --git a/internal/notifier/bitbucket_test.go b/internal/notifier/bitbucket_test.go index a5c07e41c..45f5c4f91 100644 --- a/internal/notifier/bitbucket_test.go +++ b/internal/notifier/bitbucket_test.go @@ -17,9 +17,18 @@ limitations under the License. package notifier import ( + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" "testing" + eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" ) func TestNewBitbucketBasic(t *testing.T) { @@ -48,3 +57,59 @@ func TestNewBitbucketInvalidToken(t *testing.T) { _, err := NewBitbucket("kustomization/gitops-system/0c9c2e41", "https://bitbucket.org/foo/bar", "bar", nil) g.Expect(err).To(HaveOccurred()) } + +func TestBitbucket_Post_UsesCommitStatusAsName(t *testing.T) { + g := NewWithT(t) + + commitStatus := "custom/status/from-expr" + var gotName, gotKey string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/statuses/build/"): + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"error":{"message":"Not found"}}`)) + case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/statuses/build"): + body, err := io.ReadAll(r.Body) + g.Expect(err).ToNot(HaveOccurred()) + var payload map[string]any + g.Expect(json.Unmarshal(body, &payload)).To(Succeed()) + gotName, _ = payload["name"].(string) + gotKey, _ = payload["key"].(string) + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(`{}`)) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer ts.Close() + + b, err := NewBitbucket(commitStatus, "https://bitbucket.org/foo/bar", "foo:bar", nil) + g.Expect(err).ToNot(HaveOccurred()) + + apiURL, err := url.Parse(ts.URL + "/2.0") + g.Expect(err).ToNot(HaveOccurred()) + b.Client.SetApiBaseURL(*apiURL) + b.Client.HttpClient = ts.Client() + + event := eventv1.Event{ + Severity: eventv1.EventSeverityInfo, + InvolvedObject: corev1.ObjectReference{ + Kind: "Kustomization", + Name: "gitops-system", + }, + Metadata: map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:69b59063470310ebbd88a9156325322a124e55a3", + }, + Reason: "ApplySucceeded", + } + + err = b.Post(context.Background(), event) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(gotName).To(Equal(commitStatus)) + g.Expect(gotKey).To(Equal(sha1String(commitStatus))) + + // Regression guard: Name must not fall back to formatNameAndDescription. + formattedName, _ := formatNameAndDescription(event) + g.Expect(gotName).ToNot(Equal(formattedName)) +} diff --git a/internal/notifier/bitbucketserver.go b/internal/notifier/bitbucketserver.go index ee207eb8b..26db3130b 100644 --- a/internal/notifier/bitbucketserver.go +++ b/internal/notifier/bitbucketserver.go @@ -138,9 +138,11 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error { return fmt.Errorf("couldn't convert to bitbucket server state: %w", err) } - name, desc := formatNameAndDescription(event) - name = name + " [" + desc + "]" //Bitbucket server displays this data on browser. Thus adding description here. + // Use CommitStatus as Name (plus Description for the Bitbucket Server UI), matching + // Bitbucket Cloud / GitHub / GitLab / Azure DevOps so commitStatusExpr is visible. + _, desc := formatNameAndDescription(event) id := b.CommitStatus + name := id + " [" + desc + "]" // Bitbucket Server shows Name in the browser. // key has a limitation of 40 characters in bitbucket api key := sha1String(id) diff --git a/internal/notifier/bitbucketserver_test.go b/internal/notifier/bitbucketserver_test.go index ba386b724..5cafc9b9f 100644 --- a/internal/notifier/bitbucketserver_test.go +++ b/internal/notifier/bitbucketserver_test.go @@ -148,6 +148,46 @@ func TestNewBitbucketServerEmptyCommitStatus(t *testing.T) { g.Expect(err.Error()).To(Equal("commit status cannot be empty")) } +func TestBitbucketServer_Post_UsesCommitStatusAsName(t *testing.T) { + g := NewWithT(t) + + commitStatus := "custom/status/from-expr" + var gotName, gotKey string + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + w.WriteHeader(http.StatusNotFound) + case http.MethodPost: + body, err := io.ReadAll(r.Body) + g.Expect(err).ToNot(HaveOccurred()) + var payload bbServerBuildStatusSetRequest + g.Expect(json.Unmarshal(body, &payload)).To(Succeed()) + gotName = payload.Name + gotKey = payload.Key + w.WriteHeader(http.StatusNoContent) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + defer ts.Close() + + b, err := NewBitbucketServer(commitStatus, ts.URL+"/scm/projectfoo/repobar.git", "token", nil, "", "") + g.Expect(err).ToNot(HaveOccurred()) + + event := generateTestEventKustomization("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }) + g.Expect(b.Post(context.Background(), event)).To(Succeed()) + + _, desc := formatNameAndDescription(event) + g.Expect(gotName).To(Equal(commitStatus + " [" + desc + "]")) + g.Expect(gotKey).To(Equal(sha1String(commitStatus))) + + formattedName, _ := formatNameAndDescription(event) + g.Expect(gotName).ToNot(Equal(formattedName + " [" + desc + "]")) +} + func TestPostBitbucketServerBadCommitHash(t *testing.T) { g := NewWithT(t) b, err := NewBitbucketServer("kustomization/gitops-system/0c9c2e41", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "") @@ -368,8 +408,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { if tt.name == "Validate duplicate commit status successful match" { w.WriteHeader(http.StatusOK) w.Header().Add("Content-Type", "application/json") - name, desc := formatNameAndDescription(tt.event) - name = name + " [" + desc + "]" + _, desc := formatNameAndDescription(tt.event) + name := tt.commitStatus + " [" + desc + "]" jsondata, _ := json.Marshal(&bbServerBuildStatus{ Name: name, Description: desc, @@ -439,8 +479,8 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { // Validate description g.Expect(payload.Description).To(Equal("reason")) - // Validate name(with description appended) - g.Expect(payload.Name).To(Equal("kustomization/hello-world" + " [" + payload.Description + "]")) + // Name must use CommitStatus (commitStatusExpr), not formatNameAndDescription. + g.Expect(payload.Name).To(Equal(tt.commitStatus + " [" + payload.Description + "]")) g.Expect(payload.Url).To(ContainSubstring("/scm/projectfoo/repobar.git"))