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(ui,api): ascode action form & analysis #6513

Merged
merged 16 commits into from
Mar 22, 2023
Merged
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
22 changes: 22 additions & 0 deletions engine/api/v2_jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package api

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

"github.com/gorilla/mux"
"github.com/invopop/jsonschema"

"github.com/ovh/cds/engine/api/entity"
"github.com/ovh/cds/engine/api/rbac"
"github.com/ovh/cds/engine/service"
"github.com/ovh/cds/sdk"
)
Expand All @@ -17,10 +20,29 @@ func (api *API) getJsonSchemaHandler() ([]service.RbacChecker, service.Handler)
vars := mux.Vars(req)
t := vars["type"]

u := getUserConsumer(ctx)

var schema *jsonschema.Schema
switch t {
case sdk.EntityTypeWorkerModel:
schema = sdk.GetWorkerModelJsonSchema()
case sdk.EntityTypeAction:
var actionNames []string
if u != nil {
keys, err := rbac.LoadAllProjectKeysAllowed(ctx, api.mustDB(), sdk.ProjectRoleRead, u.AuthConsumerUser.AuthentifiedUserID)
if err != nil {
return err
}
actionFullNames, err := entity.UnsafeLoadAllByTypeAndProjectKeys(ctx, api.mustDB(), sdk.EntityTypeAction, keys)
if err != nil {
return nil
}
for _, an := range actionFullNames {
actionNames = append(actionNames, fmt.Sprintf("%s/%s/%s/%s@%s", an.ProjectKey, an.VCSName, an.RepoName, an.Name, an.Branch))
}
}

schema = sdk.GetActionJsonSchema(actionNames)
}
return service.WriteJSON(w, schema, http.StatusOK)
}
Expand Down
7 changes: 5 additions & 2 deletions engine/api/v2_repository_analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,12 @@ func (api *API) analyzeRepository(ctx context.Context, projectRepoID string, ana
}

userRoles := make(map[string]bool)
skippedFiles := make([]string, 0)
skippedFiles := make(sdk.StringSlice, 0)
for i := range entities {
e := &entities[i]

// Check user role
if _, has := userRoles[e.Type]; !has {

roleName, err := sdk.GetManageRoleByEntity(e.Type)
if err != nil {
return api.stopAnalysis(ctx, analysis, err)
Expand Down Expand Up @@ -422,6 +421,7 @@ func (api *API) analyzeRepository(ctx context.Context, projectRepoID string, ana
}
}
}
skippedFiles.Unique()
analysis.Data.Error = strings.Join(skippedFiles, "\n")
if len(skippedFiles) == len(analysis.Data.Entities) {
analysis.Status = sdk.RepositoryAnalysisStatusSkipped
Expand Down Expand Up @@ -544,6 +544,9 @@ func (api *API) handleEntitiesFiles(_ context.Context, filesContent map[string][
case strings.HasPrefix(filePath, ".cds/worker-models/"):
var wms []sdk.V2WorkerModel
es, err = sdk.ReadEntityFile(dir, fileName, content, &wms, sdk.EntityTypeWorkerModel, *analysis)
case strings.HasPrefix(filePath, ".cds/actions/"):
var actions []sdk.V2Action
es, err = sdk.ReadEntityFile(dir, fileName, content, &actions, sdk.EntityTypeAction, *analysis)
default:
continue
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ require (
sigs.k8s.io/yaml v1.2.0 // indirect
)

replace github.com/invopop/jsonschema v0.6.0 => github.com/sguiheux/jsonschema v0.0.0-20220907155307-5db145196fe8
replace github.com/invopop/jsonschema v0.6.0 => github.com/sguiheux/jsonschema v0.0.0-20230316145935-733732e89063

replace gopkg.in/yaml.v2 v2.4.0 => gopkg.in/yaml.v2 v2.3.0

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -837,8 +837,8 @@ github.com/sguiheux/go-coverage v0.0.0-20190710153556-287b082a7197 h1:qu90yDtRE5
github.com/sguiheux/go-coverage v0.0.0-20190710153556-287b082a7197/go.mod h1:0hhKrsUsoT7yvxwNGKa+TSYNA26DNWMqReeZEQq/9FI=
github.com/sguiheux/go-nfs-client v0.0.0-20210311091651-4f075a6103cc h1:2hQK9ZA+R4QK6YSeI6J8h40fv1pQVmsoLwdn0omv4NE=
github.com/sguiheux/go-nfs-client v0.0.0-20210311091651-4f075a6103cc/go.mod h1:JWMmlL5pWPL6DVIvix8TwfsDIfw8Cu1uyvid9Js3nyE=
github.com/sguiheux/jsonschema v0.0.0-20220907155307-5db145196fe8 h1:ZVa5RAVxY0ddYqLOS35oAvq5P3L5KbDdb9ubfLFP3L4=
github.com/sguiheux/jsonschema v0.0.0-20220907155307-5db145196fe8/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
github.com/sguiheux/jsonschema v0.0.0-20230316145935-733732e89063 h1:pI5cRAlDxjm+koDMzaphEtnURxpk1BKVw31+byq6BFk=
github.com/sguiheux/jsonschema v0.0.0-20230316145935-733732e89063/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
Expand Down
8 changes: 5 additions & 3 deletions sdk/entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

const (
EntityTypeWorkerModel = "WorkerModel"

EntityNamePattern = "^[a-zA-Z0-9._-]{1,}$"
EntityTypeAction = "Action"
EntityNamePattern = "^[a-zA-Z0-9._-]{1,}$"
)

type EntityFullName struct {
Expand Down Expand Up @@ -45,8 +45,10 @@ func GetManageRoleByEntity(entityType string) (string, error) {
switch entityType {
case EntityTypeWorkerModel:
return ProjectRoleManageWorkerModel, nil
case EntityTypeAction:
return ProjectRoleManageAction, nil
}
return "", WrapError(ErrInvalidData, "unknown entity of type %s", entityType)
return "", NewErrorFrom(ErrInvalidData, "unknown entity of type %s", entityType)
}

type Lintable interface {
Expand Down
39 changes: 39 additions & 0 deletions sdk/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,42 @@ func GetWorkerModelJsonSchema() *jsonschema.Schema {

return wmSchema
}

func GetActionJsonSchema(publicActionNames []string) *jsonschema.Schema {
actionSchema := jsonschema.Reflect(&V2Action{})

if actionSchema.Definitions == nil {
actionSchema.Definitions = make(map[string]*jsonschema.Schema)
}

propName, _ := actionSchema.Definitions["V2Action"].Properties.Get("name")
name := propName.(*jsonschema.Schema)
name.Pattern = EntityNamePattern

// Pattern on input/output keys
propInput, _ := actionSchema.Definitions["V2Action"].Properties.Get("inputs")
input := propInput.(*jsonschema.Schema)
input.PatternProperties[EntityActionInputKey] = input.PatternProperties[".*"]
delete(input.PatternProperties, ".*")

propOutput, _ := actionSchema.Definitions["V2Action"].Properties.Get("outputs")
output := propOutput.(*jsonschema.Schema)
output.PatternProperties[EntityActionInputKey] = output.PatternProperties[".*"]
delete(output.PatternProperties, ".*")

// Pattern on step id
propId, _ := actionSchema.Definitions["ActionStep"].Properties.Get("id")
stepId := propId.(*jsonschema.Schema)
stepId.Pattern = EntityActionStepID

// Enum on step uses
if len(publicActionNames) > 0 {
propStepUses, _ := actionSchema.Definitions["ActionStep"].Properties.Get("uses")
stepUses := propStepUses.(*jsonschema.Schema)
for _, actName := range publicActionNames {
stepUses.Enum = append(stepUses.Enum, actName)
}
}

return actionSchema
}
1 change: 1 addition & 0 deletions sdk/rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
ProjectRoleRead = "read"
ProjectRoleManage = "manage"
ProjectRoleManageWorkerModel = "manage-worker-model"
ProjectRoleManageAction = "manage-action"

// Hatchery Role
HatcheryRoleSpawn = "start-worker"
Expand Down
2 changes: 1 addition & 1 deletion sdk/rbac_project.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package sdk

var (
ProjectRoles = []string{ProjectRoleRead, ProjectRoleManage, ProjectRoleManageWorkerModel}
ProjectRoles = []string{ProjectRoleRead, ProjectRoleManage, ProjectRoleManageWorkerModel, ProjectRoleManageAction}
)

type RBACProject struct {
Expand Down
76 changes: 76 additions & 0 deletions sdk/v2_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package sdk

import (
"encoding/json"

"github.com/xeipuuv/gojsonschema"
)

const (
EntityActionInputKey = "^[a-zA-Z]{1,}$"
EntityActionStepID = "^[a-zA-Z]{0,}$"
)

type V2Action struct {
Name string `json:"name" jsonschema_extras:"order=1" jsonschema_description:"Name of the action"`
Description string `json:"description,omitempty" jsonschema_extras:"order=2"`
Inputs map[string]ActionInput `json:"inputs,omitempty" jsonschema_extras:"order=3,mode=edit" jsonschema_description:"Inputs of the action"`
Outputs map[string]ActionOutput `json:"outputs,omitempty" jsonschema_extras:"order=4,mode=edit" jsonschema_description:"Outputs compute by the action"`
Runs ActionRuns `json:"runs" jsonschema_extras:"order=5"`
}

type ActionRuns struct {
Steps []ActionStep `json:"steps" jsonschema_description:"List of sequential steps executed by the action"`
}

type ActionInput struct {
Description string `json:"description,omitempty" jsonschema_extras:"order=2"`
Default string `json:"default,omitempty" jsonschema_extras:"order=1" jsonschema_description:"Default input value used if the caller do not specified anything"`
}

type ActionOutput struct {
Description string `json:"description,omitempty" jsonschema_extras:"order=2"`
Value string `json:"value" jsonschema_extras:"order=1"`
}

type ActionStep struct {
ID string `json:"id,omitempty" jsonschema_extras:"order=2" jsonschema_description:"Identifier of the step"`
Uses string `json:"uses,omitempty" jsonschema:"oneof_required=uses" jsonschema_extras:"order=1,onchange=loadentity" jsonschema_description:"Sub action to call"`
Run string `json:"run,omitempty" jsonschema:"oneof_required=run" jsonschema_extras:"order=1" jsonschema_description:"Script to execute"`
With map[string]string `json:"with,omitempty" jsonschema:"oneof_not_required=run" jsonschema_extras:"order=3,mode=use" jsonschema_description:"Action parameters"`
}

type ActionStepUsesWith map[string]string

func (a V2Action) GetName() string {
return a.Name
}

func (a V2Action) Lint() []error {
Copy link
Member

Choose a reason for hiding this comment

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

do we need unit test on Lint() ?

actionSchema := GetActionJsonSchema(nil)
actionSchemaS, err := actionSchema.MarshalJSON()
if err != nil {
return []error{NewErrorFrom(err, "unable to load action schema")}
}
schemaLoader := gojsonschema.NewStringLoader(string(actionSchemaS))

modelJson, err := json.Marshal(a)
if err != nil {
return []error{NewErrorFrom(err, "unable to marshal action")}
}
documentLoader := gojsonschema.NewStringLoader(string(modelJson))

result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return []error{NewErrorFrom(err, "unable to validate action")}
}
if result.Valid() {
return nil
}

errors := make([]error, 0, len(result.Errors()))
for _, e := range result.Errors() {
errors = append(errors, NewErrorFrom(ErrInvalidData, e.String()))
}
return errors
}
10 changes: 5 additions & 5 deletions tests/08_v2_analyze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ testcases:
- script: rm -Rf /tmp/myrepo && git clone "http://{{.git.user}}:{{.git.password}}@localhost:3000/{{.git.user}}/myrepo.git" /tmp/myrepo
- script: mkdir -p /tmp/myrepo/.cds/worker-models
- script: cat ./fixtures/worker-model/docker-debian9.yml > /tmp/myrepo/.cds/worker-models/docker-debian9.yml
- script: mkdir -p /tmp/myrepo/.cds/actions
- script: cat ./fixtures/ITCLIPRJVCS/my-action.yml > /tmp/myrepo/.cds/actions/my-action.yml
- script: cd /tmp/myrepo && git config user.email "{{.git.user}}@gitea.eu" && git config user.name "{{.git.user}}"
- script: cd /tmp/myrepo && git add /tmp/myrepo/.cds/* && git commit . --gpg-sign=2B74B3591CEFB2F534265465E027B500E97E52E7 -m "add file and sign" && git push

Expand Down Expand Up @@ -61,11 +63,9 @@ testcases:
- result.code ShouldEqual 0
- 'result.systemout ShouldContainSubstring "status: Success"'
- 'result.systemout ShouldContainSubstring "key_sign_id: E027B500E97E52E7"'
- script: {{.cdsctl}} -f {{.cdsctl.config}} experimental worker-model list ITCLIPRJVCS my_vcs_server {{.git.user}}/myrepo --format json
assertions:
- result.code ShouldEqual 0
- result.systemoutjson.systemoutjson0.name ShouldEqual docker-debian

- 'result.systemout ShouldContainSubstring "my-action.yml"'
- 'result.systemout ShouldContainSubstring "docker-debian9.yml"'
- 'result.systemout ShouldNotContainSubstring "error:"'
- name: Push workflow and run it
steps:
- script: {{.cdsctl}} -f {{.cdsctl.config}} workflow push ITCLIPRJVCS ./fixtures/ITCLIPRJVCS/workflow.yml ./fixtures/ITCLIPRJVCS/pipeline.pip.yml ./fixtures/ITCLIPRJVCS/application.app.yml --skip-update-files
Expand Down
18 changes: 18 additions & 0 deletions tests/fixtures/ITCLIPRJVCS/my-action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: test-action
author: cds_team
description: simple test action as code
inputs:
name:
description: name of the person to greet
required: true
default: Steven
outputs:
name:
description: name of the person who was greeted
value: ${{ steps.hello.outputs.person }}
runs:
steps:
- run: echo Welcome in nthis new action
- id: hello
run: echo Hello ${{ inputs.name }}
- run: echo "name=${{ inputs.name }}" >> $CDS_OUTPUT
Copy link
Member

Choose a reason for hiding this comment

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

Any tests with use and with ?

3 changes: 3 additions & 0 deletions tests/fixtures/rbac/rbac_admin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ projects:
- role: manage-worker-model
projects: [ITCLIPRJVCS]
users: [cds.integration.tests.rw]
- role: manage-action
projects: [ITCLIPRJVCS]
users: [cds.integration.tests.rw]
globals:
- role: manage-permission
users: [cds.integration.tests.rw]
1 change: 1 addition & 0 deletions ui/src/app/model/project.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class ProjectRepository {
}

export const EntityWorkerModel = "WorkerModel";
export const EntityAction = "Action";

export class Entity {
id: string;
Expand Down
Loading