From aaa02ba1a0900c74884c1f295ce62d39cc2458bd Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Fri, 20 Sep 2019 17:08:25 -0700 Subject: [PATCH] added build link endpoint with redirect --- cmd/drone-server/inject_service.go | 6 +- cmd/drone-server/wire_gen.go | 4 +- core/linker.go | 23 +++++++ go.mod | 2 +- go.sum | 3 + handler/api/api.go | 5 ++ handler/api/repos/builds/link/link.go | 74 ++++++++++++++++++++++ handler/api/repos/builds/link/link_test.go | 15 +++++ service/linker/linker.go | 40 ++++++++++++ service/linker/linker_test.go | 15 +++++ trigger/trigger.go | 4 +- 11 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 core/linker.go create mode 100644 handler/api/repos/builds/link/link.go create mode 100644 handler/api/repos/builds/link/link_test.go create mode 100644 service/linker/linker.go create mode 100644 service/linker/linker_test.go diff --git a/cmd/drone-server/inject_service.go b/cmd/drone-server/inject_service.go index 45e9b12a5..580ed422c 100644 --- a/cmd/drone-server/inject_service.go +++ b/cmd/drone-server/inject_service.go @@ -21,12 +21,13 @@ import ( "github.com/drone/drone/metric/sink" "github.com/drone/drone/pubsub" "github.com/drone/drone/service/commit" - "github.com/drone/drone/service/content" + contents "github.com/drone/drone/service/content" "github.com/drone/drone/service/content/cache" "github.com/drone/drone/service/hook" "github.com/drone/drone/service/hook/parser" + "github.com/drone/drone/service/linker" "github.com/drone/drone/service/netrc" - "github.com/drone/drone/service/org" + orgs "github.com/drone/drone/service/org" "github.com/drone/drone/service/repo" "github.com/drone/drone/service/status" "github.com/drone/drone/service/syncer" @@ -47,6 +48,7 @@ var serviceSet = wire.NewSet( cron.New, livelog.New, orgs.New, + linker.New, parser.New, pubsub.New, token.Renewer, diff --git a/cmd/drone-server/wire_gen.go b/cmd/drone-server/wire_gen.go index 7ad0caa67..ef1c884a4 100644 --- a/cmd/drone-server/wire_gen.go +++ b/cmd/drone-server/wire_gen.go @@ -15,6 +15,7 @@ import ( "github.com/drone/drone/service/commit" "github.com/drone/drone/service/hook/parser" "github.com/drone/drone/service/license" + "github.com/drone/drone/service/linker" "github.com/drone/drone/service/org" "github.com/drone/drone/service/token" "github.com/drone/drone/service/user" @@ -79,6 +80,7 @@ func InitializeApplication(config2 config.Config) (application, error) { runner := provideRunner(buildManager, secretService, registryService, config2) hookService := provideHookService(client, renewer, config2) licenseService := license.NewService(userStore, repositoryStore, buildStore, coreLicense) + coreLinker := linker.New(client) permStore := perm.New(db) repositoryService := provideRepositoryService(client, renewer, config2) session, err := provideSession(userStore, config2) @@ -87,7 +89,7 @@ func InitializeApplication(config2 config.Config) (application, error) { } batcher := batch.New(db) syncer := provideSyncer(repositoryService, repositoryStore, userStore, batcher, config2) - server := api.New(buildStore, commitService, cronStore, corePubsub, globalSecretStore, hookService, logStore, coreLicense, licenseService, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, triggerer, userStore, webhookSender) + server := api.New(buildStore, commitService, cronStore, corePubsub, globalSecretStore, hookService, logStore, coreLicense, licenseService, coreLinker, permStore, repositoryStore, repositoryService, scheduler, secretStore, stageStore, stepStore, statusService, session, logStream, syncer, system, triggerer, userStore, webhookSender) organizationService := orgs.New(client, renewer) userService := user.New(client) admissionService := provideAdmissionPlugin(client, organizationService, userService, config2) diff --git a/core/linker.go b/core/linker.go new file mode 100644 index 000000000..b8b19ae84 --- /dev/null +++ b/core/linker.go @@ -0,0 +1,23 @@ +// Copyright 2019 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 core + +import "context" + +// Linker provides a deep link to to a git resource in the +// source control management system for a given build. +type Linker interface { + Link(ctx context.Context, repo *Repository, build *Build) (string, error) +} diff --git a/go.mod b/go.mod index 99b861bf5..05acd8184 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/drone/envsubst v1.0.1 github.com/drone/go-license v1.0.2 github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2 - github.com/drone/go-scm v1.5.1-0.20190826160521-fda52b1e0829 + github.com/drone/go-scm v1.6.1-0.20190920225054-0d1ea63283ad github.com/drone/signal v1.0.0 github.com/dustin/go-humanize v1.0.0 github.com/ghodss/yaml v1.0.0 diff --git a/go.sum b/go.sum index 837309c1b..37354c045 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,9 @@ github.com/drone/go-scm v1.5.1-0.20190820185953-16026ee6136f h1:9XvpwHypYIs/6G2p github.com/drone/go-scm v1.5.1-0.20190820185953-16026ee6136f/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw= github.com/drone/go-scm v1.5.1-0.20190826160521-fda52b1e0829 h1:aCxLIAH07bdq1+ftSxrj743yYt9h/JBd8GV9ZSBeTaQ= github.com/drone/go-scm v1.5.1-0.20190826160521-fda52b1e0829/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw= +github.com/drone/go-scm v1.6.0 h1:PZZWLeSHHwdc6zbSQpg9n0CNoRB+8DAINzX9X/wJifY= +github.com/drone/go-scm v1.6.1-0.20190920225054-0d1ea63283ad h1:rVQW27ofuahCt6Vur7h8oV+fi7JW8yxCiw6PzQmqEFw= +github.com/drone/go-scm v1.6.1-0.20190920225054-0d1ea63283ad/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw= github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= github.com/drone/signal v1.0.0/go.mod h1:S8t92eFT0g4WUgEc/LxG+LCuiskpMNsG0ajAMGnyZpc= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= diff --git a/handler/api/api.go b/handler/api/api.go index 62c7db9ec..67d8b1405 100644 --- a/handler/api/api.go +++ b/handler/api/api.go @@ -27,6 +27,7 @@ import ( "github.com/drone/drone/handler/api/queue" "github.com/drone/drone/handler/api/repos" "github.com/drone/drone/handler/api/repos/builds" + "github.com/drone/drone/handler/api/repos/builds/link" "github.com/drone/drone/handler/api/repos/builds/logs" "github.com/drone/drone/handler/api/repos/builds/stages" "github.com/drone/drone/handler/api/repos/collabs" @@ -65,6 +66,7 @@ func New( logs core.LogStore, license *core.License, licenses core.LicenseService, + linker core.Linker, perms core.PermStore, repos core.RepositoryStore, repoz core.RepositoryService, @@ -91,6 +93,7 @@ func New( Logs: logs, License: license, Licenses: licenses, + Linker: linker, Perms: perms, Repos: repos, Repoz: repoz, @@ -120,6 +123,7 @@ type Server struct { Logs core.LogStore License *core.License Licenses core.LicenseService + Linker core.Linker Perms core.PermStore Repos core.RepositoryStore Repoz core.RepositoryService @@ -175,6 +179,7 @@ func (s Server) Handler() http.Handler { r.Get("/latest", builds.HandleLast(s.Repos, s.Builds, s.Stages)) r.Get("/{number}", builds.HandleFind(s.Repos, s.Builds, s.Stages)) + r.Get("/{number}/link", link.HandleLink(s.Repos, s.Builds, s.Linker)) r.Get("/{number}/logs/{stage}/{step}", logs.HandleFind(s.Repos, s.Builds, s.Stages, s.Steps, s.Logs)) r.With( diff --git a/handler/api/repos/builds/link/link.go b/handler/api/repos/builds/link/link.go new file mode 100644 index 000000000..f70f65922 --- /dev/null +++ b/handler/api/repos/builds/link/link.go @@ -0,0 +1,74 @@ +// Copyright 2019 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 link + +import ( + "net/http" + "strconv" + + "github.com/drone/drone/core" + "github.com/drone/drone/handler/api/render" + + "github.com/go-chi/chi" +) + +// payload wraps the link and returns to the +// client as a valid json object. +type payload struct { + Link string `json:"link"` +} + +// HandleLink returns an http.HandlerFunc that redirects the +// user to the git resource in the remote source control +// management system. +func HandleLink( + repos core.RepositoryStore, + builds core.BuildStore, + linker core.Linker, +) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + ctx = r.Context() + namespace = chi.URLParam(r, "owner") + name = chi.URLParam(r, "name") + ) + number, err := strconv.ParseInt(chi.URLParam(r, "number"), 10, 64) + if err != nil { + render.BadRequest(w, err) + return + } + repo, err := repos.FindName(ctx, namespace, name) + if err != nil { + render.NotFound(w, err) + return + } + build, err := builds.FindNumber(ctx, repo.ID, number) + if err != nil { + render.NotFound(w, err) + return + } + to, err := linker.Link(ctx, repo, build) + if err != nil { + render.NotFound(w, err) + return + } + if r.FormValue("redirect") == "true" { + http.Redirect(w, r, to, http.StatusSeeOther) + return + } + v := &payload{to} + render.JSON(w, v, http.StatusOK) + } +} diff --git a/handler/api/repos/builds/link/link_test.go b/handler/api/repos/builds/link/link_test.go new file mode 100644 index 000000000..2a10f2fe4 --- /dev/null +++ b/handler/api/repos/builds/link/link_test.go @@ -0,0 +1,15 @@ +// Copyright 2019 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 link diff --git a/service/linker/linker.go b/service/linker/linker.go new file mode 100644 index 000000000..a1513bc50 --- /dev/null +++ b/service/linker/linker.go @@ -0,0 +1,40 @@ +// Copyright 2019 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 linker + +import ( + "context" + + "github.com/drone/drone/core" + "github.com/drone/go-scm/scm" +) + +// New returns a new Linker server. +func New(client *scm.Client) core.Linker { + return &service{ + client: client, + } +} + +type service struct { + client *scm.Client +} + +func (s *service) Link(ctx context.Context, repo *core.Repository, build *core.Build) (string, error) { + return s.client.Linker.Resource(ctx, repo.Slug, scm.Reference{ + Path: build.Ref, + Sha: build.After, + }) +} diff --git a/service/linker/linker_test.go b/service/linker/linker_test.go new file mode 100644 index 000000000..5dd85e24e --- /dev/null +++ b/service/linker/linker_test.go @@ -0,0 +1,15 @@ +// Copyright 2019 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 linker diff --git a/trigger/trigger.go b/trigger/trigger.go index 19b2c0d9e..8a1fd46ee 100644 --- a/trigger/trigger.go +++ b/trigger/trigger.go @@ -208,7 +208,7 @@ func (t *triggerer) Trigger(ctx context.Context, repo *core.Repository, base *co if err != nil { logger = logger.WithError(err) logger.Warnln("trigger: cannot convert yaml") - return nil, err + return t.createBuildError(ctx, repo, base, err.Error()) } // this code is temporarily in place to detect and convert @@ -221,7 +221,7 @@ func (t *triggerer) Trigger(ctx context.Context, repo *core.Repository, base *co if err != nil { logger = logger.WithError(err) logger.Warnln("trigger: cannot convert yaml") - return nil, err + return t.createBuildError(ctx, repo, base, err.Error()) } manifest, err := yaml.ParseString(raw.Data)