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(api,hooks): add v2 hook signature and github repository analysis #6255

Merged
merged 12 commits into from
Aug 26, 2022
17 changes: 17 additions & 0 deletions cli/cdsctl/experimental_project_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func projectRepository() *cobra.Command {
cli.NewListCommand(projectRepositoryListCmd, projectRepositoryListFunc, nil, withAllCommandModifiers()...),
cli.NewDeleteCommand(projectRepositoryDeleteCmd, projectRepositoryDeleteFunc, nil, withAllCommandModifiers()...),
cli.NewCommand(projectRepositoryAddCmd, projectRepositoryAddFunc, nil, withAllCommandModifiers()...),
cli.NewGetCommand(projectRepositoryHookCreateCmd, projectRepositoryHookCreateFunc, nil, withAllCommandModifiers()...),
})
}

Expand Down Expand Up @@ -118,3 +119,19 @@ var projectRepositoryDeleteCmd = cli.Command{
func projectRepositoryDeleteFunc(v cli.Values) error {
return client.ProjectRepositoryDelete(context.Background(), v.GetString(_ProjectKey), v.GetString("vcs-name"), v.GetString("repository-name"))
}

var projectRepositoryHookCreateCmd = cli.Command{
Name: "hook",
Short: "Display data to be able to create a hook",
Ctx: []cli.Arg{
{Name: _ProjectKey},
},
Args: []cli.Arg{
{Name: "vcs-name"},
{Name: "repository-name"},
},
}

func projectRepositoryHookCreateFunc(v cli.Values) (interface{}, error) {
return client.ProjectRepositoryHookAccessLink(context.Background(), v.GetString(_ProjectKey), v.GetString("vcs-name"), v.GetString("repository-name"))
}
3 changes: 3 additions & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,12 +433,15 @@ func (api *API) InitRouter() {

r.Handle("/v2/repository/analyze", Scope(sdk.AuthConsumerScopeHooks), r.POSTv2(api.postRepositoryAnalysisHandler))
r.Handle("/v2/project/repositories", Scope(sdk.AuthConsumerScopeHooks), r.GETv2(api.getAllRepositoriesHandler))
r.Handle("/v2/project/repositories/{repositoryIdentifier}/hook", Scope(sdk.AuthConsumerScopeHooks), r.GETv2(api.getRepositoryHookHandler))

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))
r.Handle("/v2/project/{projectKey}/vcs/{vcsIdentifier}/repository/{repositoryIdentifier}", nil, r.DELETEv2(api.deleteProjectRepositoryHandler))
r.Handle("/v2/project/{projectKey}/vcs/{vcsIdentifier}/repository/{repositoryIdentifier}/analysis", nil, r.GETv2(api.getProjectRepositoryAnalysesHandler))
r.Handle("/v2/project/{projectKey}/vcs/{vcsIdentifier}/repository/{repositoryIdentifier}/analysis/{analysisID}", nil, r.GETv2(api.getProjectRepositoryAnalysisHandler))
r.Handle("/v2/project/{projectKey}/vcs/{vcsIdentifier}/repository/{repositoryIdentifier}/hook/link", nil, r.GETv2(api.getRepositoryHookLinkHandler))

r.Handle("/v2/user/gpgkey/{gpgKeyID}", Scope(sdk.AuthConsumerScopeUser), r.GETv2(api.getUserGPGKeyHandler))
r.Handle("/v2/user/{user}/gpgkey", Scope(sdk.AuthConsumerScopeUser), r.GETv2(api.getUserGPGKeysHandler), r.POSTv2(api.postUserGPGGKeyHandler))
Expand Down
42 changes: 23 additions & 19 deletions engine/api/repository/dao_vcs_project_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,14 @@ func Delete(db gorpmapper.SqlExecutorWithTx, vcsProjectID string, name string) e
return sdk.WrapError(err, "cannot delete project_repository %s / %s", vcsProjectID, name)
}

func LoadRepositoryByVCSAndID(ctx context.Context, db gorp.SqlExecutor, vcsProjectID, repoID string) (*sdk.ProjectRepository, error) {
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository WHERE project_repository.vcs_project_id = $1 AND project_repository.id = $2`).Args(vcsProjectID, repoID)
func getRepository(ctx context.Context, db gorp.SqlExecutor, query gorpmapping.Query, opts ...gorpmapping.GetOptionFunc) (*sdk.ProjectRepository, error) {
var res dbProjectRepository
found, err := gorpmapping.Get(ctx, db, query, &res)
found, err := gorpmapping.Get(ctx, db, query, &res, opts...)
if err != nil {
return nil, err
}
if !found {
return nil, sdk.NewErrorFrom(sdk.ErrNotFound, "repository: %s", repoID)
return nil, sdk.WithStack(sdk.ErrNotFound)
}

isValid, err := gorpmapping.CheckSignature(res, res.Signature)
Expand All @@ -58,26 +57,22 @@ func LoadRepositoryByVCSAndID(ctx context.Context, db gorp.SqlExecutor, vcsProje
return &res.ProjectRepository, nil
}

func LoadRepositoryByName(ctx context.Context, db gorp.SqlExecutor, vcsProjectID string, repoName string, opts ...gorpmapping.GetOptionFunc) (*sdk.ProjectRepository, error) {
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository WHERE project_repository.vcs_project_id = $1 AND project_repository.name = $2`).Args(vcsProjectID, repoName)
var res dbProjectRepository
found, err := gorpmapping.Get(ctx, db, query, &res, opts...)
func LoadRepositoryByVCSAndID(ctx context.Context, db gorp.SqlExecutor, vcsProjectID, repoID string, opts ...gorpmapping.GetOptionFunc) (*sdk.ProjectRepository, error) {
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository WHERE project_repository.vcs_project_id = $1 AND project_repository.id = $2`).Args(vcsProjectID, repoID)
repo, err := getRepository(ctx, db, query, opts...)
if err != nil {
return nil, err
}
if !found {
return nil, sdk.WrapError(sdk.ErrNotFound, "repository %s", repoName)
return nil, sdk.WrapError(err, "unable to get repository %s", repo.ID)
}
return repo, nil
}

isValid, err := gorpmapping.CheckSignature(res, res.Signature)
func LoadRepositoryByName(ctx context.Context, db gorp.SqlExecutor, vcsProjectID string, repoName string, opts ...gorpmapping.GetOptionFunc) (*sdk.ProjectRepository, error) {
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository WHERE project_repository.vcs_project_id = $1 AND project_repository.name = $2`).Args(vcsProjectID, repoName)
repo, err := getRepository(ctx, db, query, opts...)
if err != nil {
return nil, err
}
if !isValid {
log.Error(ctx, "project_repository %d / %s data corrupted", res.ID, res.Name)
return nil, sdk.WithStack(sdk.ErrNotFound)
return nil, sdk.WrapError(err, "unable to get repository %s/%s", vcsProjectID, repoName)
}
return &res.ProjectRepository, nil
return repo, nil
}

func LoadAllRepositoriesByVCSProjectID(ctx context.Context, db gorp.SqlExecutor, vcsProjectID string) ([]sdk.ProjectRepository, error) {
Expand All @@ -102,6 +97,15 @@ func LoadAllRepositoriesByVCSProjectID(ctx context.Context, db gorp.SqlExecutor,
return repositories, nil
}

func LoadRepositoryByID(ctx context.Context, db gorp.SqlExecutor, id string, opts ...gorpmapping.GetOptionFunc) (sdk.ProjectRepository, error) {
sguiheux marked this conversation as resolved.
Show resolved Hide resolved
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository WHERE id = $1`).Args(id)
repo, err := getRepository(ctx, db, query, opts...)
if err != nil {
return sdk.ProjectRepository{}, sdk.WrapError(err, "unable to get repository %s", id)
}
return *repo, nil
}

func LoadAllRepositories(ctx context.Context, db gorp.SqlExecutor) ([]sdk.ProjectRepository, error) {
query := gorpmapping.NewQuery(`SELECT project_repository.* FROM project_repository`)
var res []dbProjectRepository
Expand Down
27 changes: 27 additions & 0 deletions engine/api/v2_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ package api
import (
"context"
"net/http"
"net/url"

"github.com/gorilla/mux"

"github.com/ovh/cds/engine/api/database/gorpmapping"
"github.com/ovh/cds/engine/api/rbac"
"github.com/ovh/cds/engine/api/repository"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)

// getAllRepositoriesHandler Get all repositories
Expand All @@ -20,3 +25,25 @@ func (api *API) getAllRepositoriesHandler() ([]service.RbacChecker, service.Hand
return service.WriteJSON(w, repos, http.StatusOK)
}
}

func (api *API) getRepositoryHookHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.IsHookService),
func(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
vars := mux.Vars(req)
repoIdentifier, err := url.PathUnescape(vars["repositoryIdentifier"])
if !sdk.IsValidUUID(repoIdentifier) {
return sdk.NewErrorFrom(sdk.ErrWrongRequest, "this handler need the repository uuid")
sguiheux marked this conversation as resolved.
Show resolved Hide resolved
}
repo, err := repository.LoadRepositoryByID(ctx, api.mustDB(), repoIdentifier, gorpmapping.GetOptions.WithDecryption)
if err != nil {
return err
}
h := sdk.Hook{
HookSignKey: repo.HookSignKey,
UUID: repo.ID,
HookType: sdk.RepositoryEntitiesHook,
Configuration: repo.HookConfiguration,
}
return service.WriteJSON(w, h, http.StatusOK)
}
}
53 changes: 49 additions & 4 deletions engine/api/v2_project_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"context"
"fmt"
"net/http"
"net/url"

Expand All @@ -12,17 +13,18 @@ import (
"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/gorpmapper"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)

func (api *API) getRepositoryByIdentifier(ctx context.Context, vcsID string, repositoryIdentifier string) (*sdk.ProjectRepository, error) {
func (api *API) getRepositoryByIdentifier(ctx context.Context, vcsID string, repositoryIdentifier string, opts ...gorpmapper.GetOptionFunc) (*sdk.ProjectRepository, error) {
var repo *sdk.ProjectRepository
var err error
if sdk.IsValidUUID(repositoryIdentifier) {
repo, err = repository.LoadRepositoryByVCSAndID(ctx, api.mustDB(), vcsID, repositoryIdentifier)
repo, err = repository.LoadRepositoryByVCSAndID(ctx, api.mustDB(), vcsID, repositoryIdentifier, opts...)
} else {
repo, err = repository.LoadRepositoryByName(ctx, api.mustDB(), vcsID, repositoryIdentifier)
repo, err = repository.LoadRepositoryByName(ctx, api.mustDB(), vcsID, repositoryIdentifier, opts...)
}
if err != nil {
return nil, err
Expand Down Expand Up @@ -151,7 +153,11 @@ func (api *API) postProjectRepositoryHandler() ([]service.RbacChecker, service.H
if len(srvs) < 1 {
return sdk.NewErrorFrom(sdk.ErrNotFound, "unable to find hook uservice")
}
repositoryHookRegister := sdk.NewEntitiesHook(repoDB.ID, pKey, vcsProject.Type, vcsProject.Name, repoDB.Name)
repositoryHookRegister, err := sdk.NewEntitiesHook(repoDB.ID, pKey, vcsProject.Type, vcsProject.Name, repoDB.Name)
if err != nil {
return err
}

_, 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)
Expand All @@ -169,6 +175,7 @@ func (api *API) postProjectRepositoryHandler() ([]service.RbacChecker, service.H
repoDB.CloneURL = vcsRepo.HTTPCloneURL
}
repoDB.Auth = repoBody.Auth
repoDB.HookSignKey = repositoryHookRegister.HookSignKey

// Update repository with Hook configuration
repoDB.HookConfiguration = repositoryHookRegister.Configuration
Expand Down Expand Up @@ -206,3 +213,41 @@ func (api *API) getVCSProjectRepositoryAllHandler() ([]service.RbacChecker, serv
return service.WriteJSON(w, repositories, http.StatusOK)
}
}

func (api *API) getRepositoryHookLinkHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(rbac.ProjectManage),
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)
}
repositoryIdentifier, err := url.PathUnescape(vars["repositoryIdentifier"])
if err != nil {
return sdk.WithStack(err)
}

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

repo, err := api.getRepositoryByIdentifier(ctx, vcsProject.ID, repositoryIdentifier, gorpmapper.GetOptions.WithDecryption)
if err != nil {
return err
}
srvs, err := services.LoadAllByType(ctx, api.mustDB(), sdk.TypeHooks)
if err != nil {
return err
}
if len(srvs) == 0 {
return sdk.NewErrorFrom(sdk.ErrNotFound, "no hook service found")
}
hook := sdk.HookAccessData{
URL: fmt.Sprintf("%s/v2/webhook/repository/%s/%s", srvs[0].HTTPURL, vcsProject.Type, repo.ID),
HookSignKey: repo.HookSignKey,
sguiheux marked this conversation as resolved.
Show resolved Hide resolved
}
return service.WriteJSON(w, hook, http.StatusOK)
}
}
Loading