From a88f9d0743705f27d8498c7d8895c5fb643c2f5d Mon Sep 17 00:00:00 2001 From: Bradley Rydzewski Date: Fri, 13 Mar 2020 13:27:07 -0400 Subject: [PATCH] added endpoints for branch summary --- Taskfile.yml | 7 +- core/build.go | 17 ++ core/hook.go | 1 + go.mod | 2 + handler/api/api.go | 8 + handler/api/repos/builds/branches/create.go | 15 ++ .../api/repos/builds/branches/create_test.go | 5 + handler/api/repos/builds/branches/delete.go | 62 +++++ .../api/repos/builds/branches/delete_test.go | 5 + handler/api/repos/builds/branches/list.go | 61 +++++ .../api/repos/builds/branches/list_test.go | 5 + handler/api/repos/builds/find_test.go | 2 +- handler/api/repos/builds/pulls/create.go | 15 ++ handler/api/repos/builds/pulls/create_test.go | 5 + handler/api/repos/builds/pulls/delete.go | 62 +++++ handler/api/repos/builds/pulls/delete_test.go | 5 + handler/api/repos/builds/pulls/list.go | 61 +++++ handler/api/repos/builds/pulls/list_test.go | 5 + handler/web/hook.go | 153 +----------- mock/mock_gen.go | 72 ++++++ service/hook/parser/parse.go | 34 +++ store/build/build.go | 231 +++++++++++++++++- store/build/build_test.go | 112 ++++++++- store/shared/db/dbtest/dbtest.go | 1 + store/shared/migrate/mysql/ddl_gen.go | 29 +++ .../mysql/files/014_create_table_refs.sql | 16 ++ store/shared/migrate/postgres/ddl_gen.go | 29 +++ .../postgres/files/015_create_table_refs.sql | 16 ++ store/shared/migrate/sqlite/ddl_gen.go | 29 +++ .../sqlite/files/014_create_table_refs.sql | 16 ++ 30 files changed, 933 insertions(+), 148 deletions(-) create mode 100644 handler/api/repos/builds/branches/create.go create mode 100644 handler/api/repos/builds/branches/create_test.go create mode 100644 handler/api/repos/builds/branches/delete.go create mode 100644 handler/api/repos/builds/branches/delete_test.go create mode 100644 handler/api/repos/builds/branches/list.go create mode 100644 handler/api/repos/builds/branches/list_test.go create mode 100644 handler/api/repos/builds/pulls/create.go create mode 100644 handler/api/repos/builds/pulls/create_test.go create mode 100644 handler/api/repos/builds/pulls/delete.go create mode 100644 handler/api/repos/builds/pulls/delete_test.go create mode 100644 handler/api/repos/builds/pulls/list.go create mode 100644 handler/api/repos/builds/pulls/list_test.go create mode 100644 store/shared/migrate/mysql/files/014_create_table_refs.sql create mode 100644 store/shared/migrate/postgres/files/015_create_table_refs.sql create mode 100644 store/shared/migrate/sqlite/files/014_create_table_refs.sql diff --git a/Taskfile.yml b/Taskfile.yml index bc573f2c5..6ae77d930 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -143,16 +143,17 @@ tasks: test-postgres: env: DRONE_DATABASE_DRIVER: postgres - DRONE_DATABASE_DATASOURCE: host=localhost user=postgres dbname=postgres sslmode=disable + DRONE_DATABASE_DATASOURCE: host=localhost user=postgres password=postgres dbname=postgres sslmode=disable GO111MODULE: 'on' cmds: - cmd: docker kill postgres ignore_error: true - silent: true - - silent: true + silent: false + - silent: false cmd: > docker run -p 5432:5432 + --env POSTGRES_PASSWORD=postgres --env POSTGRES_USER=postgres --name postgres --detach diff --git a/core/build.go b/core/build.go index b5fdcfa77..2d92de37f 100644 --- a/core/build.go +++ b/core/build.go @@ -71,6 +71,14 @@ type BuildStore interface { // ListRef returns a list of builds from the datastore by ref. ListRef(context.Context, int64, string, int, int) ([]*Build, error) + // LatestBranches returns the latest builds from the + // datastore by branch. + LatestBranches(context.Context, int64) ([]*Build, error) + + // LatestPulls returns the latest builds from the + // datastore by pull requeset. + LatestPulls(context.Context, int64) ([]*Build, error) + // Pending returns a list of pending builds from the // datastore by repository id (DEPRECATED). Pending(context.Context) ([]*Build, error) @@ -88,6 +96,15 @@ type BuildStore interface { // Delete deletes a build from the datastore. Delete(context.Context, *Build) error + // DeletePull deletes a pull request index from the datastore. + DeletePull(context.Context, int64, int) error + + // DeleteBranch deletes a branch index from the datastore. + DeleteBranch(context.Context, int64, string) error + + // DeleteDeploy deletes a deploy index from the datastore. + DeleteDeploy(context.Context, int64, string) error + // Purge deletes builds from the database where the build number is less than n. Purge(context.Context, int64, int64) error diff --git a/core/hook.go b/core/hook.go index 5993d6210..ef8fc1d31 100644 --- a/core/hook.go +++ b/core/hook.go @@ -22,6 +22,7 @@ import ( // Hook action constants. const ( ActionOpen = "open" + ActionClose = "close" ActionCreate = "create" ActionDelete = "delete" ActionSync = "sync" diff --git a/go.mod b/go.mod index fd69c1456..afddf276c 100644 --- a/go.mod +++ b/go.mod @@ -100,3 +100,5 @@ require ( ) replace github.com/h2non/gock => gopkg.in/h2non/gock.v1 v1.0.14 + +go 1.13 diff --git a/handler/api/api.go b/handler/api/api.go index a34963385..0c86262bb 100644 --- a/handler/api/api.go +++ b/handler/api/api.go @@ -27,7 +27,9 @@ 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/branches" "github.com/drone/drone/handler/api/repos/builds/logs" + "github.com/drone/drone/handler/api/repos/builds/pulls" "github.com/drone/drone/handler/api/repos/builds/stages" "github.com/drone/drone/handler/api/repos/collabs" "github.com/drone/drone/handler/api/repos/crons" @@ -184,6 +186,12 @@ func (s Server) Handler() http.Handler { r.Get("/", builds.HandleList(s.Repos, s.Builds)) r.With(acl.CheckWriteAccess()).Post("/", builds.HandleCreate(s.Repos, s.Commits, s.Triggerer)) + r.Get("/branches", branches.HandleList(s.Repos, s.Builds)) + r.With(acl.CheckWriteAccess()).Delete("/branches/*", branches.HandleDelete(s.Repos, s.Builds)) + + r.Get("/pulls", pulls.HandleList(s.Repos, s.Builds)) + r.With(acl.CheckWriteAccess()).Delete("/pulls/{pull}", pulls.HandleDelete(s.Repos, s.Builds)) + 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/branches/create.go b/handler/api/repos/builds/branches/create.go new file mode 100644 index 000000000..b85043db1 --- /dev/null +++ b/handler/api/repos/builds/branches/create.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 branches diff --git a/handler/api/repos/builds/branches/create_test.go b/handler/api/repos/builds/branches/create_test.go new file mode 100644 index 000000000..8860172ff --- /dev/null +++ b/handler/api/repos/builds/branches/create_test.go @@ -0,0 +1,5 @@ +// Copyright 2019 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by the Drone Non-Commercial License +// that can be found in the LICENSE file. + +package branches diff --git a/handler/api/repos/builds/branches/delete.go b/handler/api/repos/builds/branches/delete.go new file mode 100644 index 000000000..93d3a1d8e --- /dev/null +++ b/handler/api/repos/builds/branches/delete.go @@ -0,0 +1,62 @@ +// 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 branches + +import ( + "net/http" + + "github.com/drone/drone/core" + "github.com/drone/drone/handler/api/render" + "github.com/drone/drone/logger" + + "github.com/go-chi/chi" +) + +// HandleDelete returns an http.HandlerFunc that handles an +// http.Request to delete a branch entry from the datastore. +func HandleDelete( + repos core.RepositoryStore, + builds core.BuildStore, +) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + namespace = chi.URLParam(r, "owner") + name = chi.URLParam(r, "name") + branch = chi.URLParam(r, "*") + ) + repo, err := repos.FindName(r.Context(), namespace, name) + if err != nil { + render.NotFound(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot find repository") + return + } + + err = builds.DeleteBranch(r.Context(), repo.ID, branch) + if err != nil { + render.InternalError(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot delete branch") + } else { + w.WriteHeader(http.StatusNoContent) + } + } +} diff --git a/handler/api/repos/builds/branches/delete_test.go b/handler/api/repos/builds/branches/delete_test.go new file mode 100644 index 000000000..8860172ff --- /dev/null +++ b/handler/api/repos/builds/branches/delete_test.go @@ -0,0 +1,5 @@ +// Copyright 2019 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by the Drone Non-Commercial License +// that can be found in the LICENSE file. + +package branches diff --git a/handler/api/repos/builds/branches/list.go b/handler/api/repos/builds/branches/list.go new file mode 100644 index 000000000..84c6a51da --- /dev/null +++ b/handler/api/repos/builds/branches/list.go @@ -0,0 +1,61 @@ +// 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 branches + +import ( + "net/http" + + "github.com/drone/drone/core" + "github.com/drone/drone/handler/api/render" + "github.com/drone/drone/logger" + + "github.com/go-chi/chi" +) + +// HandleList returns an http.HandlerFunc that writes a json-encoded +// list of build history to the response body. +func HandleList( + repos core.RepositoryStore, + builds core.BuildStore, +) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + namespace = chi.URLParam(r, "owner") + name = chi.URLParam(r, "name") + ) + repo, err := repos.FindName(r.Context(), namespace, name) + if err != nil { + render.NotFound(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot find repository") + return + } + + results, err := builds.LatestBranches(r.Context(), repo.ID) + if err != nil { + render.InternalError(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot list builds") + } else { + render.JSON(w, results, 200) + } + } +} diff --git a/handler/api/repos/builds/branches/list_test.go b/handler/api/repos/builds/branches/list_test.go new file mode 100644 index 000000000..8860172ff --- /dev/null +++ b/handler/api/repos/builds/branches/list_test.go @@ -0,0 +1,5 @@ +// Copyright 2019 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by the Drone Non-Commercial License +// that can be found in the LICENSE file. + +package branches diff --git a/handler/api/repos/builds/find_test.go b/handler/api/repos/builds/find_test.go index b57742675..52d1bf419 100644 --- a/handler/api/repos/builds/find_test.go +++ b/handler/api/repos/builds/find_test.go @@ -10,8 +10,8 @@ import ( "net/http/httptest" "testing" - "github.com/drone/drone/mock" "github.com/drone/drone/handler/api/errors" + "github.com/drone/drone/mock" "github.com/go-chi/chi" "github.com/golang/mock/gomock" diff --git a/handler/api/repos/builds/pulls/create.go b/handler/api/repos/builds/pulls/create.go new file mode 100644 index 000000000..4a8d007c8 --- /dev/null +++ b/handler/api/repos/builds/pulls/create.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 pulls diff --git a/handler/api/repos/builds/pulls/create_test.go b/handler/api/repos/builds/pulls/create_test.go new file mode 100644 index 000000000..f141d870c --- /dev/null +++ b/handler/api/repos/builds/pulls/create_test.go @@ -0,0 +1,5 @@ +// Copyright 2019 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by the Drone Non-Commercial License +// that can be found in the LICENSE file. + +package pulls diff --git a/handler/api/repos/builds/pulls/delete.go b/handler/api/repos/builds/pulls/delete.go new file mode 100644 index 000000000..e08af3669 --- /dev/null +++ b/handler/api/repos/builds/pulls/delete.go @@ -0,0 +1,62 @@ +// 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 pulls + +import ( + "net/http" + "strconv" + + "github.com/drone/drone/core" + "github.com/drone/drone/handler/api/render" + "github.com/drone/drone/logger" + "github.com/go-chi/chi" +) + +// HandleDelete returns an http.HandlerFunc that handles an +// http.Request to delete a branch entry from the datastore. +func HandleDelete( + repos core.RepositoryStore, + builds core.BuildStore, +) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + namespace = chi.URLParam(r, "owner") + name = chi.URLParam(r, "name") + number, _ = strconv.Atoi(chi.URLParam(r, "pull")) + ) + repo, err := repos.FindName(r.Context(), namespace, name) + if err != nil { + render.NotFound(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot find repository") + return + } + + err = builds.DeletePull(r.Context(), repo.ID, number) + if err != nil { + render.InternalError(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot delete pr") + } else { + w.WriteHeader(http.StatusNoContent) + } + } +} diff --git a/handler/api/repos/builds/pulls/delete_test.go b/handler/api/repos/builds/pulls/delete_test.go new file mode 100644 index 000000000..f141d870c --- /dev/null +++ b/handler/api/repos/builds/pulls/delete_test.go @@ -0,0 +1,5 @@ +// Copyright 2019 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by the Drone Non-Commercial License +// that can be found in the LICENSE file. + +package pulls diff --git a/handler/api/repos/builds/pulls/list.go b/handler/api/repos/builds/pulls/list.go new file mode 100644 index 000000000..a284623f9 --- /dev/null +++ b/handler/api/repos/builds/pulls/list.go @@ -0,0 +1,61 @@ +// 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 pulls + +import ( + "net/http" + + "github.com/drone/drone/core" + "github.com/drone/drone/handler/api/render" + "github.com/drone/drone/logger" + + "github.com/go-chi/chi" +) + +// HandleList returns an http.HandlerFunc that writes a json-encoded +// list of build history to the response body. +func HandleList( + repos core.RepositoryStore, + builds core.BuildStore, +) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var ( + namespace = chi.URLParam(r, "owner") + name = chi.URLParam(r, "name") + ) + repo, err := repos.FindName(r.Context(), namespace, name) + if err != nil { + render.NotFound(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot find repository") + return + } + + results, err := builds.LatestPulls(r.Context(), repo.ID) + if err != nil { + render.InternalError(w, err) + logger.FromRequest(r). + WithError(err). + WithField("namespace", namespace). + WithField("name", name). + Debugln("api: cannot list builds") + } else { + render.JSON(w, results, 200) + } + } +} diff --git a/handler/api/repos/builds/pulls/list_test.go b/handler/api/repos/builds/pulls/list_test.go new file mode 100644 index 000000000..f141d870c --- /dev/null +++ b/handler/api/repos/builds/pulls/list_test.go @@ -0,0 +1,5 @@ +// Copyright 2019 Drone.IO Inc. All rights reserved. +// Use of this source code is governed by the Drone Non-Commercial License +// that can be found in the LICENSE file. + +package pulls diff --git a/handler/web/hook.go b/handler/web/hook.go index 581b39d47..c836995dd 100644 --- a/handler/web/hook.go +++ b/handler/web/hook.go @@ -84,146 +84,6 @@ func HandleHook( // TODO handle ping requests // TODO consider using scm.Repository in the function callback. - // // TODO break this to a separate function that - // // we can unit test. - // fn := func(webhook interface{}) (string, error) { - // var remote scm.Repository - // switch v := core.(type) { - // case *scm.PushHook: - // remote = v.Repo - // case *scm.BranchHook: - // remote = v.Repo - // case *scm.TagHook: - // remote = v.Repo - // case *scm.PullRequestHook: - // remote = v.Repo - // case *scm.IssueHook: - // remote = v.Repo - // case *scm.IssueCommentHook: - // remote = v.Repo - // case *scm.PullRequestCommentHook: - // remote = v.Repo - // case *scm.ReviewCommentHook: - // remote = v.Repo - // } - // repo, err := repos.FindName(r.Context(), remote.Namespace, remote.Name) - // if err != nil { - // hlog.FromRequest(r).Error(). - // Err(err). - // Str("namespace", remote.Namespace). - // Str("name", remote.Name). - // Msg("cannot find repository") - // return "", err - // } - // return repo.Token, nil - // } - - // hook, err := client.Webhooks.Parse(r, fn) - // if err != nil { - // hlog.FromRequest(r).Error(). - // Err(err). - // Msg("cannot parse webhook") - // writeError(w, err) - // return - // } - - // var name, namespace string - // var base = new(core.Build) - // switch v := hook.(type) { - // case *scm.PushHook: - // namespace, name = v.Repo.Namespace, v.Repo.Name - // base.Event = core.EventPush - - // base.Link = v.Commit.Link - // base.Timestamp = v.Commit.Author.Date.Unix() - // base.Title = "" - // base.Message = v.Commit.Message - // base.Before = "" - // base.After = v.Commit.Sha - // base.Ref = v.Ref - // base.Source = strings.TrimPrefix(v.Ref, "refs/heads/") - // base.Target = strings.TrimPrefix(v.Ref, "refs/heads/") - // base.Author = v.Commit.Author.Login - // base.AuthorName = v.Commit.Author.Name - // base.AuthorEmail = v.Commit.Author.Email - // base.AuthorAvatar = v.Commit.Author.Avatar - // base.Sender = v.Sender.Login - - // // TODO: this is a deficiency with gogs - // if base.AuthorAvatar == "" { - // base.AuthorAvatar = v.Sender.Avatar - // } - // case *scm.TagHook: - - // namespace, name = v.Repo.Namespace, v.Repo.Name - // base.Event = core.EventTag - // base.Action = v.Action.String() - - // base.Link = "" // TODO - // base.Timestamp = 0 // TODO - // base.Title = "" - // base.Message = "" // TODO - // base.Before = "" // TODO - // base.After = v.Ref.Sha - // base.Ref = v.Ref.Name // TODO prepend refs/tags? - // base.Source = "" - // base.Target = "" - // base.Author = v.Sender.Login - // base.AuthorName = v.Sender.Name - // base.AuthorEmail = v.Sender.Email - // base.AuthorAvatar = v.Sender.Avatar - // base.Sender = v.Sender.Login - - // switch v.Action { - // case scm.ActionCreate: - // default: - // hlog.FromRequest(r).Debug(). - // Str("namespace", namespace). - // Str("name", name). - // Str("event", base.Event). - // Str("action", base.Action). - // Msgf("ignore webhook with action %s", base.Action) - // w.WriteHeader(200) - // return - // } - - // case *scm.PullRequestHook: - // namespace, name = v.Repo.Namespace, v.Repo.Name - // base.Event = core.EventPullRequest - // base.Action = v.Action.String() - - // base.Link = v.PullRequest.Link - // base.Timestamp = v.PullRequest.Created.Unix() - // base.Title = v.PullRequest.Title - // base.Message = "" // TODO - // base.Before = "" // TODO - // base.After = v.PullRequest.Sha - // base.Ref = v.PullRequest.Ref - // base.Source = v.PullRequest.Source - // base.Target = v.PullRequest.Target - // base.Author = v.PullRequest.Author.Login - // base.AuthorName = v.PullRequest.Author.Name - // base.AuthorEmail = v.PullRequest.Author.Email - // base.AuthorAvatar = v.PullRequest.Author.Avatar - // base.Sender = v.Sender.Login - - // switch v.Action { - // case scm.ActionCreate, scm.ActionOpen, scm.ActionSync: - // default: - // hlog.FromRequest(r).Debug(). - // Str("namespace", namespace). - // Str("name", name). - // Str("event", base.Event). - // Str("action", base.Action). - // Msgf("ignore pull request hook with action %s", base.Action) - // w.WriteHeader(200) - // return - // } - // default: - // w.WriteHeader(200) - // return - // } - log := logrus.WithFields(logrus.Fields{ "namespace": remote.Namespace, "name": remote.Name, @@ -251,6 +111,19 @@ func HandleHook( ctx = logger.WithContext(ctx, log) defer cancel() + if hook.Event == core.EventPush && hook.Action == core.ActionDelete { + log.WithField("branch", hook.Target).Debugln("branch deleted") + builds.DeleteBranch(ctx, repo.ID, hook.Target) + w.WriteHeader(http.StatusNoContent) + return + } + if hook.Event == core.EventPullRequest && hook.Action == core.ActionClose { + log.WithField("ref", hook.Ref).Debugln("pull request closed") + builds.DeletePull(ctx, repo.ID, scm.ExtractPullRequest(hook.Ref)) + w.WriteHeader(http.StatusNoContent) + return + } + builds, err := triggerer.Trigger(ctx, repo, hook) if err != nil { writeError(w, err) diff --git a/mock/mock_gen.go b/mock/mock_gen.go index d1bec5b9c..44e8be6ee 100644 --- a/mock/mock_gen.go +++ b/mock/mock_gen.go @@ -737,6 +737,48 @@ func (mr *MockBuildStoreMockRecorder) Delete(arg0, arg1 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockBuildStore)(nil).Delete), arg0, arg1) } +// DeleteBranch mocks base method +func (m *MockBuildStore) DeleteBranch(arg0 context.Context, arg1 int64, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteBranch", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteBranch indicates an expected call of DeleteBranch +func (mr *MockBuildStoreMockRecorder) DeleteBranch(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteBranch", reflect.TypeOf((*MockBuildStore)(nil).DeleteBranch), arg0, arg1, arg2) +} + +// DeleteDeploy mocks base method +func (m *MockBuildStore) DeleteDeploy(arg0 context.Context, arg1 int64, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDeploy", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteDeploy indicates an expected call of DeleteDeploy +func (mr *MockBuildStoreMockRecorder) DeleteDeploy(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDeploy", reflect.TypeOf((*MockBuildStore)(nil).DeleteDeploy), arg0, arg1, arg2) +} + +// DeletePull mocks base method +func (m *MockBuildStore) DeletePull(arg0 context.Context, arg1 int64, arg2 int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePull", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePull indicates an expected call of DeletePull +func (mr *MockBuildStoreMockRecorder) DeletePull(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePull", reflect.TypeOf((*MockBuildStore)(nil).DeletePull), arg0, arg1, arg2) +} + // Find mocks base method func (m *MockBuildStore) Find(arg0 context.Context, arg1 int64) (*core.Build, error) { m.ctrl.T.Helper() @@ -782,6 +824,36 @@ func (mr *MockBuildStoreMockRecorder) FindRef(arg0, arg1, arg2 interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindRef", reflect.TypeOf((*MockBuildStore)(nil).FindRef), arg0, arg1, arg2) } +// LatestBranches mocks base method +func (m *MockBuildStore) LatestBranches(arg0 context.Context, arg1 int64) ([]*core.Build, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LatestBranches", arg0, arg1) + ret0, _ := ret[0].([]*core.Build) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LatestBranches indicates an expected call of LatestBranches +func (mr *MockBuildStoreMockRecorder) LatestBranches(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestBranches", reflect.TypeOf((*MockBuildStore)(nil).LatestBranches), arg0, arg1) +} + +// LatestPulls mocks base method +func (m *MockBuildStore) LatestPulls(arg0 context.Context, arg1 int64) ([]*core.Build, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LatestPulls", arg0, arg1) + ret0, _ := ret[0].([]*core.Build) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LatestPulls indicates an expected call of LatestPulls +func (mr *MockBuildStoreMockRecorder) LatestPulls(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LatestPulls", reflect.TypeOf((*MockBuildStore)(nil).LatestPulls), arg0, arg1) +} + // List mocks base method func (m *MockBuildStore) List(arg0 context.Context, arg1 int64, arg2, arg3 int) ([]*core.Build, error) { m.ctrl.T.Helper() diff --git a/service/hook/parser/parse.go b/service/hook/parser/parse.go index b5b44b3a7..95e696cb8 100644 --- a/service/hook/parser/parse.go +++ b/service/hook/parser/parse.go @@ -232,6 +232,23 @@ func (p *parser) Parse(req *http.Request, secretFunc func(string) string) (*core } return hook, repo, nil case *scm.PullRequestHook: + + // TODO(bradrydzewski) cleanup the pr close hook code. + if v.Action == scm.ActionClose { + return &core.Hook{ + Trigger: core.TriggerHook, + Event: core.EventPullRequest, + Action: core.ActionClose, + After: v.PullRequest.Sha, + Ref: v.PullRequest.Ref, + }, &core.Repository{ + UID: v.Repo.ID, + Namespace: v.Repo.Namespace, + Name: v.Repo.Name, + Slug: scm.Join(v.Repo.Namespace, v.Repo.Name), + }, nil + } + if v.Action != scm.ActionOpen && v.Action != scm.ActionSync { return nil, nil, nil } @@ -284,6 +301,23 @@ func (p *parser) Parse(req *http.Request, secretFunc func(string) string) (*core } return hook, repo, nil case *scm.BranchHook: + + // TODO(bradrydzewski) cleanup the branch hook code. + if v.Action == scm.ActionDelete { + return &core.Hook{ + Trigger: core.TriggerHook, + Event: core.EventPush, + After: v.Ref.Sha, + Action: core.ActionDelete, + Target: scm.TrimRef(v.Ref.Name), + }, &core.Repository{ + UID: v.Repo.ID, + Namespace: v.Repo.Namespace, + Name: v.Repo.Name, + Slug: scm.Join(v.Repo.Namespace, v.Repo.Name), + }, nil + } + if v.Action != scm.ActionCreate { return nil, nil, nil } diff --git a/store/build/build.go b/store/build/build.go index 890bc60d2..4dcca20f8 100644 --- a/store/build/build.go +++ b/store/build/build.go @@ -16,11 +16,18 @@ package build import ( "context" + "fmt" + "regexp" + "time" "github.com/drone/drone/core" "github.com/drone/drone/store/shared/db" ) +// regular expression to extract the pull request number +// from the git ref (e.g. refs/pulls/{d}/head) +var pr = regexp.MustCompile("\\d+") + // New returns a new Buildcore. func New(db *db.DB) core.BuildStore { return &buildStore{db} @@ -122,6 +129,42 @@ func (s *buildStore) ListRef(ctx context.Context, repo int64, ref string, limit, return out, err } +// LatestBranches returns a list of the latest build by branch. +func (s *buildStore) LatestBranches(ctx context.Context, repo int64) ([]*core.Build, error) { + return s.latest(ctx, repo, "branch") +} + +// LatestPulls returns a list of the latest builds by pull requests. +func (s *buildStore) LatestPulls(ctx context.Context, repo int64) ([]*core.Build, error) { + return s.latest(ctx, repo, "pull_request") +} + +// LatestDeploys returns a list of the latest builds by target deploy. +func (s *buildStore) LatestDeploys(ctx context.Context, repo int64) ([]*core.Build, error) { + return s.latest(ctx, repo, "deployment") +} + +func (s *buildStore) latest(ctx context.Context, repo int64, event string) ([]*core.Build, error) { + var out []*core.Build + err := s.db.View(func(queryer db.Queryer, binder db.Binder) error { + params := map[string]interface{}{ + "latest_repo_id": repo, + "latest_type": event, + } + stmt, args, err := binder.BindNamed(queryLatestList, params) + if err != nil { + return err + } + rows, err := queryer.Query(stmt, args...) + if err != nil { + return err + } + out, err = scanRows(rows) + return err + }) + return out, err +} + // Pending returns a list of pending builds from the datastore by repository id. func (s *buildStore) Pending(ctx context.Context) ([]*core.Build, error) { var out []*core.Build @@ -152,10 +195,31 @@ func (s *buildStore) Running(ctx context.Context) ([]*core.Build, error) { // Create persists a build to the datacore. func (s *buildStore) Create(ctx context.Context, build *core.Build, stages []*core.Stage) error { - if s.db.Driver() == db.Postgres { - return s.createPostgres(ctx, build, stages) + var err error + switch s.db.Driver() { + case db.Postgres: + err = s.createPostgres(ctx, build, stages) + default: + err = s.create(ctx, build, stages) } - return s.create(ctx, build, stages) + if err != nil { + return err + } + var event, name string + switch build.Event { + case core.EventPullRequest: + event = "pull_request" + name = pr.FindString(build.Ref) + case core.EventPush: + event = "branch" + name = build.Target + case core.EventPromote, core.EventRollback: + event = "deployment" + name = build.Deploy + default: + return nil + } + return s.index(ctx, build.ID, build.RepoID, event, name) } func (s *buildStore) create(ctx context.Context, build *core.Build, stages []*core.Stage) error { @@ -255,6 +319,33 @@ func (s *buildStore) Update(ctx context.Context, build *core.Build) error { return err } +func (s *buildStore) index(ctx context.Context, build, repo int64, event, name string) error { + return s.db.Lock(func(execer db.Execer, binder db.Binder) error { + params := map[string]interface{}{ + "latest_repo_id": repo, + "latest_build_id": build, + "latest_type": event, + "latest_name": name, + "latest_created": time.Now().Unix(), + "latest_updated": time.Now().Unix(), + "latest_deleted": time.Now().Unix(), + } + stmtInsert := stmtInsertLatest + switch s.db.Driver() { + case db.Postgres: + stmtInsert = stmtInsertLatestPg + case db.Mysql: + stmtInsert = stmtInsertLatestMysql + } + stmt, args, err := binder.BindNamed(stmtInsert, params) + if err != nil { + return err + } + _, err = execer.Exec(stmt, args...) + return err + }) +} + // Delete deletes a build from the datacore. func (s *buildStore) Delete(ctx context.Context, build *core.Build) error { return s.db.Lock(func(execer db.Execer, binder db.Binder) error { @@ -268,6 +359,57 @@ func (s *buildStore) Delete(ctx context.Context, build *core.Build) error { }) } +// DeletePull deletes a pull request index from the datastore. +func (s *buildStore) DeletePull(ctx context.Context, repo int64, number int) error { + return s.db.Lock(func(execer db.Execer, binder db.Binder) error { + params := map[string]interface{}{ + "latest_repo_id": repo, + "latest_name": fmt.Sprint(number), + "latest_type": "pull_request", + } + stmt, args, err := binder.BindNamed(stmtDeleteLatest, params) + if err != nil { + return err + } + _, err = execer.Exec(stmt, args...) + return err + }) +} + +// DeleteBranch deletes a branch index from the datastore. +func (s *buildStore) DeleteBranch(ctx context.Context, repo int64, branch string) error { + return s.db.Lock(func(execer db.Execer, binder db.Binder) error { + params := map[string]interface{}{ + "latest_repo_id": repo, + "latest_name": branch, + "latest_type": "branch", + } + stmt, args, err := binder.BindNamed(stmtDeleteLatest, params) + if err != nil { + return err + } + _, err = execer.Exec(stmt, args...) + return err + }) +} + +// DeleteDeploy deletes a deploy index from the datastore. +func (s *buildStore) DeleteDeploy(ctx context.Context, repo int64, environment string) error { + return s.db.Lock(func(execer db.Execer, binder db.Binder) error { + params := map[string]interface{}{ + "latest_repo_id": repo, + "latest_name": environment, + "latest_type": "deployment", + } + stmt, args, err := binder.BindNamed(stmtDeleteLatest, params) + if err != nil { + return err + } + _, err = execer.Exec(stmt, args...) + return err + }) +} + // Purge deletes builds from the database where the build number is less than n. func (s *buildStore) Purge(ctx context.Context, repo, number int64) error { build := &core.Build{ @@ -580,3 +722,86 @@ DELETE FROM builds WHERE build_repo_id = :build_repo_id AND build_number < :build_number ` + +// +// latest builds index +// + +const stmtInsertLatest = ` +INSERT INTO latest ( + latest_repo_id +,latest_build_id +,latest_type +,latest_name +,latest_created +,latest_updated +,latest_deleted +) VALUES ( + :latest_repo_id +,:latest_build_id +,:latest_type +,:latest_name +,:latest_created +,:latest_updated +,:latest_deleted +) ON CONFLICT (latest_repo_id, latest_type, latest_name) +DO UPDATE SET latest_build_id = EXCLUDED.latest_build_id +` + +const stmtInsertLatestPg = ` +INSERT INTO latest ( + latest_repo_id +,latest_build_id +,latest_type +,latest_name +,latest_created +,latest_updated +,latest_deleted +) VALUES ( + :latest_repo_id +,:latest_build_id +,:latest_type +,:latest_name +,:latest_created +,:latest_updated +,:latest_deleted +) ON CONFLICT (latest_repo_id, latest_type, latest_name) +DO UPDATE SET latest_build_id = EXCLUDED.latest_build_id +` + +const stmtInsertLatestMysql = ` +INSERT INTO latest ( + latest_repo_id +,latest_build_id +,latest_type +,latest_name +,latest_created +,latest_updated +,latest_deleted +) VALUES ( + :latest_repo_id +,:latest_build_id +,:latest_type +,:latest_name +,:latest_created +,:latest_updated +,:latest_deleted +) ON DUPLICATE KEY UPDATE latest_build_id = :latest_build_id +` + +const stmtDeleteLatest = ` +DELETE FROM latest +WHERE latest_repo_id = :latest_repo_id + AND latest_type = :latest_type + AND latest_name = :latest_name +` + +const queryLatestList = queryBase + ` +FROM builds +WHERE build_id IN ( + SELECT latest_build_id + FROM latest + WHERE latest_repo_id = :latest_repo_id + AND latest_type = :latest_type +) +` diff --git a/store/build/build_test.go b/store/build/build_test.go index a8376214d..59a466784 100644 --- a/store/build/build_test.go +++ b/store/build/build_test.go @@ -9,8 +9,8 @@ import ( "database/sql" "testing" - "github.com/drone/drone/store/shared/db" "github.com/drone/drone/core" + "github.com/drone/drone/store/shared/db" "github.com/drone/drone/store/shared/db/dbtest" ) @@ -34,6 +34,7 @@ func TestBuild(t *testing.T) { t.Run("Count", testBuildCount(store)) t.Run("Pending", testBuildPending(store)) t.Run("Running", testBuildRunning(store)) + t.Run("Latest", testBuildLatest(store)) } func testBuildCreate(store *buildStore) func(t *testing.T) { @@ -41,7 +42,9 @@ func testBuildCreate(store *buildStore) func(t *testing.T) { build := &core.Build{ RepoID: 1, Number: 99, + Event: core.EventPush, Ref: "refs/heads/master", + Target: "master", } stage := &core.Stage{ RepoID: 42, @@ -307,6 +310,113 @@ func testBuildRunning(store *buildStore) func(t *testing.T) { } } +func testBuildLatest(store *buildStore) func(t *testing.T) { + return func(t *testing.T) { + store.db.Update(func(execer db.Execer, binder db.Binder) error { + execer.Exec("DELETE FROM stages") + execer.Exec("DELETE FROM latest") + execer.Exec("DELETE FROM builds") + return nil + }) + + // + // step 1: insert the initial builds + // + + build := &core.Build{ + RepoID: 1, + Number: 99, + Event: core.EventPush, + Ref: "refs/heads/master", + Target: "master", + } + + err := store.Create(noContext, build, []*core.Stage{}) + if err != nil { + t.Error(err) + return + } + + develop := &core.Build{ + RepoID: 1, + Number: 100, + Event: core.EventPush, + Ref: "refs/heads/develop", + Target: "develop", + } + err = store.Create(noContext, develop, []*core.Stage{}) + if err != nil { + t.Error(err) + return + } + + err = store.Create(noContext, &core.Build{ + RepoID: 1, + Number: 999, + Event: core.EventPullRequest, + Ref: "refs/pulls/10/head", + Source: "develop", + Target: "master", + }, []*core.Stage{}) + if err != nil { + t.Error(err) + return + } + + // + // step 2: verify the latest build number was captured + // + + latest, _ := store.LatestBranches(noContext, build.RepoID) + if len(latest) != 2 { + t.Errorf("Expect latest branch list == 1, got %d", len(latest)) + return + } + if got, want := latest[0].Number, build.Number; got != want { + t.Errorf("Expected latest master build number %d, got %d", want, got) + } + if got, want := latest[1].Number, develop.Number; got != want { + t.Errorf("Expected latest develop build number %d, got %d", want, got) + return + } + + build = &core.Build{ + RepoID: 1, + Number: 101, + Event: core.EventPush, + Ref: "refs/heads/master", + Target: "master", + } + err = store.Create(noContext, build, []*core.Stage{}) + if err != nil { + t.Error(err) + return + } + + latest, _ = store.LatestBranches(noContext, build.RepoID) + if len(latest) != 2 { + t.Errorf("Expect latest branch list == 1") + return + } + if got, want := latest[1].Number, build.Number; got != want { + t.Errorf("Expected latest build number %d, got %d", want, got) + return + } + + err = store.DeleteBranch(noContext, build.RepoID, build.Target) + if err != nil { + t.Error(err) + return + } + + latest, _ = store.LatestBranches(noContext, build.RepoID) + if len(latest) != 1 { + t.Errorf("Expect latest branch list == 1 after delete") + return + } + } +} + func testBuild(item *core.Build) func(t *testing.T) { return func(t *testing.T) { if got, want := item.RepoID, int64(1); got != want { diff --git a/store/shared/db/dbtest/dbtest.go b/store/shared/db/dbtest/dbtest.go index 0fb780a80..d1802ef62 100644 --- a/store/shared/db/dbtest/dbtest.go +++ b/store/shared/db/dbtest/dbtest.go @@ -47,6 +47,7 @@ func Reset(d *db.DB) { tx.Exec("DELETE FROM logs") tx.Exec("DELETE FROM steps") tx.Exec("DELETE FROM stages") + tx.Exec("DELETE FROM latest") tx.Exec("DELETE FROM builds") tx.Exec("DELETE FROM perms") tx.Exec("DELETE FROM repos") diff --git a/store/shared/migrate/mysql/ddl_gen.go b/store/shared/migrate/mysql/ddl_gen.go index f5ded94a7..3547d4739 100644 --- a/store/shared/migrate/mysql/ddl_gen.go +++ b/store/shared/migrate/mysql/ddl_gen.go @@ -136,6 +136,14 @@ var migrations = []struct { name: "alter-table-builds-add-column-deploy-id", stmt: alterTableBuildsAddColumnDeployId, }, + { + name: "create-table-latest", + stmt: createTableLatest, + }, + { + name: "create-index-latest-repo", + stmt: createIndexLatestRepo, + }, } // Migrate performs the database migration. If the migration fails @@ -605,3 +613,24 @@ CREATE TABLE IF NOT EXISTS orgsecrets ( var alterTableBuildsAddColumnDeployId = ` ALTER TABLE builds ADD COLUMN build_deploy_id INTEGER NOT NULL DEFAULT 0; ` + +// +// 014_create_table_refs.sql +// + +var createTableLatest = ` +CREATE TABLE IF NOT EXISTS latest ( + latest_repo_id INTEGER +,latest_build_id INTEGER +,latest_type VARCHAR(50) +,latest_name VARCHAR(500) +,latest_created INTEGER +,latest_updated INTEGER +,latest_deleted INTEGER +,PRIMARY KEY(latest_repo_id, latest_type, latest_name) +); +` + +var createIndexLatestRepo = ` +CREATE INDEX ix_latest_repo ON latest (latest_repo_id); +` diff --git a/store/shared/migrate/mysql/files/014_create_table_refs.sql b/store/shared/migrate/mysql/files/014_create_table_refs.sql new file mode 100644 index 000000000..f342b88d5 --- /dev/null +++ b/store/shared/migrate/mysql/files/014_create_table_refs.sql @@ -0,0 +1,16 @@ +-- name: create-table-latest + +CREATE TABLE IF NOT EXISTS latest ( + latest_repo_id INTEGER +,latest_build_id INTEGER +,latest_type VARCHAR(50) +,latest_name VARCHAR(500) +,latest_created INTEGER +,latest_updated INTEGER +,latest_deleted INTEGER +,PRIMARY KEY(latest_repo_id, latest_type, latest_name) +); + +-- name: create-index-latest-repo + +CREATE INDEX ix_latest_repo ON latest (latest_repo_id); diff --git a/store/shared/migrate/postgres/ddl_gen.go b/store/shared/migrate/postgres/ddl_gen.go index 80206886a..cd1fd6048 100644 --- a/store/shared/migrate/postgres/ddl_gen.go +++ b/store/shared/migrate/postgres/ddl_gen.go @@ -132,6 +132,14 @@ var migrations = []struct { name: "alter-table-builds-add-column-deploy-id", stmt: alterTableBuildsAddColumnDeployId, }, + { + name: "create-table-latest", + stmt: createTableLatest, + }, + { + name: "create-index-latest-repo", + stmt: createIndexLatestRepo, + }, } // Migrate performs the database migration. If the migration fails @@ -583,3 +591,24 @@ CREATE TABLE IF NOT EXISTS orgsecrets ( var alterTableBuildsAddColumnDeployId = ` ALTER TABLE builds ADD COLUMN build_deploy_id INTEGER NOT NULL DEFAULT 0; ` + +// +// 015_create_table_refs.sql +// + +var createTableLatest = ` +CREATE TABLE IF NOT EXISTS latest ( + latest_repo_id INTEGER +,latest_build_id INTEGER +,latest_type VARCHAR(50) +,latest_name VARCHAR(500) +,latest_created INTEGER +,latest_updated INTEGER +,latest_deleted INTEGER +,PRIMARY KEY(latest_repo_id, latest_type, latest_name) +); +` + +var createIndexLatestRepo = ` +CREATE INDEX IF NOT EXISTS ix_latest_repo ON latest (latest_repo_id); +` diff --git a/store/shared/migrate/postgres/files/015_create_table_refs.sql b/store/shared/migrate/postgres/files/015_create_table_refs.sql new file mode 100644 index 000000000..dcd3ccc18 --- /dev/null +++ b/store/shared/migrate/postgres/files/015_create_table_refs.sql @@ -0,0 +1,16 @@ +-- name: create-table-latest + +CREATE TABLE IF NOT EXISTS latest ( + latest_repo_id INTEGER +,latest_build_id INTEGER +,latest_type VARCHAR(50) +,latest_name VARCHAR(500) +,latest_created INTEGER +,latest_updated INTEGER +,latest_deleted INTEGER +,PRIMARY KEY(latest_repo_id, latest_type, latest_name) +); + +-- name: create-index-latest-repo + +CREATE INDEX IF NOT EXISTS ix_latest_repo ON latest (latest_repo_id); diff --git a/store/shared/migrate/sqlite/ddl_gen.go b/store/shared/migrate/sqlite/ddl_gen.go index fb71be511..6573f3181 100644 --- a/store/shared/migrate/sqlite/ddl_gen.go +++ b/store/shared/migrate/sqlite/ddl_gen.go @@ -132,6 +132,14 @@ var migrations = []struct { name: "alter-table-builds-add-column-deploy-id", stmt: alterTableBuildsAddColumnDeployId, }, + { + name: "create-table-latest", + stmt: createTableLatest, + }, + { + name: "create-index-latest-repo", + stmt: createIndexLatestRepo, + }, } // Migrate performs the database migration. If the migration fails @@ -585,3 +593,24 @@ CREATE TABLE IF NOT EXISTS orgsecrets ( var alterTableBuildsAddColumnDeployId = ` ALTER TABLE builds ADD COLUMN build_deploy_id NUMBER NOT NULL DEFAULT 0; ` + +// +// 014_create_table_refs.sql +// + +var createTableLatest = ` +CREATE TABLE IF NOT EXISTS latest ( + latest_repo_id INTEGER +,latest_build_id INTEGER +,latest_type TEXT -- branch | tag | pull_request | promote +,latest_name TEXT -- master | v1.0.0, | 42 | production +,latest_created INTEGER +,latest_updated INTEGER +,latest_deleted INTEGER +,PRIMARY KEY(latest_repo_id, latest_type, latest_name) +); +` + +var createIndexLatestRepo = ` +CREATE INDEX IF NOT EXISTS ix_latest_repo ON latest (latest_repo_id); +` diff --git a/store/shared/migrate/sqlite/files/014_create_table_refs.sql b/store/shared/migrate/sqlite/files/014_create_table_refs.sql new file mode 100644 index 000000000..7a9f197a1 --- /dev/null +++ b/store/shared/migrate/sqlite/files/014_create_table_refs.sql @@ -0,0 +1,16 @@ +-- name: create-table-latest + +CREATE TABLE IF NOT EXISTS latest ( + latest_repo_id INTEGER +,latest_build_id INTEGER +,latest_type TEXT -- branch | tag | pull_request | promote +,latest_name TEXT -- master | v1.0.0, | 42 | production +,latest_created INTEGER +,latest_updated INTEGER +,latest_deleted INTEGER +,PRIMARY KEY(latest_repo_id, latest_type, latest_name) +); + +-- name: create-index-latest-repo + +CREATE INDEX IF NOT EXISTS ix_latest_repo ON latest (latest_repo_id);