Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eventsources/bitbucketserver): add OneEventPerChange config option for webhook event handling #3135

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6,688 changes: 6,688 additions & 0 deletions api/event-source.html

Large diffs are not rendered by default.

6,830 changes: 6,830 additions & 0 deletions api/event-source.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion api/jsonschema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@
"type": "object"
},
"io.argoproj.events.v1alpha1.BitbucketBasicAuth": {
"description": "BasicAuth holds the information required to authenticate user via basic auth mechanism",
"description": "BitbucketBasicAuth holds the information required to authenticate user via basic auth mechanism",
"properties": {
"password": {
"$ref": "#/definitions/io.k8s.api.core.v1.SecretKeySelector",
Expand Down Expand Up @@ -685,6 +685,10 @@
"description": "Metadata holds the user defined metadata which will passed along the event payload.",
"type": "object"
},
"oneEventPerChange": {
"description": "OneEventPerChange controls whether to process each change in a repo:refs_changed webhook event as a separate event. This setting is useful when multiple tags are pushed simultaneously for the same commit, and each tag needs to independently trigger an action, such as a distinct workflow in Argo Workflows. When enabled, the BitbucketServerEventSource publishes an individual BitbucketServerEventData for each change, ensuring independent processing of each tag or reference update in a single webhook event.",
"type": "boolean"
},
"projectKey": {
"description": "DeprecatedProjectKey is the key of project for which integration needs to set up. Deprecated: use Repositories instead. Will be unsupported in v1.8.",
"type": "string"
Expand Down
6 changes: 5 additions & 1 deletion api/openapi-spec/swagger.json

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

32 changes: 30 additions & 2 deletions docs/APIs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2845,8 +2845,8 @@ BitbucketBasicAuth

<p>

BasicAuth holds the information required to authenticate user via basic
auth mechanism
BitbucketBasicAuth holds the information required to authenticate user
via basic auth mechanism
</p>

</p>
Expand Down Expand Up @@ -3437,13 +3437,40 @@ under review.

<td>

<code>oneEventPerChange</code></br> <em> bool </em>
</td>

<td>

<em>(Optional)</em>
<p>

OneEventPerChange controls whether to process each change in a
repo:refs_changed webhook event as a separate event. This setting is
useful when multiple tags are pushed simultaneously for the same commit,
and each tag needs to independently trigger an action, such as a
distinct workflow in Argo Workflows. When enabled, the
BitbucketServerEventSource publishes an individual
BitbucketServerEventData for each change, ensuring independent
processing of each tag or reference update in a single webhook event.
</p>

</td>

</tr>

<tr>

<td>

<code>accessToken</code></br> <em>
<a href="https://v1-18.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#secretkeyselector-v1-core">
Kubernetes core/v1.SecretKeySelector </a> </em>
</td>

<td>

<em>(Optional)</em>
<p>

AccessToken is reference to K8s secret which holds the bitbucket api
Expand All @@ -3465,6 +3492,7 @@ Kubernetes core/v1.SecretKeySelector </a> </em>

<td>

<em>(Optional)</em>
<p>

WebhookSecret is reference to K8s secret which holds the bitbucket
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ require (
go.uber.org/ratelimit v0.3.1
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.25.0
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
google.golang.org/api v0.181.0
google.golang.org/grpc v1.63.2
Expand Down Expand Up @@ -303,6 +302,7 @@ require (
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
Expand Down
31 changes: 21 additions & 10 deletions pkg/apis/events/v1alpha1/eventsource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ type BitbucketAuth struct {
OAuthToken *corev1.SecretKeySelector `json:"oauthToken,omitempty" protobuf:"bytes,2,opt,name=oauthToken"`
}

// BasicAuth holds the information required to authenticate user via basic auth mechanism
// BitbucketBasicAuth holds the information required to authenticate user via basic auth mechanism
type BitbucketBasicAuth struct {
// Username refers to the K8s secret that holds the username.
Username *corev1.SecretKeySelector `json:"username" protobuf:"bytes,1,name=username"`
Expand Down Expand Up @@ -1013,27 +1013,35 @@ type BitbucketServerEventSource struct {
// This helps in optimizing the event handling process by avoiding unnecessary triggers for branch reference changes that are already part of a pull request under review.
// +optional
SkipBranchRefsChangedOnOpenPR bool `json:"skipBranchRefsChangedOnOpenPR,omitempty" protobuf:"varint,7,opt,name=skipBranchRefsChangedOnOpenPR"`
// OneEventPerChange controls whether to process each change in a repo:refs_changed webhook event as a separate event. This setting is useful when multiple tags are
// pushed simultaneously for the same commit, and each tag needs to independently trigger an action, such as a distinct workflow in Argo Workflows. When enabled, the
// BitbucketServerEventSource publishes an individual BitbucketServerEventData for each change, ensuring independent processing of each tag or reference update in a
// single webhook event.
// +optional
OneEventPerChange bool `json:"oneEventPerChange,omitempty" protobuf:"varint,8,opt,name=oneEventPerChange"`
// AccessToken is reference to K8s secret which holds the bitbucket api access information.
AccessToken *corev1.SecretKeySelector `json:"accessToken,omitempty" protobuf:"bytes,8,opt,name=accessToken"`
// +optional
AccessToken *corev1.SecretKeySelector `json:"accessToken,omitempty" protobuf:"bytes,9,opt,name=accessToken"`
// WebhookSecret is reference to K8s secret which holds the bitbucket webhook secret (for HMAC validation).
WebhookSecret *corev1.SecretKeySelector `json:"webhookSecret,omitempty" protobuf:"bytes,9,opt,name=webhookSecret"`
// +optional
WebhookSecret *corev1.SecretKeySelector `json:"webhookSecret,omitempty" protobuf:"bytes,10,opt,name=webhookSecret"`
// BitbucketServerBaseURL is the base URL for API requests to a custom endpoint.
BitbucketServerBaseURL string `json:"bitbucketserverBaseURL" protobuf:"bytes,10,opt,name=bitbucketserverBaseURL"`
BitbucketServerBaseURL string `json:"bitbucketserverBaseURL" protobuf:"bytes,11,opt,name=bitbucketserverBaseURL"`
// DeleteHookOnFinish determines whether to delete the Bitbucket Server hook for the project once the event source is stopped.
// +optional
DeleteHookOnFinish bool `json:"deleteHookOnFinish,omitempty" protobuf:"varint,11,opt,name=deleteHookOnFinish"`
DeleteHookOnFinish bool `json:"deleteHookOnFinish,omitempty" protobuf:"varint,12,opt,name=deleteHookOnFinish"`
// Metadata holds the user defined metadata which will passed along the event payload.
// +optional
Metadata map[string]string `json:"metadata,omitempty" protobuf:"bytes,12,rep,name=metadata"`
Metadata map[string]string `json:"metadata,omitempty" protobuf:"bytes,13,rep,name=metadata"`
// Filter
// +optional
Filter *EventSourceFilter `json:"filter,omitempty" protobuf:"bytes,13,opt,name=filter"`
Filter *EventSourceFilter `json:"filter,omitempty" protobuf:"bytes,14,opt,name=filter"`
// TLS configuration for the bitbucketserver client.
// +optional
TLS *TLSConfig `json:"tls,omitempty" protobuf:"bytes,14,opt,name=tls"`
TLS *TLSConfig `json:"tls,omitempty" protobuf:"bytes,15,opt,name=tls"`
// CheckInterval is a duration in which to wait before checking that the webhooks exist, e.g. 1s, 30m, 2h... (defaults to 1m)
// +optional
CheckInterval string `json:"checkInterval" protobuf:"bytes,15,opt,name=checkInterval"`
CheckInterval string `json:"checkInterval" protobuf:"bytes,16,opt,name=checkInterval"`
}

type BitbucketServerRepository struct {
Expand All @@ -1044,7 +1052,10 @@ type BitbucketServerRepository struct {
}

func (b BitbucketServerEventSource) ShouldCreateWebhooks() bool {
return b.AccessToken != nil && b.Webhook != nil && b.Webhook.URL != ""
return b.AccessToken != nil &&
b.Webhook != nil &&
b.Webhook.URL != "" &&
(len(b.GetBitbucketServerRepositories()) > 0 || len(b.Projects) > 0)
}

func (b BitbucketServerEventSource) GetBitbucketServerRepositories() []BitbucketServerRepository {
Expand Down
91 changes: 91 additions & 0 deletions pkg/apis/events/v1alpha1/eventsource_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
)

func TestGetReplicas(t *testing.T) {
Expand Down Expand Up @@ -41,3 +42,93 @@ func TestGithubEventSourceGetRepositories(t *testing.T) {
}
assert.Equal(t, len(es.GetOwnedRepositories()), 2)
}

// TestShouldCreateWebhooks tests the ShouldCreateWebhooks function on the BitbucketServerEventSource type.
func TestShouldCreateWebhooks(t *testing.T) {
// Create a dummy SecretKeySelector for testing
dummySecretKeySelector := &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{
Name: "dummy-secret",
},
Key: "token",
}

tests := []struct {
name string
b BitbucketServerEventSource
want bool
}{
{
name: "Valid Webhooks - Both Projects and Repositories",
b: BitbucketServerEventSource{
AccessToken: dummySecretKeySelector,
Webhook: &WebhookContext{URL: "http://example.com"},
Projects: []string{"Project1"},
Repositories: []BitbucketServerRepository{{ProjectKey: "Proj1", RepositorySlug: "Repo1"}},
},
want: true,
},
{
name: "No AccessToken",
b: BitbucketServerEventSource{
Webhook: &WebhookContext{URL: "http://example.com"},
Projects: []string{"Project1"},
Repositories: []BitbucketServerRepository{{ProjectKey: "Proj1", RepositorySlug: "Repo1"}},
},
want: false,
},
{
name: "No Webhook",
b: BitbucketServerEventSource{
AccessToken: dummySecretKeySelector,
Projects: []string{"Project1"},
Repositories: []BitbucketServerRepository{{ProjectKey: "Proj1", RepositorySlug: "Repo1"}},
},
want: false,
},
{
name: "No URL",
b: BitbucketServerEventSource{
AccessToken: dummySecretKeySelector,
Webhook: &WebhookContext{},
Projects: []string{"Project1"},
Repositories: []BitbucketServerRepository{{ProjectKey: "Proj1", RepositorySlug: "Repo1"}},
},
want: false,
},
{
name: "No Projects or Repositories",
b: BitbucketServerEventSource{
AccessToken: dummySecretKeySelector,
Webhook: &WebhookContext{URL: "http://example.com"},
},
want: false,
},
{
name: "Valid with Only Repositories",
b: BitbucketServerEventSource{
AccessToken: dummySecretKeySelector,
Webhook: &WebhookContext{URL: "http://example.com"},
Repositories: []BitbucketServerRepository{{ProjectKey: "Proj1", RepositorySlug: "Repo1"}},
},
want: true,
},
{
name: "Valid with Only Projects",
b: BitbucketServerEventSource{
AccessToken: dummySecretKeySelector,
Webhook: &WebhookContext{URL: "http://example.com"},
Projects: []string{"Project1"},
},
want: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.b.ShouldCreateWebhooks(); got != tt.want {
t.Errorf("%s: ShouldCreateWebhooks() = %v, want %v", tt.name, got, tt.want)
}
})
}
}
Loading