Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(hooks, api): manage entities hook #6219

Merged
merged 27 commits into from
Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4712a0f
feat(api): add reopsitory on project
sguiheux Jun 10, 2022
4442dbb
feat(vcs): start add gitea implementation
sguiheux Jun 13, 2022
ddc9c17
ci: add it
sguiheux Jun 13, 2022
da4980e
chore: merge with master
sguiheux Jun 13, 2022
0ea8f77
fix(api): avoid import cycles rbac -> assets -> rbac
sguiheux Jun 13, 2022
5b67512
test: fix gitea host when using docker compose
sguiheux Jun 14, 2022
a975439
test: fix gitea host when using docker compose
sguiheux Jun 14, 2022
9bfd7e2
fix: venom step
sguiheux Jun 14, 2022
db95caa
feat: start vcs on docker compose
sguiheux Jun 14, 2022
eb07a75
fix: test args file
sguiheux Jun 14, 2022
b63367f
fix: code review
sguiheux Jun 14, 2022
781f79c
fix: use identifier instead of name
sguiheux Jun 14, 2022
0ef0071
feat(hook): add route to manage entities hooks
sguiheux Jun 24, 2022
20d5c9c
feat(api,hook): add hook synchronization
sguiheux Jun 24, 2022
1e06d57
feat(hooks): add header vcs type
sguiheux Jun 24, 2022
bc44464
feat: export vcs type in sdk
sguiheux Jun 27, 2022
2f2b49c
fix(hook): save lowercase keys
sguiheux Jun 27, 2022
4d27281
chore: merge with master
sguiheux Jun 28, 2022
f1f061f
fix: comment
sguiheux Jun 28, 2022
59b88af
fix: revert os-ansible-inventory
sguiheux Jun 28, 2022
de6ca9d
fix: add gitignore
sguiheux Jun 28, 2022
dce3e88
fix: hook unit test
sguiheux Jun 28, 2022
32b0df4
fix(hook): pub key is optional
sguiheux Jul 1, 2022
6918612
fix(hook): add rbac and fix name
sguiheux Jul 4, 2022
971c0a0
fix: unit test
sguiheux Jul 4, 2022
de6362a
Merge branch 'master' into hookTmp
sguiheux Jul 4, 2022
929c37d
Merge branch 'master' into hookTmp
sguiheux Jul 5, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading