diff --git a/go.mod b/go.mod index 798560783..c29961e39 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/chainguard-dev/git-urls v1.0.2 github.com/containrrr/shoutrrr v0.8.0 github.com/fluxcd/cli-utils v0.36.0-flux.2 + github.com/fluxcd/kustomize-controller/api v1.2.1 github.com/fluxcd/notification-controller/api v1.2.0 github.com/fluxcd/pkg/apis/event v0.6.0 github.com/fluxcd/pkg/apis/meta v1.2.0 @@ -82,6 +83,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.16.0 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect + github.com/fluxcd/pkg/apis/kustomize v1.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-fed/httpsig v1.1.0 // indirect diff --git a/go.sum b/go.sum index 0360323d6..fd7a887cb 100644 --- a/go.sum +++ b/go.sum @@ -753,10 +753,14 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fluxcd/cli-utils v0.36.0-flux.2 h1:7nlXfAJ7iaDF34IdbyId+wBf7beL2qvzDBLmVBJSDVo= github.com/fluxcd/cli-utils v0.36.0-flux.2/go.mod h1:TQtgRf9OjQBzE5FJ9UDV6WNz9Po3pzAtk3NQmQEN5l8= +github.com/fluxcd/kustomize-controller/api v1.2.1 h1:+WgQOU7jpqz9bA4djPWmaeYAp9cG7c/TdcIYku3Jrzk= +github.com/fluxcd/kustomize-controller/api v1.2.1/go.mod h1:0Kgc4uYnr5jCm4H8JwArkR0v4WTmXeX/9KgoDbxluVc= github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= github.com/fluxcd/pkg/apis/event v0.6.0 h1:AUaeee1CGWb65BLqVximHXG8Gcu6vWuYONIq6tVpjgo= github.com/fluxcd/pkg/apis/event v0.6.0/go.mod h1:OEzWcX/oPbMmkCvC9QGoK27JXFvUZgBhLD+zgxZe47A= +github.com/fluxcd/pkg/apis/kustomize v1.2.0 h1:vkVs+OumxaWso0jNCqdgFFfMHdh+qtZhykTkjl7OgmA= +github.com/fluxcd/pkg/apis/kustomize v1.2.0/go.mod h1:VF7tR/WuVFeum+HaMTHwp+eCtsHiiQlY6ihgqtAnW/M= github.com/fluxcd/pkg/apis/meta v1.2.0 h1:O766PzGAdMdQKybSflGL8oV0+GgCNIkdsxfalRyzeO8= github.com/fluxcd/pkg/apis/meta v1.2.0/go.mod h1:fU/Az9AoVyIxC0oI4ihG0NVMNnvrcCzdEym3wxjIQsc= github.com/fluxcd/pkg/git v0.16.0 h1:xgfMpgsVaxGLechKNaSUif9jnt2Ji/HkwIwxXeDoADk= @@ -1085,8 +1089,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= diff --git a/internal/notifier/bitbucketserver.go b/internal/notifier/bitbucketserver.go index 5ccf1116b..82c442b2e 100644 --- a/internal/notifier/bitbucketserver.go +++ b/internal/notifier/bitbucketserver.go @@ -30,6 +30,7 @@ import ( "strings" "time" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/apis/meta" "github.com/hashicorp/go-retryablehttp" @@ -128,10 +129,6 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool // Post Bitbucket Server build status func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error { - // Skip progressing events - if event.HasReason(meta.ProgressingReason) { - return nil - } revString, ok := event.Metadata[eventv1.MetaRevisionKey] if !ok { return errors.New("missing revision metadata") @@ -140,7 +137,7 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error { if err != nil { return fmt.Errorf("could not parse revision: %w", err) } - state, err := b.state(event.Severity) + state, err := b.state(event) if err != nil { return fmt.Errorf("couldn't convert to bitbucket server state: %w", err) } @@ -167,15 +164,20 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error { return nil } -func (b BitbucketServer) state(severity string) (string, error) { - switch severity { - case eventv1.EventSeverityInfo: - return "SUCCESSFUL", nil - case eventv1.EventSeverityError: +func (b BitbucketServer) state(event eventv1.Event) (string, error) { + if event.Severity == eventv1.EventSeverityError || event.Reason == kustomizev1.PruneFailedReason || event.Reason == kustomizev1.ArtifactFailedReason || event.Reason == kustomizev1.BuildFailedReason || event.Reason == kustomizev1.HealthCheckFailedReason || event.Reason == kustomizev1.ReconciliationFailedReason { return "FAILED", nil - default: - return "", errors.New("bitbucket server state generated on info or error events only") } + if event.Reason == kustomizev1.DependencyNotReadyReason { + return "UNKNOWN", nil + } + if event.Reason == meta.ProgressingReason { + return "INPROGRESS", nil + } + if event.Severity == eventv1.EventSeverityInfo { + return "SUCCESSFUL", nil + } + return "", errors.New("bitbucket server state could not be generated for this event") } func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev, state, name, desc, id, key, u string) (bool, error) { @@ -192,10 +194,10 @@ func (b BitbucketServer) duplicateBitbucketServerStatus(ctx context.Context, rev // Make a GET call d, err := b.Client.Do(req) - if err != nil && d.StatusCode != http.StatusNotFound { + if err != nil { return false, fmt.Errorf("failed api call to check duplicate commit status: %w", err) } - if isError(d) && d.StatusCode != http.StatusNotFound { + if d != nil && isError(d) && d.StatusCode != http.StatusNotFound { defer d.Body.Close() return false, fmt.Errorf("failed api call to check duplicate commit status: %d - %s", d.StatusCode, http.StatusText(d.StatusCode)) } diff --git a/internal/notifier/bitbucketserver_test.go b/internal/notifier/bitbucketserver_test.go index fa8207e18..20fc1ad75 100644 --- a/internal/notifier/bitbucketserver_test.go +++ b/internal/notifier/bitbucketserver_test.go @@ -26,7 +26,9 @@ import ( "net/http" "net/http/httptest" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" + "github.com/fluxcd/pkg/apis/meta" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -63,9 +65,11 @@ func TestPostBitbucketServerMissingRevision(t *testing.T) { assert.Nil(t, err) //Validate missing revision - err = b.Post(context.TODO(), generateTestEventKustomization("info", map[string]string{ + err = b.Post(context.TODO(), generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ "dummybadrevision": "bad", - })) + }, + kustomizev1.ReconciliationSucceededReason, + )) assert.NotNil(t, err) assert.Equal(t, err.Error(), "missing revision metadata") } @@ -75,9 +79,11 @@ func TestPostBitbucketServerBadCommitHash(t *testing.T) { assert.Nil(t, err) //Validate extract commit hash - err = b.Post(context.TODO(), generateTestEventKustomization("info", map[string]string{ + err = b.Post(context.TODO(), generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "badhash", - })) + }, + kustomizev1.ReconciliationSucceededReason, + )) assert.NotNil(t, err) assert.Equal(t, err.Error(), "could not parse revision: failed to extract commit hash from 'badhash' revision") @@ -90,13 +96,15 @@ func TestPostBitbucketServerBadBitbucketState(t *testing.T) { //Validate conversion to bitbucket state err = b.Post(context.TODO(), generateTestEventKustomization("badserveritystate", map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - })) + }, + kustomizev1.ReconciliationSucceededReason, + )) assert.NotNil(t, err) - assert.Equal(t, err.Error(), "couldn't convert to bitbucket server state: bitbucket server state generated on info or error events only") + assert.Equal(t, err.Error(), "couldn't convert to bitbucket server state: bitbucket server state could not be generated for this event") } -func generateTestEventKustomization(severity string, metadata map[string]string) eventv1.Event { +func generateTestEventKustomization(severity string, metadata map[string]string, reason string) eventv1.Event { return eventv1.Event{ InvolvedObject: corev1.ObjectReference{ Kind: "Kustomization", @@ -106,7 +114,7 @@ func generateTestEventKustomization(severity string, metadata map[string]string) Severity: severity, Timestamp: metav1.Now(), Message: "message", - Reason: "reason", + Reason: reason, Metadata: metadata, ReportingController: "kustomize-controller", ReportingInstance: "kustomize-controller-xyz", @@ -135,12 +143,15 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("info", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), - key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{ + }, + kustomizev1.ReconciliationSucceededReason), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + kustomizev1.ReconciliationSucceededReason, + ))), }, { name: "Validate Basic Auth and Post State=Successful", @@ -152,12 +163,16 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("info", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), - key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{ + }, + kustomizev1.ReconciliationSucceededReason, + ), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + kustomizev1.ReconciliationSucceededReason, + ))), }, { name: "Validate Post State=Failed", @@ -169,12 +184,15 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("error", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), - key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{ + }, + kustomizev1.ReconciliationFailedReason), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + kustomizev1.ReconciliationFailedReason, + ))), }, { name: "Fail if bad json response in existing commit status", @@ -188,12 +206,16 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("error", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), - key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{ + }, + kustomizev1.ReconciliationFailedReason, + ), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + kustomizev1.ReconciliationFailedReason, + ))), }, { name: "Fail if status code is non-200 in existing commit status", @@ -207,12 +229,16 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("error", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), - key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{ + }, + kustomizev1.ReconciliationFailedReason, + ), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + kustomizev1.ReconciliationFailedReason, + ))), }, { name: "Bad post- Unauthorized", @@ -226,12 +252,16 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("error", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityError, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), + }, + kustomizev1.ReconciliationFailedReason, + ), key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + kustomizev1.ReconciliationFailedReason, + ))), }, { name: "Validate duplicate commit status successful match", @@ -243,12 +273,58 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { "x-atlassian-token": "no-check", "x-requested-with": "XMLHttpRequest", }, - event: generateTestEventKustomization("info", map[string]string{ + event: generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }, + kustomizev1.ReconciliationSucceededReason, + ), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }, + kustomizev1.ReconciliationSucceededReason, + ))), + }, + { + name: "Validate payload state INPROGRESS - for 'reconciliation in progress' builds", + username: "hello", + password: "password", + provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", + headers: map[string]string{ + "Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")), + "x-atlassian-token": "no-check", + "x-requested-with": "XMLHttpRequest", + }, + event: generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }), - key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{ + }, + meta.ProgressingReason, + ), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", - }))), + }, + meta.ProgressingReason, + ))), + }, + { + name: "Validate payload state UNKNOWN - for 'dependency not ready' builds", + username: "hello", + password: "password", + provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", + headers: map[string]string{ + "Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte("hello"+":"+"password")), + "x-atlassian-token": "no-check", + "x-requested-with": "XMLHttpRequest", + }, + event: generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }, + kustomizev1.DependencyNotReadyReason, + ), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization(eventv1.EventSeverityInfo, map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }, + kustomizev1.DependencyNotReadyReason, + ))), }, } @@ -329,23 +405,42 @@ func TestBitBucketServerPostValidateRequest(t *testing.T) { // Validate Key require.Equal(t, payload.Key, tt.key) - // Validate that state can be only SUCCESSFUL or FAILED - if payload.State != "SUCCESSFUL" && payload.State != "FAILED" { + // Validate that state can be only SUCCESSFUL, FAILED, INPROGRESS, UNKNOWN + if payload.State != "SUCCESSFUL" && payload.State != "FAILED" && payload.State != "INPROGRESS" && payload.State != "UNKNOWN" { require.Fail(t, "Invalid state") } + stateChecked := 0 - // If severity of event is info, state should be SUCCESSFUL - if tt.event.Severity == "info" { - require.Equal(t, "SUCCESSFUL", payload.State) + if stateChecked == 0 && (tt.event.Severity == eventv1.EventSeverityError || tt.event.Reason == kustomizev1.PruneFailedReason || tt.event.Reason == kustomizev1.ArtifactFailedReason || tt.event.Reason == kustomizev1.BuildFailedReason || tt.event.Reason == kustomizev1.HealthCheckFailedReason || tt.event.Reason == kustomizev1.ReconciliationFailedReason) { + require.Equal(t, "FAILED", payload.State) + require.Equal(t, "reconciliation failed", payload.Description) + stateChecked = 1 } - // If severity of event is error, state should be FAILED - if tt.event.Severity == "error" { - require.Equal(t, "FAILED", payload.State) + if stateChecked == 0 && tt.event.Reason == kustomizev1.DependencyNotReadyReason { + require.Equal(t, "UNKNOWN", payload.State) + require.Equal(t, "dependency not ready", payload.Description) + stateChecked = 1 } + if stateChecked == 0 && tt.event.Reason == meta.ProgressingReason { + require.Equal(t, "INPROGRESS", payload.State) + require.Equal(t, "progressing", payload.Description) + stateChecked = 1 + } + + if stateChecked == 0 && tt.event.Severity == "info" { + require.Equal(t, "SUCCESSFUL", payload.State) + require.Equal(t, "reconciliation succeeded", payload.Description) + } + + //if tt.event.Severity == "error" { + // require.Equal(t, "FAILED", payload.State) + // require.Equal(t, "reconciliation failed", payload.Description) + //} + // Validate description - require.Equal(t, "reason", payload.Description) + //require.Equal(t, "abc", payload.Description) // Validate name(with description appended) require.Equal(t, "kustomization/hello-world"+" ["+payload.Description+"]", payload.Name)