From b633a6508ada4ac091b529987350542502385491 Mon Sep 17 00:00:00 2001 From: gdasson Date: Fri, 3 Nov 2023 11:55:48 -0500 Subject: [PATCH] Incorporated review comments. Added tests, fixed docs, utilize resty/http library for HTTP calls for BBServer --- api/v1beta1/provider_types.go | 45 ++-- ...ification.toolkit.fluxcd.io_providers.yaml | 1 - docs/spec/v1beta2/providers.md | 35 +-- go.mod | 18 +- go.sum | 56 +++-- internal/notifier/bitbucketserver.go | 201 +++++++++------ internal/notifier/bitbucketserver_test.go | 238 +++++++++++++++++- internal/notifier/util.go | 17 -- 8 files changed, 431 insertions(+), 180 deletions(-) diff --git a/api/v1beta1/provider_types.go b/api/v1beta1/provider_types.go index 72fea2b52..e3622fed8 100644 --- a/api/v1beta1/provider_types.go +++ b/api/v1beta1/provider_types.go @@ -30,7 +30,7 @@ const ( // ProviderSpec defines the desired state of Provider type ProviderSpec struct { // Type of provider - // +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;bitbucketserver;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch; + // +kubebuilder:validation:Enum=slack;discord;msteams;rocket;generic;generic-hmac;github;gitlab;bitbucket;azuredevops;googlechat;webex;sentry;azureeventhub;telegram;lark;matrix;opsgenie;alertmanager;grafana;githubdispatch; // +required Type string `json:"type"` @@ -77,28 +77,27 @@ type ProviderSpec struct { } const ( - GenericProvider string = "generic" - GenericHMACProvider string = "generic-hmac" - SlackProvider string = "slack" - GrafanaProvider string = "grafana" - DiscordProvider string = "discord" - MSTeamsProvider string = "msteams" - RocketProvider string = "rocket" - GitHubDispatchProvider string = "githubdispatch" - GitHubProvider string = "github" - GitLabProvider string = "gitlab" - BitbucketServerProvider string = "bitbucketserver" - BitbucketProvider string = "bitbucket" - AzureDevOpsProvider string = "azuredevops" - GoogleChatProvider string = "googlechat" - WebexProvider string = "webex" - SentryProvider string = "sentry" - AzureEventHubProvider string = "azureeventhub" - TelegramProvider string = "telegram" - LarkProvider string = "lark" - Matrix string = "matrix" - OpsgenieProvider string = "opsgenie" - AlertManagerProvider string = "alertmanager" + GenericProvider string = "generic" + GenericHMACProvider string = "generic-hmac" + SlackProvider string = "slack" + GrafanaProvider string = "grafana" + DiscordProvider string = "discord" + MSTeamsProvider string = "msteams" + RocketProvider string = "rocket" + GitHubDispatchProvider string = "githubdispatch" + GitHubProvider string = "github" + GitLabProvider string = "gitlab" + BitbucketProvider string = "bitbucket" + AzureDevOpsProvider string = "azuredevops" + GoogleChatProvider string = "googlechat" + WebexProvider string = "webex" + SentryProvider string = "sentry" + AzureEventHubProvider string = "azureeventhub" + TelegramProvider string = "telegram" + LarkProvider string = "lark" + Matrix string = "matrix" + OpsgenieProvider string = "opsgenie" + AlertManagerProvider string = "alertmanager" ) // ProviderStatus defines the observed state of Provider diff --git a/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml b/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml index e2e14cb0d..032076c16 100644 --- a/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml +++ b/config/crd/bases/notification.toolkit.fluxcd.io_providers.yaml @@ -94,7 +94,6 @@ spec: - generic-hmac - github - gitlab - - bitbucketserver - bitbucket - azuredevops - googlechat diff --git a/docs/spec/v1beta2/providers.md b/docs/spec/v1beta2/providers.md index 054d5e9c3..71ef83019 100644 --- a/docs/spec/v1beta2/providers.md +++ b/docs/spec/v1beta2/providers.md @@ -1514,44 +1514,21 @@ You can create the secret with `kubectl` like this: kubectl create secret generic bitbucket-token --from-literal=token=: ``` -#### BitBucketServer +#### BitBucketServer/Data Center -For bitbucketserver (a.k.a. Bitbucket Data Center), the following auth methods are available:
a) Basic Auth(username/password) -
+When `.spec.type` is set to `bitbucket`, the following auth methods are available:
+a) Basic Auth(username/password)
b) [HTTP access tokens](https://confluence.atlassian.com/bitbucketserver/http-access-tokens-939515499.html) -For Basic Auth(username/password), the secret should be created in following format: +For Basic Authentication, the referenced secret must contain a `username` and a `password` field -``` -apiVersion: v1 -data: - password: Qml0YnVja2V0QDIwMjM= - username: Zm9vYmFydXNlcg== -kind: Secret -metadata: - name: bb-server-username-password -type: Opaque -``` - -You may create the secret using this command as well: +You can create the secret with `kubectl` like this: ```shell kubectl create secret generic bb-server-username-password --from-literal=username= --from-literal=password= ``` -For HTTP access tokens, the secret should be created in following format: - -``` -apiVersion: v1 -data: - token: QkJEQy1PREl4T0RZeE16SXlOelV5T3R0b3JNak8wNTlQMnJZVGI2RUg3bVBPTTVUbw== -kind: Secret -metadata: - name: bb-server-token -type: Opaque -``` - -You may create the secret using this command as well: +For HTTP access tokens, the secret can be created like this: ```shell kubectl create secret generic bb-server-token --from-literal=token= diff --git a/go.mod b/go.mod index 0eda9ebea..b3f0e2e1e 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,12 @@ replace github.com/fluxcd/notification-controller/api => ./api require ( cloud.google.com/go/pubsub v1.33.0 - code.gitea.io/sdk/gitea v0.15.1 + code.gitea.io/sdk/gitea v0.16.0 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 github.com/Azure/azure-amqp-common-go/v4 v4.2.0 github.com/Azure/azure-event-hubs-go/v3 v3.6.1 github.com/DataDog/datadog-api-client-go/v2 v2.15.0 github.com/PagerDuty/go-pagerduty v1.7.0 - github.com/antihax/optional v1.0.0 github.com/containrrr/shoutrrr v0.8.0 github.com/fluxcd/notification-controller/api v1.1.0 github.com/fluxcd/pkg/apis/event v0.5.2 @@ -21,20 +20,20 @@ require ( github.com/fluxcd/pkg/masktoken v0.2.0 github.com/fluxcd/pkg/runtime v0.42.0 github.com/fluxcd/pkg/ssa v0.32.0 - github.com/gdasson/bitbucketv1go v1.0.0 github.com/getsentry/sentry-go v0.23.0 github.com/go-logr/logr v1.2.4 + github.com/go-resty/resty/v2 v2.10.0 github.com/google/go-github/v53 v53.2.0 github.com/hashicorp/go-retryablehttp v0.7.4 - github.com/ktrysmt/go-bitbucket v0.9.66 + github.com/ktrysmt/go-bitbucket v0.9.68 github.com/microsoft/azure-devops-go-api/azuredevops/v6 v6.0.1 - github.com/onsi/gomega v1.27.10 + github.com/onsi/gomega v1.28.0 github.com/sethvargo/go-limiter v0.7.2 github.com/slok/go-http-metrics v0.10.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 github.com/whilp/git-urls v1.0.0 - github.com/xanzy/go-gitlab v0.90.0 + github.com/xanzy/go-gitlab v0.93.1 golang.org/x/oauth2 v0.13.0 golang.org/x/text v0.13.0 google.golang.org/api v0.138.0 @@ -68,13 +67,14 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/devigned/tab v0.1.1 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect @@ -84,6 +84,7 @@ require ( github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-errors/errors v1.4.2 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-logr/zapr v1.2.4 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -137,13 +138,14 @@ require ( go.opencensus.io v0.24.0 // indirect go.starlark.net v0.0.0-20230302034142-4b1e35fe2254 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.25.0 // indirect + go.uber.org/zap v1.26.0 // indirect golang.org/x/crypto v0.14.0 // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/term v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.13.0 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect diff --git a/go.sum b/go.sum index 9aca93dc8..821580cfe 100644 --- a/go.sum +++ b/go.sum @@ -604,9 +604,8 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M= cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA= cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= -code.gitea.io/gitea-vet v0.2.1/go.mod h1:zcNbT/aJEmivCAhfmkHOlT645KNOf9W2KnkLgFjGGfE= -code.gitea.io/sdk/gitea v0.15.1 h1:WJreC7YYuxbn0UDaPuWIe/mtiNKTvLN8MLkaw71yx/M= -code.gitea.io/sdk/gitea v0.15.1/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMVoHRBA= +code.gitea.io/sdk/gitea v0.16.0 h1:gAfssETO1Hv9QbE+/nhWu7EjoFQYKt6kPoyDytQgw00= +code.gitea.io/sdk/gitea v0.16.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= @@ -656,21 +655,19 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PagerDuty/go-pagerduty v1.7.0 h1:S1NcMKECxT5hJwV4VT+QzeSsSiv4oWl1s2821dUqG/8= github.com/PagerDuty/go-pagerduty v1.7.0/go.mod h1:PuFyJKRz1liIAH4h5KVXVD18Obpp1ZXRdxHvmGXooro= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= -github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -715,6 +712,8 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= @@ -765,13 +764,13 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/gdasson/bitbucketv1go v1.0.0 h1:sRiO7XgqY4WaolHruzPufPLRODfSUGUVkWLDHy4Ba5M= -github.com/gdasson/bitbucketv1go v1.0.0/go.mod h1:VKmMS4gxRLRBKoyzNVYc7P+crDC7/UQ97+mVc6OqE34= github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE= github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= @@ -795,6 +794,8 @@ github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= +github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo= +github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -943,7 +944,7 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -986,8 +987,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/ktrysmt/go-bitbucket v0.9.66 h1:nRKRY0XvV/VkY2JemejtJA49dcPdzYAt24LzmSWtO+U= -github.com/ktrysmt/go-bitbucket v0.9.66/go.mod h1:Qqa5bm6HwkLOViah8VIMxv1dj/hH1733nqmR8Q5ZHMs= +github.com/ktrysmt/go-bitbucket v0.9.68 h1:UZZTfuFtH7/ZYTgPmqDBJHtifwuD3WZrk0GD6P1H4pA= +github.com/ktrysmt/go-bitbucket v0.9.68/go.mod h1:XLuRkR9Tb2lkZRbgMRQz9t3fKHYBbO1y4sZ0wA3hs5A= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= @@ -1027,9 +1028,9 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/ginkgo/v2 v2.12.0 h1:UIVDowFPwpg6yMUpPjGkYvf06K3RAiJXUhCxEwQVHRI= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= @@ -1099,8 +1100,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU= github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE= -github.com/xanzy/go-gitlab v0.90.0 h1:j8ZUHfLfXdnC+B8njeNaW/kM44c1zw8fiuNj7D+qQN8= -github.com/xanzy/go-gitlab v0.90.0/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= +github.com/xanzy/go-gitlab v0.93.1 h1:f7J33cw/P9b/8paIOoH0F3H+TFrswvWHs6yUgoTp9LY= +github.com/xanzy/go-gitlab v0.93.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= @@ -1136,8 +1137,8 @@ go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9i go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= -go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1145,16 +1146,18 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1275,7 +1278,7 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1308,7 +1311,7 @@ golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= -golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1411,7 +1414,7 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1425,7 +1428,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1444,7 +1447,6 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1489,7 +1491,6 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1518,7 +1519,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/notifier/bitbucketserver.go b/internal/notifier/bitbucketserver.go index eaed63456..64b09c979 100644 --- a/internal/notifier/bitbucketserver.go +++ b/internal/notifier/bitbucketserver.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2023 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -20,46 +20,67 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/json" "errors" "fmt" "net/http" "strings" - "github.com/antihax/optional" eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/fluxcd/pkg/apis/meta" - "github.com/gdasson/bitbucketv1go" + "github.com/go-resty/resty/v2" + giturls "github.com/whilp/git-urls" ) -// Bitbucket is a Bitbucket Server notifier. +// BitbucketServer is a notifier for BitBucket Server and Data Center. type BitbucketServer struct { ProjectKey string RepositorySlug string ProviderUID string ProviderAddress string - Credentials *bitbucketv1go.Credentials - Client *bitbucketv1go.APIClient + Host string + Client *resty.Client } -// NewBitbucket creates and returns a new Bitbucket notifier. +const ( + BuildEndPoint = "/rest/api/latest/projects/{projectKey}/repos/{repositorySlug}/commits/{commitId}/builds" + GetBuildStatusQueryString = "key" +) + +type RestBuildStatus struct { + Name string `json:"name,omitempty"` + Key string `json:"key,omitempty"` + Parent string `json:"parent,omitempty"` + State string `json:"state,omitempty"` + Ref string `json:"ref,omitempty"` + BuildNumber string `json:"buildNumber,omitempty"` + Description string `json:"description,omitempty"` + Duration int64 `json:"duration,omitempty"` + UpdatedDate int64 `json:"updatedDate,omitempty"` + CreatedDate int64 `json:"createdDate,omitempty"` + Url string `json:"url,omitempty"` +} + +type RestBuildStatusSetRequest struct { + BuildNumber string `json:"buildNumber,omitempty"` + Description string `json:"description,omitempty"` + Duration int64 `json:"duration,omitempty"` + Key string `json:"key"` + LastUpdated int64 `json:"lastUpdated,omitempty"` + Name string `json:"name,omitempty"` + Parent string `json:"parent,omitempty"` + Ref string `json:"ref,omitempty"` + State string `json:"state"` + Url string `json:"url"` +} + +// NewBitbucketServer creates and returns a new NewBitbucketServer notifier. func NewBitbucketServer(providerUID string, addr string, token string, certPool *x509.CertPool, username string, password string) (*BitbucketServer, error) { hst, id, err := parseBitbucketServerGitAddress(addr) if err != nil { return nil, err } - c := &bitbucketv1go.Credentials{} - if len(token) > 0 { - c.RestBearerTokenCredentials.Token = token - } - if len(username) > 0 && len(password) > 0 { - c.RestUsernamePasswordCredentials.Username = username - c.RestUsernamePasswordCredentials.Password = password - } - if len(token) == 0 && (len(username) == 0 || len(password) == 0) { - return nil, errors.New("invalid credentials, expected to be one of username,password or APIToken") - } - comp := strings.Split(id, "/") if len(comp) != 2 { return nil, fmt.Errorf("invalid repository id %q", id) @@ -67,50 +88,51 @@ func NewBitbucketServer(providerUID string, addr string, token string, certPool projectkey := comp[0] reposlug := comp[1] - bitbucketConfig := bitbucketv1go.NewConfiguration() - bitbucketConfig.BasePath = hst + "/rest" - bitbucketConfig.AddDefaultHeader("x-atlassian-token", "no-check") - bitbucketConfig.AddDefaultHeader("x-requested-with", "XMLHttpRequest") - if certPool != nil { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: certPool, - }, - } - hc := &http.Client{Transport: tr} - bitbucketConfig.HTTPClient = hc + bitbucketClient := resty.New() + if len(token) == 0 && (len(username) == 0 || len(password) == 0) { + return nil, errors.New("invalid credentials, expected to be one of username/password or API Token") + } + if len(token) > 0 { + bitbucketClient.SetAuthToken(token) + } else if len(username) > 0 && len(password) > 0 { + bitbucketClient.SetBasicAuth(username, password) } - bitbucketClient := bitbucketv1go.NewAPIClient(bitbucketConfig) + bitbucketClient.SetHeader("x-atlassian-token", "no-check") + bitbucketClient.SetHeader("x-requested-with", "XMLHttpRequest") + if certPool != nil { + bitbucketClient.SetTLSClientConfig(&tls.Config{ + RootCAs: certPool, + }) + } return &BitbucketServer{ ProjectKey: projectkey, RepositorySlug: reposlug, ProviderUID: providerUID, - Credentials: c, + Host: hst, ProviderAddress: addr, Client: bitbucketClient, }, nil } -// Post Bitbucket build status +// 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") } rev, err := parseRevision(revString) if err != nil { - return err + return fmt.Errorf("Could not parse revision: %v", err) } state, err := toBitbucketServerState(event.Severity) if err != nil { - return err + return fmt.Errorf("couldn't convert to bitbucket server state: %v", err) } name, desc := formatNameAndDescription(event) @@ -119,43 +141,18 @@ func (b BitbucketServer) Post(ctx context.Context, event eventv1.Event) error { // key has a limitation of 40 characters in bitbucket api key := sha1String(id) - if len(b.Credentials.RestUsernamePasswordCredentials.Username) > 0 && len(b.Credentials.RestUsernamePasswordCredentials.Password) > 0 { - ctx = context.WithValue(ctx, bitbucketv1go.ContextBasicAuth, bitbucketv1go.BasicAuth{ - UserName: b.Credentials.RestUsernamePasswordCredentials.Username, - Password: b.Credentials.RestUsernamePasswordCredentials.Password, - }) - } - if len(b.Credentials.RestBearerTokenCredentials.Token) > 0 { - ctx = context.WithValue(ctx, bitbucketv1go.ContextAccessToken, b.Credentials.RestBearerTokenCredentials.Token) - } - - existingCommitStatus, httpResponse, err := b.Client.BuildsAndDeploymentsApi.Get(ctx, b.ProjectKey, rev, b.RepositorySlug, - &bitbucketv1go.BuildsAndDeploymentsApiGetOpts{ - Key: optional.NewString(key), - }) - if err != nil && httpResponse.StatusCode != 404 { - return fmt.Errorf("could not get commit status: %v", err) + dupe, err := checkDuplicateCommitStatus(ctx, b, rev, state, name, desc, id, key) + if err != nil { + return fmt.Errorf("could not get existing commit status: %v", err) } - if httpResponse.StatusCode == 200 { - // Do not post duplicate build status - if existingCommitStatus.Key == key && existingCommitStatus.State == state && existingCommitStatus.Description == desc && existingCommitStatus.Name == name { - return nil + if dupe == false { + _, err = postBuildStatus(ctx, b, rev, state, name, desc, id, key) + if err != nil { + return fmt.Errorf("could not post build status: %v", err) } } - _, err = b.Client.BuildsAndDeploymentsApi.Add(ctx, b.ProjectKey, rev, b.RepositorySlug, - &bitbucketv1go.BuildsAndDeploymentsApiAddOpts{ - Body: optional.NewInterface(bitbucketv1go.RestBuildStatusSetRequest{ - Key: key, - State: state, - Url: b.ProviderAddress, - Description: desc, - Name: name, - })}) - if err != nil { - return fmt.Errorf("could not post build status: %v", err) - } return nil } @@ -166,6 +163,70 @@ func toBitbucketServerState(severity string) (string, error) { case eventv1.EventSeverityError: return "FAILED", nil default: - return "", errors.New("can't convert to bitbucket server state") + return "", errors.New("Bitbucket server state generated on info or error events only") + } +} + +func checkDuplicateCommitStatus(ctx context.Context, b BitbucketServer, rev, state, name, desc, id, key string) (bool, error) { + d, err := b.Client.R(). + SetContext(ctx). + SetQueryParam(GetBuildStatusQueryString, key). + Get(createApiPath(b, rev)) + if err != nil && d.StatusCode() != http.StatusNotFound { + return false, err + } + + if d.StatusCode() == http.StatusOK { + var existingCommitStatus RestBuildStatus + json.Unmarshal(d.Body(), &existingCommitStatus) + // Do not post duplicate build status + if existingCommitStatus.Key == key && existingCommitStatus.State == state && existingCommitStatus.Description == desc && existingCommitStatus.Name == name { + return true, nil + } + } + return false, nil +} + +func postBuildStatus(ctx context.Context, b BitbucketServer, rev, state, name, desc, id, key string) (*resty.Response, error) { + r, err := b.Client.R(). + SetContext(ctx). + SetBody(RestBuildStatusSetRequest{ + Key: key, + State: state, + Url: b.ProviderAddress, + Description: desc, + Name: name, + }). + Post(createApiPath(b, rev)) + if err != nil { + return r, err } + + return r, nil +} + +func createApiPath(b BitbucketServer, rev string) string { + // create path and map variables + localVarPath := b.Host + BuildEndPoint + localVarPath = strings.Replace(localVarPath, "{"+"projectKey"+"}", fmt.Sprintf("%v", b.ProjectKey), -1) + localVarPath = strings.Replace(localVarPath, "{"+"commitId"+"}", fmt.Sprintf("%v", rev), -1) + localVarPath = strings.Replace(localVarPath, "{"+"repositorySlug"+"}", fmt.Sprintf("%v", b.RepositorySlug), -1) + return localVarPath +} + +func parseBitbucketServerGitAddress(s string) (string, string, error) { + u, err := giturls.Parse(s) + if err != nil { + return "", "", fmt.Errorf("failed parsing URL %q: %w", s, err) + } + + scheme := u.Scheme + if u.Scheme != "http" && u.Scheme != "https" { + return "", "", fmt.Errorf("Unsupported git scheme %s in address %q. Please provide address in http/https format for BitbucketServer provider", u.Scheme, s) + } + + id := strings.TrimPrefix(u.Path, "/scm/") //https://community.atlassian.com/t5/Bitbucket-questions/remote-url-in-Bitbucket-server-what-does-scm-represent-is-it/qaq-p/2060987 + id = strings.TrimSuffix(id, ".git") + host := fmt.Sprintf("%s://%s", scheme, u.Host) + return host, id, nil } diff --git a/internal/notifier/bitbucketserver_test.go b/internal/notifier/bitbucketserver_test.go index 0db9963b1..da3680fff 100644 --- a/internal/notifier/bitbucketserver_test.go +++ b/internal/notifier/bitbucketserver_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Flux authors +Copyright 2023 The Flux authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,28 +17,39 @@ limitations under the License. package notifier import ( + "context" + "encoding/base64" + "encoding/json" + "io" "testing" + "net/http" + "net/http/httptest" + + eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestNewBitbucketServerBasic(t *testing.T) { b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "dummyuser", "testpassword") assert.Nil(t, err) - assert.Equal(t, b.Credentials.RestUsernamePasswordCredentials.Username, "dummyuser") - assert.Equal(t, b.Credentials.RestUsernamePasswordCredentials.Password, "testpassword") + assert.Equal(t, b.Client.UserInfo.Username, "dummyuser") + assert.Equal(t, b.Client.UserInfo.Password, "testpassword") } func TestNewBitbucketServerToken(t *testing.T) { b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "") assert.Nil(t, err) - assert.Equal(t, b.Credentials.RestBearerTokenCredentials.Token, "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP") + assert.Equal(t, b.Client.Token, "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP") } func TestNewBitbucketServerInvalidCreds(t *testing.T) { _, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "", nil, "", "") assert.NotNil(t, err) - assert.Equal(t, err.Error(), "invalid credentials, expected to be one of username,password or APIToken") + assert.Equal(t, err.Error(), "invalid credentials, expected to be one of username/password or API Token") } func TestNewBitbucketServerInvalidUrl(t *testing.T) { @@ -52,3 +63,220 @@ func TestNewBitbucketServerInvalidRepo(t *testing.T) { assert.NotNil(t, err) assert.Equal(t, err.Error(), "invalid repository id \"projectfoo/repobar/invalid\"") } + +func TestPostBitbucketServerMissingRevision(t *testing.T) { + b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "") + assert.Nil(t, err) + + //Validate missing revision + err = b.Post(context.TODO(), generateTestEventKustomization("info", map[string]string{ + "dummybadrevision": "bad", + })) + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "missing revision metadata") +} + +func TestPostBitbucketServerBadCommitHash(t *testing.T) { + b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "") + assert.Nil(t, err) + + //Validate extract commit hash + err = b.Post(context.TODO(), generateTestEventKustomization("info", map[string]string{ + eventv1.MetaRevisionKey: "badhash", + })) + assert.NotNil(t, err) + assert.Equal(t, err.Error(), "Could not parse revision: failed to extract commit hash from 'badhash' revision") + +} + +func TestPostBitbucketServerBadBitbucketState(t *testing.T) { + b, err := NewBitbucketServer("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", "https://example.com:7990/scm/projectfoo/repobar.git", "BBDC-ODIxODYxMzIyNzUyOttorMjO059P2rYTb6EH7mP", nil, "", "") + assert.Nil(t, err) + + //Validate conversion to bitbucket state + err = b.Post(context.TODO(), generateTestEventKustomization("badserveritystate", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + })) + 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") + +} + +func generateTestEventKustomization(severity string, metadata map[string]string) eventv1.Event { + return eventv1.Event{ + InvolvedObject: corev1.ObjectReference{ + Kind: "Kustomization", + Namespace: "flux-system", + Name: "hello-world", + }, + Severity: severity, + Timestamp: metav1.Now(), + Message: "message", + Reason: "reason", + Metadata: metadata, + ReportingController: "kustomize-controller", + ReportingInstance: "kustomize-controller-xyz", + } +} + +func TestBitBucketServerPostValidateRequest(t *testing.T) { + tests := []struct { + name string + headers map[string]string + username string + password string + token string + event eventv1.Event + provideruid string + key string + }{ + { + name: "Validate Token Auth ", + username: "", + password: "", + token: "goodtoken", + provideruid: "0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", + headers: map[string]string{ + "Authorization": "Bearer goodtoken", + "x-atlassian-token": "no-check", + "x-requested-with": "XMLHttpRequest", + }, + event: generateTestEventKustomization("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }))), + }, + { + name: "Validate Basic Auth", + username: "hello", + password: "password", + token: "", + 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("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }))), + }, + { + name: "Validate Post State=Successful", + username: "hello", + password: "password", + token: "", + 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("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("info", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }))), + }, + { + name: "Validate Post State=Failed", + username: "hello", + password: "password", + token: "", + 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("error", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }), + key: sha1String(generateCommitStatusID("0c9c2e41-d2f9-4f9b-9c41-bebc1984d67a", generateTestEventKustomization("error", map[string]string{ + eventv1.MetaRevisionKey: "main@sha1:5394cb7f48332b2de7c17dd8b8384bbc84b7e738", + }))), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + // Validate Headers + for key, value := range tt.headers { + require.Equal(t, value, r.Header.Get(key)) + } + + // Validate URI + require.Equal(t, r.URL.Path, "/rest/api/latest/projects/projectfoo/repos/repobar/commits/5394cb7f48332b2de7c17dd8b8384bbc84b7e738/builds") + + // Validate Get Build Status call + if r.Method == http.MethodGet { + + //Validate that this GET request has a query string with "key" as the query paraneter + require.Equal(t, r.URL.Query().Get(GetBuildStatusQueryString), tt.key) + + // Validate that this GET request has no body + require.Equal(t, http.NoBody, r.Body) + } + + // Validate Post BuildStatus call + if r.Method == http.MethodPost { + + // Validate that this POST request has no query string + require.Equal(t, len(r.URL.Query()), 0) + + // Validate that this POST request has Content-Type: application/json header + require.Equal(t, "application/json", r.Header.Get("Content-Type")) + + // Read json body of the request + b, err := io.ReadAll(r.Body) + require.NoError(t, err) + + // Parse json request into Payload Request body struct + var payload RestBuildStatusSetRequest + err = json.Unmarshal(b, &payload) + require.NoError(t, err) + + // 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" { + require.Fail(t, "Invalid state") + } + + // If severity of event is info, state should be SUCCESSFUL + if tt.event.Severity == "info" { + require.Equal(t, "SUCCESSFUL", payload.State) + } + + // If severity of event is error, state should be FAILED + if tt.event.Severity == "error" { + require.Equal(t, "FAILED", payload.State) + } + + // Validate description + require.Equal(t, "reason", payload.Description) + + // Validate name(with description appended) + require.Equal(t, "kustomization/hello-world"+" ["+payload.Description+"]", payload.Name) + + require.Contains(t, payload.Url, "/scm/projectfoo/repobar.git") + + } + })) + defer ts.Close() + c, err := NewBitbucketServer(tt.provideruid, ts.URL+"/scm/projectfoo/repobar.git", tt.token, nil, tt.username, tt.password) + require.NoError(t, err) + err = c.Post(context.TODO(), tt.event) + require.NoError(t, err) + }) + } +} diff --git a/internal/notifier/util.go b/internal/notifier/util.go index 296a6a9ad..c7b59bde7 100644 --- a/internal/notifier/util.go +++ b/internal/notifier/util.go @@ -47,23 +47,6 @@ func parseGitAddress(s string) (string, string, error) { return host, id, nil } -func parseBitbucketServerGitAddress(s string) (string, string, error) { - u, err := giturls.Parse(s) - if err != nil { - return "", "", fmt.Errorf("failed parsing URL %q: %w", s, err) - } - - scheme := u.Scheme - if u.Scheme != "http" && u.Scheme != "https" { - return "", "", fmt.Errorf("Unsupported git scheme %s in address %q. Please provide address in http/https format for BitbucketServer provider", u.Scheme, s) - } - - id := strings.TrimPrefix(u.Path, "/scm/") //https://community.atlassian.com/t5/Bitbucket-questions/remote-url-in-Bitbucket-server-what-does-scm-represent-is-it/qaq-p/2060987 - id = strings.TrimSuffix(id, ".git") - host := fmt.Sprintf("%s://%s", scheme, u.Host) - return host, id, nil -} - func formatNameAndDescription(event eventv1.Event) (string, string) { name := fmt.Sprintf("%v/%v", event.InvolvedObject.Kind, event.InvolvedObject.Name) name = strings.ToLower(name)