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: project v2 handler #7256

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions engine/api/api_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,15 @@ func (api *API) InitRouter() {
r.Handle("/v2/migrate/project/{projectKey}/variableset/environment", nil, r.POSTv2(api.postMigrateEnvironmentVariableToVariableSetHandler))
r.Handle("/v2/migrate/project/{projectKey}/variableset/ascode", nil, r.POSTv2(api.postMigrateAsCodeVariableToVariableSetItemHandler))

r.Handle("/v2/project", nil, r.GETv2(api.getProjectsV2Handler))
r.Handle("/v2/project/{projectKey}", nil, r.GETv2(api.getProjectV2Handler), r.PUTv2(api.updateProjectV2Handler), r.DELETEv2(api.deleteProjectV2Handler))
r.Handle("/v2/project/{projectKey}/integrations", nil, r.GETv2(api.getProjectV2IntegrationsHandler), r.POSTv2(api.postProjectV2IntegrationHandler))
r.Handle("/v2/project/{projectKey}/integrations/{integrationName}", nil, r.GETv2(api.getProjectV2IntegrationHandler), r.PUTv2(api.putProjectV2IntegrationHandler), r.DELETEv2(api.deleteProjectV2IntegrationHandler))
r.Handle("/v2/project/{projectKey}/keys", nil, r.GETv2(api.getKeysInProjectV2Handler), r.POSTv2(api.addKeyInProjectV2Handler))
r.Handle("/v2/project/{projectKey}/keys/{name}", nil, r.DELETEv2(api.deleteKeyInProjectV2Handler))
r.Handle("/v2/project/{projectKey}/keys/{name}/disable", nil, r.POSTv2(api.postDisableKeyInProjectV2Handler))
r.Handle("/v2/project/{projectKey}/keys/{name}/enable", nil, r.POSTv2(api.postEnableKeyInProjectV2Handler))

r.Handle("/v2/project/{projectKey}/type/{type}/access", Scope(sdk.AuthConsumerScopeService), r.GETv2(api.getProjectV2AccessHandler))

r.Handle("/v2/project/{projectKey}/notification", nil, r.GETv2(api.getProjectNotifsHandler), r.POSTv2(api.postProjectNotificationHandler))
Expand Down
13 changes: 13 additions & 0 deletions engine/api/project/dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/go-gorp/gorp"
"github.com/lib/pq"
"github.com/rockbears/log"

"github.com/ovh/cds/engine/api/database/gorpmapping"
Expand Down Expand Up @@ -81,6 +82,18 @@ func LoadAllByGroupIDs(ctx context.Context, db gorp.SqlExecutor, store cache.Sto
return loadprojects(ctx, db, opts, query, args...)
}

func LoadAllByKeys(ctx context.Context, db gorp.SqlExecutor, keys []string) (sdk.Projects, error) {
var end func()
ctx, end = telemetry.Span(ctx, "project.LoadAllByKeys")
defer end()
query := `SELECT project.*
FROM project
WHERE project.projectkey = ANY($1)
ORDER by project.name, project.projectkey ASC`
args := []interface{}{pq.StringArray(keys)}
return loadprojects(ctx, db, nil, query, args...)
}

// LoadAll returns all projects
func LoadAll(ctx context.Context, db gorp.SqlExecutor, store cache.Store, opts ...LoadOptionFunc) (sdk.Projects, error) {
var end func()
Expand Down
2 changes: 1 addition & 1 deletion engine/api/router_rbac_rule_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"

"github.com/go-gorp/gorp"

"github.com/ovh/cds/engine/cache"
"github.com/ovh/cds/sdk"
)
Expand Down
167 changes: 167 additions & 0 deletions engine/api/v2_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,178 @@ import (

"github.com/gorilla/mux"
"github.com/ovh/cds/engine/api/authentication"
"github.com/ovh/cds/engine/api/event_v2"
"github.com/ovh/cds/engine/api/permission"
"github.com/ovh/cds/engine/api/project"
"github.com/ovh/cds/engine/api/rbac"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)

func (api *API) getProjectsV2Handler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(),
func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {

u := getUserConsumer(ctx)
if u == nil {
return sdk.WithStack(sdk.ErrForbidden)
}

// For admin
if isAdmin(ctx) {
projects, err := project.LoadAll(ctx, api.mustDB(), api.Cache)
if err != nil {
return err
}
return service.WriteJSON(w, projects, http.StatusOK)
}

// Normal user
keys, err := rbac.LoadAllProjectKeysAllowed(ctx, api.mustDB(), sdk.ProjectRoleRead, u.AuthConsumerUser.AuthentifiedUserID)
if err != nil {
return err
}

projects, err := project.LoadAllByKeys(ctx, api.mustDB(), keys)
if err != nil {
return err
}
return service.WriteJSON(w, projects, http.StatusOK)
}
}

func (api *API) deleteProjectV2Handler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(api.projectManage),
func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// Get project name in URL
vars := mux.Vars(r)
key := vars["projectKey"]

u := getUserConsumer(ctx)
if u == nil {
return sdk.WithStack(sdk.ErrForbidden)
}

p, err := project.Load(ctx, api.mustDB(), key, project.LoadOptions.WithPipelines, project.LoadOptions.WithApplications)
if err != nil {
if !sdk.ErrorIs(err, sdk.ErrNoProject) {
return sdk.WrapError(err, "deleteProject> load project '%s' from db", key)
}
return sdk.WrapError(err, "cannot load project %s", key)
}

// TODO Delete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need on v2 routes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That why there is a TODO. I don't know yet how it will be implement on UI side

if len(p.Pipelines) > 0 {
return sdk.WrapError(sdk.ErrProjectHasPipeline, "project '%s' still used by %d pipelines", key, len(p.Pipelines))
}

if len(p.Applications) > 0 {
return sdk.WrapError(sdk.ErrProjectHasApplication, "project '%s' still used by %d applications", key, len(p.Applications))
}
//

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

if err := project.Delete(tx, p.Key); err != nil {
return err
}
if err := tx.Commit(); err != nil {
return sdk.WithStack(err)
}

event_v2.PublishProjectEvent(ctx, api.Cache, sdk.EventProjectDeleted, *p, *u.AuthConsumerUser.AuthentifiedUser)
return nil
}
}

func (api *API) updateProjectV2Handler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(api.projectRead),
func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// Get project name in URL
vars := mux.Vars(r)
key := vars["projectKey"]

u := getUserConsumer(ctx)
if u == nil {
return sdk.WithStack(sdk.ErrForbidden)
}

proj := &sdk.Project{}
if err := service.UnmarshalBody(r, proj); err != nil {
return sdk.WithStack(err)
}

if proj.Name == "" {
return sdk.WrapError(sdk.ErrInvalidProjectName, "project name must no be empty")
}

// Check Request
if key != proj.Key {
return sdk.WrapError(sdk.ErrWrongRequest, "bad Project key %s/%s ", key, proj.Key)
}

if proj.WorkflowRetention <= 0 {
proj.WorkflowRetention = api.Config.WorkflowV2.WorkflowRunRetention
}

// Check is project exist
p, err := project.Load(ctx, api.mustDB(), key, project.LoadOptions.WithIcon)
if err != nil {
return err
}
// Update in DB is made given the primary key
proj.ID = p.ID
proj.VCSServers = p.VCSServers
if proj.Icon == "" {
p.Icon = proj.Icon
}
if err := project.Update(api.mustDB(), proj); err != nil {
return sdk.WrapError(err, "cannot update project %s", key)
}
event_v2.PublishProjectEvent(ctx, api.Cache, sdk.EventProjectUpdated, *proj, *u.AuthConsumerUser.AuthentifiedUser)

// TODO REMOVE
proj.Permissions.Readable = true
proj.Permissions.Writable = true

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

func (api *API) getProjectV2Handler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(api.projectRead),
func(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
// Get project name in URL
vars := mux.Vars(r)
key := vars["projectKey"]

p, errProj := project.Load(ctx, api.mustDB(), key)
if errProj != nil {
return sdk.WrapError(errProj, "getProjectHandler (%s)", key)
}

// TODO REMOVE
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

if isAdmin(ctx) {
p.Permissions = sdk.Permissions{Readable: true, Writable: true, Executable: true}
} else {
permissions, err := permission.LoadProjectMaxLevelPermission(ctx, api.mustDB(), []string{p.Key}, getUserConsumer(ctx).GetGroupIDs())
if err != nil {
return err
}
p.Permissions = permissions.Permissions(p.Key)
if isMaintainer(ctx) {
p.Permissions.Readable = true
}
}

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

func (api *API) getProjectV2AccessHandler() ([]service.RbacChecker, service.Handler) {
return service.RBAC(api.isCDNService),
func(ctx context.Context, w http.ResponseWriter, req *http.Request) error {
Expand Down
Loading