From 341bea60d26e3dfbf07a4abdc1c92cc3d354ee55 Mon Sep 17 00:00:00 2001 From: Zaki Shaikh Date: Fri, 15 Nov 2024 17:46:04 +0530 Subject: [PATCH] Configure E2E test setup on PAC for bitbucket server configured e2e test for bitbucket server on PAC and wrote one test for pull request. https://issues.redhat.com/browse/SRVKP-6758 Signed-off-by: Zaki Shaikh --- go.mod | 1 + go.sum | 14 +- test/bitbucket_server_pull_request_test.go | 54 ++ test/pkg/bitbucketserver/crd.go | 60 ++ test/pkg/bitbucketserver/pr.go | 63 ++ test/pkg/bitbucketserver/setup.go | 98 +++ vendor/github.com/jenkins-x/go-scm/COPYRIGHT | 3 + vendor/github.com/jenkins-x/go-scm/LICENSE | 29 + .../jenkins-x/go-scm/pkg/hmac/hmac.go | 48 ++ vendor/github.com/jenkins-x/go-scm/scm/app.go | 41 + .../github.com/jenkins-x/go-scm/scm/client.go | 252 ++++++ .../github.com/jenkins-x/go-scm/scm/commit.go | 54 ++ .../github.com/jenkins-x/go-scm/scm/const.go | 265 +++++++ .../jenkins-x/go-scm/scm/content.go | 57 ++ .../github.com/jenkins-x/go-scm/scm/deploy.go | 95 +++ .../go-scm/scm/driver/internal/null/bool.go | 48 ++ .../go-scm/scm/driver/internal/null/int.go | 58 ++ .../go-scm/scm/driver/internal/null/string.go | 49 ++ .../go-scm/scm/driver/stash/content.go | 99 +++ .../jenkins-x/go-scm/scm/driver/stash/git.go | 342 ++++++++ .../go-scm/scm/driver/stash/issue.go | 117 +++ .../go-scm/scm/driver/stash/milestone.go | 31 + .../go-scm/scm/driver/stash/multipart.go | 26 + .../jenkins-x/go-scm/scm/driver/stash/org.go | 198 +++++ .../jenkins-x/go-scm/scm/driver/stash/pr.go | 520 ++++++++++++ .../jenkins-x/go-scm/scm/driver/stash/repo.go | 637 +++++++++++++++ .../go-scm/scm/driver/stash/review.go | 47 ++ .../go-scm/scm/driver/stash/stash.go | 167 ++++ .../jenkins-x/go-scm/scm/driver/stash/user.go | 102 +++ .../jenkins-x/go-scm/scm/driver/stash/util.go | 75 ++ .../go-scm/scm/driver/stash/webhook.go | 390 +++++++++ .../github.com/jenkins-x/go-scm/scm/errors.go | 62 ++ vendor/github.com/jenkins-x/go-scm/scm/git.go | 102 +++ .../github.com/jenkins-x/go-scm/scm/issue.go | 161 ++++ .../jenkins-x/go-scm/scm/labels/labels.go | 56 ++ .../jenkins-x/go-scm/scm/milestone.go | 42 + vendor/github.com/jenkins-x/go-scm/scm/org.go | 108 +++ vendor/github.com/jenkins-x/go-scm/scm/pr.go | 232 ++++++ .../jenkins-x/go-scm/scm/release.go | 67 ++ .../github.com/jenkins-x/go-scm/scm/repo.go | 205 +++++ .../github.com/jenkins-x/go-scm/scm/review.go | 110 +++ .../github.com/jenkins-x/go-scm/scm/token.go | 34 + .../go-scm/scm/transport/internal/clone.go | 22 + .../go-scm/scm/transport/oauth2/oauth2.go | 62 ++ .../go-scm/scm/transport/oauth2/refresh.go | 137 ++++ .../go-scm/scm/transport/oauth2/source.go | 41 + .../github.com/jenkins-x/go-scm/scm/user.go | 73 ++ .../github.com/jenkins-x/go-scm/scm/util.go | 101 +++ .../jenkins-x/go-scm/scm/webhook.go | 739 ++++++++++++++++++ vendor/modules.txt | 9 + 50 files changed, 6401 insertions(+), 2 deletions(-) create mode 100644 test/bitbucket_server_pull_request_test.go create mode 100644 test/pkg/bitbucketserver/crd.go create mode 100644 test/pkg/bitbucketserver/pr.go create mode 100644 test/pkg/bitbucketserver/setup.go create mode 100644 vendor/github.com/jenkins-x/go-scm/COPYRIGHT create mode 100644 vendor/github.com/jenkins-x/go-scm/LICENSE create mode 100644 vendor/github.com/jenkins-x/go-scm/pkg/hmac/hmac.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/app.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/client.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/commit.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/const.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/content.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/deploy.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/bool.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/int.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/string.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/content.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/git.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/issue.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/milestone.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/multipart.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/org.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/pr.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/repo.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/review.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/stash.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/user.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/util.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/driver/stash/webhook.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/errors.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/git.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/issue.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/labels/labels.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/milestone.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/org.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/pr.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/release.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/repo.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/review.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/token.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/transport/internal/clone.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/oauth2.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/refresh.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/source.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/user.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/util.go create mode 100644 vendor/github.com/jenkins-x/go-scm/scm/webhook.go diff --git a/go.mod b/go.mod index 14ce34556..878850e52 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/google/go-github/v64 v64.0.0 github.com/google/go-github/v66 v66.0.0 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b + github.com/jenkins-x/go-scm v1.14.53 github.com/jonboulle/clockwork v0.4.0 github.com/juju/ansiterm v1.0.0 github.com/ktrysmt/go-bitbucket v0.9.81 diff --git a/go.sum b/go.sum index 5f6501eb9..d320322b2 100644 --- a/go.sum +++ b/go.sum @@ -264,6 +264,8 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4= github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -289,6 +291,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jenkins-x/go-scm v1.14.53 h1:Utc9FbHzCuyXQ9F7gmXxNLUwG70Fp2niNS+m8+eGXcY= +github.com/jenkins-x/go-scm v1.14.53/go.mod h1:1RPxLZndnvu31XhFZ+RTvXiHmMX70HkQ17bRupTQxGs= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -437,9 +441,15 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260 h1:xKXiRdBUtMVp64NaxACcyX4kvfmHJ9KrLU+JvyB1mdM= +github.com/shurcooL/githubv4 v0.0.0-20190718010115-4ba037080260/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -592,8 +602,6 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -860,6 +868,8 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= +gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/test/bitbucket_server_pull_request_test.go b/test/bitbucket_server_pull_request_test.go new file mode 100644 index 000000000..4e78955f5 --- /dev/null +++ b/test/bitbucket_server_pull_request_test.go @@ -0,0 +1,54 @@ +//go:build e2e +// +build e2e + +package test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype" + tbbs "github.com/openshift-pipelines/pipelines-as-code/test/pkg/bitbucketserver" + "github.com/openshift-pipelines/pipelines-as-code/test/pkg/wait" + + "github.com/tektoncd/pipeline/pkg/names" + "gotest.tools/v3/assert" +) + +func TestBitbucketServerPullRequest(t *testing.T) { + targetNS := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-ns") + ctx := context.Background() + bitbucketWSOwner := os.Getenv("TEST_BITBUCKET_SERVER_E2E_REPOSITORY") + + ctx, runcnx, opts, client, err := tbbs.Setup(ctx) + assert.NilError(t, err) + + repo := tbbs.CreateCRD(ctx, t, client, runcnx, bitbucketWSOwner, targetNS) + runcnx.Clients.Log.Infof("Repository %s has been created", repo.Name) + defer tbbs.TearDownNs(ctx, t, runcnx, targetNS) + + title := "TestPullRequest - " + targetNS + numberOfFiles := 5 + files := map[string]string{} + for i := range numberOfFiles { + files[fmt.Sprintf("pipelinerun-%d.yaml", i)] = "testdata/pipelinerun.yaml" + } + + pr := tbbs.CreatePR(ctx, t, client, runcnx, bitbucketWSOwner, files, title, targetNS) + runcnx.Clients.Log.Infof("Pull Request with title '%s' is created", pr.Title) + defer tbbs.TearDownBranch(ctx, t, runcnx, client, pr.Number, bitbucketWSOwner, targetNS) + + successOpts := wait.SuccessOpt{ + TargetNS: targetNS, + OnEvent: triggertype.PullRequest.String(), + NumberofPRMatch: 5, + MinNumberStatus: 5, + } + wait.Succeeded(ctx, t, runcnx, opts, successOpts) +} + +// Local Variables: +// compile-command: "go test -tags=e2e -v -run TestBitbucketServerPullRequest$ ." +// End: diff --git a/test/pkg/bitbucketserver/crd.go b/test/pkg/bitbucketserver/crd.go new file mode 100644 index 000000000..48c92b047 --- /dev/null +++ b/test/pkg/bitbucketserver/crd.go @@ -0,0 +1,60 @@ +package bitbucketserver + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1" + "github.com/openshift-pipelines/pipelines-as-code/pkg/params" + pacrepo "github.com/openshift-pipelines/pipelines-as-code/test/pkg/repository" + "github.com/openshift-pipelines/pipelines-as-code/test/pkg/secret" + + "github.com/jenkins-x/go-scm/scm" + "gotest.tools/v3/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func CreateCRD(ctx context.Context, t *testing.T, client *scm.Client, run *params.Run, orgAndRepo, targetNS string) *scm.Repository { + repo, resp, err := client.Repositories.Find(ctx, orgAndRepo) + assert.NilError(t, err, "error getting repository: http status code: %d: %v", resp.Status, err) + + url := strings.ReplaceAll(repo.Link, "/browse", "") + repository := &v1alpha1.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: targetNS, + }, + Spec: v1alpha1.RepositorySpec{ + URL: url, + }, + } + + err = pacrepo.CreateNS(ctx, targetNS, run) + assert.NilError(t, err) + run.Clients.Log.Infof("Namespace %s is created", targetNS) + + token, _ := os.LookupEnv("TEST_BITBUCKET_SERVER_TOKEN") + apiURL, _ := os.LookupEnv("TEST_BITBUCKET_SERVER_API_URL") + apiUser, _ := os.LookupEnv("TEST_BITBUCKET_SERVER_USER") + webhookSecret := os.Getenv("TEST_BITBUCKET_SERVER_WEBHOOK_SECRET") + secretName := "bitbucket-server-webhook-config" + err = secret.Create(ctx, run, map[string]string{ + "provider.token": token, + "webhook.secret": webhookSecret, + }, targetNS, secretName) + assert.NilError(t, err) + run.Clients.Log.Infof("PipelinesAsCode Secret %s is created", secretName) + + repository.Spec.GitProvider = &v1alpha1.GitProvider{ + URL: apiURL, + User: apiUser, + Secret: &v1alpha1.Secret{Name: secretName}, + WebhookSecret: &v1alpha1.Secret{Name: secretName}, + } + + err = pacrepo.CreateRepo(ctx, targetNS, run, repository) + assert.NilError(t, err) + + return repo +} diff --git a/test/pkg/bitbucketserver/pr.go b/test/pkg/bitbucketserver/pr.go new file mode 100644 index 000000000..64280c350 --- /dev/null +++ b/test/pkg/bitbucketserver/pr.go @@ -0,0 +1,63 @@ +package bitbucketserver + +import ( + "context" + "fmt" + "testing" + + "github.com/openshift-pipelines/pipelines-as-code/pkg/params" + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype" + "github.com/openshift-pipelines/pipelines-as-code/test/pkg/options" + "github.com/openshift-pipelines/pipelines-as-code/test/pkg/payload" + + "github.com/jenkins-x/go-scm/scm" + "gotest.tools/v3/assert" +) + +func CreatePR(ctx context.Context, t *testing.T, client *scm.Client, runcnx *params.Run, orgAndRepo string, files map[string]string, title, targetNS string) *scm.PullRequest { + mainBranchRef := "refs/heads/main" + branch, resp, err := client.Git.CreateRef(ctx, orgAndRepo, targetNS, mainBranchRef) + assert.NilError(t, err, "error creating branch: http status code: %d : %v", resp.Status, err) + runcnx.Clients.Log.Infof("Branch %s has been created", branch.Name) + + files, err = payload.GetEntries(files, targetNS, options.MainBranch, triggertype.PullRequest.String(), map[string]string{}) + assert.NilError(t, err) + err = pushFilesToBranch(ctx, runcnx, client, orgAndRepo, targetNS, files) + assert.NilError(t, err, "error pushing files to branch: %v", err) + + prOpts := &scm.PullRequestInput{ + Title: title, + Body: "Test PAC on bitbucket server", + Head: targetNS, + Base: "main", + } + pr, resp, err := client.PullRequests.Create(ctx, orgAndRepo, prOpts) + assert.NilError(t, err, "error creating pull request: http status code: %d : %v", resp.Status, err) + return pr +} + +// pushFilesToBranch pushes multiple files to bitbucket server repo because +// bitbucket server doesn't support uploading multiple files in an API call. +// reference: https://community.developer.atlassian.com/t/rest-api-to-update-multiple-files/28731/2 +func pushFilesToBranch(ctx context.Context, run *params.Run, client *scm.Client, repoAndOrg, branchName string, files map[string]string) error { + if len(files) == 0 { + return fmt.Errorf("no file to commit") + } + + for file, content := range files { + param := &scm.ContentParams{ + Branch: branchName, + Message: "test commit", + Data: []byte(content), + Signature: scm.Signature{Name: "Zaki Shaikh", Email: "zaki@example.com"}, + } + path := fmt.Sprintf(".tekton/%s", file) + _, err := client.Contents.Create(ctx, repoAndOrg, path, param) + if err != nil { + return err + } + } + run.Clients.Log.Infof("%d files committed to branch %s", len(files), branchName) + + return nil +} diff --git a/test/pkg/bitbucketserver/setup.go b/test/pkg/bitbucketserver/setup.go new file mode 100644 index 000000000..0d0316579 --- /dev/null +++ b/test/pkg/bitbucketserver/setup.go @@ -0,0 +1,98 @@ +package bitbucketserver + +import ( + "context" + "fmt" + "net/http" + "os" + "strings" + "testing" + + "github.com/openshift-pipelines/pipelines-as-code/pkg/params" + "github.com/openshift-pipelines/pipelines-as-code/pkg/params/info" + "github.com/openshift-pipelines/pipelines-as-code/test/pkg/options" + "github.com/openshift-pipelines/pipelines-as-code/test/pkg/repository" + + "github.com/jenkins-x/go-scm/scm" + "github.com/jenkins-x/go-scm/scm/driver/stash" + "github.com/jenkins-x/go-scm/scm/transport/oauth2" + "gotest.tools/v3/assert" +) + +func Setup(ctx context.Context) (context.Context, *params.Run, options.E2E, *scm.Client, error) { + bitbucketServerUser := os.Getenv("TEST_BITBUCKET_SERVER_USER") + bitbucketServerToken := os.Getenv("TEST_BITBUCKET_SERVER_TOKEN") + bitbucketWSOwner := os.Getenv("TEST_BITBUCKET_SERVER_E2E_REPOSITORY") + bitbucketServerAPIURL := os.Getenv("TEST_BITBUCKET_SERVER_API_URL") + + for _, value := range []string{ + "BITBUCKET_SERVER_USER", "BITBUCKET_SERVER_TOKEN", "BITBUCKET_SERVER_E2E_REPOSITORY", "BITBUCKET_SERVER_API_URL", "BITBUCKET_SERVER_WEBHOOK_SECRET", + } { + if env := os.Getenv("TEST_" + value); env == "" { + return ctx, nil, options.E2E{}, nil, fmt.Errorf("\"TEST_%s\" env variable is required, skipping", value) + } + } + + split := strings.Split(bitbucketWSOwner, "/") + + run := params.New() + if err := run.Clients.NewClients(ctx, &run.Info); err != nil { + return ctx, nil, options.E2E{}, nil, err + } + e2eoptions := options.E2E{ + Organization: split[0], + Repo: split[1], + } + + event := info.NewEvent() + event.Provider = &info.Provider{ + Token: bitbucketServerToken, + URL: bitbucketServerAPIURL, + User: bitbucketServerUser, + } + + client, err := stash.New(bitbucketServerAPIURL) + if err != nil { + return ctx, nil, options.E2E{}, nil, err + } + + client.Client = &http.Client{ + Transport: &oauth2.Transport{ + Source: oauth2.StaticTokenSource( + &scm.Token{ + Token: bitbucketServerToken, + }, + ), + }, + } + + return ctx, run, e2eoptions, client, nil +} + +func TearDownNs(ctx context.Context, t *testing.T, runcnx *params.Run, targetNS string) { + if os.Getenv("TEST_NOCLEANUP") == "true" { + runcnx.Clients.Log.Infof("Not cleaning up and closing PR since TEST_NOCLEANUP is set") + return + } + + repository.NSTearDown(ctx, t, runcnx, targetNS) +} + +func TearDownBranch(ctx context.Context, t *testing.T, runcnx *params.Run, client *scm.Client, prID int, orgAndRepo, ref string) { + if os.Getenv("TEST_NOCLEANUP") == "true" { + runcnx.Clients.Log.Infof("Not cleaning up and closing PR since TEST_NOCLEANUP is set") + return + } + + if prID != -1 { + runcnx.Clients.Log.Infof("Deleting PR #%d", prID) + _, err := client.PullRequests.DeletePullRequest(ctx, orgAndRepo, prID) + assert.NilError(t, err) + } + + if ref != "" { + runcnx.Clients.Log.Infof("Deleting Branch %s", ref) + _, err := client.Git.DeleteRef(ctx, orgAndRepo, ref) + assert.NilError(t, err) + } +} diff --git a/vendor/github.com/jenkins-x/go-scm/COPYRIGHT b/vendor/github.com/jenkins-x/go-scm/COPYRIGHT new file mode 100644 index 000000000..7e4f2993e --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/COPYRIGHT @@ -0,0 +1,3 @@ +Copyright 2017 Drone.IO Inc. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file. \ No newline at end of file diff --git a/vendor/github.com/jenkins-x/go-scm/LICENSE b/vendor/github.com/jenkins-x/go-scm/LICENSE new file mode 100644 index 000000000..c76dc223a --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2017, Drone.IO Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/jenkins-x/go-scm/pkg/hmac/hmac.go b/vendor/github.com/jenkins-x/go-scm/pkg/hmac/hmac.go new file mode 100644 index 000000000..9a90caed2 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/pkg/hmac/hmac.go @@ -0,0 +1,48 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hmac + +import ( + "crypto/hmac" + "crypto/sha1" // #nosec + "crypto/sha256" + "encoding/hex" + "hash" + "strings" +) + +// Validate checks the hmac signature of the mssasge +// using a hex encoded signature. +func Validate(h func() hash.Hash, message, key []byte, signature string) bool { + decoded, err := hex.DecodeString(signature) + if err != nil { + return false + } + return validate(h, message, key, decoded) +} + +// ValidatePrefix checks the hmac signature of the message +// using the message prefix to determine the signing algorithm. +func ValidatePrefix(message, key []byte, signature string) bool { + parts := strings.Split(signature, "=") + if len(parts) != 2 { + return false + } + switch parts[0] { + case "sha1": + return Validate(sha1.New, message, key, parts[1]) + case "sha256": + return Validate(sha256.New, message, key, parts[1]) + default: + return false + } +} + +func validate(h func() hash.Hash, message, key, signature []byte) bool { + mac := hmac.New(h, key) + mac.Write(message) // #nosec + sum := mac.Sum(nil) + return hmac.Equal(signature, sum) +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/app.go b/vendor/github.com/jenkins-x/go-scm/scm/app.go new file mode 100644 index 000000000..0a7cddc06 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/app.go @@ -0,0 +1,41 @@ +package scm + +import ( + "context" + "time" +) + +type ( + // InstallationToken is the token used for interacting with the app + InstallationToken struct { + Token string + ExpiresAt *time.Time + } + + // Installation represents a GitHub app install + Installation struct { + ID int64 + AppID int64 + TargetID int64 + TargetType string + RepositorySelection string + Account Account + AccessTokensLink string + RepositoriesURL string + Link string + Events []string + CreatedAt *time.Time + UpdatedAt *time.Time + } + + // AppService for GitHub App support + AppService interface { + CreateInstallationToken(ctx context.Context, id int64) (*InstallationToken, *Response, error) + + GetRepositoryInstallation(ctx context.Context, fullName string) (*Installation, *Response, error) + + GetOrganisationInstallation(ctx context.Context, organisation string) (*Installation, *Response, error) + + GetUserInstallation(ctx context.Context, user string) (*Installation, *Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/client.go b/vendor/github.com/jenkins-x/go-scm/scm/client.go new file mode 100644 index 000000000..d6b152a9c --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/client.go @@ -0,0 +1,252 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "errors" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" +) + +var ( + // ErrNotFound indicates a resource is not found. + ErrNotFound = errors.New("not Found") + + // ErrNotSupported indicates a resource endpoint is not + // supported or implemented. + ErrNotSupported = errors.New("not Supported") + + // ErrNotAuthorized indicates the request is not + // authorized or the user does not have access to the + // resource. + ErrNotAuthorized = errors.New("not Authorized") + + // ErrForbidden indicates the user does not have access to + // the resource, this is similar to 401, but in this case, + // re-authenticating will make no difference. + ErrForbidden = errors.New("Forbidden") +) + +type ( + // Request represents an HTTP request. + Request struct { + Method string + Path string + Header http.Header + Body io.Reader + } + + // Response represents an HTTP response. + Response struct { + ID string + Status int + Header http.Header + Body io.ReadCloser + + Page Page // Page values + Rate Rate // Rate limit snapshot + } + + // Page represents parsed link rel values for + // pagination. + Page struct { + Next int + NextURL string + Last int + First int + Prev int + } + + // Rate represents the rate limit for the current + // client. + Rate struct { + Limit int + Remaining int + Reset int64 + } + + // ListOptions specifies optional pagination + // parameters. + ListOptions struct { + URL string + Page int + Size int + From string + To string + Sort string + } + + // GraphQLService the API to performing GraphQL queries + GraphQLService interface { + Query(ctx context.Context, q interface{}, vars map[string]interface{}) error + } + + // Client manages communication with a version control + // system API. + Client struct { + mu sync.Mutex + + // HTTP client used to communicate with the API. + Client *http.Client + + // Base URL for API requests. + BaseURL *url.URL + GraphQLURL *url.URL + + // Username is the optional user name for the client + Username string + + // Services used for communicating with the API. + Driver Driver + Apps AppService + Contents ContentService + Deployments DeploymentService + Git GitService + GraphQL GraphQLService + Organizations OrganizationService + Issues IssueService + Milestones MilestoneService + Releases ReleaseService + PullRequests PullRequestService + Repositories RepositoryService + Reviews ReviewService + Users UserService + Webhooks WebhookService + Commits CommitService + + // DumpResponse optionally specifies a function to + // dump the the response body for debugging purposes. + // This can be set to httputil.DumpResponse. + DumpResponse func(*http.Response, bool) ([]byte, error) + + // snapshot of the request rate limit. + rate Rate + } +) + +// Rate returns a snapshot of the request rate limit for +// the current client. +func (c *Client) Rate() Rate { + c.mu.Lock() + defer c.mu.Unlock() + return c.rate +} + +// SetRate set the last recorded request rate limit for +// the current client. +func (c *Client) SetRate(rate Rate) { + c.mu.Lock() + c.rate = rate + c.mu.Unlock() +} + +// Do sends an API request and returns the API response. +// The API response is JSON decoded and stored in the +// value pointed to by v, or returned as an error if an +// API error has occurred. If v implements the io.Writer +// interface, the raw response will be written to v, +// without attempting to decode it. +func (c *Client) Do(ctx context.Context, in *Request) (*Response, error) { + uri, err := c.BaseURL.Parse(in.Path) + if err != nil { + return nil, err + } + + // creates a new http request with context. + req, err := http.NewRequest(in.Method, uri.String(), in.Body) + if err != nil { + return nil, err + } + // hack to prevent the client from un-escaping the + // encoded github path parameters when parsing the url. + if strings.Contains(in.Path, "%2F") { + req.URL.Opaque = strings.Split(req.URL.RawPath, "?")[0] + } + + req = req.WithContext(ctx) + if in.Header != nil { + req.Header = in.Header + } + + // use the default client if none provided. + client := c.Client + if client == nil { + client = http.DefaultClient + } + // The callers of this method should do the closing + //nolint:bodyclose + res, err := client.Do(req) + if err != nil { + return nil, err + } + + // dumps the response for debugging purposes. + if c.DumpResponse != nil { + _, err = c.DumpResponse(res, true) + } + return newResponse(res), err +} + +// newResponse creates a new Response for the provided +// http.Response. r must not be nil. +func newResponse(r *http.Response) *Response { + res := &Response{ + Status: r.StatusCode, + Header: r.Header, + Body: r.Body, + } + res.PopulatePageValues() + return res +} + +// PopulatePageValues parses the HTTP Link response headers +// and populates the various pagination link values in the +// Response. +// +// Copyright 2013 The go-github AUTHORS. All rights reserved. +// https://github.com/google/go-github +func (r *Response) PopulatePageValues() { + links := strings.Split(r.Header.Get("Link"), ",") + for _, link := range links { + segments := strings.Split(strings.TrimSpace(link), ";") + + if len(segments) < 2 { + continue + } + + if !strings.HasPrefix(segments[0], "<") || + !strings.HasSuffix(segments[0], ">") { + continue + } + + url, err := url.Parse(segments[0][1 : len(segments[0])-1]) + if err != nil { + continue + } + + page := url.Query().Get("page") + if page == "" { + continue + } + + for _, segment := range segments[1:] { + switch strings.TrimSpace(segment) { + case `rel="next"`: + r.Page.Next, _ = strconv.Atoi(page) + case `rel="prev"`: + r.Page.Prev, _ = strconv.Atoi(page) + case `rel="first"`: + r.Page.First, _ = strconv.Atoi(page) + case `rel="last"`: + r.Page.Last, _ = strconv.Atoi(page) + } + } + } +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/commit.go b/vendor/github.com/jenkins-x/go-scm/scm/commit.go new file mode 100644 index 000000000..b17e95d80 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/commit.go @@ -0,0 +1,54 @@ +package scm + +import ( + "context" + "time" +) + +type ( + // CommitStatus for commit status + CommitStatus struct { + Status string + Created time.Time + Started time.Time + Name string + AllowFailure bool + Author CommitStatusAuthor + Description string + Sha string + TargetURL string + Finished time.Time + ID int + Ref string + Coverage float64 + } + + // CommitStatusAuthor for commit author + CommitStatusAuthor struct { + Username string + State string + WebURL string + AvatarURL string + ID int + Name string + } + + // CommitStatusUpdateOptions for update options + CommitStatusUpdateOptions struct { + ID string + Sha string + State string + Ref string + Name string + TargetURL string + Description string + Coverage float64 + PipelineID *int + } +) + +// CommitService commit interface +type CommitService interface { + UpdateCommitStatus(ctx context.Context, + repo string, sha string, options *CommitStatusUpdateOptions) (*CommitStatus, *Response, error) +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/const.go b/vendor/github.com/jenkins-x/go-scm/scm/const.go new file mode 100644 index 000000000..d2555c51c --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/const.go @@ -0,0 +1,265 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "encoding/json" + "fmt" + "strings" +) + +// State represents the commit state. +type State int + +// State values. +const ( + StateUnknown State = iota + StateFailure + StateCanceled + StateError + StateExpected + StatePending + StateRunning + StateSuccess +) + +// String returns a string representation of the State +func (s State) String() string { + switch s { + case StateUnknown: + return "unknown" + case StatePending: + return "pending" + case StateRunning: + return "running" + case StateSuccess: + return "success" + case StateFailure: + return "failure" + case StateCanceled: + return "cancelled" + case StateExpected: + return "expected" + case StateError: + return "error" + default: + return "unknown" + } +} + +// ToState converts the given text to a state +func ToState(s string) State { + switch strings.ToLower(s) { + case "pending": + return StatePending + case "running": + return StateRunning + case "success": + return StateSuccess + case "failure": + return StateFailure + case "cancelled": + return StateCanceled + case "expected": + return StateExpected + case "error": + return StateError + default: + return StateUnknown + } +} + +// MarshalJSON marshals State to JSON +func (s State) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`%q`, s.String())), nil +} + +// UnmarshalJSON unmarshals JSON to State +func (s *State) UnmarshalJSON(b []byte) error { + *s = ToState(strings.Trim(string(b), `"`)) + return nil +} + +// Action identifies webhook actions. +type Action int + +// Action values. +const ( + ActionCreate Action = iota + 1 + ActionUpdate + ActionDelete + // issues + ActionOpen + ActionReopen + ActionClose + ActionLabel + ActionUnlabel + // pull requests + ActionSync + ActionMerge + ActionAssigned + ActionUnassigned + ActionReviewRequested + ActionReviewRequestRemoved + ActionReadyForReview + ActionConvertedToDraft + // reviews + ActionEdited + ActionSubmitted + ActionDismissed + // check run / check suite + ActionCompleted +) + +// String returns the string representation of Action. +func (a Action) String() (s string) { + switch a { + case ActionCreate: + return "created" + case ActionUpdate: + return "updated" + case ActionDelete: + return "deleted" + case ActionLabel: + return "labeled" + case ActionUnlabel: + return "unlabeled" + case ActionOpen: + return "opened" + case ActionReopen: + return "reopened" + case ActionClose: + return "closed" + case ActionSync: + return "synchronized" + case ActionMerge: + return "merged" + case ActionEdited: + return "edited" + case ActionSubmitted: + return "submitted" + case ActionDismissed: + return "dismissed" + case ActionAssigned: + return "assigned" + case ActionUnassigned: + return "unassigned" + case ActionReviewRequested: + return "review_requested" + case ActionReviewRequestRemoved: + return "review_request_removed" + case ActionReadyForReview: + return "ready_for_review" + case ActionConvertedToDraft: + return "converted_to_draft" + case ActionCompleted: + return "completed" + default: + return "" + } +} + +// MarshalJSON returns the JSON-encoded Action. +func (a Action) MarshalJSON() ([]byte, error) { + return json.Marshal(a.String()) +} + +// UnmarshalJSON unmarshales the JSON-encoded Action. +func (a *Action) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + switch s { + case "created": + *a = ActionCreate + case "updated": + *a = ActionUpdate + case "deleted": + *a = ActionDelete + case "labeled": + *a = ActionLabel + case "unlabeled": + *a = ActionUnlabel + case "opened": + *a = ActionOpen + case "reopened": + *a = ActionReopen + case "closed": + *a = ActionClose + case "synchronized": + *a = ActionSync + case "merged": + *a = ActionMerge + case "completed": + *a = ActionCompleted + case "ready_for_review": + *a = ActionReadyForReview + case "converted_to_draft": + *a = ActionConvertedToDraft + case "submitted": + *a = ActionSubmitted + case "dismissed": + *a = ActionDismissed + case "edited": + *a = ActionEdited + case "assigned": + *a = ActionAssigned + case "unassigned": + *a = ActionUnassigned + case "review_requested": + *a = ActionReviewRequested + case "review_request_removed": + *a = ActionReviewRequestRemoved + } + return nil +} + +// Driver identifies source code management driver. +type Driver int + +// Driver values. +const ( + DriverUnknown Driver = iota + DriverGithub + DriverGitlab + DriverGogs + DriverGitea + DriverBitbucket + DriverStash + DriverCoding + DriverFake + DriverAzure +) + +// String returns the string representation of Driver. +func (d Driver) String() (s string) { + switch d { + case DriverGithub: + return "github" + case DriverGitlab: + return "gitlab" + case DriverGogs: + return "gogs" + case DriverGitea: + return "gitea" + case DriverBitbucket: + return "bitbucket" + case DriverStash: + return "stash" + case DriverCoding: + return "coding" + case DriverFake: + return "fake" + case DriverAzure: + return "azure" + default: + return "unknown" + } +} + +// SearchTimeFormat is a time.Time format string for ISO8601 which is the +// format that GitHub requires for times specified as part of a search query. +const SearchTimeFormat = "2006-01-02T15:04:05Z" diff --git a/vendor/github.com/jenkins-x/go-scm/scm/content.go b/vendor/github.com/jenkins-x/go-scm/scm/content.go new file mode 100644 index 000000000..231d7751c --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/content.go @@ -0,0 +1,57 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import "context" + +type ( + // Content stores the contents of a repository file. + Content struct { + Path string + Data []byte + Sha string + BlobID string + } + + // ContentParams provide parameters for creating and + // updating repository content. + ContentParams struct { + Ref string + Branch string + Message string + Data []byte + Sha string + BlobID string + Signature Signature + } + + // FileEntry returns the details of a file + FileEntry struct { + Name string + Path string + Type string + Size int + Sha string + Link string + } + + // ContentService provides access to repository content. + ContentService interface { + // Find returns the repository file content by path. + Find(ctx context.Context, repo, path, ref string) (*Content, *Response, error) + + // List the files or directories at the given path + List(ctx context.Context, repo, path, ref string, opts *ListOptions) ([]*FileEntry, *Response, error) + + // Create creates a new repository file. + Create(ctx context.Context, repo, path string, params *ContentParams) (*Response, error) + + // Update updates a repository file. + Update(ctx context.Context, repo, path string, params *ContentParams) (*Response, error) + + // Delete deletes a repository file. + Delete(ctx context.Context, repo, path string, params *ContentParams) (*Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/deploy.go b/vendor/github.com/jenkins-x/go-scm/scm/deploy.go new file mode 100644 index 000000000..0ac10871f --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/deploy.go @@ -0,0 +1,95 @@ +package scm + +import ( + "context" + "time" +) + +type ( + // Deployment represents a request to deploy a version/ref/sha in some environment + Deployment struct { + ID string + Namespace string + Name string + Link string + Sha string + Ref string + Task string + FullName string + Description string + OriginalEnvironment string + Environment string + RepositoryLink string + StatusLink string + Author *User + Created time.Time + Updated time.Time + TransientEnvironment bool + ProductionEnvironment bool + Payload interface{} + } + + // DeploymentInput the input to create a new deployment + DeploymentInput struct { + Ref string + Task string + Payload string + Environment string + Description string + RequiredContexts []string + AutoMerge bool + TransientEnvironment bool + ProductionEnvironment bool + } + + // DeploymentStatus represents the status of a deployment + DeploymentStatus struct { + ID string + State string + Author *User + Description string + Environment string + DeploymentLink string + EnvironmentLink string + LogLink string + RepositoryLink string + TargetLink string + Created time.Time + Updated time.Time + } + + // DeploymentStatusInput the input to creating a status of a deployment + DeploymentStatusInput struct { + State string + TargetLink string + LogLink string + Description string + Environment string + EnvironmentLink string + AutoInactive bool + } + + // DeploymentService a service for working with deployments and deployment services + DeploymentService interface { + // Find find a deployment by id. + Find(ctx context.Context, repoFullName string, deploymentID string) (*Deployment, *Response, error) + + // List returns a list of deployments. + List(ctx context.Context, repoFullName string, opts *ListOptions) ([]*Deployment, *Response, error) + + // Create creates a new deployment. + Create(ctx context.Context, repoFullName string, deployment *DeploymentInput) (*Deployment, *Response, error) + + // Delete deletes a deployment. + Delete(ctx context.Context, repoFullName string, deploymentID string) (*Response, error) + + // FindStatus find a deployment status by id. + FindStatus(ctx context.Context, repoFullName string, deploymentID string, statusID string) (*DeploymentStatus, *Response, error) + + // List returns a list of deployments. + ListStatus(ctx context.Context, repoFullName string, deploymentID string, options *ListOptions) ([]*DeploymentStatus, *Response, error) + + // Create creates a new deployment. + CreateStatus(ctx context.Context, repoFullName string, deploymentID string, deployment *DeploymentStatusInput) (*DeploymentStatus, *Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/bool.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/bool.go new file mode 100644 index 000000000..f020eb71b --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/bool.go @@ -0,0 +1,48 @@ +// Copyright (c) 2014, Greg Roseberry +// All rights reserved. + +package null + +import ( + "database/sql" + "encoding/json" + "fmt" + "reflect" +) + +// Bool is a nullable bool. It does not consider false values +// to be null. It will decode to null, not false, if null. +type Bool struct { + sql.NullBool +} + +// UnmarshalJSON implements json.Unmarshaler. It supports +// number and null input. 0 will not be considered a null +// Bool. It also supports unmarshalling a sql.NullBool. +func (b *Bool) UnmarshalJSON(data []byte) error { + var v interface{} + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + switch x := v.(type) { + case bool: + b.Bool = x + case map[string]interface{}: + err = json.Unmarshal(data, &b.NullBool) + case nil: + b.Valid = false + return nil + default: + err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Bool", reflect.TypeOf(v).Name()) + } + b.Valid = err == nil + return err +} + +// IsZero returns true for invalid Bools, for future omitempty +// support (Go 1.4?). A non-null Bool with a 0 value will not be +// considered zero. +func (b Bool) IsZero() bool { + return !b.Valid +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/int.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/int.go new file mode 100644 index 000000000..90057f5a7 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/int.go @@ -0,0 +1,58 @@ +// Copyright (c) 2014, Greg Roseberry +// All rights reserved. + +package null + +import ( + "database/sql" + "encoding/json" + "fmt" + "reflect" + "strconv" +) + +// Int is an nullable int64. It does not consider zero +// values to be null. It will decode to null, not zero, +// if null. +type Int struct { + sql.NullInt64 +} + +// UnmarshalJSON implements json.Unmarshaler. It supports +// number and null input. 0 will not be considered a null +// Int. It also supports unmarshalling a sql.NullInt64. +func (i *Int) UnmarshalJSON(data []byte) error { + var v interface{} + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + switch x := v.(type) { + case float64: + // Unmarshal again, directly to int64, to avoid intermediate float64 + err = json.Unmarshal(data, &i.Int64) + case string: + str := x + if str == "" { + i.Valid = false + return nil + } + i.Int64, err = strconv.ParseInt(str, 10, 64) + case map[string]interface{}: + err = json.Unmarshal(data, &i.NullInt64) + case nil: + i.Valid = false + return nil + default: + err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int", reflect.TypeOf(v).Name()) + } + i.Valid = err == nil + return err +} + +// IsZero returns true for invalid Ints, for future omitempty +// support (Go 1.4?). A non-null Int with a 0 value will not +// be considered zero. +func (i Int) IsZero() bool { + return !i.Valid +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/string.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/string.go new file mode 100644 index 000000000..b106f986a --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/internal/null/string.go @@ -0,0 +1,49 @@ +// Copyright (c) 2014, Greg Roseberry +// All rights reserved. + +package null + +import ( + "database/sql" + "encoding/json" + "fmt" + "reflect" +) + +// String is a nullable string. It supports SQL and JSON +// serialization. It will marshal to null if null. Blank +// string input will be considered null. +type String struct { + sql.NullString +} + +// UnmarshalJSON implements json.Unmarshaler. +// It supports string and null input. Blank string input +// does not produce a null String. It also supports +// unmarshalling a sql.NullString. +func (s *String) UnmarshalJSON(data []byte) error { + var v interface{} + err := json.Unmarshal(data, &v) + if err != nil { + return err + } + switch x := v.(type) { + case string: + s.String = x + case map[string]interface{}: + err = json.Unmarshal(data, &s.NullString) + case nil: + s.Valid = false + return nil + default: + err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.String", reflect.TypeOf(v).Name()) + } + s.Valid = err == nil + return err +} + +// IsZero returns true for null strings, for potential +// future omitempty support. +func (s String) IsZero() bool { + return !s.Valid +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/content.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/content.go new file mode 100644 index 000000000..603d7d332 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/content.go @@ -0,0 +1,99 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "bytes" + "context" + "fmt" + "net/url" + + "github.com/jenkins-x/go-scm/scm" +) + +type contentService struct { + client *wrapper +} + +func (s *contentService) Find(ctx context.Context, repo, path, ref string) (*scm.Content, *scm.Response, error) { + namespace, name := scm.Split(repo) + endpoint := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/raw/%s?at=%s", namespace, name, path, url.QueryEscape(ref)) + buf := new(bytes.Buffer) + res, err := s.client.do(ctx, "GET", endpoint, nil, buf) + return &scm.Content{ + Path: path, + Data: buf.Bytes(), + }, res, err +} + +func (s *contentService) List(ctx context.Context, repo, path, ref string, opts *scm.ListOptions) ([]*scm.FileEntry, *scm.Response, error) { + namespace, name := scm.Split(repo) + endpoint := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/files/%s?at=%s&%s", namespace, name, path, ref, encodeListOptions(opts)) + out := new(contents) + res, err := s.client.do(ctx, "GET", endpoint, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertFileEntryList(out), res, err +} + +func (s *contentService) Create(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { + namespace, repoName := scm.Split(repo) + endpoint := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/browse/%s", namespace, repoName, path) + message := params.Message + if params.Signature.Name != "" && params.Signature.Email != "" { + message = fmt.Sprintf("%s\nSigned-off-by: %s <%s>", params.Message, params.Signature.Name, params.Signature.Email) + } + in := &contentCreateUpdate{ + Message: message, + Branch: params.Branch, + Content: params.Data, + } + return s.client.do(ctx, "PUT", endpoint, in, nil) +} + +func (s *contentService) Update(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { + namespace, repoName := scm.Split(repo) + endpoint := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/browse/%s", namespace, repoName, path) + message := params.Message + if params.Signature.Name != "" && params.Signature.Email != "" { + message = fmt.Sprintf("%s\nSigned-off-by: %s <%s>", params.Message, params.Signature.Name, params.Signature.Email) + } + in := &contentCreateUpdate{ + Message: message, + Branch: params.Branch, + Content: params.Data, + Sha: params.Sha, + } + + return s.client.do(ctx, "PUT", endpoint, in, nil) +} + +func (s *contentService) Delete(ctx context.Context, repo, path string, params *scm.ContentParams) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +type contents struct { + pagination + Values []string `json:"values"` +} + +type contentCreateUpdate struct { + Branch string `json:"branch"` + Message string `json:"message"` + Content []byte `json:"content"` + Sha string `json:"sourceCommitId"` +} + +func convertFileEntryList(from *contents) []*scm.FileEntry { + var to []*scm.FileEntry + for _, v := range from.Values { + to = append(to, &scm.FileEntry{ + Path: v, + }) + } + return to +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/git.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/git.go new file mode 100644 index 000000000..c311be909 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/git.go @@ -0,0 +1,342 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "context" + "fmt" + "net/url" + "strings" + "time" + + "github.com/jenkins-x/go-scm/scm" +) + +// TODO(bradrydzewski) commit link is an empty string. +// TODO(bradrydzewski) commit list is not supported; behavior incompatible with other drivers. + +type gitService struct { + client *wrapper +} + +func (s *gitService) FindRef(ctx context.Context, repo, ref string) (string, *scm.Response, error) { + ref = strings.TrimPrefix(ref, "heads/") + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/commits/%s", namespace, name, url.PathEscape(ref)) + out := commit{} + res, err := s.client.do(ctx, "GET", path, nil, &out) + if err != nil { + return "", res, err + } + if out.ID != "" { + return out.ID, res, err + } + idx := strings.LastIndex(ref, "/") + if idx >= 0 { + ref = ref[idx+1:] + } + return ref, res, err +} + +func (s *gitService) CreateRef(ctx context.Context, repo, ref, sha string) (*scm.Reference, *scm.Response, error) { + namespace, repoName := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/branches", namespace, repoName) + out := new(branch) + in := &createBranch{ + Name: ref, + StartPoint: sha, + } + resp, err := s.client.do(ctx, "POST", path, in, out) + return convertBranch(out), resp, err +} + +func (s *gitService) DeleteRef(ctx context.Context, repo, ref string) (*scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/branch-utils/latest/projects/%s/repos/%s/branches", namespace, name) + in := deleteRefInput{Name: ref} + return s.client.do(ctx, "DELETE", path, &in, nil) +} + +func (s *gitService) FindBranch(ctx context.Context, repo, branch string) (*scm.Reference, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/branches?filterText=%s", namespace, name, url.QueryEscape(branch)) + out := new(branches) + res, err := s.client.do(ctx, "GET", path, nil, out) + if err != nil { + return nil, res, err + } + for _, v := range out.Values { + if v.DisplayID == branch { + return convertBranch(v), res, err + } + } + return nil, res, scm.ErrNotFound +} + +func (s *gitService) FindCommit(ctx context.Context, repo, ref string) (*scm.Commit, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/commits/%s", namespace, name, url.PathEscape(ref)) + out := new(commit) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertCommit(out), res, err +} + +func (s *gitService) FindTag(ctx context.Context, repo, tag string) (*scm.Reference, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/tags?filterText=%s", namespace, name, url.QueryEscape(tag)) + out := new(branches) + res, err := s.client.do(ctx, "GET", path, nil, out) + if err != nil { + return nil, res, err + } + for _, v := range out.Values { + if v.DisplayID == tag { + return convertTag(v), res, err + } + } + return nil, res, scm.ErrNotFound +} + +func (s *gitService) ListBranches(ctx context.Context, repo string, opts *scm.ListOptions) ([]*scm.Reference, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/branches?%s", namespace, name, encodeListOptions(opts)) + out := new(branches) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertBranchList(out), res, err +} + +func (s *gitService) ListCommits(ctx context.Context, repo string, opts scm.CommitListOptions) ([]*scm.Commit, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *gitService) ListTags(ctx context.Context, repo string, opts *scm.ListOptions) ([]*scm.Reference, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/tags?%s", namespace, name, encodeListOptions(opts)) + out := new(branches) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertTagList(out), res, err +} + +func (s *gitService) ListChanges(ctx context.Context, repo, ref string, opts *scm.ListOptions) ([]*scm.Change, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/commits/%s/changes?%s", namespace, name, url.PathEscape(ref), encodeListOptions(opts)) + out := new(diffstats) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertDiffstats(out), res, err +} + +func (s *gitService) CompareCommits(ctx context.Context, repo, ref1, ref2 string, opts *scm.ListOptions) ([]*scm.Change, *scm.Response, error) { + namespace, name := scm.Split(repo) + opts.From = url.PathEscape(ref1) + opts.To = url.PathEscape(ref2) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/compare/changes?%s", namespace, name, encodeListOptions(opts)) + out := new(diffstats) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertDiffstats(out), res, err +} + +type deleteRefInput struct { + DryRun bool `json:"dryRun,omitempty"` + EndPoint string `json:"endPoint,omitempty"` + Name string `json:"name,omitempty"` +} + +type branch struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + Type string `json:"type"` + LatestCommit string `json:"latestCommit"` + LatestChangeset string `json:"latestChangeset"` + IsDefault bool `json:"isDefault"` +} + +type createBranch struct { + Name string `json:"name"` + StartPoint string `json:"startPoint"` +} + +type branches struct { + pagination + Values []*branch `json:"values"` +} + +type diffstats struct { + pagination + Values []*diffstat +} + +type diffpath struct { + Components []string `json:"components"` + Parent string `json:"parent"` + Name string `json:"name"` + Extension string `json:"extension"` + ToString string `json:"toString"` +} + +type diffstat struct { + ContentID string `json:"contentId"` + FromContentID string `json:"fromContentId"` + Path diffpath `json:"path"` + SrcPath *diffpath `json:"srcPath,omitempty"` + PercentUnchanged int `json:"percentUnchanged"` + Type string `json:"type"` + NodeType string `json:"nodeType"` + SrcExecutable bool `json:"srcExecutable"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + Properties struct { + GitChangeType string `json:"gitChangeType"` + } `json:"properties"` +} + +type commit struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + Author struct { + Name string `json:"name"` + EmailAddress string `json:"emailAddress"` + ID int `json:"id"` + DisplayName string `json:"displayName"` + Active bool `json:"active"` + Slug string `json:"slug"` + Type string `json:"type"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + } `json:"author"` + AuthorTimestamp int64 `json:"authorTimestamp"` + Committer struct { + Name string `json:"name"` + EmailAddress string `json:"emailAddress"` + ID int `json:"id"` + DisplayName string `json:"displayName"` + Active bool `json:"active"` + Slug string `json:"slug"` + Type string `json:"type"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + } `json:"committer"` + CommitterTimestamp int64 `json:"committerTimestamp"` + Message string `json:"message"` + Parents []struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + Author struct { + Name string `json:"name"` + EmailAddress string `json:"emailAddress"` + } `json:"author"` + AuthorTimestamp int64 `json:"authorTimestamp"` + Committer struct { + Name string `json:"name"` + EmailAddress string `json:"emailAddress"` + } `json:"committer"` + CommitterTimestamp int64 `json:"committerTimestamp"` + Message string `json:"message"` + Parents []struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + } `json:"parents"` + } `json:"parents"` +} + +func convertDiffstats(from *diffstats) []*scm.Change { + to := []*scm.Change{} + for _, v := range from.Values { + to = append(to, convertDiffstat(v)) + } + return to +} + +func convertDiffstat(from *diffstat) *scm.Change { + to := &scm.Change{ + Path: from.Path.ToString, + Added: from.Type == "ADD", + Modified: from.Type == "MODIFY", + Renamed: from.Type == "MOVE", + Deleted: from.Type == "DELETE", + } + if from.SrcPath != nil { + to.PreviousPath = from.SrcPath.ToString + } + return to +} + +func convertCommit(from *commit) *scm.Commit { + return &scm.Commit{ + Message: from.Message, + Sha: from.ID, + // Link: "%s/projects/%s/repos/%s/commits/%s", + Author: scm.Signature{ + Name: from.Author.DisplayName, + Email: from.Author.EmailAddress, + Date: time.Unix(from.AuthorTimestamp/1000, 0), + Login: from.Author.Slug, + Avatar: avatarLink(from.Author.EmailAddress), + }, + Committer: scm.Signature{ + Name: from.Committer.DisplayName, + Email: from.Committer.EmailAddress, + Date: time.Unix(from.CommitterTimestamp/1000, 0), + Login: from.Committer.Slug, + Avatar: avatarLink(from.Committer.EmailAddress), + }, + } +} + +func convertBranchList(from *branches) []*scm.Reference { + to := []*scm.Reference{} + for _, v := range from.Values { + to = append(to, convertBranch(v)) + } + return to +} + +func convertBranch(from *branch) *scm.Reference { + return &scm.Reference{ + Name: scm.TrimRef(from.DisplayID), + Path: scm.ExpandRef(from.DisplayID, "refs/heads/"), + Sha: from.LatestCommit, + } +} + +func convertTagList(from *branches) []*scm.Reference { + to := []*scm.Reference{} + for _, v := range from.Values { + to = append(to, convertTag(v)) + } + return to +} + +func convertTag(from *branch) *scm.Reference { + return &scm.Reference{ + Name: scm.TrimRef(from.DisplayID), + Path: scm.ExpandRef(from.DisplayID, "refs/tags/"), + Sha: from.LatestCommit, + } +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/issue.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/issue.go new file mode 100644 index 000000000..35f8c04fc --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/issue.go @@ -0,0 +1,117 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "context" + "fmt" + + "github.com/jenkins-x/go-scm/scm/labels" + + "github.com/jenkins-x/go-scm/scm" +) + +type issueService struct { + client *wrapper +} + +func (s *issueService) Search(context.Context, scm.SearchOptions) ([]*scm.SearchIssue, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) AssignIssue(ctx context.Context, repo string, number int, logins []string) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) UnassignIssue(ctx context.Context, repo string, number int, logins []string) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) ListEvents(context.Context, string, int, *scm.ListOptions) ([]*scm.ListedIssueEvent, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) ListLabels(ctx context.Context, repo string, number int, opts *scm.ListOptions) ([]*scm.Label, *scm.Response, error) { + // Get all comments, parse out labels (removing and added based off time) + cs, res, err := s.ListComments(ctx, repo, number, opts) + if err == nil { + l, err := labels.ConvertLabelComments(cs) + return l, res, err + } + return nil, res, err +} + +func (s *issueService) AddLabel(ctx context.Context, repo string, number int, label string) (*scm.Response, error) { + input := labels.CreateLabelAddComment(label) + _, res, err := s.CreateComment(ctx, repo, number, input) + return res, err +} + +func (s *issueService) DeleteLabel(ctx context.Context, repo string, number int, label string) (*scm.Response, error) { + input := labels.CreateLabelRemoveComment(label) + _, res, err := s.CreateComment(ctx, repo, number, input) + return res, err +} + +func (s *issueService) Find(ctx context.Context, repo string, number int) (*scm.Issue, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) FindComment(ctx context.Context, repo string, index, id int) (*scm.Comment, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) List(ctx context.Context, repo string, opts scm.IssueListOptions) ([]*scm.Issue, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) ListComments(ctx context.Context, repo string, index int, opts *scm.ListOptions) ([]*scm.Comment, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) Create(ctx context.Context, repo string, input *scm.IssueInput) (*scm.Issue, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) CreateComment(ctx context.Context, repo string, number int, in *scm.CommentInput) (*scm.Comment, *scm.Response, error) { + input := pullRequestCommentInput{Text: in.Body} + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/issues/%d/comments", namespace, name, number) + out := new(pullRequestComment) + res, err := s.client.do(ctx, "POST", path, &input, out) + return convertPullRequestComment(out), res, err +} + +func (s *issueService) DeleteComment(ctx context.Context, repo string, number, id int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) EditComment(ctx context.Context, repo string, number, id int, input *scm.CommentInput) (*scm.Comment, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *issueService) Close(ctx context.Context, repo string, number int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) Reopen(ctx context.Context, repo string, number int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) Lock(ctx context.Context, repo string, number int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) Unlock(ctx context.Context, repo string, number int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) SetMilestone(ctx context.Context, repo string, issueID, number int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *issueService) ClearMilestone(ctx context.Context, repo string, id int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/milestone.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/milestone.go new file mode 100644 index 000000000..6ec3c2a97 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/milestone.go @@ -0,0 +1,31 @@ +package stash + +import ( + "context" + + "github.com/jenkins-x/go-scm/scm" +) + +type milestoneService struct { + client *wrapper +} + +func (s *milestoneService) Find(ctx context.Context, repo string, id int) (*scm.Milestone, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *milestoneService) List(ctx context.Context, repo string, opts scm.MilestoneListOptions) ([]*scm.Milestone, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *milestoneService) Create(ctx context.Context, repo string, input *scm.MilestoneInput) (*scm.Milestone, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *milestoneService) Delete(ctx context.Context, repo string, id int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *milestoneService) Update(ctx context.Context, repo string, id int, input *scm.MilestoneInput) (*scm.Milestone, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/multipart.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/multipart.go new file mode 100644 index 000000000..4da89fdb7 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/multipart.go @@ -0,0 +1,26 @@ +package stash + +import "mime/multipart" + +type MultipartWriter struct { + Writer *multipart.Writer + Error error +} + +func (mw *MultipartWriter) Write(f, v string) { + if mw.Error != nil { + return + } + if v == "" { + return + } + mw.Error = mw.Writer.WriteField(f, v) +} + +func (mw *MultipartWriter) Close() { + mw.Writer.Close() +} + +func (mw *MultipartWriter) FormDataContentType() string { + return mw.Writer.FormDataContentType() +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/org.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/org.go new file mode 100644 index 000000000..8d2ed8bc5 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/org.go @@ -0,0 +1,198 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "context" + "fmt" + + "github.com/jenkins-x/go-scm/scm" +) + +type organizationService struct { + client *wrapper +} + +type project struct { + ID int `json:"id,omitempty"` + Key string `json:"key"` + Name string `json:"name"` + Description string `json:"description"` +} + +type projectResponse struct { + pagination + Values []project `json:"values"` +} + +func (s *organizationService) Create(context.Context, *scm.OrganizationInput) (*scm.Organization, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *organizationService) Delete(context.Context, string) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *organizationService) ListTeams(ctx context.Context, org string, opts *scm.ListOptions) ([]*scm.Team, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *organizationService) ListTeamMembers(ctx context.Context, id int, role string, opts *scm.ListOptions) ([]*scm.TeamMember, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *organizationService) ListOrgMembers(ctx context.Context, org string, opts *scm.ListOptions) ([]*scm.TeamMember, *scm.Response, error) { + opts.Size = 1000 + path := fmt.Sprintf("rest/api/1.0/projects/%s/permissions/users?%s", org, encodeListOptions(opts)) + out := new(participants) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertParticipantsToTeamMembers(out), res, err +} + +// IsMember checks if the user opening a pull request is part of the org +func (s *organizationService) IsMember(ctx context.Context, org, user string) (bool, *scm.Response, error) { + opts := &scm.ListOptions{ + Size: 1000, + } + // Check if user has permissions in the project + path := fmt.Sprintf("rest/api/1.0/projects/%s/permissions/users?%s", org, encodeListOptions(opts)) + out := new(participants) + res, err := s.client.do(ctx, "GET", path, nil, out) + if err != nil { + return false, res, err + } + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + for _, participant := range out.Values { + if participant.User.Name == user || participant.User.Slug == user { + return true, res, err + } + } + // Retrieve the list of groups attached to the project + groups, err := getProjectGroups(ctx, org, s, opts) + if err != nil { + return false, res, err + } + for _, pgroup := range groups { + // Get list of users in a group + users, err := usersInGroups(ctx, pgroup.Group.Name, s, opts) + if err != nil { + return false, res, err + } + for _, member := range users { + if member.Name == user || member.Slug == user { + return true, res, err + } + } + } + return false, res, err +} + +// getProjectGroups returns the groups which have some permissions in the project +func getProjectGroups(ctx context.Context, org string, os *organizationService, opts *scm.ListOptions) ([]*projGroup, error) { + path := fmt.Sprintf("rest/api/1.0/projects/%s/permissions/groups?%s", org, encodeListOptions(opts)) + out := new(projGroups) + res, err := os.client.do(ctx, "GET", path, nil, out) + if err != nil { + return nil, err + } + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return out.Values, nil +} + +// usersInGroups returns the members/users in a group +func usersInGroups(ctx context.Context, group string, os *organizationService, opts *scm.ListOptions) ([]*member, error) { + path := fmt.Sprintf("rest/api/1.0/admin/groups/more-members?context=%s&%s", group, encodeListOptions(opts)) + out := new(members) + res, err := os.client.do(ctx, "GET", path, nil, out) + if err != nil { + return nil, err + } + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return out.Values, nil +} + +func (s *organizationService) IsAdmin(ctx context.Context, org, user string) (bool, *scm.Response, error) { + opts := &scm.ListOptions{ + Size: 1000, + } + path := fmt.Sprintf("rest/api/1.0/projects/%s/permissions/users?%s", org, encodeListOptions(opts)) + out := new(participants) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + for _, participant := range out.Values { + if (participant.User.Name == user || participant.User.Slug == user) && apiStringToPermission(participant.Permission) == scm.AdminPermission { + return true, res, err + } + } + return false, res, err +} + +func (s *organizationService) Find(ctx context.Context, name string) (*scm.Organization, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *organizationService) List(ctx context.Context, opts *scm.ListOptions) ([]*scm.Organization, *scm.Response, error) { + path := fmt.Sprintf("rest/api/1.0/projects?%s", encodeListOptions(opts)) + out := new(projectResponse) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertProjectList(out.Values), res, err +} + +func (s *organizationService) ListPendingInvitations(ctx context.Context, org string, opts *scm.ListOptions) ([]*scm.OrganizationPendingInvite, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *organizationService) AcceptOrganizationInvitation(ctx context.Context, org string) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *organizationService) ListMemberships(ctx context.Context, opts *scm.ListOptions) ([]*scm.Membership, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func convertParticipantsToTeamMembers(from *participants) []*scm.TeamMember { + var teamMembers []*scm.TeamMember + for _, f := range from.Values { + teamMembers = append(teamMembers, &scm.TeamMember{Login: f.User.Name}) + } + return teamMembers +} + +func convertProjectList(from []project) []*scm.Organization { + var to []*scm.Organization + for _, v := range from { + to = append(to, convertProject(v)) + } + return to +} + +func convertProject(from project) *scm.Organization { + return &scm.Organization{ + ID: from.ID, + Name: from.Key, + Avatar: "", + Permissions: scm.Permissions{ + MembersCreateInternal: true, + MembersCreatePublic: true, + MembersCreatePrivate: true, + }, + } +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/pr.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/pr.go new file mode 100644 index 000000000..490117bfd --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/pr.go @@ -0,0 +1,520 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/jenkins-x/go-scm/scm/labels" + + "github.com/jenkins-x/go-scm/scm" +) + +type pullService struct { + client *wrapper +} + +func (s *pullService) Find(ctx context.Context, repo string, number int) (*scm.PullRequest, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d", namespace, name, number) + out := new(pullRequest) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertPullRequest(out), res, err +} + +func (s *pullService) FindComment(ctx context.Context, repo string, number, id int) (*scm.Comment, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/comments/%d", namespace, name, number, id) + out := new(pullRequestComment) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertPullRequestComment(out), res, err +} + +func (s *pullService) List(ctx context.Context, repo string, opts *scm.PullRequestListOptions) ([]*scm.PullRequest, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests", namespace, name) + out := new(pullRequests) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertPullRequests(out), res, err +} + +func (s *pullService) ListChanges(ctx context.Context, repo string, number int, opts *scm.ListOptions) ([]*scm.Change, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/changes", namespace, name, number) + out := new(diffstats) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertDiffstats(out), res, err +} + +func (s *pullService) ListCommits(ctx context.Context, repo string, number int, opts *scm.ListOptions) ([]*scm.Commit, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *pullService) ListLabels(ctx context.Context, repo string, number int, opts *scm.ListOptions) ([]*scm.Label, *scm.Response, error) { + // Get all comments, parse out labels (removing and added based off time) + cs, res, err := s.ListComments(ctx, repo, number, opts) + if err == nil { + l, err := labels.ConvertLabelComments(cs) + return l, res, err + } + return nil, res, err +} + +func (s *pullService) AddLabel(ctx context.Context, repo string, number int, label string) (*scm.Response, error) { + input := labels.CreateLabelAddComment(label) + _, res, err := s.CreateComment(ctx, repo, number, input) + return res, err +} + +func (s *pullService) DeleteLabel(ctx context.Context, repo string, number int, label string) (*scm.Response, error) { + input := labels.CreateLabelRemoveComment(label) + _, res, err := s.CreateComment(ctx, repo, number, input) + return res, err +} + +func (s *pullService) ListEvents(context.Context, string, int, *scm.ListOptions) ([]*scm.ListedIssueEvent, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *pullService) ListComments(ctx context.Context, repo string, number int, opts *scm.ListOptions) ([]*scm.Comment, *scm.Response, error) { + // TODO(bradrydzewski) the challenge with comments is that we need to use + // the activities endpoint, which returns entries that may or may not be + // comments. This complicates how we handle counts and pagination. + + // GET /rest/api/1.0/projects/PRJ/repos/my-repo/pull-requests/1/activities + + projectName, repoName := scm.Split(repo) + out := new(pullRequestActivities) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/activities", projectName, repoName, number) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertPullRequestActivities(out), res, err +} + +func (s *pullService) Merge(ctx context.Context, repo string, number int, options *scm.PullRequestMergeOptions) (*scm.Response, error) { + namespace, name := scm.Split(repo) + getPath := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d", namespace, name, number) + getOut := new(pullRequest) + res, err := s.client.do(ctx, "GET", getPath, nil, getOut) + if err != nil { + return res, err + } + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/merge?version=%d", namespace, name, number, getOut.Version) + res, err = s.client.do(ctx, "POST", path, nil, nil) + return res, err +} + +type prUpdateInput struct { + ID int `json:"id"` + Version int `json:"version"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` +} + +func (s *pullService) Update(ctx context.Context, repo string, number int, prInput *scm.PullRequestInput) (*scm.PullRequest, *scm.Response, error) { + // TODO: Figure out how to handle updating the destination branch, because I can't find the syntax currently (apb) + namespace, name := scm.Split(repo) + getPath := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d", namespace, name, number) + getOut := new(pullRequest) + res, err := s.client.do(ctx, "GET", getPath, nil, getOut) + if err != nil { + return nil, res, err + } + input := &prUpdateInput{ + ID: getOut.ID, + Version: getOut.Version, + Title: prInput.Title, + Description: prInput.Body, + } + out := new(pullRequest) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d", namespace, name, number) + res, err = s.client.do(ctx, "PUT", path, input, out) + return convertPullRequest(out), res, err +} + +func (s *pullService) Close(ctx context.Context, repo string, number int) (*scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/decline", namespace, name, number) + res, err := s.client.do(ctx, "POST", path, nil, nil) + return res, err +} + +func (s *pullService) Reopen(ctx context.Context, repo string, number int) (*scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/reopen", namespace, name, number) + res, err := s.client.do(ctx, "POST", path, nil, nil) + return res, err +} + +func (s *pullService) CreateComment(ctx context.Context, repo string, number int, in *scm.CommentInput) (*scm.Comment, *scm.Response, error) { + input := pullRequestCommentInput{Text: in.Body} + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/comments", namespace, name, number) + out := new(pullRequestComment) + res, err := s.client.do(ctx, "POST", path, &input, out) + return convertPullRequestComment(out), res, err +} + +func (s *pullService) DeleteComment(ctx context.Context, repo string, number, id int) (*scm.Response, error) { + namespace, name := scm.Split(repo) + existingComment, res, err := s.FindComment(ctx, repo, number, id) + if err != nil { + if res != nil && res.Status == http.StatusNotFound { + return res, nil + } + return res, err + } + if existingComment == nil { + return res, nil + } + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/comments/%d?version=%d", namespace, name, number, id, existingComment.Version) + return s.client.do(ctx, "DELETE", path, nil, nil) +} + +func (s *pullService) EditComment(ctx context.Context, repo string, number, id int, in *scm.CommentInput) (*scm.Comment, *scm.Response, error) { + input := pullRequestCommentInput{Text: in.Body} + namespace, name := scm.Split(repo) + existingComment, res, err := s.FindComment(ctx, repo, number, id) + if err != nil { + if res != nil && res.Status == http.StatusNotFound { + return nil, res, nil + } + return nil, res, err + } + if existingComment == nil { + return nil, res, nil + } + input.Version = existingComment.Version + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/comments/%d", namespace, name, number, id) + out := new(pullRequestComment) + res, err = s.client.do(ctx, "PUT", path, &input, out) + return convertPullRequestComment(out), res, err +} + +func (s *pullService) AssignIssue(ctx context.Context, repo string, number int, logins []string) (*scm.Response, error) { + return s.RequestReview(ctx, repo, number, logins) +} + +func (s *pullService) UnassignIssue(ctx context.Context, repo string, number int, logins []string) (*scm.Response, error) { + return s.UnrequestReview(ctx, repo, number, logins) +} + +func (s *pullService) Create(ctx context.Context, repo string, input *scm.PullRequestInput) (*scm.PullRequest, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests", namespace, name) + + in := &createPRInput{ + Title: input.Title, + Description: input.Body, + State: "OPEN", + Open: true, + Closed: false, + FromRef: createPRInputRef{ + ID: fmt.Sprintf("refs/heads/%s", input.Head), + Repository: createPRInputRepo{ + Slug: name, + Project: createPRInputRepoProject{Key: namespace}, + }, + }, + ToRef: createPRInputRef{ + ID: fmt.Sprintf("refs/heads/%s", input.Base), + Repository: createPRInputRepo{ + Slug: name, + Project: createPRInputRepoProject{Key: namespace}, + }, + }, + Locked: false, + } + + out := new(pullRequest) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertPullRequest(out), res, err +} + +func (s *pullService) RequestReview(ctx context.Context, repo string, number int, logins []string) (*scm.Response, error) { + namespace, name := scm.Split(repo) + missing := scm.MissingUsers{ + Action: "request a PR review from", + } + + var res *scm.Response + var err error + + for _, l := range logins { + input := pullRequestAssignInput{ + User: struct { + Name string `json:"name"` + }{ + Name: l, + }, + Approved: false, + Status: "UNAPPROVED", + } + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/participants/%s", namespace, name, number, url.PathEscape(l)) + res, err = s.client.do(ctx, "PUT", path, &input, nil) + if err != nil && res != nil { + missing.Users = append(missing.Users, l) + } else if err != nil { + return nil, fmt.Errorf("failed to add reviewer to PR. errmsg: %v", err) + } + if len(missing.Users) > 0 { + return nil, missing + } + } + return res, err +} + +func (s *pullService) UnrequestReview(ctx context.Context, repo string, number int, logins []string) (*scm.Response, error) { + namespace, name := scm.Split(repo) + var res *scm.Response + var err error + + for _, l := range logins { + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/participants/%s", namespace, name, number, url.PathEscape(l)) + res, err = s.client.do(ctx, "DELETE", path, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to add reviewer to PR. errmsg: %v", err) + } + } + return res, err +} + +func (s *pullService) SetMilestone(ctx context.Context, repo string, prID, number int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *pullService) ClearMilestone(ctx context.Context, repo string, prID int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *pullService) DeletePullRequest(ctx context.Context, repo string, prID int) (*scm.Response, error) { + namespace, name := scm.Split(repo) + // in Bitbucket server (stash) to delete a pull request `version` of the pull request + // must be provided in body of the request so it's worth fetching pull request via rest api + // to get latest version of the pull request. + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/pull-requests/%d", namespace, name, prID) + out := new(pullRequest) + _, err := s.client.do(ctx, http.MethodGet, path, nil, out) + if err != nil { + return nil, fmt.Errorf("failed to get pull request. err: %v", err) + } + in := map[string]int{"version": out.Version} + return s.client.do(ctx, http.MethodDelete, path, &in, nil) +} + +type createPRInput struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + State string `json:"state,omitempty"` + Open bool `json:"open,omitempty"` + Closed bool `json:"closed,omitempty"` + FromRef createPRInputRef `json:"fromRef,omitempty"` + ToRef createPRInputRef `json:"toRef,omitempty"` + Locked bool `json:"locked,omitempty"` +} + +type createPRInputRef struct { + ID string `json:"id"` + Repository createPRInputRepo `json:"repository"` +} + +type createPRInputRepo struct { + Slug string `json:"slug"` + Project createPRInputRepoProject `json:"project"` +} + +type createPRInputRepoProject struct { + Key string `json:"key"` +} + +type pullRequest struct { + ID int `json:"id"` + Version int `json:"version"` + Title string `json:"title"` + Description string `json:"description"` + State string `json:"state"` + Open bool `json:"open"` + Closed bool `json:"closed"` + CreatedDate int64 `json:"createdDate"` + UpdatedDate int64 `json:"updatedDate"` + FromRef prRepoRef `json:"fromRef"` + ToRef prRepoRef `json:"toRef"` + Locked bool `json:"locked"` + Author prUser `json:"author"` + Reviewers []prUser `json:"reviewers"` + Participants []interface{} `json:"participants"` + Links struct { + Self []link `json:"self"` + } `json:"links"` +} + +type prRepoRef struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + LatestCommit string `json:"latestCommit"` + Repository repository `json:"repository"` +} + +type prUser struct { + User user `json:"user"` + LastReviewedCommit string `json:"lastReviewedCommit"` + Role string `json:"role"` + Approved bool `json:"approved"` + Status string `json:"status"` +} + +type pullRequests struct { + pagination + Values []*pullRequest `json:"values"` +} + +func convertPullRequests(from *pullRequests) []*scm.PullRequest { + to := []*scm.PullRequest{} + for _, v := range from.Values { + to = append(to, convertPullRequest(v)) + } + return to +} + +func convertPullRequest(from *pullRequest) *scm.PullRequest { + fork := scm.Join( + from.FromRef.Repository.Project.Key, + from.FromRef.Repository.Slug, + ) + toRepo := convertRepository(&from.ToRef.Repository) + fromRepo := convertRepository(&from.FromRef.Repository) + return &scm.PullRequest{ + Number: from.ID, + Title: from.Title, + Body: from.Description, + Sha: from.FromRef.LatestCommit, + Ref: fmt.Sprintf("refs/pull-requests/%d/from", from.ID), + Source: from.FromRef.DisplayID, + Target: from.ToRef.DisplayID, + Fork: fork, + Base: scm.PullRequestBranch{ + Ref: from.ToRef.DisplayID, + Sha: from.ToRef.LatestCommit, + Repo: *toRepo, + }, + Head: scm.PullRequestBranch{ + Ref: from.FromRef.DisplayID, + Sha: from.FromRef.LatestCommit, + Repo: *fromRepo, + }, + Link: extractSelfLink(from.Links.Self), + State: strings.ToLower(from.State), + Closed: from.Closed, + Merged: from.State == "MERGED", + Reviewers: convertReviewers(from.Reviewers), + Created: time.Unix(from.CreatedDate/1000, 0), + Updated: time.Unix(from.UpdatedDate/1000, 0), + Author: scm.User{ + Login: from.Author.User.Slug, + Name: from.Author.User.DisplayName, + Email: from.Author.User.EmailAddress, + Avatar: avatarLink(from.Author.User.EmailAddress), + }, + } +} + +type pullRequestComment struct { + Properties struct { + RepositoryID int `json:"repositoryId"` + } `json:"properties"` + ID int `json:"id"` + Version int `json:"version"` + Text string `json:"text"` + Author user `json:"author"` + CreatedDate int64 `json:"createdDate"` + UpdatedDate int64 `json:"updatedDate"` + Comments []pullRequestComment `json:"comments"` + Tasks []interface{} `json:"tasks"` + PermittedOperations struct { + Editable bool `json:"editable"` + Deletable bool `json:"deletable"` + } `json:"permittedOperations"` +} + +type pullRequestCommentInput struct { + Text string `json:"text"` + Version int `json:"version"` +} + +type pullRequestAssignInput struct { + User struct { + Name string `json:"name"` + } + Approved bool `json:"approved"` + Status string `json:"status"` +} + +type pullRequestActivities struct { + pagination + Values []*pullRequestActivity `json:"values"` +} + +type pullRequestActivity struct { + ID int `json:"id"` + CreatedDate int64 `json:"createdDate"` + User user `json:"user"` + Action string `json:"action"` + CommentAction string `json:"commentAction"` + Comment *pullRequestComment `json:"comment"` + CommentAnchor interface{} `json:"commentAnchor"` +} + +func convertPullRequestActivities(from *pullRequestActivities) []*scm.Comment { + var to []*scm.Comment + + for _, v := range from.Values { + if v.Comment != nil && v.Action == "COMMENTED" { + to = append(to, convertPullRequestComment(v.Comment)) + } + } + return to +} + +func convertPullRequestComment(from *pullRequestComment) *scm.Comment { + return &scm.Comment{ + ID: from.ID, + Body: from.Text, + Version: from.Version, + Created: time.Unix(from.CreatedDate/1000, 0), + Updated: time.Unix(from.UpdatedDate/1000, 0), + Author: scm.User{ + Login: from.Author.Slug, + Name: from.Author.DisplayName, + Email: from.Author.EmailAddress, + Avatar: avatarLink(from.Author.EmailAddress), + }, + } +} + +func convertReviewers(from []prUser) []scm.User { + var answer []scm.User + + for k := range from { + answer = append(answer, *convertUser(&from[k].User)) + } + + return answer +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/repo.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/repo.go new file mode 100644 index 000000000..ec3283abe --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/repo.go @@ -0,0 +1,637 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "context" + "fmt" + "net/url" + "sort" + "strconv" + + "github.com/jenkins-x/go-scm/scm" +) + +type repository struct { + Slug string `json:"slug"` + ID int `json:"id"` + Name string `json:"name"` + ScmID string `json:"scmId"` + State string `json:"state"` + StatusMessage string `json:"statusMessage"` + Forkable bool `json:"forkable"` + Project struct { + Key string `json:"key"` + ID int `json:"id"` + Name string `json:"name"` + Public bool `json:"public"` + Type string `json:"type"` + Links struct { + Self []link `json:"self"` + } `json:"links"` + } `json:"project"` + Public bool `json:"public"` + Links struct { + Clone []link `json:"clone"` + Self []link `json:"self"` + } `json:"links"` +} + +type repositories struct { + pagination + Values []*repository `json:"values"` +} + +type link struct { + Href string `json:"href"` + Name string `json:"name"` +} + +type hooks struct { + pagination + Values []*hook `json:"values"` +} + +type hook struct { + ID int `json:"id"` + Name string `json:"name"` + CreatedDate int64 `json:"createdDate"` + UpdatedDate int64 `json:"updatedDate"` + Events []string `json:"events"` + URL string `json:"url"` + Active bool `json:"active"` + Config struct { + Secret string `json:"secret"` + } `json:"configuration"` +} + +type hookInput struct { + Name string `json:"name"` + Events []string `json:"events"` + URL string `json:"url"` + Active bool `json:"active"` + Config struct { + Secret string `json:"secret"` + } `json:"configuration"` +} + +type status struct { + State string `json:"state"` + Key string `json:"key"` + Name string `json:"name"` + URL string `json:"url"` + Desc string `json:"description"` +} + +type statuses struct { + pagination + Values []*status `json:"values"` +} + +type participants struct { + pagination + Values []*participant `json:"values"` +} + +type participant struct { + User user `json:"user"` + Permission string `json:"permission"` +} + +type projGroups struct { + pagination + Values []*projGroup +} + +type projGroup struct { + Group group `json:"group"` + Permission string `json:"permission"` +} + +type group struct { + Name string `json:"name"` +} + +type members struct { + pagination + Values []*member `json:"values"` +} + +type member struct { + Name string `json:"name"` + Slug string `json:"slug"` +} + +type repositoryService struct { + client *wrapper +} + +type repoInput struct { + Name string `json:"name"` + ScmID string `json:"scmId"` + Public bool `json:"public"` +} + +type addCollaboratorInput struct { + Name string `json:"name"` + Permission string `json:"permission"` +} + +func (s *repositoryService) Create(ctx context.Context, input *scm.RepositoryInput) (*scm.Repository, *scm.Response, error) { + in := &repoInput{ + Name: input.Name, + ScmID: "git", + Public: !input.Private, + } + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos", input.Namespace) + + out := new(repository) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertRepository(out), res, err +} + +type forkProjectInput struct { + Key string `json:"key,omitempty"` +} + +type forkInput struct { + Slug string `json:"slug,omitempty"` + Project *forkProjectInput `json:"project,omitempty"` +} + +func (s *repositoryService) Fork(ctx context.Context, input *scm.RepositoryInput, origRepo string) (*scm.Repository, *scm.Response, error) { + namespace, name := scm.Split(origRepo) + in := new(forkInput) + if input.Name != "" { + in.Slug = input.Name + } + if input.Namespace != "" { + in.Project = &forkProjectInput{Key: input.Namespace} + } + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s", namespace, name) + + out := new(repository) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertRepository(out), res, err +} + +func (s *repositoryService) FindCombinedStatus(ctx context.Context, repo, ref string) (*scm.CombinedStatus, *scm.Response, error) { + path := fmt.Sprintf("rest/build-status/1.0/commits/%s?orderBy=oldest", url.PathEscape(ref)) + out := new(statuses) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if err != nil { + return nil, res, err + } + + combinedState := scm.StateUnknown + + byContext := make(map[string]*status) + for _, s := range out.Values { + byContext[s.Key] = s + } + + keys := make([]string, 0, len(byContext)) + for k := range byContext { + keys = append(keys, k) + } + sort.Strings(keys) + var statuses []*scm.Status + for _, k := range keys { + s := byContext[k] + statuses = append(statuses, convertStatus(s)) + } + + for _, s := range statuses { + // If we've still got a default state, or the state of the current status is worse than the current state, set it. + if combinedState == scm.StateUnknown || combinedState > s.State { + combinedState = s.State + } + } + + return &scm.CombinedStatus{ + State: combinedState, + Sha: ref, + Statuses: statuses, + }, res, err +} + +func (s *repositoryService) FindUserPermission(ctx context.Context, repo, user string) (string, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/permissions/users?filter=%s", namespace, name, url.QueryEscape(user)) + out := new(participants) + res, err := s.client.do(ctx, "GET", path, nil, out) + if err != nil { + return "", res, err + } + for _, p := range out.Values { + if p.User.Name == user { + return apiStringToPermission(p.Permission), res, nil + } + } + return "", res, nil +} + +func (s *repositoryService) AddCollaborator(ctx context.Context, repo, user, permission string) (bool, bool, *scm.Response, error) { + existingPerm, res, err := s.FindUserPermission(ctx, repo, user) + if err != nil { + return false, false, res, err + } + if existingPerm == permission { + return false, true, res, nil + } + + apiPerm := permissionToAPIString(permission, false) + if apiPerm == "" { + return false, false, nil, fmt.Errorf("unknown permission '%s'", permission) + } + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/permissions/users?&name=%s&permission=%s", namespace, name, user, apiPerm) + input := &addCollaboratorInput{ + Name: user, + Permission: apiPerm, + } + res, err = s.client.do(ctx, "PUT", path, input, nil) + if err != nil { + return false, false, res, err + } + return true, false, res, nil +} + +func (s *repositoryService) IsCollaborator(ctx context.Context, repo, user string) (bool, *scm.Response, error) { + users, resp, err := s.ListCollaborators(ctx, repo, &scm.ListOptions{}) + if err != nil { + return false, resp, err + } + for k := range users { + if users[k].Name == user || users[k].Login == user { + return true, resp, err + } + } + return false, resp, err +} + +func (s *repositoryService) ListCollaborators(ctx context.Context, repo string, opts *scm.ListOptions) ([]scm.User, *scm.Response, error) { + namespace, name := scm.Split(repo) + opts.Size = 1000 + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/permissions/users?%s", namespace, name, encodeListOptions(opts)) + out := new(participants) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertParticipants(out), res, err +} + +func (s *repositoryService) ListLabels(context.Context, string, *scm.ListOptions) ([]*scm.Label, *scm.Response, error) { + // TODO implement me! + return nil, nil, nil +} + +// Find returns the repository by name. +func (s *repositoryService) Find(ctx context.Context, repo string) (*scm.Repository, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s", namespace, name) + out := new(repository) + res, err := s.client.do(ctx, "GET", path, nil, out) + outputRepo := convertRepository(out) + + // default value for repository.Branch is `master` but it may differ for other + // repositories as a repository can have `main` or `trunk` as default branch. + branch := new(branch) + pathBranch := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/branches/default", namespace, name) + _, errBranch := s.client.do(ctx, "GET", pathBranch, nil, branch) + if errBranch == nil { + outputRepo.Branch = branch.DisplayID + } + return outputRepo, res, err +} + +// FindHook returns a repository hook. +func (s *repositoryService) FindHook(ctx context.Context, repo, id string) (*scm.Hook, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks/%s", namespace, name, id) + out := new(hook) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertHook(out), res, err +} + +// FindPerms returns the repository permissions. +func (s *repositoryService) FindPerms(ctx context.Context, repo string) (*scm.Perm, *scm.Response, error) { + // HACK: test if the user has read access to the repository. + _, _, err := s.Find(ctx, repo) + if err != nil { + return &scm.Perm{ + Pull: false, + Push: false, + Admin: false, + }, nil, nil + } + + // HACK: test if the user has admin access to the repository. + _, _, err = s.ListHooks(ctx, repo, &scm.ListOptions{}) + if err == nil { + return &scm.Perm{ + Pull: true, + Push: true, + Admin: true, + }, nil, nil + } + // HACK: test if the user has write access to the repository. + _, name := scm.Split(repo) + repos, _, _ := s.listWrite(ctx, repo) + for _, repo := range repos { + if repo.Name == name { + return &scm.Perm{ + Pull: true, + Push: true, + Admin: false, + }, nil, nil + } + } + + return &scm.Perm{ + Pull: true, + Push: false, + Admin: false, + }, nil, nil +} + +// List returns the user repository list. +func (s *repositoryService) List(ctx context.Context, opts *scm.ListOptions) ([]*scm.Repository, *scm.Response, error) { + path := fmt.Sprintf("rest/api/1.0/repos?%s", encodeListRoleOptions(opts)) + out := new(repositories) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertRepositoryList(out), res, err +} + +func (s *repositoryService) ListOrganisation(ctx context.Context, org string, opts *scm.ListOptions) ([]*scm.Repository, *scm.Response, error) { + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos?%s", org, encodeListRoleOptions(opts)) + out := new(repositories) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertRepositoryList(out), res, err +} + +func (s *repositoryService) ListUser(context.Context, string, *scm.ListOptions) ([]*scm.Repository, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +// listWrite returns the user repository list. +func (s *repositoryService) listWrite(ctx context.Context, repo string) ([]*scm.Repository, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/repos?size=1000&permission=REPO_WRITE&project=%s&name=%s", namespace, name) + out := new(repositories) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertRepositoryList(out), res, err +} + +// ListHooks returns a list or repository hooks. +func (s *repositoryService) ListHooks(ctx context.Context, repo string, opts *scm.ListOptions) ([]*scm.Hook, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks?%s", namespace, name, encodeListOptions(opts)) + out := new(hooks) + res, err := s.client.do(ctx, "GET", path, nil, out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertHookList(out), res, err +} + +// ListStatus returns a list of commit statuses. +func (s *repositoryService) ListStatus(ctx context.Context, _, ref string, opts *scm.ListOptions) ([]*scm.Status, *scm.Response, error) { + path := fmt.Sprintf("rest/build-status/1.0/commits/%s?%s", url.PathEscape(ref), encodeListOptions(opts)) + out := new(statuses) + res, err := s.client.do(ctx, "GET", path, nil, &out) + if !out.pagination.LastPage.Bool { + res.Page.First = 1 + res.Page.Next = opts.Page + 1 + } + return convertStatusList(out), res, err +} + +// CreateHook creates a new repository webhook. +func (s *repositoryService) CreateHook(ctx context.Context, repo string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks", namespace, name) + in := new(hookInput) + in.URL = input.Target + in.Active = true + in.Name = input.Name + in.Config.Secret = input.Secret + // nolint + in.Events = append( + input.NativeEvents, + convertHookEvents(input.Events)..., + ) + out := new(hook) + res, err := s.client.do(ctx, "POST", path, in, out) + return convertHook(out), res, err +} + +func (s *repositoryService) UpdateHook(ctx context.Context, repo string, input *scm.HookInput) (*scm.Hook, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +// CreateStatus creates a new commit status. +// reference: https://developer.atlassian.com/server/bitbucket/how-tos/updating-build-status-for-commits/ +func (s *repositoryService) CreateStatus(ctx context.Context, repo, ref string, input *scm.StatusInput) (*scm.Status, *scm.Response, error) { + path := fmt.Sprintf("rest/build-status/1.0/commits/%s", url.PathEscape(ref)) + in := status{ + State: convertFromState(input.State), + Key: input.Label, + Name: input.Label, + URL: input.Target, + Desc: input.Desc, + } + res, err := s.client.do(ctx, "POST", path, in, nil) + return &scm.Status{ + State: input.State, + Label: input.Label, + Desc: input.Desc, + Target: input.Target, + }, res, err +} + +// DeleteHook deletes a repository webhook. +func (s *repositoryService) DeleteHook(ctx context.Context, repo, id string) (*scm.Response, error) { + namespace, name := scm.Split(repo) + path := fmt.Sprintf("rest/api/1.0/projects/%s/repos/%s/webhooks/%s", namespace, name, id) + return s.client.do(ctx, "DELETE", path, nil, nil) +} + +func (s *repositoryService) Delete(context.Context, string) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +// helper function to convert from the gogs repository list to +// the common repository structure. +func convertRepositoryList(from *repositories) []*scm.Repository { + to := []*scm.Repository{} + for _, v := range from.Values { + to = append(to, convertRepository(v)) + } + return to +} + +// helper function to convert from the gogs repository structure +// to the common repository structure. +func convertRepository(from *repository) *scm.Repository { + return &scm.Repository{ + ID: strconv.Itoa(from.ID), + Name: from.Slug, + Namespace: from.Project.Key, + FullName: fmt.Sprintf("%s/%s", from.Project.Key, from.Slug), + Link: extractSelfLink(from.Links.Self), + Branch: "master", + Private: !from.Public, + CloneSSH: extractLink(from.Links.Clone, "ssh"), + Clone: anonymizeLink(extractLink(from.Links.Clone, "http")), + } +} + +func extractLink(links []link, name string) (href string) { + for _, link := range links { + if link.Name == name { + return link.Href + } + } + return +} + +func extractSelfLink(links []link) (href string) { + for _, link := range links { + return link.Href + } + return +} + +func anonymizeLink(link string) (href string) { + parsed, err := url.Parse(link) + if err != nil { + return link + } + parsed.User = nil + return parsed.String() +} + +func convertHookList(from *hooks) []*scm.Hook { + to := []*scm.Hook{} + for _, v := range from.Values { + to = append(to, convertHook(v)) + } + return to +} + +func convertHook(from *hook) *scm.Hook { + return &scm.Hook{ + ID: strconv.Itoa(from.ID), + Name: from.Name, + Active: from.Active, + Target: from.URL, + Events: from.Events, + } +} + +func convertHookEvents(from scm.HookEvents) []string { + var events []string + if from.Push || from.Branch || from.Tag { + events = append(events, "repo:refs_changed") + } + if from.PullRequest { + events = append(events, "pr:declined", "pr:modified", "pr:deleted", "pr:opened", "pr:merged", "pr:from_ref_updated") + } + if from.PullRequestComment { + events = append(events, "pr:comment:added", "pr:comment:deleted", "pr:comment:edited") + } + return events +} + +func convertStatusList(from *statuses) []*scm.Status { + to := []*scm.Status{} + for _, v := range from.Values { + to = append(to, convertStatus(v)) + } + return to +} + +func convertStatus(from *status) *scm.Status { + return &scm.Status{ + State: convertState(from.State), + Label: from.Key, + Desc: from.Desc, + Target: from.URL, + } +} + +func convertFromState(from scm.State) string { + switch from { + case scm.StatePending, scm.StateRunning: + return "INPROGRESS" + case scm.StateSuccess: + return "SUCCESSFUL" + default: + return "FAILED" + } +} + +func convertState(from string) scm.State { + switch from { + case "FAILED": + return scm.StateFailure + case "INPROGRESS": + return scm.StatePending + case "SUCCESSFUL": + return scm.StateSuccess + default: + return scm.StateUnknown + } +} + +func convertParticipants(participants *participants) []scm.User { + answer := []scm.User{} + for _, p := range participants.Values { + answer = append(answer, *convertUser(&p.User)) + } + return answer +} + +func apiStringToPermission(apiString string) string { + switch apiString { + case "ADMIN", "PROJECT_ADMIN", "REPO_ADMIN": + return scm.AdminPermission + case "PROJECT_CREATE", "PROJECT_WRITE", "REPO_WRITE": + return scm.WritePermission + case "PROJECT_READ", "REPO_READ": + return scm.ReadPermission + default: + return scm.NoPermission + } +} + +func permissionToAPIString(perm string, isProject bool) string { + prefix := "REPO" + if isProject { + prefix = "PROJECT" + } + switch perm { + case scm.AdminPermission: + return prefix + "_ADMIN" + case scm.WritePermission: + return prefix + "_WRITE" + case scm.ReadPermission: + return prefix + "_READ" + default: + return "" + } +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/review.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/review.go new file mode 100644 index 000000000..11ff992ac --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/review.go @@ -0,0 +1,47 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "context" + + "github.com/jenkins-x/go-scm/scm" +) + +type reviewService struct { + client *wrapper +} + +func (s *reviewService) Find(ctx context.Context, repo string, number, id int) (*scm.Review, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *reviewService) List(ctx context.Context, repo string, number int, opts *scm.ListOptions) ([]*scm.Review, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *reviewService) Create(ctx context.Context, repo string, number int, input *scm.ReviewInput) (*scm.Review, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *reviewService) Delete(ctx context.Context, repo string, number, id int) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *reviewService) ListComments(ctx context.Context, repo string, prID, reviewID int, options *scm.ListOptions) ([]*scm.ReviewComment, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *reviewService) Update(ctx context.Context, repo string, prID, reviewID int, body string) (*scm.Review, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *reviewService) Submit(ctx context.Context, repo string, prID, reviewID int, input *scm.ReviewSubmitInput) (*scm.Review, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *reviewService) Dismiss(ctx context.Context, repo string, prID, reviewID int, msg string) (*scm.Review, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/stash.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/stash.go new file mode 100644 index 000000000..ec743eaf8 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/stash.go @@ -0,0 +1,167 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package stash implements a Bitbucket Server client. +package stash + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/url" + "strings" + + "github.com/jenkins-x/go-scm/scm" + "github.com/jenkins-x/go-scm/scm/driver/internal/null" +) + +// Reference API Documentation: +// https://docs.atlassian.com/bitbucket-server/rest/5.11.1/bitbucket-rest.html + +// NewWebHookService creates a new instance of the webhook service without the rest of the client +func NewWebHookService() scm.WebhookService { + return &webhookService{nil} +} + +// New returns a new Stash API client. +func New(uri string) (*scm.Client, error) { + base, err := url.Parse(uri) + if err != nil { + return nil, err + } + if !strings.HasSuffix(base.Path, "/") { + base.Path += "/" + } + client := &wrapper{new(scm.Client)} + client.BaseURL = base + // initialize services + client.Driver = scm.DriverStash + client.Contents = &contentService{client} + client.Git = &gitService{client} + client.Issues = &issueService{client} + client.Milestones = &milestoneService{client} + client.Organizations = &organizationService{client} + client.PullRequests = &pullService{client} + client.Repositories = &repositoryService{client} + client.Reviews = &reviewService{client} + client.Users = &userService{client} + client.Webhooks = &webhookService{client} + return client.Client, nil +} + +// NewDefault returns a new Stash API client. +func NewDefault() *scm.Client { + client, _ := New("http://localhost:7990") + return client +} + +// wraper wraps the Client to provide high level helper functions +// for making http requests and unmarshaling the response. +type wrapper struct { + *scm.Client +} + +// do wraps the Client.Do function by creating the Request and +// unmarshalling the response. +func (c *wrapper) do(ctx context.Context, method, path string, in, out interface{}) (*scm.Response, error) { + req := &scm.Request{ + Method: method, + Path: path, + Header: make(map[string][]string), + } + // if we are posting or putting data, we need to + // write it to the body of the request. + if in != nil { + switch contentInput := in.(type) { + case *contentCreateUpdate: + var b bytes.Buffer + mw := &MultipartWriter{Writer: multipart.NewWriter(&b)} + mw.Write("content", string(contentInput.Content)) + mw.Write("message", contentInput.Message) + mw.Write("branch", contentInput.Branch) + mw.Write("sourceCommitId", contentInput.Sha) + if mw.Error != nil { + return nil, fmt.Errorf("error writing multipart-content. err: %s", mw.Error) + } + mw.Close() + req.Body = &b + req.Header = map[string][]string{ + "Content-Type": {mw.FormDataContentType()}, + "x-atlassian-token": {"no-check"}, + } + default: + buf := new(bytes.Buffer) + err := json.NewEncoder(buf).Encode(in) // #nosec + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + req.Body = buf + } + } + req.Header.Add("X-Atlassian-Token", "no-check") + + // execute the http request + res, err := c.Client.Do(ctx, req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + // if an error is encountered, unmarshal and return the + // error response. + if res.Status == 401 { + return res, scm.ErrNotAuthorized + } else if res.Status > 300 { + err := new(Error) + // nolint + json.NewDecoder(res.Body).Decode(err) // #nosec + return res, err + } + + if out == nil { + return res, nil + } + + // if raw output is expected, copy to the provided + // buffer and exit. + if w, ok := out.(io.Writer); ok { + _, err := io.Copy(w, res.Body) + return res, err + } + + // if a json response is expected, parse and return + // the json response. + return res, json.NewDecoder(res.Body).Decode(out) +} + +// pagination represents Bitbucket pagination properties +// embedded in list responses. +type pagination struct { + Start null.Int `json:"start"` + Size null.Int `json:"size"` + Limit null.Int `json:"limit"` + LastPage null.Bool `json:"isLastPage"` + NextPage null.Int `json:"nextPageStart"` +} + +// Error represents a Stash error. +type Error struct { + Errors []struct { + Message string `json:"message"` + ExceptionName string `json:"exceptionName"` + CurrentVersion int `json:"currentVersion"` + ExpectedVersion int `json:"expectedVersion"` + } `json:"errors"` +} + +func (e *Error) Error() string { + if len(e.Errors) == 0 { + return "No message available" + } + return e.Errors[0].Message +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/user.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/user.go new file mode 100644 index 000000000..439683799 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/user.go @@ -0,0 +1,102 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "bytes" + "context" + "crypto/md5" // #nosec + "encoding/hex" + "fmt" + "net/url" + "strings" + + "github.com/jenkins-x/go-scm/scm" +) + +type userService struct { + client *wrapper +} + +func (s *userService) CreateToken(context.Context, string, string) (*scm.UserToken, *scm.Response, error) { + return nil, nil, scm.ErrNotSupported +} + +func (s *userService) DeleteToken(context.Context, int64) (*scm.Response, error) { + return nil, scm.ErrNotSupported +} + +func (s *userService) Find(ctx context.Context) (*scm.User, *scm.Response, error) { + path := "plugins/servlet/applinks/whoami" + out := new(bytes.Buffer) + res, err := s.client.do(ctx, "GET", path, nil, out) + if err != nil { + return nil, res, err + } + login := out.String() + login = strings.TrimSpace(login) + return s.FindLogin(ctx, login) +} + +func (s *userService) FindLogin(ctx context.Context, login string) (*scm.User, *scm.Response, error) { + path := fmt.Sprintf("rest/api/1.0/users/%s", url.PathEscape(login)) + out := new(user) + res, err := s.client.do(ctx, "GET", path, nil, out) + return convertUser(out), res, err +} + +func (s *userService) FindEmail(ctx context.Context) (string, *scm.Response, error) { + user, res, err := s.Find(ctx) + var email string + if err == nil { + email = user.Email + } + return email, res, err +} + +func (s *userService) ListInvitations(context.Context) ([]*scm.Invitation, *scm.Response, error) { + // bitbucket server does not have an invite concept, so always return successfully + return []*scm.Invitation{}, &scm.Response{Status: 200}, nil +} + +func (s *userService) AcceptInvitation(context.Context, int64) (*scm.Response, error) { + // bitbucket server does not have an invite concept, so always return successfully + return &scm.Response{Status: 200}, nil +} + +type user struct { + Name string `json:"name"` + EmailAddress string `json:"emailAddress"` + ID int `json:"id"` + DisplayName string `json:"displayName"` + Active bool `json:"active"` + Slug string `json:"slug"` + Type string `json:"type"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` +} + +func convertUser(from *user) *scm.User { + if from == nil { + return nil + } + return &scm.User{ + Avatar: avatarLink(from.EmailAddress), + Login: from.Slug, + Name: from.DisplayName, + Email: from.EmailAddress, + } +} + +func avatarLink(email string) string { + hasher := md5.New() // #nosec + hasher.Write([]byte(strings.ToLower(email))) // #nosec + emailHash := hex.EncodeToString(hasher.Sum(nil)) + avatarURL := fmt.Sprintf("https://www.gravatar.com/avatar/%s.jpg", emailHash) + return avatarURL +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/util.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/util.go new file mode 100644 index 000000000..519127385 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/util.go @@ -0,0 +1,75 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "net/url" + "strconv" + + "github.com/jenkins-x/go-scm/scm" +) + +func encodeListOptions(opts *scm.ListOptions) string { + params := url.Values{} + if opts.Page > 1 { + params.Set("start", strconv.Itoa( + (opts.Page-1)*opts.Size), + ) + } + if opts.Size != 0 { + params.Set("limit", strconv.Itoa(opts.Size)) + } + if opts.From != "" { + params.Set("from", opts.From) + } + if opts.To != "" { + params.Set("to", opts.To) + } + return params.Encode() +} + +func encodeListRoleOptions(opts *scm.ListOptions) string { + params := url.Values{} + if opts.Page > 1 { + params.Set("start", strconv.Itoa( + (opts.Page-1)*opts.Size), + ) + } + if opts.Size != 0 { + params.Set("limit", strconv.Itoa(opts.Size)) + } + params.Set("permission", "REPO_READ") + return params.Encode() +} + +func encodePullRequestListOptions(opts *scm.PullRequestListOptions) string { + params := url.Values{} + if opts.Page > 1 { + params.Set("start", strconv.Itoa( + (opts.Page-1)*opts.Size), + ) + } + if opts.Size != 0 { + params.Set("limit", strconv.Itoa(opts.Size)) + } + if opts.Open && opts.Closed { + params.Set("state", "all") + } else if opts.Closed { + params.Set("state", "closed") + } + return params.Encode() +} + +// func copyPagination(from pagination, to *scm.Response) error { +// if to == nil { +// return nil +// } +// to.Page.First = 1 +// if from.LastPage.Bool { +// return nil +// } +// to.Page.Next = +// return nil +// } diff --git a/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/webhook.go b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/webhook.go new file mode 100644 index 000000000..f3c90604c --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/driver/stash/webhook.go @@ -0,0 +1,390 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package stash + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "time" + + "github.com/jenkins-x/go-scm/pkg/hmac" + "github.com/jenkins-x/go-scm/scm" + "k8s.io/utils/strings/slices" +) + +// TODO(bradrydzewski) push hook does not include commit message +// TODO(bradrydzewski) push hook does not include commit link +// TODO(bradrydzewski) push hook does not include repository git+http link +// TODO(bradrydzewski) push hook does not include repository git+ssh link +// TODO(bradrydzewski) push hook does not include repository html link +// TODO(bradrydzewski) missing pull request synchrnoized webhook. See https://jira.atlassian.com/browse/BSERV-10279 +// TODO(bradrydzewski) pr hook does not include repository git+http link +// TODO(bradrydzewski) pr hook does not include repository git+ssh link +// TODO(bradrydzewski) pr hook does not include repository html link + +type webhookService struct { + client *wrapper +} + +// Parse for the bitbucket server webhook payloads see: https://confluence.atlassian.com/bitbucketserver/event-payload-938025882.html +func (s *webhookService) Parse(req *http.Request, fn scm.SecretFunc) (scm.Webhook, error) { + data, err := io.ReadAll( + io.LimitReader(req.Body, 10000000), + ) + if err != nil { + return nil, err + } + + guid := req.Header.Get("X-Request-Id") + + var hook scm.Webhook + event := req.Header.Get("X-Event-Key") + switch event { + case "repo:refs_changed": + hook, err = s.parsePushHook(data, guid) + case "pr:opened", "pr:declined", "pr:merged", "pr:from_ref_updated", "pr:modified": + hook, err = s.parsePullRequest(data) + case "pr:comment:added", "pr:comment:edited": + hook, err = s.parsePullRequestComment(data, guid) + case "pr:reviewer:approved", "pr:reviewer:unapproved", "pr:reviewer:needs_work": + hook, err = s.parsePullRequestApproval(data) + default: + return nil, scm.UnknownWebhook{Event: event} + } + if err != nil { + return nil, err + } + if hook == nil { + return nil, nil + } + + // get the gogs signature key to verify the payload + // signature. If no key is provided, no validation + // is performed. + key, err := fn(hook) + if err != nil { + return hook, err + } else if key == "" { + return hook, nil + } + + sig := req.Header.Get("X-Hub-Signature") + if !hmac.ValidatePrefix(data, []byte(key), sig) { + return hook, scm.ErrSignatureInvalid + } + + return hook, nil +} + +func (s *webhookService) parsePushHook(data []byte, guid string) (scm.Webhook, error) { + dst := new(pushHook) + err := json.Unmarshal(data, dst) + if err != nil { + return nil, err + } + if len(dst.Changes) == 0 { + return nil, errors.New("push hook has empty changeset") + } + change := dst.Changes[0] + switch { + case change.Ref.Type == "BRANCH" && !(slices.Contains([]string{"UPDATE", "ADD"}, change.Type)): + return convertBranchHook(dst), nil + case change.Ref.Type == "TAG": + return convertTagHook(dst), nil + default: + hook := convertPushHook(dst) + hook.GUID = guid + return hook, err + } +} + +func (s *webhookService) parsePullRequest(data []byte) (scm.Webhook, error) { + src := new(pullRequestHook) + err := json.Unmarshal(data, src) + if err != nil { + return nil, err + } + dst := convertPullRequestHook(src) + switch src.EventKey { + case "pr:opened": + dst.Action = scm.ActionOpen + case "pr:declined": + dst.Action = scm.ActionClose + case "pr:merged": + dst.Action = scm.ActionMerge + case "pr:from_ref_updated": + dst.Action = scm.ActionSync + case "pr:modified": + dst.Action = scm.ActionUpdate + default: + return nil, nil + } + return dst, nil +} + +func (s *webhookService) parsePullRequestComment(data []byte, guid string) (scm.Webhook, error) { + src := new(pullRequestCommentHook) + err := json.Unmarshal(data, src) + if err != nil { + return nil, err + } + dst := convertPullRequestCommentHook(src) + dst.GUID = guid + return dst, nil +} + +func (s *webhookService) parsePullRequestApproval(data []byte) (scm.Webhook, error) { + src := new(pullRequestApprovalHook) + err := json.Unmarshal(data, src) + if err != nil { + return nil, err + } + dst := convertPullRequestApprovalHook(src) + switch src.EventKey { + case "pr:reviewer:approved": + dst.Action = scm.ActionSubmitted + case "pr:reviewer:unapproved": + dst.Action = scm.ActionDismissed + case "pr:reviewer:needs_work": + dst.Action = scm.ActionEdited + default: + return nil, nil + } + + return dst, nil +} + +// +// native data structures +// + +type pushHook struct { + EventKey string `json:"eventKey"` + Date string `json:"date"` + Actor *user `json:"actor"` + Repository *repository `json:"repository"` + Changes []*change `json:"changes"` +} + +type pullRequestHook struct { + EventKey string `json:"eventKey"` + Date string `json:"date"` + Actor *user `json:"actor"` + PullRequest *pullRequest `json:"pullRequest"` + PreviousFromHash string `json:"previousFromHash"` + PreviousTitle string `json:"previousTitle"` + PreviousDescription string `json:"previousDescription"` + PreviousTarget interface{} `json:"previousTarget"` +} + +type pullRequestCommentHook struct { + EventKey string `json:"eventKey"` + Date string `json:"date"` + Author *user `json:"author"` + PullRequest *pullRequest `json:"pullRequest"` + Comment *prComment `json:"comment"` +} + +type prComment struct { + ID int `json:"id"` + Version int `json:"version"` + Text string `json:"text"` + Author *user `json:"author"` + CreatedAt int64 `json:"createdDate"` + UpdatedAt int64 `json:"updatedDate"` +} + +type change struct { + Ref struct { + ID string `json:"id"` + DisplayID string `json:"displayId"` + Type string `json:"type"` + } `json:"ref"` + RefID string `json:"refId"` + FromHash string `json:"fromHash"` + ToHash string `json:"toHash"` + Type string `json:"type"` +} + +type pullRequestApprovalHook struct { + EventKey string `json:"eventKey"` + Date string `json:"date"` + Actor *user `json:"actor"` + PullRequest *pullRequest `json:"pullRequest"` + Participant *prUser `json:"participant"` + PreviousStatus string `json:"previousParticipant"` +} + +// +// push hooks +// + +func convertPushHook(src *pushHook) *scm.PushHook { + change := src.Changes[0] + repo := convertRepository(src.Repository) + sender := convertUser(src.Actor) + signer := convertSignature(src.Actor) + signer.Date, _ = time.Parse("2006-01-02T15:04:05+0000", src.Date) + sha := change.ToHash + return &scm.PushHook{ + Ref: change.RefID, + Commit: scm.Commit{ + Sha: sha, + Message: "", + Link: "", + Author: signer, + Committer: signer, + }, + After: sha, + Repo: *repo, + Sender: *sender, + } +} + +func convertTagHook(src *pushHook) *scm.TagHook { + change := src.Changes[0] + sender := convertUser(src.Actor) + repo := convertRepository(src.Repository) + + dst := &scm.TagHook{ + Ref: scm.Reference{ + Name: change.Ref.DisplayID, + Sha: change.ToHash, + }, + Action: scm.ActionCreate, + Repo: *repo, + Sender: *sender, + } + if change.Type == "DELETE" { + dst.Action = scm.ActionDelete + dst.Ref.Sha = change.FromHash + } + return dst +} + +func convertBranchHook(src *pushHook) *scm.BranchHook { + change := src.Changes[0] + sender := convertUser(src.Actor) + repo := convertRepository(src.Repository) + + dst := &scm.BranchHook{ + Ref: scm.Reference{ + Name: change.Ref.DisplayID, + Sha: change.ToHash, + }, + Action: scm.ActionCreate, + Repo: *repo, + Sender: *sender, + } + if change.Type == "DELETE" { + dst.Action = scm.ActionDelete + dst.Ref.Sha = change.FromHash + } + return dst +} + +func convertSignature(actor *user) scm.Signature { + return scm.Signature{ + Name: actor.DisplayName, + Email: actor.EmailAddress, + Login: actor.Slug, + Avatar: avatarLink(actor.EmailAddress), + } +} + +func convertPullRequestHook(src *pullRequestHook) *scm.PullRequestHook { + toRepo := convertRepository(&src.PullRequest.ToRef.Repository) + fromRepo := convertRepository(&src.PullRequest.FromRef.Repository) + pr := convertPullRequest(src.PullRequest) + sender := convertUser(src.Actor) + pr.Base.Repo = *toRepo + pr.Head.Repo = *fromRepo + if pr.Base.Ref == "" { + pr.Base.Ref = toRepo.Branch + } + if pr.Head.Ref == "" { + pr.Head.Ref = fromRepo.Branch + } + return &scm.PullRequestHook{ + Action: scm.ActionOpen, + Repo: *toRepo, + PullRequest: *pr, + Sender: *sender, + } +} + +func convertPullRequestCommentHook(src *pullRequestCommentHook) *scm.PullRequestCommentHook { + toRepo := convertRepository(&src.PullRequest.ToRef.Repository) + fromRepo := convertRepository(&src.PullRequest.FromRef.Repository) + pr := convertPullRequest(src.PullRequest) + author := src.Comment.Author + if author == nil { + author = src.Author + } + sender := convertUser(author) + pr.Base.Repo = *toRepo + pr.Head.Repo = *fromRepo + return &scm.PullRequestCommentHook{ + Action: scm.ActionCreate, + Repo: *toRepo, + PullRequest: *pr, + Sender: *sender, + Comment: convertComment(src.Comment), + } +} + +func convertComment(src *prComment) scm.Comment { + dst := scm.Comment{} + if src != nil { + dst.ID = src.ID + dst.Body = src.Text + author := convertUser(src.Author) + if author != nil { + dst.Author = *author + } + dst.Created = time.Unix(src.CreatedAt/1000, 0) + dst.Updated = time.Unix(src.UpdatedAt/1000, 0) + } + return dst +} + +func convertPullRequestApprovalHook(src *pullRequestApprovalHook) *scm.ReviewHook { + toRepo := convertRepository(&src.PullRequest.ToRef.Repository) + fromRepo := convertRepository(&src.PullRequest.FromRef.Repository) + pr := convertPullRequest(src.PullRequest) + pr.Base.Repo = *toRepo + pr.Head.Repo = *fromRepo + if pr.Base.Ref == "" { + pr.Base.Ref = toRepo.Branch + } + if pr.Head.Ref == "" { + pr.Head.Ref = fromRepo.Branch + } + review := scm.Review{ + State: convertReviewStateFromEvent(src.EventKey), + Author: *convertUser(&src.Participant.User), + } + + return &scm.ReviewHook{ + PullRequest: *pr, + Repo: *toRepo, + Review: review, + } +} + +func convertReviewStateFromEvent(src string) string { + switch src { + case "pr:reviewer:approved": + return scm.ReviewStateApproved + case "pr:reviewer:unapproved": + return scm.ReviewStateDismissed + case "pr:reviewer:needs_work": + return scm.ReviewStateChangesRequested + default: + return scm.ReviewStatePending + } +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/errors.go b/vendor/github.com/jenkins-x/go-scm/scm/errors.go new file mode 100644 index 000000000..d28684468 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/errors.go @@ -0,0 +1,62 @@ +package scm + +import ( + "fmt" + "strings" +) + +// MissingUsers is an error specifying the users that could not be unassigned. +type MissingUsers struct { + Users []string + Action string +} + +func (m MissingUsers) Error() string { + return fmt.Sprintf("could not %s the following user(s): %s.", m.Action, strings.Join(m.Users, ", ")) +} + +// ExtraUsers is an error specifying the users that could not be unassigned. +type ExtraUsers struct { + Users []string + Action string +} + +func (e ExtraUsers) Error() string { + return fmt.Sprintf("could not %s the following user(s): %s.", e.Action, strings.Join(e.Users, ", ")) +} + +// UnknownWebhook if the webhook is unknown +type UnknownWebhook struct { + Event string +} + +func (e UnknownWebhook) Error() string { + return fmt.Sprintf("Unknown webhook event: %s.", e.Event) +} + +// IsUnknownWebhook returns true if the error is an unknown webhook +func IsUnknownWebhook(err error) bool { + _, ok := err.(UnknownWebhook) + return ok +} + +// StateCannotBeChanged represents the error that occurs when a resource cannot be changed +type StateCannotBeChanged struct { + Message string +} + +func (s StateCannotBeChanged) Error() string { + return s.Message +} + +// StateCannotBeChanged implements error +var _ error = (*StateCannotBeChanged)(nil) + +// MissingHeader if the webhook has a missing header +type MissingHeader struct { + Header string +} + +func (e MissingHeader) Error() string { + return fmt.Sprintf("400 Bad Request: Missing Header: %s", e.Header) +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/git.go b/vendor/github.com/jenkins-x/go-scm/scm/git.go new file mode 100644 index 000000000..bc6e53c23 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/git.go @@ -0,0 +1,102 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "time" +) + +// EmptyCommit is an empty commit sha. +const EmptyCommit = "0000000000000000000000000000000000000000" + +type ( + // Reference represents a git reference. + Reference struct { + Name string + Path string + Sha string + } + + // ReferenceInput provides a SHA for creating a reference. + ReferenceInput struct { + Name string + Sha string + } + + // CommitTree represents a commit tree + CommitTree struct { + Sha string + Link string + } + + // Commit represents a repository commit. + Commit struct { + Sha string + Message string + Tree CommitTree + Author Signature + Committer Signature + Link string + } + + // CommitListOptions provides options for querying a + // list of repository commits. + CommitListOptions struct { + Ref string + Sha string + Page int + Size int + Path string + } + + // Signature identifies a git commit creator. + Signature struct { + Name string + Email string + Date time.Time + + // Fields are optional. The provider may choose to + // include account information in the response. + Login string + Avatar string + } + + // GitService provides access to git resources. + GitService interface { + // FindBranch finds a git branch by name. + FindBranch(ctx context.Context, repo, name string) (*Reference, *Response, error) + + // FindCommit finds a git commit by ref. + FindCommit(ctx context.Context, repo, ref string) (*Commit, *Response, error) + + // FindTag finds a git tag by name. + FindTag(ctx context.Context, repo, name string) (*Reference, *Response, error) + + // ListBranches returns a list of git branches. + ListBranches(ctx context.Context, repo string, opts *ListOptions) ([]*Reference, *Response, error) + + // ListCommits returns a list of git commits. + ListCommits(ctx context.Context, repo string, opts CommitListOptions) ([]*Commit, *Response, error) + + // ListChanges returns the changeset between a commit and its parent. + ListChanges(ctx context.Context, repo, ref string, opts *ListOptions) ([]*Change, *Response, error) + + // CompareCommits returns the changeset between two commits. + CompareCommits(ctx context.Context, repo, ref1, ref2 string, opts *ListOptions) ([]*Change, *Response, error) + + // ListTags returns a list of git tags. + ListTags(ctx context.Context, repo string, opts *ListOptions) ([]*Reference, *Response, error) + + // FindRef returns the SHA of the given ref, such as "heads/master". + FindRef(ctx context.Context, repo, ref string) (string, *Response, error) + + // DeleteRef deletes the given ref + DeleteRef(ctx context.Context, repo, ref string) (*Response, error) + + // CreateRef creates a new ref + CreateRef(ctx context.Context, repo, ref, sha string) (*Reference, *Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/issue.go b/vendor/github.com/jenkins-x/go-scm/scm/issue.go new file mode 100644 index 000000000..4baa1f49b --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/issue.go @@ -0,0 +1,161 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "strings" + "time" +) + +type ( + // Issue represents an issue. + Issue struct { + Number int + Title string + Body string + Link string + State string + Labels []string + Closed bool + Locked bool + Author User + Assignees []User + ClosedBy *User + PullRequest *PullRequest + Created time.Time + Updated time.Time + } + + // SearchIssue for the results of a search which queries across repositories + SearchIssue struct { + Issue + Repository Repository + } + + // IssueInput provides the input fields required for + // creating or updating an issue. + IssueInput struct { + Title string + Body string + } + + // IssueListOptions provides options for querying a + // list of repository issues. + IssueListOptions struct { + Page int + Size int + Open bool + Closed bool + } + + // Comment represents a comment. + Comment struct { + ID int + Body string + Author User + Link string + Version int + Created time.Time + Updated time.Time + } + + // CommentInput provides the input fields required for + // creating an issue comment. + CommentInput struct { + Body string + } + + // ListedIssueEvent for listing events on an issue + ListedIssueEvent struct { + Event string + Actor User + Label Label + Created time.Time + } + + // SearchOptions thte query options for a search + SearchOptions struct { + Query string + Sort string + Ascending bool + } + + // IssueService provides access to issue resources. + IssueService interface { + // Find returns the issue by number. + Find(context.Context, string, int) (*Issue, *Response, error) + + // FindComment returns the issue comment. + FindComment(context.Context, string, int, int) (*Comment, *Response, error) + + // List returns the repository issue list. + List(context.Context, string, IssueListOptions) ([]*Issue, *Response, error) + + // Find returns the issue by number. + Search(context.Context, SearchOptions) ([]*SearchIssue, *Response, error) + + // ListComments returns the issue comment list. + ListComments(context.Context, string, int, *ListOptions) ([]*Comment, *Response, error) + + // ListLabels returns the labels on an issue + ListLabels(context.Context, string, int, *ListOptions) ([]*Label, *Response, error) + + // ListEvents returns the events creating and removing the labels on an issue + ListEvents(context.Context, string, int, *ListOptions) ([]*ListedIssueEvent, *Response, error) + + // Create creates a new issue. + Create(context.Context, string, *IssueInput) (*Issue, *Response, error) + + // CreateComment creates a new issue comment. + CreateComment(context.Context, string, int, *CommentInput) (*Comment, *Response, error) + + // DeleteComment deletes an issue comment. + DeleteComment(context.Context, string, int, int) (*Response, error) + + // EditComment edits an existing issue comment. + EditComment(context.Context, string, int, int, *CommentInput) (*Comment, *Response, error) + + // Close closes an issue. + Close(context.Context, string, int) (*Response, error) + + // Reopen reopens a closed issue. + Reopen(context.Context, string, int) (*Response, error) + + // Lock locks an issue discussion. + Lock(context.Context, string, int) (*Response, error) + + // Unlock unlocks an issue discussion. + Unlock(context.Context, string, int) (*Response, error) + + // AddLabel adds a label to an issue + AddLabel(ctx context.Context, repo string, number int, label string) (*Response, error) + + // DeleteLabel deletes a label from an issue + DeleteLabel(ctx context.Context, repo string, number int, label string) (*Response, error) + + // AssignIssue assigns one or more users to an issue + AssignIssue(ctx context.Context, repo string, number int, logins []string) (*Response, error) + + // UnassignIssue removes the assignment of ne or more users on an issue + UnassignIssue(ctx context.Context, repo string, number int, logins []string) (*Response, error) + + // SetMilestone adds a milestone to an issue + SetMilestone(ctx context.Context, repo string, issueID int, number int) (*Response, error) + + // ClearMilestone removes the milestone from an issue + ClearMilestone(ctx context.Context, repo string, id int) (*Response, error) + } +) + +// QueryArgument returns the query argument for the search using '+' to separate the search terms while escaping : +func (o *SearchOptions) QueryArgument() string { + query := o.Query + if query == "" { + return "" + } + q := strings.Join(strings.Fields(query), "+") + return strings.ReplaceAll(q, ":", "%3A") +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/labels/labels.go b/vendor/github.com/jenkins-x/go-scm/scm/labels/labels.go new file mode 100644 index 000000000..b05f2b457 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/labels/labels.go @@ -0,0 +1,56 @@ +package labels + +import ( + "fmt" + "sort" + "strings" + + "github.com/jenkins-x/go-scm/scm" +) + +const ( + addLabel = "/jx-label " + removeLabel = " remove" + emptyString = "" +) + +// ConvertLabelComments converts comments to labels for git providers which don't support native labels +func ConvertLabelComments(cs []*scm.Comment) ([]*scm.Label, error) { + sort.SliceStable(cs, func(i, j int) bool { + return cs[i].Created.UnixNano() < cs[j].Created.UnixNano() + }) + m := make(map[string]bool) + for _, com := range cs { + if strings.HasPrefix(com.Body, addLabel) { + t := strings.ReplaceAll(com.Body, addLabel, emptyString) + if strings.HasSuffix(t, removeLabel) { + t = strings.ReplaceAll(t, removeLabel, emptyString) + m[t] = true + } else { + m[t] = false + } + } + } + var ls []*scm.Label + for l, i := range m { + if !i { + ls = append(ls, &scm.Label{Name: l}) + } + } + return ls, nil +} + +// CreateLabelAddComment creates a label comment for git providers which don't support labels with comment with /jx-label +func CreateLabelAddComment(label string) *scm.CommentInput { + return &scm.CommentInput{ + Body: fmt.Sprintf("%s%s", addLabel, label), + } +} + +// CreateLabelRemoveComment adds a comment with /jx-label remove +func CreateLabelRemoveComment(label string) *scm.CommentInput { + input := &scm.CommentInput{ + Body: fmt.Sprintf("%s%s%s", addLabel, label, removeLabel), + } + return input +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/milestone.go b/vendor/github.com/jenkins-x/go-scm/scm/milestone.go new file mode 100644 index 000000000..54ed9683b --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/milestone.go @@ -0,0 +1,42 @@ +package scm + +import ( + "context" + "time" +) + +type ( + // MilestoneInput contains the information needed to create a milestone + MilestoneInput struct { + Title string + Description string + State string + DueDate *time.Time + } + + // MilestoneListOptions provides options for querying a list of repository milestones. + MilestoneListOptions struct { + Page int + Size int + Open bool + Closed bool + } + + // MilestoneService provides access to creating, listing, updating, and deleting milestones + MilestoneService interface { + // Find returns the milestone for the given number in the given repository + Find(context.Context, string, int) (*Milestone, *Response, error) + + // List returns a list of milestones in the given repository + List(context.Context, string, MilestoneListOptions) ([]*Milestone, *Response, error) + + // Create creates a milestone in the given repository + Create(context.Context, string, *MilestoneInput) (*Milestone, *Response, error) + + // Update updates a milestone in the given repository + Update(context.Context, string, int, *MilestoneInput) (*Milestone, *Response, error) + + // Delete deletes a milestone in the given repository + Delete(context.Context, string, int) (*Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/org.go b/vendor/github.com/jenkins-x/go-scm/scm/org.go new file mode 100644 index 000000000..94cfdb4bc --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/org.go @@ -0,0 +1,108 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" +) + +type ( + // Organization represents an organization account. + Organization struct { + ID int + Name string + Avatar string + Permissions Permissions + } + + // OrganizationPendingInvite represents a pending invite to an organisation + OrganizationPendingInvite struct { + ID int + Login string + InviterLogin string + } + + // OrganizationInput provides the input fields required for + // creating a new organization. + OrganizationInput struct { + Name string + Description string + Homepage string + Private bool + } + + // Permissions represents the possible permissions a user can have on an org + Permissions struct { + MembersCreatePrivate bool + MembersCreatePublic bool + MembersCreateInternal bool + } + // Team is a organizational team + Team struct { + ID int + Name string + Slug string + Description string + Privacy string + + // Parent is populated in queries + Parent *Team + + // ParentTeamID is only valid when creating / editing teams + ParentTeamID int + } + + // TeamMember is a member of an organizational team + TeamMember struct { + Login string `json:"login"` + IsAdmin bool `json:"isAdmin,omitempty"` + } + + // Membership describes the membership a user has to an organisation + Membership struct { + State string + Role string + OrganizationName string + } + + // OrganizationService provides access to organization resources. + OrganizationService interface { + // Find returns the organization by name. + Find(context.Context, string) (*Organization, *Response, error) + + // Create creates an organization. + Create(context.Context, *OrganizationInput) (*Organization, *Response, error) + + // Delete deletes an organization. + Delete(context.Context, string) (*Response, error) + + // List returns the user organization list. + List(context.Context, *ListOptions) ([]*Organization, *Response, error) + + // ListTeams returns the user organization list. + ListTeams(ctx context.Context, org string, ops *ListOptions) ([]*Team, *Response, error) + + // IsMember returns true if the user is a member of the organization + IsMember(ctx context.Context, org string, user string) (bool, *Response, error) + + // IsAdmin returns true if the user is an admin of the organization + IsAdmin(ctx context.Context, org string, user string) (bool, *Response, error) + + // ListTeamMembers lists the members of a team with a given role + ListTeamMembers(ctx context.Context, id int, role string, ops *ListOptions) ([]*TeamMember, *Response, error) + + // ListOrgMembers lists the members of the organization + ListOrgMembers(ctx context.Context, org string, ops *ListOptions) ([]*TeamMember, *Response, error) + + // ListPendingInvitations lists the pending invitations for an organisation + ListPendingInvitations(ctx context.Context, org string, ops *ListOptions) ([]*OrganizationPendingInvite, *Response, error) + + // AcceptPendingInvitation accepts a pending invitation for an organisation + AcceptOrganizationInvitation(ctx context.Context, org string) (*Response, error) + + // ListMemberships lists organisation memberships for the authenticated user + ListMemberships(ctx context.Context, opts *ListOptions) ([]*Membership, *Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/pr.go b/vendor/github.com/jenkins-x/go-scm/scm/pr.go new file mode 100644 index 000000000..f200878c1 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/pr.go @@ -0,0 +1,232 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "strings" + "time" +) + +type ( + // MergeableState represents whether the PR can be merged + MergeableState string + + // PullRequest represents a repository pull request. + PullRequest struct { + Number int + Title string + Body string + Labels []*Label + Sha string + Ref string + Source string + Target string + Base PullRequestBranch + Head PullRequestBranch + Fork string + State string + Closed bool + Draft bool + Merged bool + Mergeable bool + Rebaseable bool + MergeableState MergeableState + MergeSha string + Author User + Assignees []User + Reviewers []User + Milestone Milestone + Created time.Time + Updated time.Time + + // Link links to the main pull request page + Link string + + // DiffLink links to the diff report of a pull request + DiffLink string + } + + // PullRequestInput provides the input needed to create or update a PR. + PullRequestInput struct { + Title string + Head string + Base string + Body string + } + + // Milestone the milestone + Milestone struct { + Number int + ID int + Title string + Description string + Link string + State string + DueDate *time.Time + } + + // PullRequestListOptions provides options for querying + // a list of repository merge requests. + PullRequestListOptions struct { + Page int + Size int + Open bool + Closed bool + Labels []string + UpdatedAfter *time.Time + UpdatedBefore *time.Time + CreatedAfter *time.Time + CreatedBefore *time.Time + } + + // PullRequestBranch contains information about a particular branch in a PR. + PullRequestBranch struct { + Ref string + Sha string + Repo Repository + } + + // Change represents a changed file. + Change struct { + Path string + PreviousPath string + Added bool + Modified bool + Renamed bool + Deleted bool + Patch string + Additions int + Deletions int + Changes int + BlobURL string + Sha string + } + + // PullRequestMergeOptions lets you define how a pull request will be merged. + PullRequestMergeOptions struct { + CommitTitle string // Extra detail to append to automatic commit message. (Optional.) + SHA string // SHA that pull request head must match to allow merge. (Optional.) + + // The merge method to use. Possible values include: "merge", "squash", and "rebase" with the default being merge. (Optional.) + MergeMethod string + + // Merge automatically once the pipeline completes. (Supported only in gitlab) + MergeWhenPipelineSucceeds bool + + // Signals to the SCM to remove the source branch during merge + DeleteSourceBranch bool + } + + // PullRequestService provides access to pull request resources. + PullRequestService interface { + // Find returns the repository pull request by number. + Find(context.Context, string, int) (*PullRequest, *Response, error) + + // Update modifies an existing pull request. + Update(context.Context, string, int, *PullRequestInput) (*PullRequest, *Response, error) + + // FindComment returns the pull request comment by id. + FindComment(context.Context, string, int, int) (*Comment, *Response, error) + + // Find returns the repository pull request list. + List(context.Context, string, *PullRequestListOptions) ([]*PullRequest, *Response, error) + + // ListChanges returns the pull request changeset. + ListChanges(context.Context, string, int, *ListOptions) ([]*Change, *Response, error) + + // ListCommits returns the pull request commits. + ListCommits(context.Context, string, int, *ListOptions) ([]*Commit, *Response, error) + + // ListComments returns the pull request comment list. + ListComments(context.Context, string, int, *ListOptions) ([]*Comment, *Response, error) + + // ListLabels returns the labels on a pull request + ListLabels(context.Context, string, int, *ListOptions) ([]*Label, *Response, error) + + // ListEvents returns the events creating and removing the labels on an pull request + ListEvents(context.Context, string, int, *ListOptions) ([]*ListedIssueEvent, *Response, error) + + // Merge merges the repository pull request. + Merge(context.Context, string, int, *PullRequestMergeOptions) (*Response, error) + + // Close closes the repository pull request. + Close(context.Context, string, int) (*Response, error) + + // Reopen reopens a closed repository pull request. + Reopen(context.Context, string, int) (*Response, error) + + // CreateComment creates a new pull request comment. + CreateComment(context.Context, string, int, *CommentInput) (*Comment, *Response, error) + + // DeleteComment deletes an pull request comment. + DeleteComment(context.Context, string, int, int) (*Response, error) + + // EditComment edits an existing pull request comment. + EditComment(context.Context, string, int, int, *CommentInput) (*Comment, *Response, error) + + // AddLabel adds a label to a pull request. + AddLabel(ctx context.Context, repo string, number int, label string) (*Response, error) + + // DeleteLabel deletes a label from a pull request + DeleteLabel(ctx context.Context, repo string, number int, label string) (*Response, error) + + // AssignIssue assigns one or more users to an issue + AssignIssue(ctx context.Context, repo string, number int, logins []string) (*Response, error) + + // UnassignIssue removes the assignment of ne or more users on an issue + UnassignIssue(ctx context.Context, repo string, number int, logins []string) (*Response, error) + + // Create creates a new pull request in a repo. + Create(context.Context, string, *PullRequestInput) (*PullRequest, *Response, error) + + // RequestReview adds one or more users as a reviewer on a pull request. + RequestReview(ctx context.Context, repo string, number int, logins []string) (*Response, error) + + // UnrequestReview removes one or more users as a reviewer on a pull request. + UnrequestReview(ctx context.Context, repo string, number int, logins []string) (*Response, error) + + // SetMilestone adds a milestone to a pull request + SetMilestone(ctx context.Context, repo string, prID int, number int) (*Response, error) + + // ClearMilestone removes the milestone from a pull request + ClearMilestone(ctx context.Context, repo string, prID int) (*Response, error) + + // DeletePullRequest deletes a pull request from a repo. + DeletePullRequest(ctx context.Context, repo string, prID int) (*Response, error) + } +) + +// Action values. +const ( + // MergeableStateMergeable The pull request can be merged. + MergeableStateMergeable MergeableState = "mergeable" + // MergeableStateConflicting The pull request cannot be merged due to merge conflicts. + MergeableStateConflicting MergeableState = "conflicting" + // MergeableStateUnknown The mergeability of the pull request is still being calculated. + MergeableStateUnknown MergeableState = "" +) + +// Repository returns the base repository where the PR will merge to +func (pr *PullRequest) Repository() Repository { + return pr.Base.Repo +} + +// ToMergeableState converts the given string to a mergeable state +func ToMergeableState(text string) MergeableState { + switch strings.ToLower(text) { + case "clean", "mergeable", "can_be_merged": + return MergeableStateMergeable + case "dirty", "conflict", "conflicting", "cannot_be_merged": + return MergeableStateConflicting + default: + return MergeableStateUnknown + } +} + +// String returns the string representation +func (s MergeableState) String() string { + return string(s) +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/release.go b/vendor/github.com/jenkins-x/go-scm/scm/release.go new file mode 100644 index 000000000..119adf428 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/release.go @@ -0,0 +1,67 @@ +package scm + +import ( + "context" + "time" +) + +type ( + // Release the release + Release struct { + ID int + Title string + Description string + Link string + Tag string + Commitish string + Draft bool + Prerelease bool + Created time.Time + Published time.Time + } + + // ReleaseInput contains the information needed to create a release + ReleaseInput struct { + Title string + Description string + Tag string + Commitish string + Draft bool + Prerelease bool + } + + // ReleaseListOptions provides options for querying a list of repository releases. + ReleaseListOptions struct { + Page int + Size int + Open bool + Closed bool + } + + // ReleaseService provides access to creating, listing, updating, and deleting releases + ReleaseService interface { + // Find returns the release for the given number in the given repository + Find(context.Context, string, int) (*Release, *Response, error) + + // FindByTag returns the release for the given tag in the given repository + FindByTag(context.Context, string, string) (*Release, *Response, error) + + // List returns a list of releases in the given repository + List(context.Context, string, ReleaseListOptions) ([]*Release, *Response, error) + + // Create creates a release in the given repository + Create(context.Context, string, *ReleaseInput) (*Release, *Response, error) + + // Update updates a release in the given repository + Update(context.Context, string, int, *ReleaseInput) (*Release, *Response, error) + + // Update updates a release in the given repository by tag + UpdateByTag(context.Context, string, string, *ReleaseInput) (*Release, *Response, error) + + // Delete deletes a release in the given repository + Delete(context.Context, string, int) (*Response, error) + + // Delete deletes a release in the given repository by tag + DeleteByTag(context.Context, string, string) (*Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/repo.go b/vendor/github.com/jenkins-x/go-scm/scm/repo.go new file mode 100644 index 000000000..3ec5e0fbc --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/repo.go @@ -0,0 +1,205 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "time" +) + +const ( + // NoPermission means the user has no permission to access the repository + NoPermission = "none" + // ReadPermission means the user has read access to the repository + ReadPermission = "read" + // WritePermission means the user has write/push access to the repository + WritePermission = "write" + // AdminPermission means the user has full admin access to the repository + AdminPermission = "admin" +) + +type ( + // Repository represents a git repository. + Repository struct { + ID string + Namespace string + Name string + FullName string + Perm *Perm + Branch string // default branch of the repository + Private bool + Archived bool + Clone string + CloneSSH string + Link string + Created time.Time + Updated time.Time + } + + // RepositoryInput provides the input fields required for + // creating a new repository. + RepositoryInput struct { + Namespace string + Name string + Description string + Homepage string + Private bool + } + + // Perm represents a user's repository permissions. + Perm struct { + Pull bool + Push bool + Admin bool + } + + // Hook represents a repository hook. + Hook struct { + ID string + Name string + Target string + Events []string + Active bool + SkipVerify bool + } + + // HookInput provides the input fields required for + // creating or updating repository webhooks. + HookInput struct { + Name string + Target string + Secret string + Events HookEvents + SkipVerify bool + + // NativeEvents are used to create hooks with + // provider-specific event types that cannot be + // abstracted or represented in HookEvents. + NativeEvents []string + } + + // HookEvents represents supported hook events. + HookEvents struct { + Branch bool + Deployment bool + DeploymentStatus bool + Issue bool + IssueComment bool + PullRequest bool + PullRequestComment bool + Push bool + Release bool + Review bool + ReviewComment bool + Tag bool + } + + // CombinedStatus is the latest statuses for a ref. + CombinedStatus struct { + State State + Sha string + Statuses []*Status + } + + // Status represents a commit status. + Status struct { + State State + Label string + Desc string + Target string + Link string + } + + // StatusInput provides the input fields required for + // creating or updating commit statuses. + StatusInput struct { + State State + Label string + Desc string + Target string + Link string + } + + // DeployStatus represents a deployment status. + DeployStatus struct { + Number int64 + State State + Desc string + Target string + Environment string + EnvironmentURL string + } + + // RepositoryService provides access to repository resources. + RepositoryService interface { + // Find returns a repository by name. + Find(context.Context, string) (*Repository, *Response, error) + + // FindHook returns a repository hook. + FindHook(context.Context, string, string) (*Hook, *Response, error) + + // FindPerms returns repository permissions. + FindPerms(context.Context, string) (*Perm, *Response, error) + + // List returns a list of repositories. + List(context.Context, *ListOptions) ([]*Repository, *Response, error) + + // ListOrganisation returns a list of repositories for a given organisation + ListOrganisation(context.Context, string, *ListOptions) ([]*Repository, *Response, error) + + // ListUser returns a list of repositories for a given user. + ListUser(context.Context, string, *ListOptions) ([]*Repository, *Response, error) + + // ListLabels returns the labels on a repo + ListLabels(context.Context, string, *ListOptions) ([]*Label, *Response, error) + + // ListHooks returns a list or repository hooks. + ListHooks(context.Context, string, *ListOptions) ([]*Hook, *Response, error) + + // ListStatus returns a list of commit statuses. + ListStatus(context.Context, string, string, *ListOptions) ([]*Status, *Response, error) + + // FindCombinedStatus returns the combined status for a ref + FindCombinedStatus(ctx context.Context, repo, ref string) (*CombinedStatus, *Response, error) + + // Create creates a new repository . + Create(context.Context, *RepositoryInput) (*Repository, *Response, error) + + // Fork creates a new repository as a fork of an existing one. + Fork(context.Context, *RepositoryInput, string) (*Repository, *Response, error) + + // CreateHook creates a new repository webhook. + CreateHook(context.Context, string, *HookInput) (*Hook, *Response, error) + + // UpdateHook edit a repository webhook + UpdateHook(context.Context, string, *HookInput) (*Hook, *Response, error) + + // CreateStatus creates a new commit status. + CreateStatus(context.Context, string, string, *StatusInput) (*Status, *Response, error) + + // DeleteHook deletes a repository webhook. + DeleteHook(context.Context, string, string) (*Response, error) + + // IsCollaborator returns true if the user is a collaborator on the repository + IsCollaborator(ctx context.Context, repo string, user string) (bool, *Response, error) + + // AddCollaborator adds a collaborator to the repository + AddCollaborator(ctx context.Context, repo, user, permission string) (bool, bool, *Response, error) + + // ListCollaborators lists the collaborators on a repository + ListCollaborators(ctx context.Context, repo string, ops *ListOptions) ([]User, *Response, error) + + // FindUserPermission returns the user's permission level for a repo + FindUserPermission(ctx context.Context, repo string, user string) (string, *Response, error) + + // Delete deletes a repository + Delete(ctx context.Context, repo string) (*Response, error) + } +) + +// TODO(bradrydzewski): Add endpoint to get a repository deploy key +// TODO(bradrydzewski): Add endpoint to list repository deploy keys +// TODO(bradrydzewski): Add endpoint to create a repository deploy key +// TODO(bradrydzewski): Add endpoint to delete a repository deploy key diff --git a/vendor/github.com/jenkins-x/go-scm/scm/review.go b/vendor/github.com/jenkins-x/go-scm/scm/review.go new file mode 100644 index 000000000..189fb9fc2 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/review.go @@ -0,0 +1,110 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "time" +) + +type ( + // Review represents a review. + Review struct { + ID int + Body string + Sha string + Link string + State string + Author User + Created time.Time + Updated time.Time + } + + // ReviewComment represents a review comment. + ReviewComment struct { + ID int + Body string + Path string + Sha string + Line int + Link string + Author User + Created time.Time + Updated time.Time + } + + // ReviewHook represents a review web hook + ReviewHook struct { + Action Action + PullRequest PullRequest + Repo Repository + Review Review + Installation *InstallationRef + // GUID is included in the header of the request received by Github. + GUID string + } + + // ReviewCommentInput provides the input fields required for + // creating a review comment. + ReviewCommentInput struct { + Body string + Path string + Line int + } + + // ReviewSubmitInput provides the input fields required for submitting a pending review. + ReviewSubmitInput struct { + Body string + Event string + } + + // ReviewInput provides the input fields required for creating or updating a review. + ReviewInput struct { + Body string + Sha string + Event string + Comments []*ReviewCommentInput + } + + // ReviewService provides access to review resources. + ReviewService interface { + // Find returns the review by id. + Find(context.Context, string, int, int) (*Review, *Response, error) + + // List returns the review list. + List(context.Context, string, int, *ListOptions) ([]*Review, *Response, error) + + // Create creates a review. + Create(context.Context, string, int, *ReviewInput) (*Review, *Response, error) + + // Delete deletes a review. + Delete(context.Context, string, int, int) (*Response, error) + + // ListComments returns comments from a review + ListComments(context.Context, string, int, int, *ListOptions) ([]*ReviewComment, *Response, error) + + // Update updates the body of a review + Update(context.Context, string, int, int, string) (*Review, *Response, error) + + // Submit submits a pending review + Submit(context.Context, string, int, int, *ReviewSubmitInput) (*Review, *Response, error) + + // Dismiss dismisses a review + Dismiss(context.Context, string, int, int, string) (*Review, *Response, error) + } +) + +const ( + // ReviewStateApproved is used for approved reviews + ReviewStateApproved string = "APPROVED" + // ReviewStateChangesRequested is used for reviews with changes requested + ReviewStateChangesRequested string = "CHANGES_REQUESTED" + // ReviewStateCommented is used for reviews with comments + ReviewStateCommented string = "COMMENTED" + // ReviewStateDismissed is used for reviews that have been dismissed + ReviewStateDismissed string = "DISMISSED" + // ReviewStatePending is used for reviews that are awaiting response + ReviewStatePending string = "PENDING" +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/token.go b/vendor/github.com/jenkins-x/go-scm/scm/token.go new file mode 100644 index 000000000..af417d1bb --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/token.go @@ -0,0 +1,34 @@ +// Copyright 2018 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "time" +) + +type ( + // Token represents the credentials used to authorize + // the requests to access protected resources. + Token struct { + Token string + Refresh string + Expires time.Time + } + + // TokenSource returns a token. + TokenSource interface { + Token(context.Context) (*Token, error) + } + + // TokenKey is the key to use with the context.WithValue + // function to associate an Token value with a context. + TokenKey struct{} +) + +// WithContext returns a copy of parent in which the token value is set +func WithContext(parent context.Context, token *Token) context.Context { + return context.WithValue(parent, TokenKey{}, token) +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/transport/internal/clone.go b/vendor/github.com/jenkins-x/go-scm/scm/transport/internal/clone.go new file mode 100644 index 000000000..51da3526a --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/transport/internal/clone.go @@ -0,0 +1,22 @@ +// Copyright 2018 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +import "net/http" + +// CloneRequest returns a clone of the provided +// http.Request. The clone is a shallow copy of the struct +// and its Header map. +func CloneRequest(r *http.Request) *http.Request { + // shallow copy of the struct + r2 := new(http.Request) + *r2 = *r + // deep copy of the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + return r2 +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/oauth2.go b/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/oauth2.go new file mode 100644 index 000000000..ed811f867 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/oauth2.go @@ -0,0 +1,62 @@ +// Copyright 2018 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "net/http" + + "github.com/jenkins-x/go-scm/scm" + "github.com/jenkins-x/go-scm/scm/transport/internal" +) + +// Supported authentication schemes. Note that Gogs and +// Gitea use non-standard authorization schemes. +const ( + SchemeBearer = "Bearer" + SchemeToken = "token" +) + +// Transport is an http.RoundTripper that refreshes oauth +// tokens, wrapping a base RoundTripper and refreshing the +// token if expired. +type Transport struct { + Scheme string + Source scm.TokenSource + Base http.RoundTripper +} + +// RoundTrip authorizes and authenticates the request with +// an access token from the request context. +func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { + ctx := r.Context() + token, err := t.Source.Token(ctx) + if err != nil { + return nil, err + } + if token == nil { + return t.base().RoundTrip(r) + } + r2 := internal.CloneRequest(r) + r2.Header.Set("Authorization", t.scheme()+" "+token.Token) + return t.base().RoundTrip(r2) +} + +// base returns the base transport. If no base transport +// is configured, the default transport is returned. +func (t *Transport) base() http.RoundTripper { + if t.Base != nil { + return t.Base + } + return http.DefaultTransport +} + +// scheme returns the token scheme. If no scheme is +// configured, the bearer scheme is used. +func (t *Transport) scheme() string { + if t.Scheme == "" { + return SchemeBearer + } + return t.Scheme +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/refresh.go b/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/refresh.go new file mode 100644 index 000000000..a85b181ef --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/refresh.go @@ -0,0 +1,137 @@ +// Copyright 2018 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "strings" + "time" + + "github.com/jenkins-x/go-scm/scm" +) + +// expiryDelta determines how earlier a token should be considered +// expired than its actual expiration time. It is used to avoid late +// expirations due to client-server time mismatches. +const expiryDelta = time.Minute + +// Refresher is an http.RoundTripper that refreshes oauth +// tokens, wrapping a base RoundTripper and refreshing the +// token if expired. +// +// IMPORTANT the Refresher is NOT safe for concurrent use +// by multiple goroutines. +type Refresher struct { + ClientID string + ClientSecret string + Endpoint string + + Source scm.TokenSource + Client *http.Client +} + +// Token returns a token. If the token is missing or +// expired, the token is refreshed. +func (t *Refresher) Token(ctx context.Context) (*scm.Token, error) { + token, err := t.Source.Token(ctx) + if err != nil { + return nil, err + } + if !expired(token) { + return token, nil + } + err = t.Refresh(token) + if err != nil { + return nil, err + } + return token, nil +} + +// Refresh refreshes the expired token. +func (t *Refresher) Refresh(token *scm.Token) error { + values := url.Values{} + values.Set("grant_type", "refresh_token") + values.Set("refresh_token", token.Refresh) + + reader := strings.NewReader( + values.Encode(), + ) + req, err := http.NewRequest("POST", t.Endpoint, reader) + if err != nil { + return err + } + req.SetBasicAuth(t.ClientID, t.ClientSecret) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + res, err := t.client().Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode > 299 { + out := new(tokenError) + err = json.NewDecoder(res.Body).Decode(out) + if err != nil { + return err + } + return out + } + + out := new(tokenGrant) + err = json.NewDecoder(res.Body).Decode(out) + if err != nil { + return err + } + + token.Token = out.Access + token.Refresh = out.Refresh + token.Expires = time.Now().Add( + time.Duration(out.Expires) * time.Second, + ) + return nil +} + +// client returns the http transport. If no base client +// is configured, the default client is returned. +func (t *Refresher) client() *http.Client { + if t.Client != nil { + return t.Client + } + return http.DefaultClient +} + +// expired reports whether the token is expired. +func expired(token *scm.Token) bool { + if token.Refresh == "" { + return false + } + if token.Expires.IsZero() && token.Token != "" { + return false + } + return token.Expires.Add(-expiryDelta). + Before(time.Now()) +} + +// tokenGrant is the token returned by the token endpoint. +type tokenGrant struct { + Access string `json:"access_token"` + Refresh string `json:"refresh_token"` + Expires int64 `json:"expires_in"` +} + +// tokenError is the error returned when the token endpoint +// returns a non-2XX HTTP status code. +type tokenError struct { + Code string `json:"error"` + Message string `json:"error_description"` +} + +func (t *tokenError) Error() string { + return t.Message +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/source.go b/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/source.go new file mode 100644 index 000000000..9d674c142 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/transport/oauth2/source.go @@ -0,0 +1,41 @@ +// Copyright 2018 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package oauth2 + +import ( + "context" + + "github.com/jenkins-x/go-scm/scm" +) + +// StaticTokenSource returns a TokenSource that always +// returns the same token. Because the provided token t +// is never refreshed, StaticTokenSource is only useful +// for tokens that never expire. +func StaticTokenSource(t *scm.Token) scm.TokenSource { + return staticTokenSource{t} +} + +type staticTokenSource struct { + token *scm.Token +} + +func (s staticTokenSource) Token(context.Context) (*scm.Token, error) { + return s.token, nil +} + +// ContextTokenSource returns a TokenSource that returns +// a token from the http.Request context. +func ContextTokenSource() scm.TokenSource { + return contextTokenSource{} +} + +type contextTokenSource struct { +} + +func (s contextTokenSource) Token(ctx context.Context) (*scm.Token, error) { + token, _ := ctx.Value(scm.TokenKey{}).(*scm.Token) + return token, nil +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/user.go b/vendor/github.com/jenkins-x/go-scm/scm/user.go new file mode 100644 index 000000000..02f5035f8 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/user.go @@ -0,0 +1,73 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "context" + "time" +) + +type ( + // User represents a user account. + User struct { + ID int + Login string + Name string + Email string + Avatar string + Link string + IsAdmin bool `json:"isAdmin,omitempty"` + Created time.Time + Updated time.Time + } + + // Email represents a user email. + Email struct { + Value string + Primary bool + Verified bool + } + + // UserToken represents a user token. + UserToken struct { + ID int64 + Token string + } + + // Invitation represents a repo invitation + Invitation struct { + ID int64 + Repo *Repository + Invitee *User + Inviter *User + Permissions string + Link string + Created time.Time + } + + // UserService provides access to user account resources. + UserService interface { + // Find returns the authenticated user. + Find(context.Context) (*User, *Response, error) + + // CreateToken creates a user token. + CreateToken(context.Context, string, string) (*UserToken, *Response, error) + + // DeleteToken deletes a user token. + DeleteToken(context.Context, int64) (*Response, error) + + // FindEmail returns the authenticated user email. + FindEmail(context.Context) (string, *Response, error) + + // FindLogin returns the user account by username. + FindLogin(context.Context, string) (*User, *Response, error) + + // ListInvitations lists repository or organization invitations for the current user + ListInvitations(context.Context) ([]*Invitation, *Response, error) + + // AcceptInvitation accepts an invitation for the current user + AcceptInvitation(context.Context, int64) (*Response, error) + } +) diff --git a/vendor/github.com/jenkins-x/go-scm/scm/util.go b/vendor/github.com/jenkins-x/go-scm/scm/util.go new file mode 100644 index 000000000..184b64c01 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/util.go @@ -0,0 +1,101 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "strings" +) + +// Split splits the full repository name into segments. +func Split(s string) (owner, name string) { + parts := strings.SplitN(s, "/", 2) + switch len(parts) { + case 1: + name = parts[0] + case 2: + owner = parts[0] + name = parts[1] + } + return +} + +// Join joins the repository owner and name segments to +// create a fully qualified repository name. +func Join(owner, name string) string { + return owner + "/" + name +} + +// URLJoin joins the given paths so that there is only ever one '/' character between the paths +func URLJoin(paths ...string) string { + var buffer strings.Builder + last := len(paths) - 1 + for i, path := range paths { + p := path + if i > 0 { + buffer.WriteString("/") + p = strings.TrimPrefix(p, "/") + } + if i < last { + p = strings.TrimSuffix(p, "/") + } + buffer.WriteString(p) + } + return buffer.String() +} + +// TrimRef returns ref without the path prefix. +func TrimRef(ref string) string { + ref = strings.TrimPrefix(ref, "refs/heads/") + ref = strings.TrimPrefix(ref, "refs/tags/") + return ref +} + +// ExpandRef returns name expanded to the fully qualified +// reference path (e.g refs/heads/master). +func ExpandRef(name, prefix string) string { + prefix = strings.TrimSuffix(prefix, "/") + if strings.HasPrefix(name, "refs/") { + return name + } + return prefix + "/" + name +} + +// IsTag returns true if the reference path points to +// a tag object. +func IsTag(ref string) bool { + return strings.HasPrefix(ref, "refs/tags/") +} + +// ConvertStatusInputsToStatuses converts the inputs to status objects +func ConvertStatusInputsToStatuses(inputs []*StatusInput) []*Status { + answer := []*Status{} + for _, input := range inputs { + answer = append(answer, ConvertStatusInputToStatus(input)) + } + return answer +} + +// ConvertStatusInputToStatus converts the input to a status +func ConvertStatusInputToStatus(input *StatusInput) *Status { + if input == nil { + return nil + } + return &Status{ + State: input.State, + Label: input.Label, + Desc: input.Desc, + Target: input.Target, + } +} + +// IsScmNotFound returns true if the resource is not found +func IsScmNotFound(err error) bool { + if err != nil { + // I think that we should instead rely on the http status (404) + // until jenkins-x go-scm is updated t return that in the error this works for github and gitlab + return strings.Contains(err.Error(), ErrNotFound.Error()) + } + return false +} diff --git a/vendor/github.com/jenkins-x/go-scm/scm/webhook.go b/vendor/github.com/jenkins-x/go-scm/scm/webhook.go new file mode 100644 index 000000000..ec8bab6f0 --- /dev/null +++ b/vendor/github.com/jenkins-x/go-scm/scm/webhook.go @@ -0,0 +1,739 @@ +// Copyright 2017 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package scm + +import ( + "errors" + "fmt" + "net/http" + "time" +) + +var ( + // ErrSignatureInvalid is returned when the webhook + // signature is invalid or cannot be calculated. + // ErrSignatureInvalid = errors.New("Invalid webhook signature") + + // ErrUnknownEvent is returned when the webhook event + // is not recognized by the system. + ErrUnknownEvent = errors.New("unknown webhook event") +) + +// WebhookKind is the kind of webhook event represented +type WebhookKind string + +const ( + // WebhookKindBranch is for branch events + WebhookKindBranch WebhookKind = "branch" + // WebhookKindCheckRun is for check run events + WebhookKindCheckRun WebhookKind = "check_run" + // WebhookKindCheckSuite is for check suite events + WebhookKindCheckSuite WebhookKind = "check_suite" + // WebhookKindDeploy is for deploy events + WebhookKindDeploy WebhookKind = "deploy" + // WebhookKindDeploymentStatus is for deployment status events + WebhookKindDeploymentStatus WebhookKind = "deployment_status" + // WebhookKindFork is for fork events + WebhookKindFork WebhookKind = "fork" + // WebhookKindInstallation is for app installation events + WebhookKindInstallation WebhookKind = "installation" + // WebhookKindInstallationRepository is for app installation in a repository events + WebhookKindInstallationRepository WebhookKind = "installation_repository" + // WebhookKindIssue is for issue events + WebhookKindIssue WebhookKind = "issue" + // WebhookKindIssueComment is for issue comment events + WebhookKindIssueComment WebhookKind = "issue_comment" + // WebhookKindLabel is for label events + WebhookKindLabel WebhookKind = "label" + // WebhookKindPing is for ping events + WebhookKindPing WebhookKind = "ping" + // WebhookKindPullRequest is for pull request events + WebhookKindPullRequest WebhookKind = "pull_request" + // WebhookKindPullRequestComment is for pull request comment events + WebhookKindPullRequestComment WebhookKind = "pull_request_comment" + // WebhookKindPush is for push events + WebhookKindPush WebhookKind = "push" + // WebhookKindRelease is for release events + WebhookKindRelease WebhookKind = "release" + // WebhookKindRepository is for repository events + WebhookKindRepository WebhookKind = "repository" + // WebhookKindReview is for review events + WebhookKindReview WebhookKind = "review" + // WebhookKindReviewCommentHook is for review comment events + WebhookKindReviewCommentHook WebhookKind = "review_comment" + // WebhookKindStar is for star events + WebhookKindStar WebhookKind = "star" + // WebhookKindStatus is for status events + WebhookKindStatus WebhookKind = "status" + // WebhookKindTag is for tag events + WebhookKindTag WebhookKind = "tag" + // WebhookKindWatch is for watch events + WebhookKindWatch WebhookKind = "watch" +) + +var ( + // ErrSignatureInvalid is returned when the webhook + // signature is invalid or cannot be calculated. + ErrSignatureInvalid = errors.New("invalid webhook signature") +) + +type ( + // Webhook defines a webhook for repository events. + Webhook interface { + Repository() Repository + GetInstallationRef() *InstallationRef + Kind() WebhookKind + } + + // Label on a PR + Label struct { + ID int64 + URL string + Name string + Description string + Color string + } + + // PingHook a ping webhook. + PingHook struct { + Repo Repository + Sender User + Installation *InstallationRef + GUID string + } + + // PushCommit represents general info about a commit. + PushCommit struct { + ID string + Message string + Added []string + Removed []string + Modified []string + } + + // PushHook represents a push hook, eg push events. + PushHook struct { + Ref string + BaseRef string + Repo Repository + Before string + After string + Created bool + Deleted bool + Forced bool + Compare string + Commits []PushCommit + Commit Commit + Sender User + GUID string + Installation *InstallationRef + } + + // BranchHook represents a branch or tag event, + // eg create and delete github event types. + BranchHook struct { + Ref Reference + Repo Repository + Action Action + Sender User + Installation *InstallationRef + } + + // CheckRunHook represents a check run event + CheckRunHook struct { + Action Action + Repo Repository + Sender User + Label Label + Installation *InstallationRef + } + + // CheckSuiteHook represents a check suite event + CheckSuiteHook struct { + Action Action + Repo Repository + Sender User + Label Label + Installation *InstallationRef + } + + // DeployHook represents a deployment event. + // This is currently a GitHub-specific event type. + DeployHook struct { + Deployment Deployment + Action Action + Ref Reference + Repo Repository + Sender User + Label Label + Installation *InstallationRef + } + + // DeploymentStatusHook represents a deployment status event. + // This is currently a GitHub-specific event type. + DeploymentStatusHook struct { + Deployment Deployment + DeploymentStatus DeploymentStatus + Action Action + Repo Repository + Sender User + Label Label + Installation *InstallationRef + } + + // ForkHook represents a fork event + ForkHook struct { + Repo Repository + Sender User + Installation *InstallationRef + } + + // TagHook represents a tag event, eg create and delete + // github event types. + TagHook struct { + Ref Reference + Repo Repository + Action Action + Sender User + Installation *InstallationRef + } + + // IssueHook represents an issue event, eg issues. + IssueHook struct { + Action Action + Repo Repository + Issue Issue + Sender User + Installation *InstallationRef + } + + // IssueCommentHook represents an issue comment event, + // eg issue_comment. + IssueCommentHook struct { + Action Action + Repo Repository + Issue Issue + Comment Comment + Sender User + GUID string + Installation *InstallationRef + } + + // InstallationHook represents an installation of a GitHub App + InstallationHook struct { + Action Action + Repos []*Repository + Sender User + Installation *Installation + } + + // InstallationRepositoryHook represents an installation of a GitHub App + InstallationRepositoryHook struct { + Action Action + RepositorySelection string + ReposAdded []*Repository + ReposRemoved []*Repository + Sender User + Installation *Installation + } + + // InstallationRef references a GitHub app install on a webhook + InstallationRef struct { + ID int64 + NodeID string + } + + // LabelHook represents a label event + LabelHook struct { + Action Action + Repo Repository + Sender User + Label Label + Installation *InstallationRef + } + + // ReleaseHook represents a release event + ReleaseHook struct { + Action Action + Repo Repository + Release Release + Sender User + Label Label + Installation *InstallationRef + } + + // RepositoryHook represents a repository event + RepositoryHook struct { + Action Action + Repo Repository + Sender User + Installation *InstallationRef + } + + // StatusHook represents a status event + StatusHook struct { + Action Action + Repo Repository + Sender User + Label Label + Installation *InstallationRef + } + + // Account represents the account of a GitHub app install + Account struct { + ID int + Login string + Link string + } + + // PullRequestHookBranchFrom represents the branch or ref a PR is from + PullRequestHookBranchFrom struct { + From string + } + + // PullRequestHookBranch represents a branch in a PR + PullRequestHookBranch struct { + Ref PullRequestHookBranchFrom + Sha PullRequestHookBranchFrom + Repo Repository + } + + // PullRequestHookChanges represents the changes in a PR + PullRequestHookChanges struct { + Base PullRequestHookBranch + } + + // PullRequestHook represents an pull request event, + // eg pull_request. + PullRequestHook struct { + Action Action + Repo Repository + Label Label + PullRequest PullRequest + Sender User + Changes PullRequestHookChanges + GUID string + Installation *InstallationRef + } + + // PullRequestCommentHook represents an pull request + // comment event, eg pull_request_comment. + PullRequestCommentHook struct { + Action Action + Repo Repository + PullRequest PullRequest + Comment Comment + Sender User + GUID string + Installation *InstallationRef + } + + // ReviewCommentHook represents a pull request review + // comment, eg pull_request_review_comment. + ReviewCommentHook struct { + Action Action + Repo Repository + PullRequest PullRequest + Review Review + Installation *InstallationRef + } + + // WatchHook represents a watch event. This is currently GitHub-specific. + WatchHook struct { + Action string + Repo Repository + Sender User + Installation *InstallationRef + } + + // StarHook represents a star event. This is currently GitHub-specific. + StarHook struct { + Action Action + StarredAt time.Time + Repo Repository + Sender User + } + + // WebhookWrapper lets us parse any webhook + WebhookWrapper struct { + PingHook *PingHook `json:",omitempty"` + PushHook *PushHook `json:",omitempty"` + BranchHook *BranchHook `json:",omitempty"` + CheckRunHook *CheckRunHook `json:",omitempty"` + CheckSuiteHook *CheckSuiteHook `json:",omitempty"` + DeployHook *DeployHook `json:",omitempty"` + DeploymentStatusHook *DeploymentStatusHook `json:",omitempty"` + ForkHook *ForkHook `json:",omitempty"` + TagHook *TagHook `json:",omitempty"` + IssueHook *IssueHook `json:",omitempty"` + IssueCommentHook *IssueCommentHook `json:",omitempty"` + InstallationHook *InstallationHook `json:",omitempty"` + InstallationRepositoryHook *InstallationRepositoryHook `json:",omitempty"` + LabelHook *LabelHook `json:",omitempty"` + ReleaseHook *ReleaseHook `json:",omitempty"` + RepositoryHook *RepositoryHook `json:",omitempty"` + PullRequestHook *PullRequestHook `json:",omitempty"` + PullRequestCommentHook *PullRequestCommentHook `json:",omitempty"` + ReviewCommentHook *ReviewCommentHook `json:",omitempty"` + WatchHook *WatchHook `json:",omitempty"` + StarHook *StarHook `json:",omitempty"` + } + + // SecretFunc provides the Webhook parser with the + // secret key used to validate webhook authenticity. + SecretFunc func(webhook Webhook) (string, error) + + // WebhookService provides abstract functions for + // parsing and validating webhooks requests. + WebhookService interface { + // Parse returns the parsed the repository webhook payload. + Parse(req *http.Request, fn SecretFunc) (Webhook, error) + } +) + +// Kind returns the kind of webhook +func (h *PingHook) Kind() WebhookKind { return WebhookKindPing } + +// Kind returns the kind of webhook +func (h *PushHook) Kind() WebhookKind { return WebhookKindPush } + +// Kind returns the kind of webhook +func (h *BranchHook) Kind() WebhookKind { return WebhookKindBranch } + +// Kind returns the kind of webhook +func (h *DeployHook) Kind() WebhookKind { return WebhookKindDeploy } + +// Kind returns the kind of webhook +func (h *TagHook) Kind() WebhookKind { return WebhookKindTag } + +// Kind returns the kind of webhook +func (h *IssueHook) Kind() WebhookKind { return WebhookKindIssue } + +// Kind returns the kind of webhook +func (h *IssueCommentHook) Kind() WebhookKind { return WebhookKindIssueComment } + +// Kind returns the kind of webhook +func (h *PullRequestHook) Kind() WebhookKind { return WebhookKindPullRequest } + +// Kind returns the kind of webhook +func (h *PullRequestCommentHook) Kind() WebhookKind { return WebhookKindPullRequestComment } + +// Kind returns the kind of webhook +func (h *ReviewHook) Kind() WebhookKind { return WebhookKindReview } + +// Kind returns the kind of webhook +func (h *ReviewCommentHook) Kind() WebhookKind { return WebhookKindReviewCommentHook } + +// Kind returns the kind of webhook +func (h *InstallationHook) Kind() WebhookKind { return WebhookKindInstallation } + +// Kind returns the kind of webhook +func (h *LabelHook) Kind() WebhookKind { return WebhookKindLabel } + +// Kind returns the kind of webhook +func (h *StatusHook) Kind() WebhookKind { return WebhookKindStatus } + +// Kind returns the kind of webhook +func (h *CheckRunHook) Kind() WebhookKind { return WebhookKindCheckRun } + +// Kind returns the kind of webhook +func (h *CheckSuiteHook) Kind() WebhookKind { return WebhookKindCheckSuite } + +// Kind returns the kind of webhook +func (h *DeploymentStatusHook) Kind() WebhookKind { return WebhookKindDeploymentStatus } + +// Kind returns the kind of webhook +func (h *ReleaseHook) Kind() WebhookKind { return WebhookKindRelease } + +// Kind returns the kind of webhook +func (h *RepositoryHook) Kind() WebhookKind { return WebhookKindRepository } + +// Kind returns the kind of webhook +func (h *ForkHook) Kind() WebhookKind { return WebhookKindFork } + +// Kind returns the kind of webhook +func (h *InstallationRepositoryHook) Kind() WebhookKind { return WebhookKindInstallationRepository } + +// Kind returns the kind of webhook +func (h *WatchHook) Kind() WebhookKind { return WebhookKindWatch } + +// Kind returns the kind of webhook +func (h *StarHook) Kind() WebhookKind { return WebhookKindStar } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *PingHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *PushHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *BranchHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *DeployHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *TagHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *IssueHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *IssueCommentHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *PullRequestHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *PullRequestCommentHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *ReviewCommentHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *ReviewHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *LabelHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *StatusHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *CheckRunHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *CheckSuiteHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *DeploymentStatusHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *ReleaseHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *RepositoryHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *ForkHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *WatchHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *StarHook) Repository() Repository { return h.Repo } + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *InstallationHook) Repository() Repository { + if len(h.Repos) > 0 { + return *h.Repos[0] + } + return Repository{} +} + +// Repository defines the repository webhook and provides a convenient way to get the associated repository without +// having to cast the type. +func (h *InstallationRepositoryHook) Repository() Repository { + if len(h.ReposAdded) > 0 { + return *h.ReposAdded[0] + } + return Repository{} +} + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *PingHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *PushHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *BranchHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *DeployHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *TagHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *IssueHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *IssueCommentHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *PullRequestHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *PullRequestCommentHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *ReviewHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *ReviewCommentHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *LabelHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *StatusHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *CheckRunHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *CheckSuiteHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *DeploymentStatusHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *ReleaseHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *RepositoryHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *ForkHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *WatchHook) GetInstallationRef() *InstallationRef { return h.Installation } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *StarHook) GetInstallationRef() *InstallationRef { return nil } + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *InstallationHook) GetInstallationRef() *InstallationRef { + if h.Installation == nil { + return nil + } + return &InstallationRef{ + ID: h.Installation.ID, + } +} + +// GetInstallationRef returns the installation reference if the webhook is invoked on a +// GitHub App +func (h *InstallationRepositoryHook) GetInstallationRef() *InstallationRef { + if h.Installation == nil { + return nil + } + return &InstallationRef{ + ID: h.Installation.ID, + } +} + +// ToWebhook converts the webhook wrapper to a webhook +func (h *WebhookWrapper) ToWebhook() (Webhook, error) { + if h == nil { + return nil, fmt.Errorf("no webhook supplied") + } + if h.PingHook != nil { + return h.PingHook, nil + } + if h.PushHook != nil { + return h.PushHook, nil + } + if h.BranchHook != nil { + return h.BranchHook, nil + } + if h.CheckRunHook != nil { + return h.CheckRunHook, nil + } + if h.CheckSuiteHook != nil { + return h.CheckSuiteHook, nil + } + if h.DeployHook != nil { + return h.DeployHook, nil + } + if h.DeploymentStatusHook != nil { + return h.DeploymentStatusHook, nil + } + if h.ForkHook != nil { + return h.ForkHook, nil + } + if h.TagHook != nil { + return h.TagHook, nil + } + if h.IssueHook != nil { + return h.IssueHook, nil + } + if h.IssueCommentHook != nil { + return h.IssueCommentHook, nil + } + if h.InstallationHook != nil { + return h.InstallationHook, nil + } + if h.InstallationRepositoryHook != nil { + return h.InstallationRepositoryHook, nil + } + if h.LabelHook != nil { + return h.LabelHook, nil + } + if h.RepositoryHook != nil { + return h.RepositoryHook, nil + } + if h.PullRequestHook != nil { + return h.PullRequestHook, nil + } + if h.PullRequestCommentHook != nil { + return h.PullRequestCommentHook, nil + } + if h.ReviewCommentHook != nil { + return h.ReviewCommentHook, nil + } + if h.WatchHook != nil { + return h.WatchHook, nil + } + if h.StarHook != nil { + return h.StarHook, nil + } + return nil, fmt.Errorf("unsupported webhook") +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3ab86da6c..445e2f56f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -249,6 +249,15 @@ github.com/imdario/mergo # github.com/inconshreveable/mousetrap v1.1.0 ## explicit; go 1.18 github.com/inconshreveable/mousetrap +# github.com/jenkins-x/go-scm v1.14.53 +## explicit; go 1.22.3 +github.com/jenkins-x/go-scm/pkg/hmac +github.com/jenkins-x/go-scm/scm +github.com/jenkins-x/go-scm/scm/driver/internal/null +github.com/jenkins-x/go-scm/scm/driver/stash +github.com/jenkins-x/go-scm/scm/labels +github.com/jenkins-x/go-scm/scm/transport/internal +github.com/jenkins-x/go-scm/scm/transport/oauth2 # github.com/jonboulle/clockwork v0.4.0 ## explicit; go 1.15 github.com/jonboulle/clockwork