From 1c6d751d50dded95fa6c28e9842bb9bf36c89440 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Wed, 24 Apr 2019 14:05:47 -0700 Subject: [PATCH] trigger build from branch or sha, closed #2679 --- CHANGELOG.md | 4 + handler/api/api.go | 6 +- handler/api/repos/builds/create.go | 66 +++------- handler/api/repos/builds/create_test.go | 162 ++++++++++++++++++++++++ 4 files changed, 190 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fdb11c4..72de277b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +- endpoint to trigger new build for default branch, by [@bradrydzewski](https://github.com/bradrydzewski). [#2679](https://github.com/drone/drone/issues/2679). +- endpoint to trigger new build for branch, by [@bradrydzewski](https://github.com/bradrydzewski). [#2679](https://github.com/drone/drone/issues/2679). +- endpoint to trigger new build for branch and sha, by [@bradrydzewski](https://github.com/bradrydzewski). [#2679](https://github.com/drone/drone/issues/2679). + ## [1.1.0] - 2019-04-23 ### Added diff --git a/handler/api/api.go b/handler/api/api.go index 3ae736e9..5dec8c73 100644 --- a/handler/api/api.go +++ b/handler/api/api.go @@ -169,9 +169,9 @@ func (s Server) Handler() http.Handler { ).Post("/repair", repos.HandleRepair(s.Hooks, s.Repoz, s.Repos, s.Users, s.System.Link)) r.Route("/builds", func(r chi.Router) { - r.Get("/", builds.HandleList(s.Repos, s.Builds)) - // TODO(bradrydzewski) temporarily disabled until we finalize the endpoint. - // r.Post("/", builds.HandleCreate(s.Repos, s.Commits, s.Triggerer)) + r.With(acl.CheckWriteAccess()).Get("/", builds.HandleList(s.Repos, s.Builds)) + r.With(acl.CheckWriteAccess()).Post("/", builds.HandleCreate(s.Repos, s.Commits, s.Triggerer)) + 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}/logs/{stage}/{step}", logs.HandleFind(s.Repos, s.Builds, s.Stages, s.Steps, s.Logs)) diff --git a/handler/api/repos/builds/create.go b/handler/api/repos/builds/create.go index fb7dc3a0..7fa78254 100644 --- a/handler/api/repos/builds/create.go +++ b/handler/api/repos/builds/create.go @@ -15,7 +15,6 @@ package builds import ( - "encoding/json" "net/http" "github.com/drone/drone/core" @@ -26,13 +25,6 @@ import ( "github.com/go-chi/chi" ) -type createBuild struct { - Params map[string]string `json:"params"` - Commit *string `json:"commit"` - Branch *string `json:"branch"` - Ref *string `json:"ref"` -} - // HandleCreate returns an http.HandlerFunc that processes http // requests to create a build for the specified commit. func HandleCreate( @@ -45,36 +37,29 @@ func HandleCreate( ctx = r.Context() namespace = chi.URLParam(r, "owner") name = chi.URLParam(r, "name") + sha = r.FormValue("commit") + branch = r.FormValue("branch") user, _ = request.UserFrom(ctx) ) - in := new(createBuild) - err := json.NewDecoder(r.Body).Decode(in) - if err != nil { - render.BadRequest(w, err) - return - } - if in.Ref == nil || in.Branch == nil { - render.BadRequestf(w, "Missing branch or ref") - return - } - if in.Params == nil { - // cannot remember if parameters must be non-nil, - // so we set the value to prevent a possible - // downstream nil pointer. just being overly cautious ... - in.Params = map[string]string{} - } + repo, err := repos.FindName(ctx, namespace, name) if err != nil { render.NotFound(w, err) return } + + // if the user does not provide a branch, assume the + // default repository branch. + if branch == "" { + branch = repo.Branch + } + // expand the branch to a git reference. + ref := scm.ExpandRef(branch, "refs/heads") + var commit *core.Commit - if in.Commit == nil { - commit, err = commits.Find(ctx, user, repo.Slug, *in.Commit) - } else if in.Ref != nil { - commit, err = commits.FindRef(ctx, user, repo.Slug, *in.Ref) - } else if in.Branch != nil { - ref := scm.ExpandRef(*in.Branch, "refs/heads") + if sha != "" { + commit, err = commits.Find(ctx, user, repo.Slug, sha) + } else { commit, err = commits.FindRef(ctx, user, repo.Slug, ref) } if err != nil { @@ -89,26 +74,17 @@ func HandleCreate( Timestamp: commit.Author.Date, Title: "", // we expect this to be empty. Message: commit.Message, - Before: "", // we expect this to be empty. + Before: commit.Sha, After: commit.Sha, - Ref: "", // set below - Source: "", // set below - Target: "", // set below + Ref: ref, + Source: branch, + Target: branch, Author: commit.Author.Login, AuthorName: commit.Author.Name, AuthorEmail: commit.Author.Email, AuthorAvatar: commit.Author.Avatar, - Sender: "", // todo: what value should we use? - Params: in.Params, - } - if in.Branch != nil { - hook.Source = *in.Branch - hook.Target = *in.Branch - } - if in.Ref != nil { - branch := scm.TrimRef(*in.Ref) - hook.Source = branch - hook.Target = branch + Sender: user.Login, + Params: map[string]string{}, // todo: popular from query parameters } result, err := triggerer.Trigger(r.Context(), repo, hook) diff --git a/handler/api/repos/builds/create_test.go b/handler/api/repos/builds/create_test.go index 49523703..d5817a52 100644 --- a/handler/api/repos/builds/create_test.go +++ b/handler/api/repos/builds/create_test.go @@ -3,3 +3,165 @@ // that can be found in the LICENSE file. package builds + +import ( + "context" + "encoding/json" + "net/http/httptest" + "net/url" + "testing" + + "github.com/drone/drone/core" + "github.com/drone/drone/handler/api/request" + "github.com/drone/drone/mock" + + "github.com/go-chi/chi" + "github.com/golang/mock/gomock" + "github.com/google/go-cmp/cmp" +) + +func TestCreate(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + mockCommit := &core.Commit{ + Sha: "cce10d5c4760d1d6ede99db850ab7e77efe15579", + Ref: "refs/heads/master", + Message: "updated README.md", + Link: "https://github.com/octocatl/hello-world/commit/cce10d5c4760d1d6ede99db850ab7e77efe15579", + Author: &core.Committer{ + Name: "The Octocat", + Email: "octocat@github.com", + Login: "octocat", + Avatar: "https://github.com/octocat.png", + }, + } + + checkBuild := func(_ context.Context, _ *core.Repository, hook *core.Hook) error { + if got, want := hook.Trigger, mockUser.Login; got != want { + t.Errorf("Want hook Trigger By %s, got %s", want, got) + } + if got, want := hook.Event, core.EventPush; got != want { + t.Errorf("Want hook Event %s, got %s", want, got) + } + if got, want := hook.Link, mockCommit.Link; got != want { + t.Errorf("Want hook Link %s, got %s", want, got) + } + if got, want := hook.Message, mockCommit.Message; got != want { + t.Errorf("Want hook Message %s, got %s", want, got) + } + if got, want := hook.Before, mockCommit.Sha; got != want { + t.Errorf("Want hook Before %s, got %s", want, got) + } + if got, want := hook.After, mockCommit.Sha; got != want { + t.Errorf("Want hook After %s, got %s", want, got) + } + if got, want := hook.Ref, mockCommit.Ref; got != want { + t.Errorf("Want hook Ref %s, got %s", want, got) + } + if got, want := hook.Source, "master"; got != want { + t.Errorf("Want hook Source %s, got %s", want, got) + } + if got, want := hook.Target, "master"; got != want { + t.Errorf("Want hook Target %s, got %s", want, got) + } + if got, want := hook.Author, mockCommit.Author.Login; got != want { + t.Errorf("Want hook Author %s, got %s", want, got) + } + if got, want := hook.AuthorName, mockCommit.Author.Name; got != want { + t.Errorf("Want hook AuthorName %s, got %s", want, got) + } + if got, want := hook.AuthorEmail, mockCommit.Author.Email; got != want { + t.Errorf("Want hook AuthorEmail %s, got %s", want, got) + } + if got, want := hook.AuthorAvatar, mockCommit.Author.Avatar; got != want { + t.Errorf("Want hook AuthorAvatar %s, got %s", want, got) + } + if got, want := hook.Sender, mockUser.Login; got != want { + t.Errorf("Want hook Sender %s, got %s", want, got) + } + return nil + } + + repos := mock.NewMockRepositoryStore(controller) + repos.EXPECT().FindName(gomock.Any(), gomock.Any(), mockRepo.Name).Return(mockRepo, nil) + + commits := mock.NewMockCommitService(controller) + commits.EXPECT().Find(gomock.Any(), mockUser, mockRepo.Slug, mockCommit.Sha).Return(mockCommit, nil) + + triggerer := mock.NewMockTriggerer(controller) + triggerer.EXPECT().Trigger(gomock.Any(), mockRepo, gomock.Any()).Return(mockBuild, nil).Do(checkBuild) + + c := new(chi.Context) + c.URLParams.Add("owner", "octocat") + c.URLParams.Add("name", "hello-world") + + params := &url.Values{} + params.Set("branch", "master") + params.Set("commit", mockCommit.Sha) + + w := httptest.NewRecorder() + r := httptest.NewRequest("POST", "/?"+params.Encode(), nil) + r = r.WithContext( + context.WithValue(request.WithUser(r.Context(), mockUser), chi.RouteCtxKey, c), + ) + + HandleCreate(repos, commits, triggerer)(w, r) + if got, want := w.Code, 200; want != got { + t.Errorf("Want response code %d, got %d", want, got) + } + + got, want := new(core.Build), mockBuild + json.NewDecoder(w.Body).Decode(got) + if diff := cmp.Diff(got, want); len(diff) != 0 { + t.Errorf(diff) + } +} + +func TestCreate_FromHead(t *testing.T) { + controller := gomock.NewController(t) + defer controller.Finish() + + mockCommit := &core.Commit{ + Sha: "cce10d5c4760d1d6ede99db850ab7e77efe15579", + Ref: "refs/heads/master", + Message: "updated README.md", + Link: "https://github.com/octocatl/hello-world/commit/cce10d5c4760d1d6ede99db850ab7e77efe15579", + Author: &core.Committer{ + Name: "The Octocat", + Email: "octocat@github.com", + Login: "octocat", + Avatar: "https://github.com/octocat.png", + }, + } + + repos := mock.NewMockRepositoryStore(controller) + repos.EXPECT().FindName(gomock.Any(), gomock.Any(), mockRepo.Name).Return(mockRepo, nil) + + commits := mock.NewMockCommitService(controller) + commits.EXPECT().FindRef(gomock.Any(), mockUser, mockRepo.Slug, mockCommit.Ref).Return(mockCommit, nil) + + triggerer := mock.NewMockTriggerer(controller) + triggerer.EXPECT().Trigger(gomock.Any(), mockRepo, gomock.Any()).Return(mockBuild, nil) + + c := new(chi.Context) + c.URLParams.Add("owner", "octocat") + c.URLParams.Add("name", "hello-world") + + w := httptest.NewRecorder() + r := httptest.NewRequest("POST", "/", nil) + r = r.WithContext( + context.WithValue(request.WithUser(r.Context(), mockUser), chi.RouteCtxKey, c), + ) + + HandleCreate(repos, commits, triggerer)(w, r) + if got, want := w.Code, 200; want != got { + t.Errorf("Want response code %d, got %d", want, got) + } + + got, want := new(core.Build), mockBuild + json.NewDecoder(w.Body).Decode(got) + if diff := cmp.Diff(got, want); len(diff) != 0 { + t.Errorf(diff) + } +}