From cdc13705641ca477cc1d27de9fe83708d56dd01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Ga=C4=87e=C5=A1a?= Date: Tue, 30 May 2023 20:00:31 +0200 Subject: [PATCH] feat: [CODE-389]: status checks API layer --- cli/server/harness.wire.go | 2 + cli/server/harness.wire_gen.go | 5 +- cli/server/standalone.wire.go | 2 + cli/server/standalone.wire_gen.go | 5 +- gitrpc/validate.go | 17 +++ internal/api/controller/check/check_list.go | 34 +++++ internal/api/controller/check/check_report.go | 122 ++++++++++++++++++ internal/api/controller/check/controller.go | 65 ++++++++++ internal/api/controller/check/wire.go | 30 +++++ internal/api/handler/check/check_list.go | 41 ++++++ internal/api/handler/check/check_report.go | 50 +++++++ internal/api/openapi/check.go | 54 ++++++++ internal/api/openapi/openapi.go | 1 + internal/router/api.go | 43 ++++-- internal/router/wire.go | 7 +- internal/store/database/check.go | 100 +++++++------- ...8_alter_check_add_payload_version.down.sql | 4 + ...018_alter_check_add_payload_version.up.sql | 4 + ...8_alter_check_add_payload_version.down.sql | 3 + ...018_alter_check_add_payload_version.up.sql | 3 + types/check.go | 9 +- types/enum/check.go | 22 +++- types/enum/permission.go | 7 + 23 files changed, 570 insertions(+), 60 deletions(-) create mode 100644 gitrpc/validate.go create mode 100644 internal/api/controller/check/check_list.go create mode 100644 internal/api/controller/check/check_report.go create mode 100644 internal/api/controller/check/controller.go create mode 100644 internal/api/controller/check/wire.go create mode 100644 internal/api/handler/check/check_list.go create mode 100644 internal/api/handler/check/check_report.go create mode 100644 internal/api/openapi/check.go create mode 100644 internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.down.sql create mode 100644 internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.up.sql create mode 100644 internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.down.sql create mode 100644 internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.up.sql diff --git a/cli/server/harness.wire.go b/cli/server/harness.wire.go index f31cfc305..4556f5c27 100644 --- a/cli/server/harness.wire.go +++ b/cli/server/harness.wire.go @@ -23,6 +23,7 @@ import ( harnessevents "github.com/harness/gitness/harness/services/events" "github.com/harness/gitness/harness/store" "github.com/harness/gitness/harness/types/check" + checkcontroller "github.com/harness/gitness/internal/api/controller/check" "github.com/harness/gitness/internal/api/controller/githook" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" @@ -90,6 +91,7 @@ func initSystem(ctx context.Context, config *gitnesstypes.Config) (*system, erro pubsub.WireSet, codecomments.WireSet, gitrpccron.WireSet, + checkcontroller.WireSet, ) return &system{}, nil } diff --git a/cli/server/harness.wire_gen.go b/cli/server/harness.wire_gen.go index 29836fec5..ab785268a 100644 --- a/cli/server/harness.wire_gen.go +++ b/cli/server/harness.wire_gen.go @@ -20,6 +20,7 @@ import ( events4 "github.com/harness/gitness/harness/services/events" "github.com/harness/gitness/harness/store" "github.com/harness/gitness/harness/types/check" + check2 "github.com/harness/gitness/internal/api/controller/check" "github.com/harness/gitness/internal/api/controller/githook" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" @@ -164,7 +165,9 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) { return nil, err } githookController := githook.ProvideController(db, authorizer, principalStore, repoStore, eventsReporter) - apiHandler := router.ProvideAPIHandler(config, authenticator, accountClient, controller, principalController, spaceController, repoController, pullreqController, webhookController, githookController) + checkStore := database.ProvideCheckStore(db, principalInfoCache) + checkController := check2.ProvideController(db, authorizer, checkStore, repoStore, gitrpcInterface) + apiHandler := router.ProvideAPIHandler(config, authenticator, accountClient, controller, principalController, spaceController, repoController, pullreqController, webhookController, githookController, checkController) gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface) webHandler := router2.ProvideWebHandler(config) routerRouter := router2.ProvideRouter(config, apiHandler, gitHandler, webHandler) diff --git a/cli/server/standalone.wire.go b/cli/server/standalone.wire.go index d289a2705..963ce1ae5 100644 --- a/cli/server/standalone.wire.go +++ b/cli/server/standalone.wire.go @@ -14,6 +14,7 @@ import ( "github.com/harness/gitness/gitrpc" gitrpcserver "github.com/harness/gitness/gitrpc/server" gitrpccron "github.com/harness/gitness/gitrpc/server/cron" + checkcontroller "github.com/harness/gitness/internal/api/controller/check" "github.com/harness/gitness/internal/api/controller/githook" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" @@ -85,6 +86,7 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) { pubsub.WireSet, codecomments.WireSet, gitrpccron.WireSet, + checkcontroller.WireSet, ) return &system{}, nil } diff --git a/cli/server/standalone.wire_gen.go b/cli/server/standalone.wire_gen.go index d6a229b1d..2d35ecc5a 100644 --- a/cli/server/standalone.wire_gen.go +++ b/cli/server/standalone.wire_gen.go @@ -11,6 +11,7 @@ import ( "github.com/harness/gitness/gitrpc" server2 "github.com/harness/gitness/gitrpc/server" "github.com/harness/gitness/gitrpc/server/cron" + check2 "github.com/harness/gitness/internal/api/controller/check" "github.com/harness/gitness/internal/api/controller/githook" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" @@ -130,7 +131,9 @@ func initSystem(ctx context.Context, config *types.Config) (*system, error) { githookController := githook.ProvideController(db, authorizer, principalStore, repoStore, eventsReporter) serviceaccountController := serviceaccount.NewController(principalUID, authorizer, principalStore, spaceStore, repoStore, tokenStore) principalController := principal.NewController(principalStore) - apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController) + checkStore := database.ProvideCheckStore(db, principalInfoCache) + checkController := check2.ProvideController(db, authorizer, checkStore, repoStore, gitrpcInterface) + apiHandler := router.ProvideAPIHandler(config, authenticator, repoController, spaceController, pullreqController, webhookController, githookController, serviceaccountController, controller, principalController, checkController) gitHandler := router.ProvideGitHandler(config, provider, repoStore, authenticator, authorizer, gitrpcInterface) webHandler := router.ProvideWebHandler(config) routerRouter := router.ProvideRouter(config, apiHandler, gitHandler, webHandler) diff --git a/gitrpc/validate.go b/gitrpc/validate.go new file mode 100644 index 000000000..e6fca189b --- /dev/null +++ b/gitrpc/validate.go @@ -0,0 +1,17 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package gitrpc + +import "regexp" + +var matchCommitSHA = regexp.MustCompile("^[0-9a-f]+$") + +func ValidateCommitSHA(commitSHA string) bool { + if len(commitSHA) != 40 && len(commitSHA) != 64 { + return false + } + + return matchCommitSHA.MatchString(commitSHA) +} diff --git a/internal/api/controller/check/check_list.go b/internal/api/controller/check/check_list.go new file mode 100644 index 000000000..1e3aae3b7 --- /dev/null +++ b/internal/api/controller/check/check_list.go @@ -0,0 +1,34 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package check + +import ( + "context" + "fmt" + + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +// ListChecks return an array of status check results for a commit in a repository. +func (c *Controller) ListChecks( + ctx context.Context, + session *auth.Session, + repoRef string, + commitSHA string, +) ([]*types.Check, error) { + repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionRepoView) + if err != nil { + return nil, fmt.Errorf("failed to acquire access access to repo: %w", err) + } + + list, err := c.checkStore.List(ctx, repo.ID, commitSHA) + if err != nil { + return nil, fmt.Errorf("failed to list status check results for repo=%s: %w", repo.UID, err) + } + + return list, nil +} diff --git a/internal/api/controller/check/check_report.go b/internal/api/controller/check/check_report.go new file mode 100644 index 000000000..2c6bacf92 --- /dev/null +++ b/internal/api/controller/check/check_report.go @@ -0,0 +1,122 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package check + +import ( + "context" + "encoding/json" + "fmt" + "regexp" + "time" + + "github.com/harness/gitness/gitrpc" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" +) + +type ReportInput struct { + CheckUID string `json:"check_uid"` + Status enum.CheckStatus `json:"status"` + Summary string `json:"summary"` + Link string `json:"link"` + Payload types.CheckPayload `json:"payload"` +} + +var regexpCheckUID = "^[a-zA-Z_][0-9a-zA-Z-_.$]{0,127}$" +var matcherCheckUID = regexp.MustCompile(regexpCheckUID) + +// Validate validates and sanitizes the ReportInput data. +func (in *ReportInput) Validate() error { + if in.CheckUID == "" { + return usererror.BadRequest("Status check UID is missing") + } + + if !matcherCheckUID.MatchString(in.CheckUID) { + return usererror.BadRequestf("Status check UID must match the regular expression: %s", regexpCheckUID) + } + + _, ok := in.Status.Sanitize() + if !ok { + return usererror.BadRequest("Invalid value provided for status check status") + } + + payloadKind, ok := in.Payload.Kind.Sanitize() + if !ok { + return usererror.BadRequest("Invalid value provided for the payload type") + } + in.Payload.Kind = payloadKind + + switch in.Payload.Kind { + case enum.CheckPayloadKindExternal: + // the default external type does not support payload: clear it here + in.Payload.Version = "" + in.Payload.Data = []byte{'{', '}'} + + if in.Link == "" { // the link is mandatory for the external + return usererror.BadRequest("Link is missing") + } + } + + return nil +} + +// Report modifies an existing or creates a new (if none yet exists) status check report for a specific commit. +func (c *Controller) Report( + ctx context.Context, + session *auth.Session, + repoRef string, + commitSHA string, + in *ReportInput, + metadata map[string]string, +) (*types.Check, error) { + repo, err := c.getRepoCheckAccess(ctx, session, repoRef, enum.PermissionCommitCheckReport) + if err != nil { + return nil, fmt.Errorf("failed to acquire access access to repo: %w", err) + } + + if errValidate := in.Validate(); errValidate != nil { + return nil, errValidate + } + + if !gitrpc.ValidateCommitSHA(commitSHA) { + return nil, usererror.BadRequest("invalid commit SHA provided") + } + + _, err = c.gitRPCClient.GetCommit(ctx, &gitrpc.GetCommitParams{ + ReadParams: gitrpc.ReadParams{RepoUID: repo.GitUID}, + SHA: commitSHA, + }) + if err != nil { + return nil, fmt.Errorf("failed to commit sha=%s: %w", commitSHA, err) + } + + now := time.Now().UnixMilli() + + metadataJson, _ := json.Marshal(metadata) + + statusCheckReport := &types.Check{ + CreatedBy: session.Principal.ID, + Created: now, + Updated: now, + RepoID: repo.ID, + CommitSHA: commitSHA, + UID: in.CheckUID, + Status: in.Status, + Summary: in.Summary, + Link: in.Link, + Payload: in.Payload, + Metadata: metadataJson, + ReportedBy: *session.Principal.ToPrincipalInfo(), + } + + err = c.checkStore.Upsert(ctx, statusCheckReport) + if err != nil { + return nil, fmt.Errorf("failed to upsert status check result for repo=%s: %w", repo.UID, err) + } + + return statusCheckReport, nil +} diff --git a/internal/api/controller/check/controller.go b/internal/api/controller/check/controller.go new file mode 100644 index 000000000..5b39c6b0a --- /dev/null +++ b/internal/api/controller/check/controller.go @@ -0,0 +1,65 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package check + +import ( + "context" + "fmt" + + "github.com/harness/gitness/gitrpc" + apiauth "github.com/harness/gitness/internal/api/auth" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/internal/auth" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + "github.com/harness/gitness/types" + "github.com/harness/gitness/types/enum" + + "github.com/jmoiron/sqlx" +) + +type Controller struct { + db *sqlx.DB + authorizer authz.Authorizer + checkStore store.CheckStore + reqCheckStore store.ReqCheckStore + repoStore store.RepoStore + gitRPCClient gitrpc.Interface +} + +func NewController( + db *sqlx.DB, + authorizer authz.Authorizer, + checkStore store.CheckStore, + repoStore store.RepoStore, + gitRPCClient gitrpc.Interface, +) *Controller { + return &Controller{ + db: db, + authorizer: authorizer, + checkStore: checkStore, + repoStore: repoStore, + gitRPCClient: gitRPCClient, + } +} + +func (c *Controller) getRepoCheckAccess(ctx context.Context, + session *auth.Session, repoRef string, reqPermission enum.Permission, +) (*types.Repository, error) { + if repoRef == "" { + return nil, usererror.BadRequest("A valid repository reference must be provided.") + } + + repo, err := c.repoStore.FindByRef(ctx, repoRef) + if err != nil { + return nil, fmt.Errorf("failed to find repository: %w", err) + } + + if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, reqPermission, false); err != nil { + return nil, fmt.Errorf("access check failed: %w", err) + } + + return repo, nil +} diff --git a/internal/api/controller/check/wire.go b/internal/api/controller/check/wire.go new file mode 100644 index 000000000..682f62738 --- /dev/null +++ b/internal/api/controller/check/wire.go @@ -0,0 +1,30 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package check + +import ( + "github.com/harness/gitness/gitrpc" + "github.com/harness/gitness/internal/auth/authz" + "github.com/harness/gitness/internal/store" + + "github.com/google/wire" + "github.com/jmoiron/sqlx" +) + +// WireSet provides a wire set for this package. +var WireSet = wire.NewSet( + ProvideController, +) + +func ProvideController(db *sqlx.DB, authorizer authz.Authorizer, + checkStore store.CheckStore, + repoStore store.RepoStore, + rpcClient gitrpc.Interface, +) *Controller { + return NewController(db, authorizer, + checkStore, + repoStore, + rpcClient) +} diff --git a/internal/api/handler/check/check_list.go b/internal/api/handler/check/check_list.go new file mode 100644 index 000000000..9747b140e --- /dev/null +++ b/internal/api/handler/check/check_list.go @@ -0,0 +1,41 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pullreq + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleCheckList is an HTTP handler for listing status check results for a repository. +func HandleCheckList(checkCtrl *check.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + repoRef, err := request.GetRepoRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + commitSHA, err := request.GetCommitSHAFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + list, err := checkCtrl.ListChecks(ctx, session, repoRef, commitSHA) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, list) + } +} diff --git a/internal/api/handler/check/check_report.go b/internal/api/handler/check/check_report.go new file mode 100644 index 000000000..a88539a9c --- /dev/null +++ b/internal/api/handler/check/check_report.go @@ -0,0 +1,50 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package pullreq + +import ( + "encoding/json" + "net/http" + + "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/render" + "github.com/harness/gitness/internal/api/request" +) + +// HandleCheckReport is an HTTP handler for reporting status check results. +func HandleCheckReport(checkCtrl *check.Controller) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session, _ := request.AuthSessionFrom(ctx) + + repoRef, err := request.GetRepoRefFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + commitSHA, err := request.GetCommitSHAFromPath(r) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + in := new(check.ReportInput) + err = json.NewDecoder(r.Body).Decode(in) + if err != nil { + render.BadRequestf(w, "Invalid Request Body: %s.", err) + return + } + + statusCheck, err := checkCtrl.Report(ctx, session, + repoRef, commitSHA, in, map[string]string{}) + if err != nil { + render.TranslatedUserError(w, err) + return + } + + render.JSON(w, http.StatusOK, statusCheck) + } +} diff --git a/internal/api/openapi/check.go b/internal/api/openapi/check.go new file mode 100644 index 000000000..1de65167a --- /dev/null +++ b/internal/api/openapi/check.go @@ -0,0 +1,54 @@ +// Copyright 2022 Harness Inc. All rights reserved. +// Use of this source code is governed by the Polyform Free Trial License +// that can be found in the LICENSE.md file for this repository. + +package openapi + +import ( + "net/http" + + "github.com/harness/gitness/internal/api/controller/check" + "github.com/harness/gitness/internal/api/usererror" + "github.com/harness/gitness/types" + + "github.com/swaggest/openapi-go/openapi3" +) + +type reportStatusCheckResultRequest struct { + repoRequest + CommitSHA string `path:"commit_sha"` + check.ReportInput +} + +type listStatusCheckResultsRequest struct { + repoRequest + CommitSHA string `path:"commit_sha"` +} + +func checkOperations(reflector *openapi3.Reflector) { + const tag = "status_checks" + + reportStatusCheckResults := openapi3.Operation{} + reportStatusCheckResults.WithTags(tag) + reportStatusCheckResults.WithMapOfAnything(map[string]interface{}{"operationId": "reportStatusCheckResults"}) + _ = reflector.SetRequest(&reportStatusCheckResults, new(reportStatusCheckResultRequest), http.MethodPut) + _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(types.Check), http.StatusOK) + _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&reportStatusCheckResults, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodPut, "/repos/{repo_ref}/checks/commits/{commit_sha}", + reportStatusCheckResults) + + listStatusCheckResults := openapi3.Operation{} + listStatusCheckResults.WithTags(tag) + listStatusCheckResults.WithMapOfAnything(map[string]interface{}{"operationId": "listStatusCheckResults"}) + _ = reflector.SetRequest(&listStatusCheckResults, new(listStatusCheckResultsRequest), http.MethodGet) + _ = reflector.SetJSONResponse(&listStatusCheckResults, new([]types.Check), http.StatusOK) + _ = reflector.SetJSONResponse(&listStatusCheckResults, new(usererror.Error), http.StatusBadRequest) + _ = reflector.SetJSONResponse(&listStatusCheckResults, new(usererror.Error), http.StatusInternalServerError) + _ = reflector.SetJSONResponse(&listStatusCheckResults, new(usererror.Error), http.StatusUnauthorized) + _ = reflector.SetJSONResponse(&listStatusCheckResults, new(usererror.Error), http.StatusForbidden) + _ = reflector.Spec.AddOperation(http.MethodGet, "/repos/{repo_ref}/checks/commits/{commit_sha}", + listStatusCheckResults) +} diff --git a/internal/api/openapi/openapi.go b/internal/api/openapi/openapi.go index 759aae824..72df723b7 100644 --- a/internal/api/openapi/openapi.go +++ b/internal/api/openapi/openapi.go @@ -43,6 +43,7 @@ func Generate() *openapi3.Spec { resourceOperations(&reflector) pullReqOperations(&reflector) webhookOperations(&reflector) + checkOperations(&reflector) // // define security scheme diff --git a/internal/router/api.go b/internal/router/api.go index bc3bff094..87663e5b3 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -8,6 +8,7 @@ import ( "fmt" "net/http" + "github.com/harness/gitness/internal/api/controller/check" "github.com/harness/gitness/internal/api/controller/githook" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" @@ -17,6 +18,7 @@ import ( "github.com/harness/gitness/internal/api/controller/user" "github.com/harness/gitness/internal/api/controller/webhook" "github.com/harness/gitness/internal/api/handler/account" + handlercheck "github.com/harness/gitness/internal/api/handler/check" handlergithook "github.com/harness/gitness/internal/api/handler/githook" handlerprincipal "github.com/harness/gitness/internal/api/handler/principal" handlerpullreq "github.com/harness/gitness/internal/api/handler/pullreq" @@ -64,7 +66,9 @@ func NewAPIHandler( githookCtrl *githook.Controller, saCtrl *serviceaccount.Controller, userCtrl *user.Controller, - principalCtrl *principal.Controller) APIHandler { + principalCtrl *principal.Controller, + checkCtrl *check.Controller, +) APIHandler { // Use go-chi router for inner routing. r := chi.NewRouter() @@ -85,7 +89,8 @@ func NewAPIHandler( r.Use(middlewareauthn.Attempt(authenticator, authn.SourceRouterAPI)) r.Route("/v1", func(r chi.Router) { - setupRoutesV1(r, repoCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl) + setupRoutesV1(r, repoCtrl, spaceCtrl, pullreqCtrl, webhookCtrl, githookCtrl, + saCtrl, userCtrl, principalCtrl, checkCtrl) }) // wrap router in terminatedPath encoder. @@ -106,11 +111,18 @@ func corsHandler(config *types.Config) func(http.Handler) http.Handler { } func setupRoutesV1(r chi.Router, - repoCtrl *repo.Controller, spaceCtrl *space.Controller, - pullreqCtrl *pullreq.Controller, webhookCtrl *webhook.Controller, githookCtrl *githook.Controller, - saCtrl *serviceaccount.Controller, userCtrl *user.Controller, principalCtrl *principal.Controller) { + repoCtrl *repo.Controller, + spaceCtrl *space.Controller, + pullreqCtrl *pullreq.Controller, + webhookCtrl *webhook.Controller, + githookCtrl *githook.Controller, + saCtrl *serviceaccount.Controller, + userCtrl *user.Controller, + principalCtrl *principal.Controller, + checkCtrl *check.Controller, +) { setupSpaces(r, spaceCtrl, repoCtrl) - setupRepos(r, repoCtrl, pullreqCtrl, webhookCtrl) + setupRepos(r, repoCtrl, pullreqCtrl, webhookCtrl, checkCtrl) setupUser(r, userCtrl) setupServiceAccounts(r, saCtrl) setupPrincipals(r, principalCtrl) @@ -151,8 +163,12 @@ func setupSpaces(r chi.Router, spaceCtrl *space.Controller, repoCtrl *repo.Contr }) } -func setupRepos(r chi.Router, repoCtrl *repo.Controller, pullreqCtrl *pullreq.Controller, - webhookCtrl *webhook.Controller) { +func setupRepos(r chi.Router, + repoCtrl *repo.Controller, + pullreqCtrl *pullreq.Controller, + webhookCtrl *webhook.Controller, + checkCtrl *check.Controller, +) { r.Route("/repos", func(r chi.Router) { // Create takes path and parentId via body, not uri r.Post("/", handlerrepo.HandleCreate(repoCtrl)) @@ -239,6 +255,8 @@ func setupRepos(r chi.Router, repoCtrl *repo.Controller, pullreqCtrl *pullreq.Co SetupPullReq(r, pullreqCtrl) SetupWebhook(r, webhookCtrl) + + SetupChecks(r, checkCtrl) }) }) } @@ -312,6 +330,15 @@ func SetupWebhook(r chi.Router, webhookCtrl *webhook.Controller) { }) } +func SetupChecks(r chi.Router, checkCtrl *check.Controller) { + r.Route("/checks", func(r chi.Router) { + r.Route(fmt.Sprintf("/commits/{%s}", request.PathParamCommitSHA), func(r chi.Router) { + r.Put("/", handlercheck.HandleCheckReport(checkCtrl)) + r.Get("/", handlercheck.HandleCheckList(checkCtrl)) + }) + }) +} + func setupUser(r chi.Router, userCtrl *user.Controller) { r.Route("/user", func(r chi.Router) { // enforce principal authenticated and it's a user diff --git a/internal/router/wire.go b/internal/router/wire.go index 2d0947639..fa27b7fa8 100644 --- a/internal/router/wire.go +++ b/internal/router/wire.go @@ -6,6 +6,7 @@ package router import ( "github.com/harness/gitness/gitrpc" + "github.com/harness/gitness/internal/api/controller/check" "github.com/harness/gitness/internal/api/controller/githook" "github.com/harness/gitness/internal/api/controller/principal" "github.com/harness/gitness/internal/api/controller/pullreq" @@ -62,9 +63,11 @@ func ProvideAPIHandler( githookCtrl *githook.Controller, saCtrl *serviceaccount.Controller, userCtrl *user.Controller, - principalCtrl *principal.Controller) APIHandler { + principalCtrl *principal.Controller, + checkCtrl *check.Controller, +) APIHandler { return NewAPIHandler(config, authenticator, repoCtrl, spaceCtrl, pullreqCtrl, - webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl) + webhookCtrl, githookCtrl, saCtrl, userCtrl, principalCtrl, checkCtrl) } func ProvideWebHandler(config *types.Config) WebHandler { diff --git a/internal/store/database/check.go b/internal/store/database/check.go index 00cc29782..2d99a204e 100644 --- a/internal/store/database/check.go +++ b/internal/store/database/check.go @@ -46,29 +46,31 @@ const ( ,check_updated ,check_repo_id ,check_commit_sha - ,check_type ,check_uid ,check_status ,check_summary ,check_link ,check_payload - ,check_metadata` + ,check_metadata + ,check_payload_kind + ,check_payload_version` ) type check struct { - ID int64 `db:"check_id"` - CreatedBy int64 `db:"check_created_by"` - Created int64 `db:"check_created"` - Updated int64 `db:"check_updated"` - RepoID int64 `db:"check_repo_id"` - CommitSHA string `db:"check_commit_sha"` - Type string `db:"check_type"` - UID string `db:"check_uid"` - Status enum.CheckStatus `db:"check_status"` - Summary string `db:"check_summary"` - Link string `db:"check_link"` - Payload json.RawMessage `db:"check_payload"` - Metadata json.RawMessage `db:"check_metadata"` + ID int64 `db:"check_id"` + CreatedBy int64 `db:"check_created_by"` + Created int64 `db:"check_created"` + Updated int64 `db:"check_updated"` + RepoID int64 `db:"check_repo_id"` + CommitSHA string `db:"check_commit_sha"` + UID string `db:"check_uid"` + Status enum.CheckStatus `db:"check_status"` + Summary string `db:"check_summary"` + Link string `db:"check_link"` + Payload json.RawMessage `db:"check_payload"` + Metadata json.RawMessage `db:"check_metadata"` + PayloadKind enum.CheckPayloadKind `db:"check_payload_kind"` + PayloadVersion string `db:"check_payload_version"` } // Upsert creates new or updates an existing status check result. @@ -80,26 +82,28 @@ func (s *CheckStore) Upsert(ctx context.Context, check *types.Check) error { ,check_updated ,check_repo_id ,check_commit_sha - ,check_type ,check_uid ,check_status ,check_summary ,check_link ,check_payload ,check_metadata + ,check_payload_kind + ,check_payload_version ) VALUES ( :check_created_by ,:check_created ,:check_updated ,:check_repo_id ,:check_commit_sha - ,:check_type ,:check_uid ,:check_status ,:check_summary ,:check_link ,:check_payload ,:check_metadata + ,:check_payload_kind + ,:check_payload_version ) ON CONFLICT (check_repo_id, check_commit_sha, check_uid) DO UPDATE SET @@ -109,7 +113,9 @@ func (s *CheckStore) Upsert(ctx context.Context, check *types.Check) error { ,check_link = :check_link ,check_payload = :check_payload ,check_metadata = :check_metadata - RETURNING check_id` + ,check_payload_kind = :check_payload_kind + ,check_payload_version = :check_payload_version + RETURNING check_id, check_created_by, check_created` db := dbtx.GetAccessor(ctx, s.db) @@ -118,7 +124,7 @@ func (s *CheckStore) Upsert(ctx context.Context, check *types.Check) error { return processSQLErrorf(err, "Failed to bind status check object") } - if err = db.QueryRowContext(ctx, query, arg...).Scan(&check.ID); err != nil { + if err = db.QueryRowContext(ctx, query, arg...).Scan(&check.ID, &check.CreatedBy, &check.Created); err != nil { return processSQLErrorf(err, "Upsert query failed") } @@ -182,19 +188,20 @@ func (s *CheckStore) ListRecent(ctx context.Context, repoID int64, since time.Ti func mapInternalCheck(c *types.Check) *check { m := &check{ - ID: c.ID, - CreatedBy: c.CreatedBy, - Created: c.Created, - Updated: c.Updated, - RepoID: c.RepoID, - CommitSHA: c.CommitSHA, - Type: c.Type, - UID: c.UID, - Status: c.Status, - Summary: c.Summary, - Link: c.Link, - Payload: c.Payload, - Metadata: c.Metadata, + ID: c.ID, + CreatedBy: c.CreatedBy, + Created: c.Created, + Updated: c.Updated, + RepoID: c.RepoID, + CommitSHA: c.CommitSHA, + UID: c.UID, + Status: c.Status, + Summary: c.Summary, + Link: c.Link, + Payload: c.Payload.Data, + Metadata: c.Metadata, + PayloadKind: c.Payload.Kind, + PayloadVersion: c.Payload.Version, } return m @@ -202,19 +209,22 @@ func mapInternalCheck(c *types.Check) *check { func mapCheck(c *check) *types.Check { return &types.Check{ - ID: c.ID, - CreatedBy: c.CreatedBy, - Created: c.Created, - Updated: c.Updated, - RepoID: c.RepoID, - CommitSHA: c.CommitSHA, - Type: c.Type, - UID: c.UID, - Status: c.Status, - Summary: c.Summary, - Link: c.Link, - Payload: c.Payload, - Metadata: c.Metadata, + ID: c.ID, + CreatedBy: c.CreatedBy, + Created: c.Created, + Updated: c.Updated, + RepoID: c.RepoID, + CommitSHA: c.CommitSHA, + UID: c.UID, + Status: c.Status, + Summary: c.Summary, + Link: c.Link, + Metadata: c.Metadata, + Payload: types.CheckPayload{ + Version: c.PayloadVersion, + Kind: c.PayloadKind, + Data: c.Payload, + }, ReportedBy: types.PrincipalInfo{}, } } diff --git a/internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.down.sql b/internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.down.sql new file mode 100644 index 000000000..5bf0a515a --- /dev/null +++ b/internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.down.sql @@ -0,0 +1,4 @@ +ALTER TABLE checks + ADD COLUMN check_type TEXT NOT NULL DEFAULT '', + DROP COLUMN check_payload_version, + DROP COLUMN check_payload_kind; diff --git a/internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.up.sql b/internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.up.sql new file mode 100644 index 000000000..c4e6502c0 --- /dev/null +++ b/internal/store/database/migrate/postgres/0018_alter_check_add_payload_version.up.sql @@ -0,0 +1,4 @@ +ALTER TABLE checks + ADD COLUMN check_payload_version TEXT NOT NULL DEFAULT '', + ADD COLUMN check_payload_kind TEXT NOT NULL DEFAULT '', + DROP COLUMN check_type; diff --git a/internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.down.sql b/internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.down.sql new file mode 100644 index 000000000..4298c5757 --- /dev/null +++ b/internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE checks ADD COLUMN check_type TEXT NOT NULL DEFAULT ''; +ALTER TABLE checks DROP COLUMN check_payload_version; +ALTER TABLE checks DROP COLUMN check_payload_kind; diff --git a/internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.up.sql b/internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.up.sql new file mode 100644 index 000000000..42f2c613c --- /dev/null +++ b/internal/store/database/migrate/sqlite/0018_alter_check_add_payload_version.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE checks ADD COLUMN check_payload_version TEXT NOT NULL DEFAULT ''; +ALTER TABLE checks ADD COLUMN check_payload_kind TEXT NOT NULL DEFAULT ''; +ALTER TABLE checks DROP COLUMN check_type; diff --git a/types/check.go b/types/check.go index 161e0f3d3..46763f536 100644 --- a/types/check.go +++ b/types/check.go @@ -17,17 +17,22 @@ type Check struct { Updated int64 `json:"updated"` RepoID int64 `json:"-"` // status checks are always returned for a commit in a repository CommitSHA string `json:"-"` // status checks are always returned for a commit in a repository - Type string `json:"type"` UID string `json:"uid"` Status enum.CheckStatus `json:"status"` Summary string `json:"summary"` Link string `json:"link"` - Payload json.RawMessage `json:"payload"` Metadata json.RawMessage `json:"metadata"` + Payload CheckPayload `json:"payload"` ReportedBy PrincipalInfo `json:"reported_by"` } +type CheckPayload struct { + Version string `json:"version"` + Kind enum.CheckPayloadKind `json:"kind"` + Data json.RawMessage `json:"data"` +} + type ReqCheck struct { ID int64 `json:"id"` CreatedBy int64 `json:"-"` // clients will use "added_by" diff --git a/types/enum/check.go b/types/enum/check.go index 2ebc891a6..acf190f27 100644 --- a/types/enum/check.go +++ b/types/enum/check.go @@ -11,7 +11,7 @@ func (CheckStatus) Enum() []interface{} { return toInterfaceSlic func (s CheckStatus) Sanitize() (CheckStatus, bool) { return Sanitize(s, GetAllCheckStatuses) } func GetAllCheckStatuses() ([]CheckStatus, CheckStatus) { return checkStatuses, "" } -// PullReqState enumeration. +// CheckStatus enumeration. const ( CheckStatusPending CheckStatus = "pending" CheckStatusRunning CheckStatus = "running" @@ -27,3 +27,23 @@ var checkStatuses = sortEnum([]CheckStatus{ CheckStatusFailure, CheckStatusError, }) + +// CheckPayloadKind defines status payload type. +type CheckPayloadKind string + +func (CheckPayloadKind) Enum() []interface{} { return toInterfaceSlice(checkPayloadTypes) } +func (s CheckPayloadKind) Sanitize() (CheckPayloadKind, bool) { + return Sanitize(s, GetAllCheckPayloadTypes) +} +func GetAllCheckPayloadTypes() ([]CheckPayloadKind, CheckPayloadKind) { + return checkPayloadTypes, CheckPayloadKindExternal +} + +// CheckPayloadKind enumeration. +const ( + CheckPayloadKindExternal CheckPayloadKind = "external" +) + +var checkPayloadTypes = sortEnum([]CheckPayloadKind{ + CheckPayloadKindExternal, +}) diff --git a/types/enum/permission.go b/types/enum/permission.go index 264db69cf..1a3302156 100644 --- a/types/enum/permission.go +++ b/types/enum/permission.go @@ -70,3 +70,10 @@ const ( PermissionServiceDelete Permission = "service_delete" PermissionServiceEditAdmin Permission = "service_editadmin" ) + +const ( + /* + ----- COMMIT CHECK ----- + */ + PermissionCommitCheckReport Permission = "commitCheck_report" +)