diff --git a/cmd/server/flags.go b/cmd/server/flags.go index f61c960cd..4d0df5226 100644 --- a/cmd/server/flags.go +++ b/cmd/server/flags.go @@ -453,65 +453,6 @@ var flags = []cli.Flag{ Usage: "stash skip ssl verification", }, // - // Coding - // - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_CODING"}, - Name: "coding", - Usage: "coding driver is enabled", - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CODING_URL"}, - Name: "coding-server", - Usage: "coding server address", - Value: "https://coding.net", - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CODING_CLIENT"}, - Name: "coding-client", - Usage: "coding oauth2 client id", - FilePath: os.Getenv("WOODPECKER_CODING_CLIENT_FILE"), - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CODING_SECRET"}, - Name: "coding-secret", - Usage: "coding oauth2 client secret", - FilePath: os.Getenv("WOODPECKER_CODING_SECRET_FILE"), - }, - &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_CODING_SCOPE"}, - Name: "coding-scope", - Usage: "coding oauth scope", - Value: cli.NewStringSlice( - "user", - "project", - "project:depot", - ), - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CODING_GIT_MACHINE"}, - Name: "coding-git-machine", - Usage: "coding machine name", - Value: "git.coding.net", - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CODING_GIT_USERNAME"}, - Name: "coding-git-username", - Usage: "coding machine user username", - FilePath: os.Getenv("WOODPECKER_CODING_GIT_USERNAME_FILE"), - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CODING_GIT_PASSWORD"}, - Name: "coding-git-password", - Usage: "coding machine user password", - FilePath: os.Getenv("WOODPECKER_CODING_GIT_PASSWORD_FILE"), - }, - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_CODING_SKIP_VERIFY"}, - Name: "coding-skip-verify", - Usage: "coding skip ssl verification", - }, - // // development flags // &cli.StringFlag{ diff --git a/cmd/server/setup.go b/cmd/server/setup.go index 7cd477050..6f0d1706c 100644 --- a/cmd/server/setup.go +++ b/cmd/server/setup.go @@ -39,7 +39,6 @@ import ( "github.com/woodpecker-ci/woodpecker/server/forge" "github.com/woodpecker-ci/woodpecker/server/forge/bitbucket" "github.com/woodpecker-ci/woodpecker/server/forge/bitbucketserver" - "github.com/woodpecker-ci/woodpecker/server/forge/coding" "github.com/woodpecker-ci/woodpecker/server/forge/gitea" "github.com/woodpecker-ci/woodpecker/server/forge/github" "github.com/woodpecker-ci/woodpecker/server/forge/gitlab" @@ -201,8 +200,6 @@ func setupForge(c *cli.Context) (forge.Forge, error) { return setupGogs(c) case c.Bool("gitea"): return setupGitea(c) - case c.Bool("coding"): - return setupCoding(c) default: return nil, fmt.Errorf("version control system not configured") } @@ -288,21 +285,6 @@ func setupGitHub(c *cli.Context) (forge.Forge, error) { return github.New(opts) } -// helper function to setup the Coding forge from the CLI arguments. -func setupCoding(c *cli.Context) (forge.Forge, error) { - opts := coding.Opts{ - URL: c.String("coding-server"), - Client: c.String("coding-client"), - Secret: c.String("coding-secret"), - Scopes: c.StringSlice("coding-scope"), - Username: c.String("coding-git-username"), - Password: c.String("coding-git-password"), - SkipVerify: c.Bool("coding-skip-verify"), - } - log.Trace().Msgf("Forge (coding) opts: %#v", opts) - return coding.New(opts) -} - func setupMetrics(g *errgroup.Group, _store store.Store) { pendingSteps := promauto.NewGauge(prometheus.GaugeOpts{ Namespace: "woodpecker", diff --git a/docs/docs/30-administration/10-server-config.md b/docs/docs/30-administration/10-server-config.md index d849a5418..6407e67ba 100644 --- a/docs/docs/30-administration/10-server-config.md +++ b/docs/docs/30-administration/10-server-config.md @@ -415,7 +415,3 @@ See [Bitbucket server configuration](forges/bitbucket_server/#configuration) ### `WOODPECKER_GITLAB_...` See [Gitlab configuration](forges/gitlab/#configuration) - -### `WOODPECKER_CODING_...` - -See [Coding configuration](forges/coding/#configuration) diff --git a/docs/docs/30-administration/11-forges/10-overview.md b/docs/docs/30-administration/11-forges/10-overview.md index 66cb7e87e..799829304 100644 --- a/docs/docs/30-administration/11-forges/10-overview.md +++ b/docs/docs/30-administration/11-forges/10-overview.md @@ -2,14 +2,14 @@ ## Supported features -| Feature | [GitHub](github/) | [Gitea](gitea/) | [Gitlab](gitlab/) | [Bitbucket](bitbucket/) | [Bitbucket Server](bitbucket_server/) | [Gogs](gogs/) | [Coding](coding/) | -| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | -| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | -| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | -| OAuth | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | -| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | :x: | -| [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: | :x: | :x: | :x: | +| Feature | [GitHub](github/) | [Gitea](gitea/) | [Gitlab](gitlab/) | [Bitbucket](bitbucket/) | [Bitbucket Server](bitbucket_server/) | [Gogs](gogs/) | +| --- | :---: | :---: | :---: | :---: | :---: | :---: | +| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | +| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | +| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: | :x: | +| OAuth | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | :x: | +| [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: | :x: | :x: | -¹) for Gitea versions 1.17 or lower not for pull requests +¹ for Gitea versions 1.17 or lower not for pull requests diff --git a/docs/docs/30-administration/11-forges/80-coding.md b/docs/docs/30-administration/11-forges/80-coding.md deleted file mode 100644 index d7f3ed738..000000000 --- a/docs/docs/30-administration/11-forges/80-coding.md +++ /dev/null @@ -1,70 +0,0 @@ -# Coding - -## Configuration - -This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations. - -### `WOODPECKER_CODING` -> Default: `false` - -Enables the Coding driver. - -### `WOODPECKER_CODING_URL` -> Default: `https://coding.net` - -Configures the Coding server address. - -### `WOODPECKER_CODING_CLIENT` -> Default: empty - -Configures the Coding OAuth client id. This is used to authorize access. - -### `WOODPECKER_CODING_CLIENT_FILE` -> Default: empty - -Read the value for `WOODPECKER_CODING_CLIENT` from the specified filepath - -### `WOODPECKER_CODING_SECRET` -> Default: empty - -Configures the Coding OAuth client secret. This is used to authorize access. - -### `WOODPECKER_CODING_SECRET_FILE` -> Default: empty - -Read the value for `WOODPECKER_CODING_SECRET` from the specified filepath - -### `WOODPECKER_CODING_SCOPE` -> Default: `user, project, project:depot` - -Comma-separated list of OAuth scopes. - -### `WOODPECKER_CODING_GIT_MACHINE` -> Default: `git.coding.net` - -TODO - -### `WOODPECKER_CODING_GIT_USERNAME` -> Default: empty - -This username is used to authenticate and clone all private repositories. - -### `WOODPECKER_CODING_GIT_USERNAME_FILE` -> Default: empty - -Read the value for `WOODPECKER_CODING_GIT_USERNAME` from the specified filepath - -### `WOODPECKER_CODING_GIT_PASSWORD` -> Default: empty - -The password is used to authenticate and clone all private repositories. - -### `WOODPECKER_CODING_GIT_PASSWORD_FILE` -> Default: empty - -Read the value for `WOODPECKER_CODING_GIT_PASSWORD` from the specified filepath - -### `WOODPECKER_CODING_SKIP_VERIFY` -> Default: `false` - -Configure if SSL verification should be skipped. diff --git a/docs/docs/91-migrations.md b/docs/docs/91-migrations.md index 88f92fa9e..0dc91b253 100644 --- a/docs/docs/91-migrations.md +++ b/docs/docs/91-migrations.md @@ -15,6 +15,7 @@ Some versions need some changes to the server configuration or the pipeline conf - Updated Prometheus gauge `*_job_*` to `*_step_*` - Renamed config env `WOODPECKER_MAX_PROCS` to `WOODPECKER_MAX_WORKFLOWS` (still available as fallback) - The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml` -> `.drone.yml` +- Dropped support for [Coding](https://coding.net/). ## 0.15.0 diff --git a/server/forge/coding/coding.go b/server/forge/coding/coding.go deleted file mode 100644 index b8ca69b18..000000000 --- a/server/forge/coding/coding.go +++ /dev/null @@ -1,374 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package coding - -import ( - "context" - "crypto/tls" - "fmt" - "net/http" - "strings" - - "golang.org/x/oauth2" - - "github.com/woodpecker-ci/woodpecker/server" - "github.com/woodpecker-ci/woodpecker/server/forge" - "github.com/woodpecker-ci/woodpecker/server/forge/coding/internal" - "github.com/woodpecker-ci/woodpecker/server/forge/common" - forge_types "github.com/woodpecker-ci/woodpecker/server/forge/types" - "github.com/woodpecker-ci/woodpecker/server/model" -) - -const ( - defaultURL = "https://coding.net" // Default Coding URL -) - -// Opts defines configuration options. -type Opts struct { - URL string // Coding server url. - Client string // Coding oauth client id. - Secret string // Coding oauth client secret. - Scopes []string // Coding oauth scopes. - Username string // Optional machine account username. - Password string // Optional machine account password. - SkipVerify bool // Skip ssl verification. -} - -// New returns a Forge implementation that integrates with a Coding Platform or -// Coding Enterprise version control hosting provider. -func New(opts Opts) (forge.Forge, error) { - r := &Coding{ - URL: defaultURL, - Client: opts.Client, - Secret: opts.Secret, - Scopes: opts.Scopes, - Username: opts.Username, - Password: opts.Password, - SkipVerify: opts.SkipVerify, - } - if opts.URL != defaultURL { - r.URL = strings.TrimSuffix(opts.URL, "/") - } - - return r, nil -} - -type Coding struct { - URL string - Client string - Secret string - Scopes []string - Username string - Password string - SkipVerify bool -} - -// Name returns the string name of this driver -func (c *Coding) Name() string { - return "coding" -} - -// Login authenticates the session and returns the -// forge user details. -func (c *Coding) Login(ctx context.Context, res http.ResponseWriter, req *http.Request) (*model.User, error) { - config := c.newConfig(server.Config.Server.Host) - - // get the OAuth errors - if err := req.FormValue("error"); err != "" { - return nil, &forge_types.AuthError{ - Err: err, - Description: req.FormValue("error_description"), - URI: req.FormValue("error_uri"), - } - } - - // get the OAuth code - code := req.FormValue("code") - if len(code) == 0 { - http.Redirect(res, req, config.AuthCodeURL("woodpecker"), http.StatusSeeOther) - return nil, nil - } - - token, err := config.Exchange(c.newContext(ctx), code) - if err != nil { - return nil, err - } - - user, err := c.newClientToken(ctx, token.AccessToken).GetCurrentUser() - if err != nil { - return nil, err - } - - return &model.User{ - Login: user.GlobalKey, - Email: user.Email, - Token: token.AccessToken, - Secret: token.RefreshToken, - Expiry: token.Expiry.UTC().Unix(), - Avatar: c.resourceLink(user.Avatar), - }, nil -} - -// Auth authenticates the session and returns the forge user -// login for the given token and secret -func (c *Coding) Auth(ctx context.Context, token, _ string) (string, error) { - user, err := c.newClientToken(ctx, token).GetCurrentUser() - if err != nil { - return "", err - } - return user.GlobalKey, nil -} - -// Refresh refreshes an oauth token and expiration for the given -// user. It returns true if the token was refreshed, false if the -// token was not refreshed, and error if it failed to refresh. -func (c *Coding) Refresh(ctx context.Context, u *model.User) (bool, error) { - config := c.newConfig("") - source := config.TokenSource(c.newContext(ctx), &oauth2.Token{RefreshToken: u.Secret}) - token, err := source.Token() - if err != nil || len(token.AccessToken) == 0 { - return false, err - } - - u.Token = token.AccessToken - u.Secret = token.RefreshToken - u.Expiry = token.Expiry.UTC().Unix() - return true, nil -} - -// Teams fetches a list of team memberships from the forge. -func (c *Coding) Teams(_ context.Context, _ *model.User) ([]*model.Team, error) { - // EMPTY: not implemented in Coding OAuth API - return nil, forge_types.ErrNotImplemented -} - -// TeamPerm fetches the named organization permissions from -// the forge for the specified user. -func (c *Coding) TeamPerm(_ *model.User, _ string) (*model.Perm, error) { - // EMPTY: not implemented in Coding OAuth API - return nil, nil -} - -// Repo fetches the repository from the forge. -func (c *Coding) Repo(ctx context.Context, u *model.User, _ model.ForgeRemoteID, owner, name string) (*model.Repo, error) { - client := c.newClient(ctx, u) - project, err := client.GetProject(owner, name) - if err != nil { - return nil, err - } - depot, err := client.GetDepot(owner, name) - if err != nil { - return nil, err - } - return &model.Repo{ - // TODO(1138) ForgeID: project.ID, - Owner: project.Owner, - Name: project.Name, - FullName: projectFullName(project.Owner, project.Name), - Avatar: c.resourceLink(project.Icon), - Link: c.resourceLink(project.DepotPath), - SCMKind: model.RepoGit, - Clone: project.HTTPSURL, - Branch: depot.DefaultBranch, - IsSCMPrivate: !project.IsPublic, - }, nil -} - -// Repos fetches a list of repos from the forge. -func (c *Coding) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) { - client := c.newClient(ctx, u) - projectList, err := client.GetProjectList() - if err != nil { - return nil, err - } - - repos := make([]*model.Repo, 0) - for _, project := range projectList { - depot, err := client.GetDepot(project.Owner, project.Name) - if err != nil { - return nil, err - } - repo := &model.Repo{ - // TODO(1138) ForgeID: project.ID, - Owner: project.Owner, - Name: project.Name, - FullName: projectFullName(project.Owner, project.Name), - Avatar: c.resourceLink(project.Icon), - Link: c.resourceLink(project.DepotPath), - SCMKind: model.RepoGit, - Clone: project.HTTPSURL, - Branch: depot.DefaultBranch, - IsSCMPrivate: !project.IsPublic, - } - repos = append(repos, repo) - } - return repos, nil -} - -// Perm fetches the named repository permissions from -// the forge for the specified user. -func (c *Coding) Perm(ctx context.Context, u *model.User, repo *model.Repo) (*model.Perm, error) { - project, err := c.newClient(ctx, u).GetProject(repo.Owner, repo.Name) - if err != nil { - return nil, err - } - - if project.Role == "owner" || project.Role == "admin" { - return &model.Perm{Pull: true, Push: true, Admin: true}, nil - } - if project.Role == "member" { - return &model.Perm{Pull: true, Push: true, Admin: false}, nil - } - return &model.Perm{Pull: false, Push: false, Admin: false}, nil -} - -// File fetches a file from the forge repository and returns in string -// format. -func (c *Coding) File(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) { - data, err := c.newClient(ctx, u).GetFile(r.Owner, r.Name, b.Commit, f) - if err != nil { - return nil, err - } - return data, nil -} - -func (c *Coding) Dir(_ context.Context, _ *model.User, _ *model.Repo, _ *model.Pipeline, _ string) ([]*forge_types.FileMeta, error) { - return nil, forge_types.ErrNotImplemented -} - -// Status sends the commit status to the forge. -func (c *Coding) Status(_ context.Context, _ *model.User, _ *model.Repo, _ *model.Pipeline, _ *model.Step) error { - // EMPTY: not implemented in Coding OAuth API - return nil -} - -// Netrc returns a .netrc file that can be used to clone -// private repositories from a forge. -func (c *Coding) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { - host, err := common.ExtractHostFromCloneURL(r.Clone) - if err != nil { - return nil, err - } - - if c.Password != "" { - return &model.Netrc{ - Login: c.Username, - Password: c.Password, - Machine: host, - }, nil - } - - return &model.Netrc{ - Login: u.Token, - Password: "x-oauth-basic", - Machine: host, - }, nil -} - -// Activate activates a repository by creating the post-commit hook. -func (c *Coding) Activate(ctx context.Context, u *model.User, r *model.Repo, link string) error { - return c.newClient(ctx, u).AddWebhook(r.Owner, r.Name, link) -} - -// Deactivate deactivates a repository by removing all previously created -// post-commit hooks matching the given link. -func (c *Coding) Deactivate(ctx context.Context, u *model.User, r *model.Repo, link string) error { - return c.newClient(ctx, u).RemoveWebhook(r.Owner, r.Name, link) -} - -// Branches returns the names of all branches for the named repository. -func (c *Coding) Branches(_ context.Context, _ *model.User, r *model.Repo) ([]string, error) { - // TODO: fetch all branches - return []string{r.Branch}, nil -} - -// BranchHead returns the sha of the head (latest commit) of the specified branch -func (c *Coding) BranchHead(_ context.Context, _ *model.User, _ *model.Repo, _ string) (string, error) { - // TODO(1138): missing implementation - return "", forge_types.ErrNotImplemented -} - -// Hook parses the post-commit hook from the Request body and returns the -// required data in a standard format. -func (c *Coding) Hook(_ context.Context, r *http.Request) (*model.Repo, *model.Pipeline, error) { - repo, pipeline, err := parseHook(r) - if pipeline != nil { - pipeline.Avatar = c.resourceLink(pipeline.Avatar) - } - return repo, pipeline, err -} - -// OrgMembership returns if user is member of organization and if user -// is admin/owner in this organization. -func (c *Coding) OrgMembership(_ context.Context, _ *model.User, _ string) (*model.OrgPerm, error) { - // TODO: Not supported in Coding OAuth API - return nil, nil -} - -// helper function to return the Coding oauth2 context using an HTTPClient that -// disables TLS verification if disabled in the forge settings. -func (c *Coding) newContext(ctx context.Context) context.Context { - if !c.SkipVerify { - return ctx - } - return context.WithValue(ctx, oauth2.HTTPClient, &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - }, - }) -} - -// helper function to return the Coding oauth2 config -func (c *Coding) newConfig(redirect string) *oauth2.Config { - return &oauth2.Config{ - ClientID: c.Client, - ClientSecret: c.Secret, - Scopes: []string{strings.Join(c.Scopes, ",")}, - Endpoint: oauth2.Endpoint{ - AuthURL: fmt.Sprintf("%s/oauth_authorize.html", c.URL), - TokenURL: fmt.Sprintf("%s/api/oauth/access_token_v2", c.URL), - }, - RedirectURL: fmt.Sprintf("%s/authorize", redirect), - } -} - -// helper function to return the Coding oauth2 client -func (c *Coding) newClient(ctx context.Context, u *model.User) *internal.Client { - return c.newClientToken(ctx, u.Token) -} - -// helper function to return the Coding oauth2 client -func (c *Coding) newClientToken(ctx context.Context, token string) *internal.Client { - client := &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: c.SkipVerify, - }, - }, - } - return internal.NewClient(ctx, c.URL, "/api", token, "woodpecker", client) -} - -func (c *Coding) resourceLink(resourcePath string) string { - if strings.HasPrefix(resourcePath, "http") { - return resourcePath - } - return c.URL + resourcePath -} diff --git a/server/forge/coding/coding_test.go b/server/forge/coding/coding_test.go deleted file mode 100644 index faa1167e1..000000000 --- a/server/forge/coding/coding_test.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package coding - -import ( - "bytes" - "context" - "net/http" - "net/http/httptest" - "testing" - - "github.com/franela/goblin" - "github.com/gin-gonic/gin" - - "github.com/woodpecker-ci/woodpecker/server/forge/coding/fixtures" - "github.com/woodpecker-ci/woodpecker/server/model" -) - -func Test_coding(t *testing.T) { - gin.SetMode(gin.TestMode) - - s := httptest.NewServer(fixtures.Handler()) - c := &Coding{URL: s.URL} - - ctx := context.Background() - g := goblin.Goblin(t) - g.Describe("Coding", func() { - g.After(func() { - s.Close() - }) - - g.Describe("Creating a forge", func() { - g.It("Should return client with specified options", func() { - forge, _ := New(Opts{ - URL: "https://coding.net", - Client: "KTNF2ALdm3ofbtxLh6IbV95Ro5AKWJUP", - Secret: "zVtxJrKhNhBcNyqCz1NggNAAmehAxnRO3Z0fXmCp", - Scopes: []string{"user", "project", "project:depot"}, - Username: "someuser", - Password: "password", - SkipVerify: true, - }) - g.Assert(forge.(*Coding).URL).Equal("https://coding.net") - g.Assert(forge.(*Coding).Client).Equal("KTNF2ALdm3ofbtxLh6IbV95Ro5AKWJUP") - g.Assert(forge.(*Coding).Secret).Equal("zVtxJrKhNhBcNyqCz1NggNAAmehAxnRO3Z0fXmCp") - g.Assert(forge.(*Coding).Scopes).Equal([]string{"user", "project", "project:depot"}) - g.Assert(forge.(*Coding).Username).Equal("someuser") - g.Assert(forge.(*Coding).Password).Equal("password") - g.Assert(forge.(*Coding).SkipVerify).Equal(true) - }) - }) - - g.Describe("Given an authorization request", func() { - g.It("Should redirect to authorize", func() { - w := httptest.NewRecorder() - r, _ := http.NewRequest("GET", "", nil) - _, err := c.Login(ctx, w, r) - g.Assert(err).IsNil() - g.Assert(w.Code).Equal(http.StatusSeeOther) - }) - g.It("Should return authenticated user", func() { - r, _ := http.NewRequest("GET", "?code=code", nil) - u, err := c.Login(ctx, nil, r) - g.Assert(err).IsNil() - g.Assert(u.Login).Equal(fakeUser.Login) - g.Assert(u.Token).Equal(fakeUser.Token) - g.Assert(u.Secret).Equal(fakeUser.Secret) - }) - }) - - g.Describe("Given an access token", func() { - g.It("Should return the authenticated user", func() { - login, err := c.Auth(ctx, fakeUser.Token, fakeUser.Secret) - g.Assert(err).IsNil() - g.Assert(login).Equal(fakeUser.Login) - }) - g.It("Should handle a failure to resolve user", func() { - _, err := c.Auth(ctx, fakeUserNotFound.Token, fakeUserNotFound.Secret) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("Given a refresh token", func() { - g.It("Should return a refresh access token", func() { - ok, err := c.Refresh(ctx, fakeUserRefresh) - g.Assert(err).IsNil() - g.Assert(ok).IsTrue() - g.Assert(fakeUserRefresh.Token).Equal("VDZupx0usVRV4oOd1FCu4xUxgk8SY0TK") - g.Assert(fakeUserRefresh.Secret).Equal("BenBQq7TWZ7Cp0aUM47nQjTz2QHNmTWcPctB609n") - }) - g.It("Should handle an invalid refresh token", func() { - ok, _ := c.Refresh(ctx, fakeUserRefreshInvalid) - g.Assert(ok).IsFalse() - }) - }) - - g.Describe("When requesting a repository", func() { - g.It("Should return the details", func() { - repo, err := c.Repo(ctx, fakeUser, "", fakeRepo.Owner, fakeRepo.Name) - g.Assert(err).IsNil() - g.Assert(repo.FullName).Equal(fakeRepo.FullName) - g.Assert(repo.Avatar).Equal(s.URL + fakeRepo.Avatar) - g.Assert(repo.Link).Equal(s.URL + fakeRepo.Link) - g.Assert(repo.SCMKind).Equal(fakeRepo.SCMKind) - g.Assert(repo.Clone).Equal(fakeRepo.Clone) - g.Assert(repo.Branch).Equal(fakeRepo.Branch) - g.Assert(repo.IsSCMPrivate).Equal(fakeRepo.IsSCMPrivate) - }) - g.It("Should handle not found errors", func() { - _, err := c.Repo(ctx, fakeUser, "", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When requesting repository permissions", func() { - g.It("Should authorize admin access for project owner", func() { - perm, err := c.Perm(ctx, fakeUser, &model.Repo{Owner: "demo1", Name: "perm_owner"}) - g.Assert(err).IsNil() - g.Assert(perm.Pull).IsTrue() - g.Assert(perm.Push).IsTrue() - g.Assert(perm.Admin).IsTrue() - }) - g.It("Should authorize admin access for project admin", func() { - perm, err := c.Perm(ctx, fakeUser, &model.Repo{Owner: "demo1", Name: "perm_admin"}) - g.Assert(err).IsNil() - g.Assert(perm.Pull).IsTrue() - g.Assert(perm.Push).IsTrue() - g.Assert(perm.Admin).IsTrue() - }) - g.It("Should authorize read access for project member", func() { - perm, err := c.Perm(ctx, fakeUser, &model.Repo{Owner: "demo1", Name: "perm_member"}) - g.Assert(err).IsNil() - g.Assert(perm.Pull).IsTrue() - g.Assert(perm.Push).IsTrue() - g.Assert(perm.Admin).IsFalse() - }) - g.It("Should authorize no access for project guest", func() { - perm, err := c.Perm(ctx, fakeUser, &model.Repo{Owner: "demo1", Name: "perm_guest"}) - g.Assert(err).IsNil() - g.Assert(perm.Pull).IsFalse() - g.Assert(perm.Push).IsFalse() - g.Assert(perm.Admin).IsFalse() - }) - g.It("Should handle not found errors", func() { - _, err := c.Perm(ctx, fakeUser, fakeRepoNotFound) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When downloading a file", func() { - g.It("Should return file for specified pipeline", func() { - data, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, ".woodpecker.yml") - g.Assert(err).IsNil() - g.Assert(string(data)).Equal("pipeline:\n test:\n image: golang:1.6\n commands:\n - go test\n") - }) - }) - - g.Describe("When requesting a netrc config", func() { - g.It("Should return the netrc file for global credential", func() { - forge, _ := New(Opts{ - Username: "someuser", - Password: "password", - }) - netrc, err := forge.Netrc(fakeUser, fakeRepo) - g.Assert(err).IsNil() - g.Assert(netrc.Login).Equal("someuser") - g.Assert(netrc.Password).Equal("password") - g.Assert(netrc.Machine).Equal("git.coding.net") - }) - g.It("Should return the netrc file for specified user", func() { - forge, _ := New(Opts{}) - netrc, err := forge.Netrc(fakeUser, fakeRepo) - g.Assert(err).IsNil() - g.Assert(netrc.Login).Equal(fakeUser.Token) - g.Assert(netrc.Password).Equal("x-oauth-basic") - g.Assert(netrc.Machine).Equal("git.coding.net") - }) - }) - - g.Describe("When activating a repository", func() { - g.It("Should create the hook", func() { - err := c.Activate(ctx, fakeUser, fakeRepo, "http://127.0.0.1") - g.Assert(err).IsNil() - }) - g.It("Should update the hook when exists", func() { - err := c.Activate(ctx, fakeUser, fakeRepo, "http://127.0.0.2") - g.Assert(err).IsNil() - }) - }) - - g.Describe("When deactivating a repository", func() { - g.It("Should successfully remove hook", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://127.0.0.3") - g.Assert(err).IsNil() - }) - g.It("Should successfully deactivate when hook already removed", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://127.0.0.4") - g.Assert(err).IsNil() - }) - }) - - g.Describe("When parsing post-commit hook body", func() { - g.It("Should parse the hook", func() { - buf := bytes.NewBufferString(fixtures.PushHook) - req, _ := http.NewRequest("POST", "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPush) - - r, _, err := c.Hook(ctx, req) - g.Assert(err).IsNil() - g.Assert(r.FullName).Equal("demo1/test1") - }) - }) - }) -} - -var ( - fakeUser = &model.User{ - Login: "demo1", - Token: "KTNF2ALdm3ofbtxLh6IbV95Ro5AKWJUP", - Secret: "zVtxJrKhNhBcNyqCz1NggNAAmehAxnRO3Z0fXmCp", - } - - fakeUserNotFound = &model.User{ - Login: "demo1", - Token: "8DpqlE0hI6yr5MLlq8ysAL4p72cKGwT0", - Secret: "8Em2dkFE8Xsze88Ar8LMG7TF4CO3VCQMgpKa0VCm", - } - - fakeUserRefresh = &model.User{ - Login: "demo1", - Secret: "i9i0HQqNR8bTY4rALYEF2itayFJNbnzC1eMFppwT", - } - - fakeUserRefreshInvalid = &model.User{ - Login: "demo1", - Secret: "invalid_refresh_token", - } - - fakeRepo = &model.Repo{ - Owner: "demo1", - Name: "test1", - FullName: "demo1/test1", - Avatar: "/static/project_icon/scenery-5.png", - Link: "/u/gilala/p/abp/git", - SCMKind: model.RepoGit, - Clone: "https://git.coding.net/demo1/test1.git", - Branch: "master", - IsSCMPrivate: true, - } - - fakeRepoNotFound = &model.Repo{ - Owner: "not_found_owner", - Name: "not_found_project", - } - - fakePipeline = &model.Pipeline{ - Commit: "4504a072cc", - } -) diff --git a/server/forge/coding/fixtures/handler.go b/server/forge/coding/fixtures/handler.go deleted file mode 100644 index 144f56c11..000000000 --- a/server/forge/coding/fixtures/handler.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package fixtures - -import ( - "fmt" - "net/http" - - "github.com/gin-gonic/gin" -) - -// Handler returns an http.Handler that is capable of handing a variety of mock -// Coding requests and returns mock responses. -func Handler() http.Handler { - gin.SetMode(gin.TestMode) - - e := gin.New() - e.POST("/api/oauth/access_token_v2", getToken) - e.GET("/api/account/current_user", getUser) - e.GET("/api/user/:gk/project/:prj", getProject) - e.GET("/api/user/:gk/project/:prj/git", getDepot) - e.GET("/api/user/:gk/project/:prj/git/blob/:ref/:path", getFile) - e.GET("/api/user/:gk/project/:prj/git/hooks", getHooks) - e.POST("/api/user/:gk/project/:prj/git/hook", postHook) - e.PUT("/api/user/:gk/project/:prj/git/hook/:id", putHook) - e.DELETE("/api/user/:gk/project/:prj/git/hook/:id", deleteHook) - - return e -} - -func getToken(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch c.PostForm("grant_type") { - case "refresh_token": - switch c.PostForm("refresh_token") { - case "i9i0HQqNR8bTY4rALYEF2itayFJNbnzC1eMFppwT": - c.String(200, refreshedTokenPayload) - default: - c.String(200, invalidRefreshTokenPayload) - } - case "authorization_code": - fallthrough - default: - switch c.PostForm("code") { - case "code": - c.String(200, tokenPayload) - default: - c.String(200, invalidCodePayload) - } - } -} - -func getUser(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch c.Query("access_token") { - case "KTNF2ALdm3ofbtxLh6IbV95Ro5AKWJUP": - c.String(200, userPayload) - default: - c.String(200, userNotFoundPayload) - } -} - -func getProject(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch fmt.Sprintf("%s/%s", c.Param("gk"), c.Param("prj")) { - case "demo1/test1": - c.String(200, fakeProjectPayload) - case "demo1/perm_owner": - c.String(200, fakePermOwnerPayload) - case "demo1/perm_admin": - c.String(200, fakePermAdminPayload) - case "demo1/perm_member": - c.String(200, fakePermMemberPayload) - case "demo1/perm_guest": - c.String(200, fakePermGuestPayload) - default: - c.String(200, projectNotFoundPayload) - } -} - -func getDepot(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch fmt.Sprintf("%s/%s", c.Param("gk"), c.Param("prj")) { - case "demo1/test1": - c.String(200, fakeDepotPayload) - default: - c.String(200, projectNotFoundPayload) - } -} - -func getFile(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch fmt.Sprintf("%s/%s/%s/%s", c.Param("gk"), c.Param("prj"), c.Param("ref"), c.Param("path")) { - case "demo1/test1/master/.woodpecker.yml", "demo1/test1/4504a072cc/.woodpecker.yml": - c.String(200, fakeFilePayload) - default: - c.String(200, fileNotFoundPayload) - } -} - -func getHooks(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - c.String(200, fakeHooksPayload) -} - -func postHook(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch c.PostForm("hook_url") { - case "http://127.0.0.1": - c.String(200, `{"code":0}`) - default: - c.String(200, `{"code":1}`) - } -} - -func putHook(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch c.Param("id") { - case "2": - c.String(200, `{"code":0}`) - default: - c.String(200, `{"code":1}`) - } -} - -func deleteHook(c *gin.Context) { - c.Header("Content-Type", "application/json;charset=UTF-8") - switch c.Param("id") { - case "3": - c.String(200, `{"code":0}`) - default: - c.String(200, `{"code":1}`) - } -} - -const tokenPayload = ` -{ - "access_token":"KTNF2ALdm3ofbtxLh6IbV95Ro5AKWJUP", - "refresh_token":"zVtxJrKhNhBcNyqCz1NggNAAmehAxnRO3Z0fXmCp", - "expires_in":36000 -} -` - -const refreshedTokenPayload = ` -{ - "access_token":"VDZupx0usVRV4oOd1FCu4xUxgk8SY0TK", - "refresh_token":"BenBQq7TWZ7Cp0aUM47nQjTz2QHNmTWcPctB609n", - "expires_in":36000 -} -` - -const invalidRefreshTokenPayload = ` -{ - "code":3006, - "msg":{ - "oauth_refresh_token_error":"Token校验失败" - } -} -` - -const invalidCodePayload = ` -{ - "code":3003, - "msg":{ - "oauth_validate_code_error":"code校验失败" - } -} -` - -const userPayload = ` -{ - "code":0, - "data":{ - "global_key":"demo1", - "email":"demo1@gmail.com", - "avatar":"/static/fruit_avatar/Fruit-20.png" - } -} -` - -const userNotFoundPayload = ` -{ - "code":1, - "msg":{ - "user_not_login":"用户未登录" - } -} -` - -const fakeProjectPayload = ` -{ - "code":0, - "data":{ - "owner_user_name":"demo1", - "name":"test1", - "depot_path":"/u/gilala/p/abp/git", - "https_url":"https://git.coding.net/demo1/test1.git", - "is_public": false, - "icon":"/static/project_icon/scenery-5.png", - "current_user_role":"owner" - } -} -` - -const fakePermOwnerPayload = ` -{ - "code":0, - "data":{ - "current_user_role":"owner" - } -} -` - -const fakePermAdminPayload = ` -{ - "code":0, - "data":{ - "current_user_role":"admin" - } -} -` - -const fakePermMemberPayload = ` -{ - "code":0, - "data":{ - "current_user_role":"member" - } -} -` - -const fakePermGuestPayload = ` -{ - "code":0, - "data":{ - "current_user_role":"guest" - } -} -` - -const fakeDepotPayload = ` -{ - "code":0, - "data":{ - "default_branch":"master" - } -} -` - -const projectNotFoundPayload = ` -{ - "code":1100, - "msg":{ - "project_not_exists":"项目不存在" - } -} -` - -const fakeFilePayload = ` -{ - "code":0, - "data":{ - "file":{ - "data":"pipeline:\n test:\n image: golang:1.6\n commands:\n - go test\n" - } - } -} -` - -const fileNotFoundPayload = ` -{ - "code":0, - "data":{ - "ref":"master" - } -} -` - -const fakeHooksPayload = ` -{ - "code":0, - "data":[ - { - "id":2, - "hook_url":"http://127.0.0.2" - }, - { - "id":3, - "hook_url":"http://127.0.0.3" - } - ] -} -` diff --git a/server/forge/coding/fixtures/hooks.go b/server/forge/coding/fixtures/hooks.go deleted file mode 100644 index af422e37f..000000000 --- a/server/forge/coding/fixtures/hooks.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package fixtures - -const PushHook = ` -{ - "ref": "refs/heads/master", - "before": "861f2315056e8925e627a6f46518b9df05896e24", - "commits": [ - { - "committer": { - "name": "demo1", - "email": "demo1@gmail.com" - }, - "web_url": "https://coding.net/u/demo1/p/test1/git/commit/5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4", - "short_message": "new file .woodpecker.yml\n", - "sha": "5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4" - } - ], - "after": "5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4", - "event": "push", - "repository": { - "owner": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - }, - "https_url": "https://git.coding.net/demo1/test1.git", - "web_url": "https://coding.net/u/demo1/p/test1", - "project_id": "99999999", - "ssh_url": "git@git.coding.net:demo1/test1.git", - "name": "test1", - "description": "" - }, - "user": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - } -} -` - -const DeleteBranchPushHook = ` -{ - "ref": "refs/heads/master", - "before": "861f2315056e8925e627a6f46518b9df05896e24", - "after": "0000000000000000000000000000000000000000", - "event": "push", - "repository": { - "owner": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - }, - "https_url": "https://git.coding.net/demo1/test1.git", - "web_url": "https://coding.net/u/demo1/p/test1", - "project_id": "99999999", - "ssh_url": "git@git.coding.net:demo1/test1.git", - "name": "test1", - "description": "" - }, - "user": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - } -} -` - -const PullRequestHook = ` -{ - "pull_request": { - "target_branch": "master", - "title": "pr1", - "body": "pr message", - "source_sha": "", - "source_repository": { - "owner": { - "path": "/u/demo2", - "web_url": "https://coding.net/u/demo2", - "global_key": "demo2", - "name": "demo2", - "avatar": "/static/fruit_avatar/Fruit-2.png" - }, - "https_url": "https://git.coding.net/demo2/test2.git", - "web_url": "https://coding.net/u/demo2/p/test2", - "project_id": "7777777", - "ssh_url": "git@git.coding.net:demo2/test2.git", - "name": "test2", - "description": "", - "git_url": "git://git.coding.net/demo2/test2.git" - }, - "source_branch": "master", - "number": 1, - "web_url": "https://coding.net/u/demo1/p/test2/git/pull/1", - "merge_commit_sha": "55e77b328b71d3ee4f9e70a5f67231b0acceeadc", - "target_sha": "", - "action": "create", - "id": 7586, - "user": { - "path": "/u/demo2", - "web_url": "https://coding.net/u/demo2", - "global_key": "demo2", - "name": "demo2", - "avatar": "/static/fruit_avatar/Fruit-2.png" - }, - "status": "CANMERGE" - }, - "repository": { - "owner": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - }, - "https_url": "https://git.coding.net/demo1/test2.git", - "web_url": "https://coding.net/u/demo1/p/test2", - "project_id": "6666666", - "ssh_url": "git@git.coding.net:demo1/test2.git", - "name": "test2", - "description": "", - "git_url": "git://git.coding.net/demo1/test2.git" - }, - "event": "pull_request" -} -` - -const MergeRequestHook = ` -{ - "merge_request": { - "target_branch": "master", - "title": "mr1", - "body": "

mr message

", - "source_sha": "", - "source_branch": "branch1", - "number": 1, - "web_url": "https://coding.net/u/demo1/p/test1/git/merge/1", - "merge_commit_sha": "74e6755580c34e9fd81dbcfcbd43ee5f30259436", - "target_sha": "", - "action": "create", - "id": 533428, - "user": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - }, - "status": "CANMERGE" - }, - "repository": { - "owner": { - "path": "/u/demo1", - "web_url": "https://coding.net/u/demo1", - "global_key": "demo1", - "name": "demo1", - "avatar": "/static/fruit_avatar/Fruit-20.png" - }, - "https_url": "https://git.coding.net/demo1/test1.git", - "web_url": "https://coding.net/u/demo1/p/test1", - "project_id": "99999999", - "ssh_url": "git@git.coding.net:demo1/test1.git", - "name": "test1", - "description": "" - }, - "event": "merge_request" -} -` diff --git a/server/forge/coding/hook.go b/server/forge/coding/hook.go deleted file mode 100644 index c534d1adf..000000000 --- a/server/forge/coding/hook.go +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package coding - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "regexp" - "strings" - - "github.com/woodpecker-ci/woodpecker/server/model" -) - -const ( - hookEvent = "X-Coding-Event" - hookPush = "push" - hookPR = "pull_request" - hookMR = "merge_request" -) - -type User struct { - GlobalKey string `json:"global_key"` - Avatar string `json:"avatar"` -} - -type Repository struct { - Name string `json:"name"` - HTTPSURL string `json:"https_url"` - SSHURL string `json:"ssh_url"` - WebURL string `json:"web_url"` - Owner *User `json:"owner"` -} - -type Committer struct { - Email string `json:"email"` - Name string `json:"name"` -} - -type Commit struct { - SHA string `json:"sha"` - ShortMessage string `json:"short_message"` - Committer *Committer `json:"committer"` -} - -type PullRequest MergeRequest - -type MergeRequest struct { - SourceBranch string `json:"source_branch"` - TargetBranch string `json:"target_branch"` - CommitSHA string `json:"merge_commit_sha"` - Status string `json:"status"` - Action string `json:"action"` - Number float64 `json:"number"` - Title string `json:"title"` - Body string `json:"body"` - WebURL string `json:"web_url"` - User *User `json:"user"` -} - -type PushHook struct { - Event string `json:"event"` - Repository *Repository `json:"repository"` - Ref string `json:"ref"` - Before string `json:"before"` - After string `json:"after"` - Commits []*Commit `json:"commits"` - User *User `json:"user"` -} - -type PullRequestHook struct { - Event string `json:"event"` - Repository *Repository `json:"repository"` - PullRequest *PullRequest `json:"pull_request"` -} - -type MergeRequestHook struct { - Event string `json:"event"` - Repository *Repository `json:"repository"` - MergeRequest *MergeRequest `json:"merge_request"` -} - -func parseHook(r *http.Request) (*model.Repo, *model.Pipeline, error) { - raw, err := io.ReadAll(r.Body) - defer r.Body.Close() - if err != nil { - return nil, nil, err - } - - switch r.Header.Get(hookEvent) { - case hookPush: - return parsePushHook(raw) - case hookPR: - return parsePullRequestHook(raw) - case hookMR: - return parseMergeRequestHook(raw) - } - return nil, nil, nil -} - -func findLastCommit(commits []*Commit, sha string) *Commit { - var lastCommit *Commit - for _, commit := range commits { - if commit.SHA == sha { - lastCommit = commit - break - } - } - if lastCommit == nil { - lastCommit = &Commit{} - } - if lastCommit.Committer == nil { - lastCommit.Committer = &Committer{} - } - return lastCommit -} - -func convertRepository(repo *Repository) (*model.Repo, error) { - // tricky stuff for a team project without a team owner instead of a user owner - re := regexp.MustCompile(`git@.+:([^/]+)/.+\.git`) - matches := re.FindStringSubmatch(repo.SSHURL) - if len(matches) != 2 { - return nil, fmt.Errorf("Unable to resolve owner from ssh url %q", repo.SSHURL) - } - - return &model.Repo{ - // TODO ForgeID: repo.ID, - Owner: matches[1], - Name: repo.Name, - FullName: projectFullName(repo.Owner.GlobalKey, repo.Name), - Link: repo.WebURL, - Clone: repo.HTTPSURL, - SCMKind: model.RepoGit, - }, nil -} - -func parsePushHook(raw []byte) (*model.Repo, *model.Pipeline, error) { - hook := &PushHook{} - err := json.Unmarshal(raw, hook) - if err != nil { - return nil, nil, err - } - - // no pipeline triggered when removing ref - if hook.After == "0000000000000000000000000000000000000000" { - return nil, nil, nil - } - - repo, err := convertRepository(hook.Repository) - if err != nil { - return nil, nil, err - } - - lastCommit := findLastCommit(hook.Commits, hook.After) - pipeline := &model.Pipeline{ - Event: model.EventPush, - Commit: hook.After, - Ref: hook.Ref, - Link: fmt.Sprintf("%s/git/commit/%s", hook.Repository.WebURL, hook.After), - Branch: strings.Replace(hook.Ref, "refs/heads/", "", -1), - Message: lastCommit.ShortMessage, - Email: lastCommit.Committer.Email, - Avatar: hook.User.Avatar, - Author: hook.User.GlobalKey, - CloneURL: hook.Repository.HTTPSURL, - } - return repo, pipeline, nil -} - -func parsePullRequestHook(raw []byte) (*model.Repo, *model.Pipeline, error) { - hook := &PullRequestHook{} - err := json.Unmarshal(raw, hook) - if err != nil { - return nil, nil, err - } - if hook.PullRequest.Status != "CANMERGE" || - (hook.PullRequest.Action != "create" && hook.PullRequest.Action != "synchronize") { - return nil, nil, nil - } - - repo, err := convertRepository(hook.Repository) - if err != nil { - return nil, nil, err - } - pipeline := &model.Pipeline{ - Event: model.EventPull, - Commit: hook.PullRequest.CommitSHA, - Link: hook.PullRequest.WebURL, - Ref: fmt.Sprintf("refs/pull/%d/MERGE", int(hook.PullRequest.Number)), - Branch: hook.PullRequest.TargetBranch, - Message: hook.PullRequest.Body, - Author: hook.PullRequest.User.GlobalKey, - Avatar: hook.PullRequest.User.Avatar, - Title: hook.PullRequest.Title, - CloneURL: hook.Repository.HTTPSURL, - Refspec: fmt.Sprintf("%s:%s", hook.PullRequest.SourceBranch, hook.PullRequest.TargetBranch), - } - - return repo, pipeline, nil -} - -func parseMergeRequestHook(raw []byte) (*model.Repo, *model.Pipeline, error) { - hook := &MergeRequestHook{} - err := json.Unmarshal(raw, hook) - if err != nil { - return nil, nil, err - } - if hook.MergeRequest.Status != "CANMERGE" || - (hook.MergeRequest.Action != "create" && hook.MergeRequest.Action != "synchronize") { - return nil, nil, nil - } - - repo, err := convertRepository(hook.Repository) - if err != nil { - return nil, nil, err - } - - pipeline := &model.Pipeline{ - Event: model.EventPull, - Commit: hook.MergeRequest.CommitSHA, - Link: hook.MergeRequest.WebURL, - Ref: fmt.Sprintf("refs/merge/%d/MERGE", int(hook.MergeRequest.Number)), - Branch: hook.MergeRequest.TargetBranch, - Message: hook.MergeRequest.Body, - Author: hook.MergeRequest.User.GlobalKey, - Avatar: hook.MergeRequest.User.Avatar, - Title: hook.MergeRequest.Title, - CloneURL: hook.Repository.HTTPSURL, - Refspec: fmt.Sprintf("%s:%s", hook.MergeRequest.SourceBranch, hook.MergeRequest.TargetBranch), - } - return repo, pipeline, nil -} diff --git a/server/forge/coding/hook_test.go b/server/forge/coding/hook_test.go deleted file mode 100644 index b78a931b2..000000000 --- a/server/forge/coding/hook_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package coding - -import ( - "io" - "net/http" - "strings" - "testing" - - "github.com/franela/goblin" - - "github.com/woodpecker-ci/woodpecker/server/forge/coding/fixtures" - "github.com/woodpecker-ci/woodpecker/server/model" -) - -func Test_hook(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Coding hook", func() { - g.It("Should parse hook", func() { - reader := io.NopCloser(strings.NewReader(fixtures.PushHook)) - r := &http.Request{ - Header: map[string][]string{ - hookEvent: {hookPush}, - }, - Body: reader, - } - - repo := &model.Repo{ - Owner: "demo1", - Name: "test1", - FullName: "demo1/test1", - Link: "https://coding.net/u/demo1/p/test1", - Clone: "https://git.coding.net/demo1/test1.git", - SCMKind: model.RepoGit, - } - - pipeline := &model.Pipeline{ - Event: model.EventPush, - Commit: "5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4", - Ref: "refs/heads/master", - Link: "https://coding.net/u/demo1/p/test1/git/commit/5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4", - Branch: "master", - Message: "new file .woodpecker.yml\n", - Email: "demo1@gmail.com", - Avatar: "/static/fruit_avatar/Fruit-20.png", - Author: "demo1", - CloneURL: "https://git.coding.net/demo1/test1.git", - } - - actualRepo, actualPipeline, err := parseHook(r) - g.Assert(err).IsNil() - g.Assert(actualRepo).Equal(repo) - g.Assert(actualPipeline).Equal(pipeline) - }) - - g.It("Should find last commit", func() { - commit1 := &Commit{SHA: "1234567890", Committer: &Committer{}} - commit2 := &Commit{SHA: "abcdef1234", Committer: &Committer{}} - commits := []*Commit{commit1, commit2} - g.Assert(findLastCommit(commits, "abcdef1234")).Equal(commit2) - }) - - g.It("Should find last commit", func() { - commit1 := &Commit{SHA: "1234567890", Committer: &Committer{}} - commit2 := &Commit{SHA: "abcdef1234", Committer: &Committer{}} - commits := []*Commit{commit1, commit2} - emptyCommit := &Commit{Committer: &Committer{}} - g.Assert(findLastCommit(commits, "00000000000")).Equal(emptyCommit) - }) - - g.It("Should convert repository", func() { - repository := &Repository{ - Name: "test_project", - HTTPSURL: "https://git.coding.net/kelvin/test_project.git", - SSHURL: "git@git.coding.net:kelvin/test_project.git", - WebURL: "https://coding.net/u/kelvin/p/test_project", - Owner: &User{ - GlobalKey: "kelvin", - Avatar: "https://dn-coding-net-production-static.qbox.me/9ed11de3-65e3-4cd8-b6aa-5abe7285ab43.jpeg?imageMogr2/auto-orient/format/jpeg/crop/!209x209a0a0", - }, - } - repo := &model.Repo{ - Owner: "kelvin", - Name: "test_project", - FullName: "kelvin/test_project", - Link: "https://coding.net/u/kelvin/p/test_project", - Clone: "https://git.coding.net/kelvin/test_project.git", - SCMKind: model.RepoGit, - } - actual, err := convertRepository(repository) - g.Assert(err).IsNil() - g.Assert(actual).Equal(repo) - }) - - g.It("Should parse push hook", func() { - repo := &model.Repo{ - Owner: "demo1", - Name: "test1", - FullName: "demo1/test1", - Link: "https://coding.net/u/demo1/p/test1", - Clone: "https://git.coding.net/demo1/test1.git", - SCMKind: model.RepoGit, - } - - pipeline := &model.Pipeline{ - Event: model.EventPush, - Commit: "5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4", - Ref: "refs/heads/master", - Link: "https://coding.net/u/demo1/p/test1/git/commit/5b9912a6ff272e9c93a4c44c278fe9b359ed1ab4", - Branch: "master", - Message: "new file .woodpecker.yml\n", - Email: "demo1@gmail.com", - Avatar: "/static/fruit_avatar/Fruit-20.png", - Author: "demo1", - CloneURL: "https://git.coding.net/demo1/test1.git", - } - - actualRepo, actualPipeline, err := parsePushHook([]byte(fixtures.PushHook)) - g.Assert(err).IsNil() - g.Assert(actualRepo).Equal(repo) - g.Assert(actualPipeline).Equal(pipeline) - }) - - g.It("Should parse delete branch push hook", func() { - actualRepo, actualPipeline, err := parsePushHook([]byte(fixtures.DeleteBranchPushHook)) - g.Assert(err).IsNil() - g.Assert(actualRepo).IsNil() - g.Assert(actualPipeline).IsNil() - }) - - g.It("Should parse pull request hook", func() { - repo := &model.Repo{ - Owner: "demo1", - Name: "test2", - FullName: "demo1/test2", - Link: "https://coding.net/u/demo1/p/test2", - Clone: "https://git.coding.net/demo1/test2.git", - SCMKind: model.RepoGit, - } - - pipeline := &model.Pipeline{ - Event: model.EventPull, - Commit: "55e77b328b71d3ee4f9e70a5f67231b0acceeadc", - Link: "https://coding.net/u/demo1/p/test2/git/pull/1", - Ref: "refs/pull/1/MERGE", - Branch: "master", - Message: "pr message", - Author: "demo2", - Avatar: "/static/fruit_avatar/Fruit-2.png", - Title: "pr1", - CloneURL: "https://git.coding.net/demo1/test2.git", - Refspec: "master:master", - } - - actualRepo, actualPipeline, err := parsePullRequestHook([]byte(fixtures.PullRequestHook)) - g.Assert(err).IsNil() - g.Assert(actualRepo).Equal(repo) - g.Assert(actualPipeline).Equal(pipeline) - }) - - g.It("Should parse merge request hook", func() { - repo := &model.Repo{ - Owner: "demo1", - Name: "test1", - FullName: "demo1/test1", - Link: "https://coding.net/u/demo1/p/test1", - Clone: "https://git.coding.net/demo1/test1.git", - SCMKind: model.RepoGit, - } - - pipeline := &model.Pipeline{ - Event: model.EventPull, - Commit: "74e6755580c34e9fd81dbcfcbd43ee5f30259436", - Link: "https://coding.net/u/demo1/p/test1/git/merge/1", - Ref: "refs/merge/1/MERGE", - Branch: "master", - Message: "

mr message

", - Author: "demo1", - Avatar: "/static/fruit_avatar/Fruit-20.png", - Title: "mr1", - CloneURL: "https://git.coding.net/demo1/test1.git", - Refspec: "branch1:master", - } - - actualRepo, actualPipeline, err := parseMergeRequestHook([]byte(fixtures.MergeRequestHook)) - g.Assert(err).IsNil() - g.Assert(actualRepo).Equal(repo) - g.Assert(actualPipeline).Equal(pipeline) - }) - }) -} diff --git a/server/forge/coding/internal/coding.go b/server/forge/coding/internal/coding.go deleted file mode 100644 index 3b89ea6d0..000000000 --- a/server/forge/coding/internal/coding.go +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" -) - -type Client struct { - baseURL string - apiPath string - token string - agent string - client *http.Client - ctx context.Context -} - -type GenericAPIResponse struct { - Code int `json:"code"` - Data json.RawMessage `json:"data,omitempty"` -} - -func NewClient(ctx context.Context, baseURL, apiPath, token, agent string, client *http.Client) *Client { - return &Client{ - baseURL: baseURL, - apiPath: apiPath, - token: token, - agent: agent, - client: client, - ctx: ctx, - } -} - -// Get Generic GET for requesting Coding OAuth API -func (c *Client) Get(u string, params url.Values) ([]byte, error) { - return c.Do(http.MethodGet, u, params) -} - -// Do Generic method for requesting Coding OAuth API -func (c *Client) Do(method, u string, params url.Values) ([]byte, error) { - if params == nil { - params = url.Values{} - } - params.Set("access_token", c.token) - - rawURL := c.baseURL + c.apiPath + u - - var req *http.Request - var err error - if method != "GET" { - req, err = http.NewRequestWithContext(c.ctx, method, rawURL+"?access_token="+c.token, strings.NewReader(params.Encode())) - req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") - } else { - req, err = http.NewRequestWithContext(c.ctx, "GET", rawURL+"?"+params.Encode(), nil) - } - if err != nil { - return nil, fmt.Errorf("fail to create request for url %q: %w", rawURL, err) - } - req.Header.Set("User-Agent", c.agent) - - resp, err := c.client.Do(req) - if err != nil { - return nil, fmt.Errorf("fail to request %s %s: %w", req.Method, req.URL, err) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%s %s respond %d", req.Method, req.URL, resp.StatusCode) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("fail to read response from %s %s: %w", req.Method, req.URL.String(), err) - } - - apiResp := &GenericAPIResponse{} - err = json.Unmarshal(body, apiResp) - if err != nil { - return nil, fmt.Errorf("fail to parse response from %s %s: %w", req.Method, req.URL.String(), err) - } - if apiResp.Code != 0 { - return nil, fmt.Errorf("Coding OAuth API respond error: %s", string(body)) - } - return apiResp.Data, nil -} diff --git a/server/forge/coding/internal/error.go b/server/forge/coding/internal/error.go deleted file mode 100644 index 4de2d47be..000000000 --- a/server/forge/coding/internal/error.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "fmt" -) - -type APIClientErr struct { - Message string - URL string - Cause error -} - -func (e APIClientErr) Error() string { - return fmt.Sprintf("%s (Requested %s): %v", e.Message, e.URL, e.Cause) -} diff --git a/server/forge/coding/internal/file.go b/server/forge/coding/internal/file.go deleted file mode 100644 index 664c4a035..000000000 --- a/server/forge/coding/internal/file.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "encoding/json" - "fmt" -) - -type Commit struct { - File *File `json:"file"` -} - -type File struct { - Data string `json:"data"` -} - -func (c *Client) GetFile(globalKey, projectName, ref, path string) ([]byte, error) { - u := fmt.Sprintf("/user/%s/project/%s/git/blob/%s/%s", globalKey, projectName, ref, path) - resp, err := c.Get(u, nil) - if err != nil { - return nil, err - } - commit := &Commit{} - err = json.Unmarshal(resp, commit) - if err != nil { - return nil, APIClientErr{"fail to parse file data", u, err} - } - if commit == nil || commit.File == nil { - return nil, nil - } - return []byte(commit.File.Data), nil -} diff --git a/server/forge/coding/internal/project.go b/server/forge/coding/internal/project.go deleted file mode 100644 index 01d1a3245..000000000 --- a/server/forge/coding/internal/project.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "encoding/json" - "fmt" - "net/url" -) - -type Project struct { - Owner string `json:"owner_user_name"` - Name string `json:"name"` - DepotPath string `json:"depot_path"` - HTTPSURL string `json:"https_url"` - IsPublic bool `json:"is_public"` - Icon string `json:"icon"` - Role string `json:"current_user_role"` -} - -type Depot struct { - DefaultBranch string `json:"default_branch"` -} - -type ProjectListData struct { - Page int `json:"page"` - PageSize int `json:"pageSize"` - TotalPage int `json:"totalPage"` - TotalRow int `json:"totalRow"` - List []*Project `json:"list"` -} - -func (c *Client) GetProject(globalKey, projectName string) (*Project, error) { - u := fmt.Sprintf("/user/%s/project/%s", globalKey, projectName) - resp, err := c.Get(u, nil) - if err != nil { - return nil, err - } - - project := &Project{} - err = json.Unmarshal(resp, project) - if err != nil { - return nil, APIClientErr{"fail to parse project data", u, err} - } - return project, nil -} - -func (c *Client) GetDepot(globalKey, projectName string) (*Depot, error) { - u := fmt.Sprintf("/user/%s/project/%s/git", globalKey, projectName) - resp, err := c.Get(u, nil) - if err != nil { - return nil, err - } - - depot := &Depot{} - err = json.Unmarshal(resp, depot) - if err != nil { - return nil, APIClientErr{"fail to parse depot data", u, err} - } - return depot, nil -} - -func (c *Client) GetProjectList() ([]*Project, error) { - u := "/user/projects" - resp, err := c.Get(u, nil) - if err != nil { - return nil, err - } - data := &ProjectListData{} - err = json.Unmarshal(resp, data) - if err != nil { - return nil, APIClientErr{"fail to parse project list data", u, err} - } - if data.TotalPage == 1 { - return data.List, nil - } - - projectList := make([]*Project, 0) - projectList = append(projectList, data.List...) - for i := 2; i <= data.TotalPage; i++ { - params := url.Values{} - params.Set("page", fmt.Sprintf("%d", i)) - resp, err := c.Get(u, params) - if err != nil { - return nil, err - } - data := &ProjectListData{} - err = json.Unmarshal(resp, data) - if err != nil { - return nil, APIClientErr{"fail to parse project list data", u, err} - } - projectList = append(projectList, data.List...) - } - return projectList, nil -} diff --git a/server/forge/coding/internal/user.go b/server/forge/coding/internal/user.go deleted file mode 100644 index b6ca42154..000000000 --- a/server/forge/coding/internal/user.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "encoding/json" -) - -type User struct { - GlobalKey string `json:"global_key"` - Email string `json:"email"` - Avatar string `json:"avatar"` -} - -func (c *Client) GetCurrentUser() (*User, error) { - u := "/account/current_user" - resp, err := c.Get(u, nil) - if err != nil { - return nil, err - } - user := &User{} - err = json.Unmarshal(resp, user) - if err != nil { - return nil, APIClientErr{"fail to parse current user data", u, err} - } - return user, nil -} diff --git a/server/forge/coding/internal/webhook.go b/server/forge/coding/internal/webhook.go deleted file mode 100644 index fd6f9d892..000000000 --- a/server/forge/coding/internal/webhook.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "encoding/json" - "fmt" - "net/url" -) - -type Webhook struct { - ID int `json:"id"` - HookURL string `json:"hook_url"` -} - -func (c *Client) GetWebhooks(globalKey, projectName string) ([]*Webhook, error) { - u := fmt.Sprintf("/user/%s/project/%s/git/hooks", globalKey, projectName) - resp, err := c.Get(u, nil) - if err != nil { - return nil, err - } - webhooks := make([]*Webhook, 0) - err = json.Unmarshal(resp, &webhooks) - if err != nil { - return nil, APIClientErr{"fail to parse webhooks data", u, err} - } - return webhooks, nil -} - -func (c *Client) AddWebhook(globalKey, projectName, link string) error { - webhooks, err := c.GetWebhooks(globalKey, projectName) - if err != nil { - return err - } - webhook := matchingHooks(webhooks, link) - if webhook != nil { - u := fmt.Sprintf("/user/%s/project/%s/git/hook/%d", globalKey, projectName, webhook.ID) - params := url.Values{} - params.Set("hook_url", link) - params.Set("type_push", "true") - params.Set("type_mr_pr", "true") - - _, err := c.Do("PUT", u, params) - if err != nil { - return APIClientErr{"fail to edit webhook", u, err} - } - return nil - } - - u := fmt.Sprintf("/user/%s/project/%s/git/hook", globalKey, projectName) - params := url.Values{} - params.Set("hook_url", link) - params.Set("type_push", "true") - params.Set("type_mr_pr", "true") - - _, err = c.Do("POST", u, params) - if err != nil { - return APIClientErr{"fail to add webhook", u, err} - } - return nil -} - -func (c *Client) RemoveWebhook(globalKey, projectName, link string) error { - webhooks, err := c.GetWebhooks(globalKey, projectName) - if err != nil { - return err - } - webhook := matchingHooks(webhooks, link) - if webhook == nil { - return nil - } - - u := fmt.Sprintf("/user/%s/project/%s/git/hook/%d", globalKey, projectName, webhook.ID) - _, err = c.Do("DELETE", u, nil) - if err != nil { - return APIClientErr{"fail to remove webhook", u, err} - } - return nil -} - -// helper function to return matching hook. -func matchingHooks(hooks []*Webhook, rawurl string) *Webhook { - link, err := url.Parse(rawurl) - if err != nil { - return nil - } - for _, hook := range hooks { - hookurl, err := url.Parse(hook.HookURL) - if err == nil && hookurl.Host == link.Host { - return hook - } - } - return nil -} diff --git a/server/forge/coding/util.go b/server/forge/coding/util.go deleted file mode 100644 index b388e9664..000000000 --- a/server/forge/coding/util.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package coding - -import ( - "fmt" -) - -func projectFullName(owner, name string) string { - return fmt.Sprintf("%s/%s", owner, name) -} diff --git a/server/forge/coding/util_test.go b/server/forge/coding/util_test.go deleted file mode 100644 index b1443fe65..000000000 --- a/server/forge/coding/util_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package coding - -import ( - "testing" - - "github.com/franela/goblin" -) - -func Test_util(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Coding util", func() { - g.It("Should form project full name", func() { - g.Assert(projectFullName("gk", "prj")).Equal("gk/prj") - }) - }) -}