From fe4e26dff22abfe169f92b2968757d5f0a143b3b Mon Sep 17 00:00:00 2001 From: Michael Merrill Date: Mon, 28 Mar 2022 09:38:31 -0400 Subject: [PATCH] feat: enable bibucket cloud scm provider (v2) (#556) Signed-off-by: mmerrill3 --- api/v1alpha1/applicationset_types.go | 17 +- api/v1alpha1/zz_generated.deepcopy.go | 25 + docs/Generators-SCM-Provider.md | 36 ++ go.mod | 14 +- go.sum | 24 +- .../crds/argoproj.io_applicationsets.yaml | 69 +++ manifests/install.yaml | 69 +++ pkg/generators/scm_provider.go | 9 + pkg/services/scm_provider/bitbucket_cloud.go | 177 ++++++ .../scm_provider/bitbucket_cloud_test.go | 510 ++++++++++++++++++ 10 files changed, 938 insertions(+), 12 deletions(-) create mode 100644 pkg/services/scm_provider/bitbucket_cloud.go create mode 100644 pkg/services/scm_provider/bitbucket_cloud_test.go diff --git a/api/v1alpha1/applicationset_types.go b/api/v1alpha1/applicationset_types.go index b6dc6bc1..db4af219 100644 --- a/api/v1alpha1/applicationset_types.go +++ b/api/v1alpha1/applicationset_types.go @@ -292,8 +292,9 @@ type GitFileGeneratorItem struct { // SCMProviderGenerator defines a generator that scrapes a SCMaaS API to find candidate repos. type SCMProviderGenerator struct { // Which provider to use and config for it. - Github *SCMProviderGeneratorGithub `json:"github,omitempty"` - Gitlab *SCMProviderGeneratorGitlab `json:"gitlab,omitempty"` + Github *SCMProviderGeneratorGithub `json:"github,omitempty"` + Gitlab *SCMProviderGeneratorGitlab `json:"gitlab,omitempty"` + Bitbucket *SCMProviderGeneratorBitbucket `json:"bitbucket,omitempty"` // Filters for which repos should be considered. Filters []SCMProviderGeneratorFilter `json:"filters,omitempty"` // Which protocol to use for the SCM URL. Default is provider-specific but ssh if possible. Not all providers @@ -330,6 +331,18 @@ type SCMProviderGeneratorGitlab struct { AllBranches bool `json:"allBranches,omitempty"` } +// SCMProviderGeneratorBitbucket defines connection info specific to Bitbucket Cloud (API version 2). +type SCMProviderGeneratorBitbucket struct { + // Bitbucket workspace to scan. Required. + Owner string `json:"owner"` + // Bitbucket user to use when authenticating. Should have a "member" role to be able to read all repositories and branches. Required + User string `json:"user"` + // The app password to use for the user. Required. See: https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/ + AppPasswordRef *SecretRef `json:"appPasswordRef"` + // Scan all branches instead of just the main branch. + AllBranches bool `json:"allBranches,omitempty"` +} + // SCMProviderGeneratorFilter is a single repository filter. // If multiple filter types are set on a single struct, they will be AND'd together. All filters must // pass for a repo to be included. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4248a934..e90291ce 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -716,6 +716,11 @@ func (in *SCMProviderGenerator) DeepCopyInto(out *SCMProviderGenerator) { *out = new(SCMProviderGeneratorGitlab) (*in).DeepCopyInto(*out) } + if in.Bitbucket != nil { + in, out := &in.Bitbucket, &out.Bitbucket + *out = new(SCMProviderGeneratorBitbucket) + (*in).DeepCopyInto(*out) + } if in.Filters != nil { in, out := &in.Filters, &out.Filters *out = make([]SCMProviderGeneratorFilter, len(*in)) @@ -741,6 +746,26 @@ func (in *SCMProviderGenerator) DeepCopy() *SCMProviderGenerator { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SCMProviderGeneratorBitbucket) DeepCopyInto(out *SCMProviderGeneratorBitbucket) { + *out = *in + if in.AppPasswordRef != nil { + in, out := &in.AppPasswordRef, &out.AppPasswordRef + *out = new(SecretRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SCMProviderGeneratorBitbucket. +func (in *SCMProviderGeneratorBitbucket) DeepCopy() *SCMProviderGeneratorBitbucket { + if in == nil { + return nil + } + out := new(SCMProviderGeneratorBitbucket) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SCMProviderGeneratorFilter) DeepCopyInto(out *SCMProviderGeneratorFilter) { *out = *in diff --git a/docs/Generators-SCM-Provider.md b/docs/Generators-SCM-Provider.md index 4107e4ac..8d0fedee 100644 --- a/docs/Generators-SCM-Provider.md +++ b/docs/Generators-SCM-Provider.md @@ -94,6 +94,42 @@ For label filtering, the repository tags are used. Available clone protocols are `ssh` and `https`. +## Bitbucket + +The Bitbucket mode uses the Bitbucket API V2 to scan a workspace in bitbucket.org. + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: myapps +spec: + generators: + - scmProvider: + bitbucket: + # The workspace id (slug). + owner: "mmerrill" + # The user to use for basic authentication with an app password. + user: "mmerrill" + # If true, scan every branch of every repository. If false, scan only the main branch. Defaults to false. + allBranches: true + # Reference to a Secret containing an app password. + appPasswordRef: + secretName: appPassword + key: password + template: + # ... +``` + +* `owner`: The workspace ID (slug) to use when looking up repositories. +* `user`: The user to use for authentication to the Bitbucket API V2 at bitbucket.org. +* `allBranches`: By default (false) the template will only be evaluated for the main branch of each repo. If this is true, every branch of every repository will be passed to the filters. If using this flag, you likely want to use a `branchMatch` filter. +* `appPasswordRef`: A `Secret` name and key containing the bitbucket app password to use for requests. + +This SCM provider does not yet support label filtering + +Available clone protocols are `ssh` and `https`. + ## Filters Filters allow selecting which repositories to generate for. Each filter can declare one or more conditions, all of which must pass. If multiple filters are present, any can match for a repository to be included. If no filters are specified, all repositories will be processed. diff --git a/go.mod b/go.mod index 736375c1..f1b0a51a 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,16 @@ require ( github.com/google/go-github/v35 v35.0.0 github.com/imdario/mergo v0.3.12 github.com/jeremywohl/flatten v1.0.1 + github.com/ktrysmt/go-bitbucket v0.9.40 + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 github.com/valyala/fasttemplate v1.2.1 github.com/xanzy/go-gitlab v0.50.0 - golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/go-playground/webhooks.v5 v5.11.0 k8s.io/api v0.23.1 k8s.io/apiextensions-apiserver v0.23.1 @@ -97,7 +102,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.11.0 // indirect github.com/prometheus/client_model v0.2.0 // indirect @@ -122,17 +126,15 @@ require ( go.uber.org/zap v1.19.1 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/exp v0.0.0-20210901193431-a062eea981d2 // indirect - golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 // indirect - golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect google.golang.org/grpc v1.40.0 // indirect - google.golang.org/protobuf v1.27.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index fd9a55b7..b6e9b6b0 100644 --- a/go.sum +++ b/go.sum @@ -586,6 +586,8 @@ github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0t github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -612,6 +614,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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.40 h1:LcvdyW7u58vfbUi9bCQB+ihyqDzoy+9WBq/odmBsXrg= +github.com/ktrysmt/go-bitbucket v0.9.40/go.mod h1:FWxy2UK7GlK5b0NSJGc5hPqnssVlkNnsChvyuOf/Xno= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -671,8 +675,11 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/ipvs v1.0.1/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= @@ -1075,6 +1082,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1-0.20210830214625-1b1db11ec8f4/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1130,8 +1138,11 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1147,8 +1158,9 @@ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1161,6 +1173,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1252,11 +1265,13 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk= golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1382,6 +1397,7 @@ google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBz google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I= +google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/manifests/crds/argoproj.io_applicationsets.yaml b/manifests/crds/argoproj.io_applicationsets.yaml index 7eda3253..eefc196d 100644 --- a/manifests/crds/argoproj.io_applicationsets.yaml +++ b/manifests/crds/argoproj.io_applicationsets.yaml @@ -2765,6 +2765,29 @@ spec: type: object scmProvider: properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object cloneProtocol: type: string filters: @@ -4907,6 +4930,29 @@ spec: type: object scmProvider: properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object cloneProtocol: type: string filters: @@ -5838,6 +5884,29 @@ spec: type: object scmProvider: properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object cloneProtocol: type: string filters: diff --git a/manifests/install.yaml b/manifests/install.yaml index fec1e96d..a4df26ca 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -2764,6 +2764,29 @@ spec: type: object scmProvider: properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object cloneProtocol: type: string filters: @@ -4906,6 +4929,29 @@ spec: type: object scmProvider: properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object cloneProtocol: type: string filters: @@ -5837,6 +5883,29 @@ spec: type: object scmProvider: properties: + bitbucket: + properties: + allBranches: + type: boolean + appPasswordRef: + properties: + key: + type: string + secretName: + type: string + required: + - key + - secretName + type: object + owner: + type: string + user: + type: string + required: + - appPasswordRef + - owner + - user + type: object cloneProtocol: type: string filters: diff --git a/pkg/generators/scm_provider.go b/pkg/generators/scm_provider.go index b4c145b9..ce8baf2f 100644 --- a/pkg/generators/scm_provider.go +++ b/pkg/generators/scm_provider.go @@ -77,6 +77,15 @@ func (g *SCMProviderGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha if err != nil { return nil, fmt.Errorf("error initializing Gitlab service: %v", err) } + } else if providerConfig.Bitbucket != nil { + appPassword, err := g.getSecretRef(ctx, providerConfig.Bitbucket.AppPasswordRef, applicationSetInfo.Namespace) + if err != nil { + return nil, fmt.Errorf("error fetching Bitbucket cloud appPassword: %v", err) + } + provider, err = scm_provider.NewBitBucketCloudProvider(ctx, providerConfig.Bitbucket.Owner, providerConfig.Bitbucket.User, appPassword, providerConfig.Bitbucket.AllBranches) + if err != nil { + return nil, fmt.Errorf("error initializing Bitbucket cloud service: %v", err) + } } else { return nil, fmt.Errorf("no SCM provider implementation configured") } diff --git a/pkg/services/scm_provider/bitbucket_cloud.go b/pkg/services/scm_provider/bitbucket_cloud.go new file mode 100644 index 00000000..f2af9d44 --- /dev/null +++ b/pkg/services/scm_provider/bitbucket_cloud.go @@ -0,0 +1,177 @@ +package scm_provider + +import ( + "context" + "fmt" + "net/http" + "strings" + + bitbucket "github.com/ktrysmt/go-bitbucket" +) + +type BitBucketCloudProvider struct { + client *ExtendedClient + allBranches bool + owner string +} + +type ExtendedClient struct { + *bitbucket.Client + username string + password string + owner string +} + +func (c *ExtendedClient) GetContents(repo *Repository, path string) (bool, error) { + urlStr := c.GetApiBaseURL() + + // Getting file contents from V2 defined at https://developer.atlassian.com/cloud/bitbucket/rest/api-group-source/#api-repositories-workspace-repo-slug-src-commit-path-get + urlStr += fmt.Sprintf("/repositories/%s/%s/src/%s/%s?format=meta", c.owner, repo.Repository, repo.SHA, path) + body := strings.NewReader("") + + req, err := http.NewRequest("GET", urlStr, body) + if err != nil { + return false, err + } + req.SetBasicAuth(c.username, c.password) + resp, err := c.HttpClient.Do(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return false, nil + } + if resp.StatusCode == http.StatusOK { + return true, nil + } + + return false, fmt.Errorf(resp.Status) +} + +var _ SCMProviderService = &BitBucketCloudProvider{} + +func NewBitBucketCloudProvider(ctx context.Context, owner string, user string, password string, allBranches bool) (*BitBucketCloudProvider, error) { + + client := &ExtendedClient{ + bitbucket.NewBasicAuth(user, password), + user, + password, + owner, + } + return &BitBucketCloudProvider{client: client, owner: owner, allBranches: allBranches}, nil +} + +func (g *BitBucketCloudProvider) GetBranches(ctx context.Context, repo *Repository) ([]*Repository, error) { + repos := []*Repository{} + branches, err := g.listBranches(repo) + if err != nil { + return nil, fmt.Errorf("error listing branches for %s/%s: %v", repo.Organization, repo.Repository, err) + } + + for _, branch := range branches { + hash, ok := branch.Target["hash"].(string) + if !ok { + return nil, fmt.Errorf("error getting SHA for branch for %s/%s/%s: %v", g.owner, repo.Repository, branch.Name, err) + } + repos = append(repos, &Repository{ + Organization: repo.Organization, + Repository: repo.Repository, + URL: repo.URL, + Branch: branch.Name, + SHA: hash, + Labels: repo.Labels, + RepositoryId: repo.RepositoryId, + }) + } + return repos, nil +} + +func (g *BitBucketCloudProvider) ListRepos(ctx context.Context, cloneProtocol string) ([]*Repository, error) { + if cloneProtocol == "" { + cloneProtocol = "ssh" + } + opt := &bitbucket.RepositoriesOptions{ + Owner: g.owner, + Role: "member", + } + repos := []*Repository{} + accountReposResp, err := g.client.Repositories.ListForAccount(opt) + if err != nil { + return nil, fmt.Errorf("error listing repositories for %s: %v", g.owner, err) + } + for _, bitBucketRepo := range accountReposResp.Items { + cloneUrl, err := findCloneURL(cloneProtocol, &bitBucketRepo) + if err != nil { + return nil, fmt.Errorf("error fetching clone url for repo %s: %v", bitBucketRepo.Slug, err) + } + repos = append(repos, &Repository{ + Organization: g.owner, + Repository: bitBucketRepo.Slug, + Branch: bitBucketRepo.Mainbranch.Name, + URL: *cloneUrl, + Labels: []string{}, + RepositoryId: bitBucketRepo.Uuid, + }) + } + return repos, nil +} + +func (g *BitBucketCloudProvider) RepoHasPath(ctx context.Context, repo *Repository, path string) (bool, error) { + contents, err := g.client.GetContents(repo, path) + if err != nil { + return false, err + } + if contents { + return true, nil + } + return false, nil +} + +func (g *BitBucketCloudProvider) listBranches(repo *Repository) ([]bitbucket.RepositoryBranch, error) { + if !g.allBranches { + repoBranch, err := g.client.Repositories.Repository.GetBranch(&bitbucket.RepositoryBranchOptions{ + Owner: g.owner, + RepoSlug: repo.Repository, + BranchName: repo.Branch, + }) + if err != nil { + return nil, err + } + return []bitbucket.RepositoryBranch{ + *repoBranch, + }, nil + } + + branches, err := g.client.Repositories.Repository.ListBranches(&bitbucket.RepositoryBranchOptions{ + Owner: g.owner, + RepoSlug: repo.Repository, + }) + if err != nil { + return nil, err + } + return branches.Branches, nil + +} + +func findCloneURL(cloneProtocol string, repo *bitbucket.Repository) (*string, error) { + + cloneLinks, ok := repo.Links["clone"].([]interface{}) + if !ok { + return nil, fmt.Errorf("unknown type returned from repo links") + } + for _, link := range cloneLinks { + linkEntry, ok := link.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unknown type returned from clone link") + } + if linkEntry["name"] == cloneProtocol { + url, ok := linkEntry["href"].(string) + if !ok { + return nil, fmt.Errorf("could not find href for clone link") + } + return &url, nil + } + } + return nil, fmt.Errorf("unknown clone protocol for Bitbucket cloud %v", cloneProtocol) +} diff --git a/pkg/services/scm_provider/bitbucket_cloud_test.go b/pkg/services/scm_provider/bitbucket_cloud_test.go new file mode 100644 index 00000000..caf1d25d --- /dev/null +++ b/pkg/services/scm_provider/bitbucket_cloud_test.go @@ -0,0 +1,510 @@ +package scm_provider + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/argoproj/applicationset/api/v1alpha1" + "github.com/stretchr/testify/assert" +) + +func TestBitbucketHasRepo(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/repositories/test-owner/testmike/src/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/.gitignore2" { + res.WriteHeader(http.StatusNotFound) + _, err := res.Write([]byte("")) + if err != nil { + assert.NoError(t, fmt.Errorf("Error in mock response %v", err)) + } + } + if req.URL.Path == "/repositories/test-owner/testmike/src/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/.gitignore" { + res.WriteHeader(http.StatusOK) + _, err := res.Write([]byte(`{ + "mimetype": null, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/src/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/.gitignore" + }, + "meta": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/src/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/.gitignore?format=meta" + }, + "history": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/filehistory/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/.gitignore" + } + }, + "escaped_path": ".gitignore", + "path": ".gitignore", + "commit": { + "type": "commit", + "hash": "dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike/commits/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + } + } + }, + "attributes": [], + "type": "commit_file", + "size": 624 + }`)) + if err != nil { + assert.NoError(t, fmt.Errorf("Error in mock response %v", err)) + } + } + })) + defer func() { testServer.Close() }() + + os.Setenv("BITBUCKET_API_BASE_URL", testServer.URL) + cases := []struct { + name, path, repo, owner, sha string + status int + }{ + { + name: "exists", + owner: "test-owner", + repo: "testmike", + path: ".gitignore", + sha: "dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798", + status: http.StatusOK, + }, + { + name: "not exists", + owner: "test-owner", + repo: "testmike", + path: ".gitignore2", + sha: "dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798", + status: http.StatusNotFound, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + provider, _ := NewBitBucketCloudProvider(context.Background(), c.owner, "user", "password", false) + repo := &Repository{ + Organization: c.owner, + Repository: c.repo, + SHA: c.sha, + Branch: "main", + } + hasPath, err := provider.RepoHasPath(context.Background(), repo, c.path) + if err != nil { + assert.Error(t, fmt.Errorf("Error in test %v", err)) + } + if c.status != http.StatusOK { + assert.False(t, hasPath) + } else { + assert.True(t, hasPath) + } + }) + } +} + +func TestBitbucketListRepos(t *testing.T) { + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusOK) + if req.URL.Path == "/repositories/test-owner/testmike/refs/branches" { + _, err := res.Write([]byte(`{ + "pagelen": 10, + "values": [ + { + "name": "main", + "links": { + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commits/main" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/refs/branches/main" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike/branch/main" + } + }, + "default_merge_strategy": "merge_commit", + "merge_strategies": [ + "merge_commit", + "squash", + "fast_forward" + ], + "type": "branch", + "target": { + "hash": "dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798", + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B76606e75-8aeb-4a87-9396-4abee652ec63%7D?ts=default" + } + }, + "type": "repository", + "name": "testMike", + "full_name": "test-owner/testmike", + "uuid": "{76606e75-8aeb-4a87-9396-4abee652ec63}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/comments" + }, + "patch": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/patch/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike/commits/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/diff/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/statuses" + } + }, + "author": { + "raw": "Mike Tester ", + "type": "author", + "user": { + "display_name": "Mike Tester", + "uuid": "{ca84788f-050b-456b-5cac-93fb4484a686}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7Bca84788f-050b-456b-5cac-93fb4484a686%7D" + }, + "html": { + "href": "https://bitbucket.org/%7Bca84788f-050b-456b-5cac-93fb4484a686%7D/" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/03450fe11788d0dbb39b804110c07b9f?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMM-4.png" + } + }, + "type": "user", + "nickname": "Mike Tester", + "account_id": "61ec57859d174000690f702b" + } + }, + "parents": [], + "date": "2022-03-07T19:37:58+00:00", + "message": "Initial commit", + "type": "commit" + } + } + ], + "page": 1, + "size": 1 + }`)) + if err != nil { + assert.NoError(t, fmt.Errorf("Error in mock response %v", err)) + } + } + if req.URL.Path == "/repositories/test-owner/testmike/refs/branches/main" { + _, err := res.Write([]byte(`{ + "name": "main", + "links": { + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commits/main" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/refs/branches/main" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike/branch/main" + } + }, + "default_merge_strategy": "merge_commit", + "merge_strategies": [ + "merge_commit", + "squash", + "fast_forward" + ], + "type": "branch", + "target": { + "hash": "dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798", + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B76606e75-8aeb-4a87-9396-4abee652ec63%7D?ts=default" + } + }, + "type": "repository", + "name": "testMike", + "full_name": "test-owner/testmike", + "uuid": "{76606e75-8aeb-4a87-9396-4abee652ec63}" + }, + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/comments" + }, + "patch": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/patch/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike/commits/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/diff/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commit/dc1edb6c7d650d8ba67719ddf7b662ad8f8fb798/statuses" + } + }, + "author": { + "raw": "Mike Tester ", + "type": "author", + "user": { + "display_name": "Mike Tester", + "uuid": "{ca84788f-050b-456b-5cac-93fb4484a686}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7Bca84788f-050b-456b-5cac-93fb4484a686%7D" + }, + "html": { + "href": "https://bitbucket.org/%7Bca84788f-050b-456b-5cac-93fb4484a686%7D/" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/03450fe11788d0dbb39b804110c07b9f?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMM-4.png" + } + }, + "type": "user", + "nickname": "Mike Tester", + "account_id": "61ec57859d174000690f702b" + } + }, + "parents": [], + "date": "2022-03-07T19:37:58+00:00", + "message": "Initial commit", + "type": "commit" + } + }`)) + if err != nil { + assert.NoError(t, fmt.Errorf("Error in mock response %v", err)) + } + } + if req.URL.Path == "/repositories/test-owner" { + _, err := res.Write([]byte(`{ + "pagelen": 10, + "values": [ + { + "scm": "git", + "has_wiki": false, + "links": { + "watchers": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/watchers" + }, + "branches": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/refs/branches" + }, + "tags": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/refs/tags" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/commits" + }, + "clone": [ + { + "href": "https://test-owner@bitbucket.org/test-owner/testmike.git", + "name": "https" + }, + { + "href": "git@bitbucket.org:test-owner/testmike.git", + "name": "ssh" + } + ], + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike" + }, + "source": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/src" + }, + "html": { + "href": "https://bitbucket.org/test-owner/testmike" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B76606e75-8aeb-4a87-9396-4abee652ec63%7D?ts=default" + }, + "hooks": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/hooks" + }, + "forks": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/forks" + }, + "downloads": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/downloads" + }, + "pullrequests": { + "href": "https://api.bitbucket.org/2.0/repositories/test-owner/testmike/pullrequests" + } + }, + "created_on": "2022-03-07T19:37:58.199968+00:00", + "full_name": "test-owner/testmike", + "owner": { + "display_name": "Mike Tester", + "uuid": "{ca84788f-050b-456b-5cac-93fb4484a686}", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/%7Bca84788f-050b-456b-5cac-93fb4484a686%7D" + }, + "html": { + "href": "https://bitbucket.org/%7Bca84788f-050b-456b-5cac-93fb4484a686%7D/" + }, + "avatar": { + "href": "https://secure.gravatar.com/avatar/03450fe11788d0dbb39b804110c07b9f?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FMM-4.png" + } + }, + "type": "user", + "nickname": "Mike Tester", + "account_id": "61ec57859d174000690f702b" + }, + "size": 58894, + "uuid": "{76606e75-8aeb-4a87-9396-4abee652ec63}", + "type": "repository", + "website": null, + "override_settings": { + "branching_model": true, + "default_merge_strategy": true, + "branch_restrictions": true + }, + "description": "", + "has_issues": false, + "slug": "testmike", + "is_private": false, + "name": "testMike", + "language": "", + "fork_policy": "allow_forks", + "project": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/test-owner/projects/TEST" + }, + "html": { + "href": "https://bitbucket.org/test-owner/workspace/projects/TEST" + }, + "avatar": { + "href": "https://bitbucket.org/account/user/test-owner/projects/TEST/avatar/32?ts=1642881431" + } + }, + "type": "project", + "name": "test", + "key": "TEST", + "uuid": "{603a1564-1509-4c97-b2a6-300a3fad2758}" + }, + "mainbranch": { + "type": "branch", + "name": "main" + }, + "workspace": { + "slug": "test-owner", + "type": "workspace", + "name": "Mike Tester", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/workspaces/test-owner" + }, + "html": { + "href": "https://bitbucket.org/test-owner/" + }, + "avatar": { + "href": "https://bitbucket.org/workspaces/test-owner/avatar/?ts=1642878863" + } + }, + "uuid": "{ca84788f-050b-456b-5cac-93fb4484a686}" + }, + "updated_on": "2022-03-07T19:37:59.933133+00:00" + } + ], + "page": 1, + "size": 1 + }`)) + if err != nil { + assert.NoError(t, fmt.Errorf("Error in mock response %v", err)) + } + } + })) + defer func() { testServer.Close() }() + + os.Setenv("BITBUCKET_API_BASE_URL", testServer.URL) + cases := []struct { + name, proto, owner string + hasError, allBranches bool + branches []string + filters []v1alpha1.SCMProviderGeneratorFilter + }{ + { + name: "blank protocol", + owner: "test-owner", + branches: []string{"main"}, + }, + { + name: "ssh protocol", + proto: "ssh", + owner: "test-owner", + }, + { + name: "https protocol", + proto: "https", + owner: "test-owner", + }, + { + name: "other protocol", + proto: "other", + owner: "test-owner", + hasError: true, + }, + { + name: "all branches", + allBranches: true, + owner: "test-owner", + branches: []string{"main"}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + provider, _ := NewBitBucketCloudProvider(context.Background(), c.owner, "user", "password", c.allBranches) + rawRepos, err := ListRepos(context.Background(), provider, c.filters, c.proto) + if c.hasError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + repos := []*Repository{} + branches := []string{} + for _, r := range rawRepos { + if r.Repository == "testmike" { + repos = append(repos, r) + branches = append(branches, r.Branch) + } + } + assert.NotEmpty(t, repos) + for _, b := range c.branches { + assert.Contains(t, branches, b) + } + } + }) + } +}