Skip to content

Commit

Permalink
feat(hooks, api): manage entities hook (#6219)
Browse files Browse the repository at this point in the history
  • Loading branch information
sguiheux committed Jul 5, 2022
1 parent 8320d28 commit deb7167
Show file tree
Hide file tree
Showing 29 changed files with 994 additions and 400 deletions.
1 change: 1 addition & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ func (api *API) InitRouter() {
r.Handle("/template/{groupName}/{templateSlug}/instance/{instanceID}", Scope(sdk.AuthConsumerScopeTemplate), r.DELETE(api.deleteTemplateInstanceHandler))
r.Handle("/template/{groupName}/{templateSlug}/usage", Scope(sdk.AuthConsumerScopeTemplate), r.GET(api.getTemplateUsageHandler))

r.Handle("/v2/project/repositories", Scope(sdk.AuthConsumerScopeHooks), r.GETv2(api.getAllRepositoriesHandler))
r.Handle("/v2/project/{projectKey}/vcs", nil, r.POSTv2(api.postVCSProjectHandler), r.GETv2(api.getVCSProjectAllHandler))
r.Handle("/v2/project/{projectKey}/vcs/{vcsIdentifier}", nil, r.PUTv2(api.putVCSProjectHandler), r.DELETEv2(api.deleteVCSProjectHandler), r.GETv2(api.getVCSProjectHandler))
r.Handle("/v2/project/{projectKey}/vcs/{vcsIdentifier}/repository", nil, r.POSTv2(api.postProjectRepositoryHandler), r.GETv2(api.getVCSProjectRepositoryAllHandler))
Expand Down
16 changes: 16 additions & 0 deletions engine/api/rbac/rule_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package rbac

import (
"context"

"github.com/go-gorp/gorp"
"github.com/ovh/cds/engine/cache"
"github.com/ovh/cds/sdk"
)

func IsHookService(_ context.Context, auth *sdk.AuthConsumer, _ cache.Store, _ gorp.SqlExecutor, _ map[string]string) error {
if auth.Service != nil && auth.Service.Type == sdk.TypeHooks {
return nil
}
return sdk.WithStack(sdk.ErrForbidden)
}
31 changes: 31 additions & 0 deletions engine/api/repository/dao_vcs_project_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ func Insert(ctx context.Context, db gorpmapper.SqlExecutorWithTx, repo *sdk.Proj
return nil
}

func Update(ctx context.Context, db gorpmapper.SqlExecutorWithTx, repo *sdk.ProjectRepository) error {
dbData := &dbProjectRepository{ProjectRepository: *repo}
if err := gorpmapping.UpdateAndSign(ctx, db, dbData); err != nil {
return err
}
*repo = dbData.ProjectRepository
return nil
}

func Delete(db gorpmapper.SqlExecutorWithTx, vcsProjectID string, name string) error {
_, err := db.Exec("DELETE FROM project_repository WHERE vcs_project_id = $1 AND name = $2", vcsProjectID, name)
return sdk.WrapError(err, "cannot delete project_repository %s / %s", vcsProjectID, name)
Expand Down Expand Up @@ -84,3 +93,25 @@ func LoadAllRepositoriesByVCSProjectID(ctx context.Context, db gorp.SqlExecutor,
}
return repositories, nil
}

func LoadAllRepositories(ctx context.Context, db gorp.SqlExecutor) ([]sdk.ProjectRepository, error) {
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository`)
var res []dbProjectRepository
if err := gorpmapping.GetAll(ctx, db, query, &res); err != nil {
return nil, err
}

repositories := make([]sdk.ProjectRepository, 0, len(res))
for _, r := range res {
isValid, err := gorpmapping.CheckSignature(r, r.Signature)
if err != nil {
return nil, err
}
if !isValid {
log.Error(ctx, "project_repository %d / %s data corrupted", r.ID, r.Name)
continue
}
repositories = append(repositories, r.ProjectRepository)
}
return repositories, nil
}
184 changes: 6 additions & 178 deletions engine/api/v2_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,192 +3,20 @@ package api
import (
"context"
"net/http"
"net/url"

"github.com/gorilla/mux"

"github.com/ovh/cds/engine/api/project"
"github.com/ovh/cds/engine/api/rbac"
"github.com/ovh/cds/engine/api/repositoriesmanager"
"github.com/ovh/cds/engine/api/vcs"
"github.com/ovh/cds/engine/api/repository"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)

func (api *API) getVCSByIdentifier(ctx context.Context, projectKey string, vcsIdentifier string) (*sdk.VCSProject, error) {
var vcsProject *sdk.VCSProject
var err error
if sdk.IsValidUUID(vcsIdentifier) {
vcsProject, err = vcs.LoadVCSByID(ctx, api.mustDB(), projectKey, vcsIdentifier)
} else {
vcsProject, err = vcs.LoadVCSByProject(ctx, api.mustDB(), projectKey, vcsIdentifier)
}
if err != nil {
return nil, err
}
return vcsProject, nil
}

func (api *API) postVCSProjectHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.ProjectManage),
func(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
pKey := vars["projectKey"]

tx, err := api.mustDB().Begin()
if err != nil {
return sdk.WithStack(err)
}
defer tx.Rollback() // nolint

project, err := project.Load(ctx, tx, pKey)
if err != nil {
return sdk.WithStack(err)
}

var vcsProject sdk.VCSProject
if err := service.UnmarshalRequest(ctx, req, &vcsProject); err != nil {
return err
}

vcsProject.ProjectID = project.ID
vcsProject.CreatedBy = getAPIConsumer(ctx).GetUsername()

if err := vcs.Insert(ctx, tx, &vcsProject); err != nil {
return err
}

vcsClient, err := repositoriesmanager.AuthorizedClient(ctx, tx, api.Cache, pKey, vcsProject.Name)
if err != nil {
return err
}

if _, err := vcsClient.Repos(ctx); err != nil {
return err
}

if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
}

return service.WriteMarshal(w, req, vcsProject, http.StatusCreated)
}
}

func (api *API) putVCSProjectHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.ProjectManage),
// getAllRepositoriesHandler Get all repositories
func (api *API) getAllRepositoriesHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.IsHookService),
func(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
pKey := vars["projectKey"]

vcsIdentifier, err := url.PathUnescape(vars["vcsIdentifier"])
if err != nil {
return sdk.NewError(sdk.ErrWrongRequest, err)
}

vcsOld, err := api.getVCSByIdentifier(ctx, pKey, vcsIdentifier)
if err != nil {
return err
}

tx, err := api.mustDB().Begin()
if err != nil {
return sdk.WithStack(err)
}
defer tx.Rollback() // nolint

var vcsProject sdk.VCSProject
if err := service.UnmarshalRequest(ctx, req, &vcsProject); err != nil {
return err
}

vcsProject.ID = vcsOld.ID
vcsProject.Created = vcsOld.Created
vcsProject.CreatedBy = vcsOld.CreatedBy
vcsProject.ProjectID = vcsOld.ProjectID

if err := vcs.Update(ctx, tx, &vcsProject); err != nil {
return err
}

if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
}

return service.WriteMarshal(w, req, vcsProject, http.StatusCreated)
}
}

func (api *API) deleteVCSProjectHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.ProjectManage),
func(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
pKey := vars["projectKey"]

vcsIdentifier, err := url.PathUnescape(vars["vcsIdentifier"])
if err != nil {
return sdk.NewError(sdk.ErrWrongRequest, err)
}

vcsProject, err := api.getVCSByIdentifier(ctx, pKey, vcsIdentifier)
if err != nil {
return err
}

tx, err := api.mustDB().Begin()
if err != nil {
return sdk.WithStack(err)
}
defer tx.Rollback() // nolint

project, err := project.Load(ctx, tx, pKey)
if err != nil {
return sdk.WithStack(err)
}

if err := vcs.Delete(tx, project.ID, vcsProject.Name); err != nil {
return err
}

if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
}

return nil
}
}

// getVCSProjectAllHandler returns list of vcs of one project key
func (api *API) getVCSProjectAllHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.ProjectRead),
func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
pKey := vars["projectKey"]

vcsProjects, err := vcs.LoadAllVCSByProject(ctx, api.mustDB(), pKey)
if err != nil {
return sdk.WrapError(err, "unable to load vcs server on project %s", pKey)
}

return service.WriteJSON(w, vcsProjects, http.StatusOK)
}
}

func (api *API) getVCSProjectHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.ProjectRead),
func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
pKey := vars["projectKey"]

vcsIdentifier, err := url.PathUnescape(vars["vcsIdentifier"])
if err != nil {
return sdk.NewError(sdk.ErrWrongRequest, err)
}

vcsProject, err := api.getVCSByIdentifier(ctx, pKey, vcsIdentifier)
repos, err := repository.LoadAllRepositories(ctx, api.mustDB())
if err != nil {
return err
}
return service.WriteMarshal(w, r, vcsProject, http.StatusOK)
return service.WriteJSON(w, repos, http.StatusOK)
}
}
36 changes: 36 additions & 0 deletions engine/api/v2_project_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/ovh/cds/engine/api/rbac"
"github.com/ovh/cds/engine/api/repositoriesmanager"
"github.com/ovh/cds/engine/api/repository"
"github.com/ovh/cds/engine/api/services"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)
Expand Down Expand Up @@ -59,6 +60,19 @@ func (api *API) deleteProjectRepositoryHandler() ([]service.RbacChecker, service
}
defer tx.Rollback() // nolint

// Remove hooks
srvs, err := services.LoadAllByType(ctx, tx, sdk.TypeHooks)
if err != nil {
return err
}
if len(srvs) < 1 {
return sdk.NewErrorFrom(sdk.ErrNotFound, "unable to find hook uservice")
}
_, code, errHooks := services.NewClient(tx, srvs).DoJSONRequest(ctx, http.MethodDelete, "/task/"+repo.ID, nil, nil)
if (errHooks != nil || code >= 400) && code != 404 {
return sdk.WrapError(errHooks, "unable to delete hook [HTTP: %d]", code)
}

if err := repository.Delete(tx, repo.VCSProjectID, repo.Name); err != nil {
return err
}
Expand Down Expand Up @@ -99,6 +113,8 @@ func (api *API) postProjectRepositoryHandler() ([]service.RbacChecker, service.H

repo.VCSProjectID = vcsProject.ID
repo.CreatedBy = getAPIConsumer(ctx).GetUsername()

// Insert Repository
if err := repository.Insert(ctx, tx, &repo); err != nil {
return err
}
Expand All @@ -111,6 +127,26 @@ func (api *API) postProjectRepositoryHandler() ([]service.RbacChecker, service.H
if _, err := vcsClient.RepoByFullname(ctx, repo.Name); err != nil {
return err
}

// Create hook
srvs, err := services.LoadAllByType(ctx, tx, sdk.TypeHooks)
if err != nil {
return err
}
if len(srvs) < 1 {
return sdk.NewErrorFrom(sdk.ErrNotFound, "unable to find hook uservice")
}
repositoryHookRegister := sdk.NewEntitiesHook(repo.ID, pKey, vcsProject.Type, vcsProject.Name, repo.Name)
_, code, errHooks := services.NewClient(tx, srvs).DoJSONRequest(ctx, http.MethodPost, "/v2/task", repositoryHookRegister, nil)
if errHooks != nil || code >= 400 {
return sdk.WrapError(errHooks, "unable to create hooks [HTTP: %d]", code)
}

// Update repository with Hook configuration
repo.HookConfiguration = repositoryHookRegister.Configuration
if err := repository.Update(ctx, tx, &repo); err != nil {
return err
}

if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
Expand Down
5 changes: 5 additions & 0 deletions engine/api/v2_project_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func Test_crudRepositoryOnProjectLambdaUserOK(t *testing.T) {

// Mock VCS
s, _ := assets.InsertService(t, db, t.Name()+"_VCS", sdk.TypeVCS)
sHooks, _ := assets.InsertService(t, db, t.Name()+"_HOOK", sdk.TypeHooks)
// Setup a mock for all services called by the API
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand All @@ -43,6 +44,7 @@ func Test_crudRepositoryOnProjectLambdaUserOK(t *testing.T) {
}
defer func() {
_ = services.Delete(db, s)
_ = services.Delete(db, sHooks)
services.NewClient = services.NewDefaultClient
}()

Expand All @@ -58,6 +60,7 @@ func Test_crudRepositoryOnProjectLambdaUserOK(t *testing.T) {
return nil, 200, nil
},
).MaxTimes(1)
servicesClients.EXPECT().DoJSONRequest(gomock.Any(), "POST", "/v2/task", gomock.Any(), gomock.Any(), gomock.Any()).Times(1)

// Creation request
repo := sdk.ProjectRepository{
Expand Down Expand Up @@ -93,6 +96,8 @@ func Test_crudRepositoryOnProjectLambdaUserOK(t *testing.T) {
require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &repositories))
require.Len(t, repositories, 1)

servicesClients.EXPECT().DoJSONRequest(gomock.Any(), "DELETE", "/task/"+repositories[0].ID, gomock.Any(), gomock.Any(), gomock.Any()).Times(1)

// Then Delete repository
varsDelete := vars
varsDelete["repositoryIdentifier"] = url.PathEscape("ovh/cds")
Expand Down

0 comments on commit deb7167

Please sign in to comment.