diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index 2327923163e..4fb927f38b5 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -3806,6 +3806,8 @@ paths: parameters: - $ref: "#/components/parameters/ID" - $ref: "#/components/parameters/TagSelector" + - $ref: "#/components/parameters/ActorName" + - $ref: "#/components/parameters/ActorType" summary: List test workflow executions description: List test workflow executions operationId: listTestWorkflowExecutionsByTestWorkflow @@ -4071,6 +4073,8 @@ paths: parameters: - $ref: "#/components/parameters/ID" - $ref: "#/components/parameters/TagSelector" + - $ref: "#/components/parameters/ActorName" + - $ref: "#/components/parameters/ActorType" summary: List test workflow executions description: List test workflow executions operationId: listTestWorkflowExecutions @@ -7158,10 +7162,77 @@ components: - testsuite - testtrigger - scheduler + - testworkflow context: type: string description: Context value depending from its type + TestWorkflowRunningContext: + description: running context for test workflow execution + type: object + required: + - interface + - actor + properties: + interface: + $ref: "#/components/schemas/TestWorkflowRunningContextInterface" + actor: + $ref: "#/components/schemas/TestWorkflowRunningContextActor" + + TestWorkflowRunningContextInterface: + description: running context interface for test workflow execution + type: object + required: + - type + properties: + name: + type: string + description: interface name + type: + $ref: "#/components/schemas/TestWorkflowRunningContextInterfaceType" + + TestWorkflowRunningContextActor: + description: running context actor for test workflow execution + type: object + required: + - type + properties: + name: + type: string + description: actor name + email: + type: string + description: actor email + executionId: + type: string + description: test workflow execution id + executionPath: + type: string + description: all test workflow execution ids starting from the root + type: + $ref: "#/components/schemas/TestWorkflowRunningContextActorType" + + TestWorkflowRunningContextInterfaceType: + description: supported interfaces for test workflow running context + type: string + enum: + - cli + - ui + - api + - ci/cd + - internal + + TestWorkflowRunningContextActorType: + description: supported actors for test workflow running context + type: string + enum: + - cron + - testtrigger + - user + - testworkflow + - testworkflowexecution + - program + Webhook: description: CRD based webhook data type: object @@ -8012,6 +8083,14 @@ components: default: false tags: $ref: "#/components/schemas/TestWorkflowTagValue" + runningContext: + description: running context for the test workflow execution (Pro edition only) + $ref: "#/components/schemas/TestWorkflowRunningContext" + parentExecutionIds: + type: array + description: parent execution ids + items: + type: string TestWorkflowWithExecution: type: object @@ -8110,6 +8189,9 @@ components: - false tags: $ref: "#/components/schemas/TestWorkflowTagValue" + runningContext: + description: running context for the test workflow execution (Pro edition only) + $ref: "#/components/schemas/TestWorkflowRunningContext" required: - id - name @@ -8144,6 +8226,9 @@ components: $ref: "#/components/schemas/TestWorkflowSummary" tags: $ref: "#/components/schemas/TestWorkflowTagValue" + runningContext: + description: running context for the test workflow execution (Pro edition only) + $ref: "#/components/schemas/TestWorkflowRunningContext" required: - id - name @@ -10672,6 +10757,19 @@ components: schema: type: string description: Test workflow execution tags + ActorName: + in: query + name: actorName + schema: + type: string + description: Test workflow running conntext actor name + ActorType: + in: query + name: actorType + schema: + type: string + description: Test workflow running conntext actor type + requestBodies: UploadsBody: description: "Upload files request body data" diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index 907a09eaa68..edcc2dbb2a4 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -385,6 +385,7 @@ func main() { triggers.WithTestkubeNamespace(cfg.TestkubeNamespace), triggers.WithWatcherNamespaces(cfg.TestkubeWatcherNamespaces), triggers.WithDisableSecretCreation(!secretConfig.AutoCreate), + triggers.WithProContext(&proContext), ) log.DefaultLogger.Info("starting trigger service") g.Go(func() error { diff --git a/cmd/kubectl-testkube/commands/testworkflows/executions.go b/cmd/kubectl-testkube/commands/testworkflows/executions.go index 1dd19f82226..66e297df848 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/executions.go +++ b/cmd/kubectl-testkube/commands/testworkflows/executions.go @@ -1,6 +1,7 @@ package testworkflows import ( + "fmt" "os" "strings" @@ -9,17 +10,18 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testworkflows/renderer" + tc "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/ui" ) func NewGetTestWorkflowExecutionsCmd() *cobra.Command { var ( - limit int - selectors []string - testWorkflowName string - logsOnly bool - tags []string + limit int + selectors []string + testWorkflowName, actorName, actorType string + logsOnly bool + tags []string ) cmd := &cobra.Command{ @@ -41,12 +43,20 @@ func NewGetTestWorkflowExecutionsCmd() *cobra.Command { client, _, err := common.GetClient(cmd) ui.ExitOnError("getting client", err) + err = validateActorType(testkube.TestWorkflowRunningContextActorType(actorType)) + ui.ExitOnError("validatig actor type", err) + if len(args) == 0 { client, _, err := common.GetClient(cmd) ui.ExitOnError("getting client", err) - executions, err := client.ListTestWorkflowExecutions(testWorkflowName, limit, - strings.Join(selectors, ","), strings.Join(tags, ",")) + options := tc.FilterTestWorkflowExecutionOptions{ + Selector: strings.Join(selectors, ","), + TagSelector: strings.Join(tags, ","), + ActorName: actorName, + ActorType: testkube.TestWorkflowRunningContextActorType(actorType), + } + executions, err := client.ListTestWorkflowExecutions(testWorkflowName, limit, options) ui.ExitOnError("getting test workflow executions list", err) err = render.List(cmd, testkube.TestWorkflowExecutionSummaries(executions.Results), os.Stdout) ui.ExitOnError("rendering list", err) @@ -87,6 +97,29 @@ func NewGetTestWorkflowExecutionsCmd() *cobra.Command { cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1") cmd.Flags().BoolVar(&logsOnly, "logs-only", false, "show only execution logs") cmd.Flags().StringSliceVarP(&tags, "tag", "", nil, "tag key value pair: --tag key1=value1") + cmd.Flags().StringVarP(&actorName, "actor-name", "", "", "test workflow running context actor name") + cmd.Flags().StringVarP(&actorType, "actor-type", "", "", "test workflow running context actor type one of cron|testtrigger|user|testworkfow|testworkflowexecution|program") return cmd } + +func validateActorType(actorType testkube.TestWorkflowRunningContextActorType) error { + if actorType == "" { + return nil + } + + actorTypes := map[testkube.TestWorkflowRunningContextActorType]struct{}{ + testkube.CRON_TestWorkflowRunningContextActorType: {}, + testkube.TESTTRIGGER_TestWorkflowRunningContextActorType: {}, + testkube.USER_TestWorkflowRunningContextActorType: {}, + testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType: {}, + testkube.TESTWORKFLOWEXECUTION_TestWorkflowRunningContextActorType: {}, + testkube.PROGRAM_TestWorkflowRunningContextActorType: {}, + } + + if _, ok := actorTypes[actorType]; !ok { + return fmt.Errorf("please pass one of cron|testtrigger|user|testworkfow|testworkflowexecution|program for actor type") + } + + return nil +} diff --git a/cmd/kubectl-testkube/commands/testworkflows/renderer/testworkflowexecution_obj.go b/cmd/kubectl-testkube/commands/testworkflows/renderer/testworkflowexecution_obj.go index 1a98a93666b..d79a8843888 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/renderer/testworkflowexecution_obj.go +++ b/cmd/kubectl-testkube/commands/testworkflows/renderer/testworkflowexecution_obj.go @@ -10,6 +10,7 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + tclcmd "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/cmd" "github.com/kubeshop/testkube/pkg/ui" ) @@ -63,6 +64,8 @@ func printPrettyOutput(ui *ui.UI, execution testkube.TestWorkflowExecution) { ui.NL() ui.Warn("Tags: ", testkube.MapToString(execution.Tags)) } + // Pro edition only (tcl protected code) + tclcmd.PrintRunningContext(ui, execution) if execution.Result != nil && execution.Result.Status != nil { ui.Warn("Status: ", string(*execution.Result.Status)) if !execution.Result.QueuedAt.IsZero() { diff --git a/cmd/kubectl-testkube/commands/testworkflows/run.go b/cmd/kubectl-testkube/commands/testworkflows/run.go index ac206360d16..b390466adc5 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/run.go +++ b/cmd/kubectl-testkube/commands/testworkflows/run.go @@ -15,9 +15,12 @@ import ( "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common/render" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/tests" "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/testworkflows/renderer" + testkubecfg "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" "github.com/kubeshop/testkube/cmd/testworkflow-init/instructions" apiclientv1 "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + tclcmd "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/cmd" + "github.com/kubeshop/testkube/pkg/telemetry" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/constants" "github.com/kubeshop/testkube/pkg/ui" ) @@ -63,11 +66,29 @@ func NewRunTestWorkflowCmd() *cobra.Command { ui.ExitOnError("getting client", err) name := args[0] + runContext := telemetry.GetCliRunContext() + interfaceType := testkube.CICD_TestWorkflowRunningContextInterfaceType + if runContext == "others|local" { + runContext = "" + interfaceType = testkube.CLI_TestWorkflowRunningContextInterfaceType + } + + cfg, err := testkubecfg.Load() + ui.ExitOnError("loading config file", err) + ui.NL() + + var runningContext *testkube.TestWorkflowRunningContext + // Pro edition only (tcl protected code) + if cfg.ContextType == testkubecfg.ContextTypeCloud { + runningContext = tclcmd.GetRunningContext(runContext, cfg.CloudContext.ApiKey, interfaceType) + } + execution, err := client.ExecuteTestWorkflow(name, testkube.TestWorkflowExecutionRequest{ Name: executionName, Config: config, DisableWebhooks: disableWebhooks, Tags: tags, + RunningContext: runningContext, }) if err != nil { // User friendly Open Source operation error diff --git a/cmd/tcl/testworkflow-toolkit/commands/execute.go b/cmd/tcl/testworkflow-toolkit/commands/execute.go index 9c8f55a1df2..e7f27871075 100644 --- a/cmd/tcl/testworkflow-toolkit/commands/execute.go +++ b/cmd/tcl/testworkflow-toolkit/commands/execute.go @@ -12,6 +12,7 @@ import ( "encoding/json" "fmt" "os" + "strings" "sync" "time" @@ -72,7 +73,7 @@ func buildTestExecution(test testworkflowsv1.StepExecuteTest, async bool) (func( exec, err := c.ExecuteTest(test.Name, test.ExecutionRequest.Name, client.ExecuteTestOptions{ RunningContext: &testkube.RunningContext{ - Type_: "testworkflow", + Type_: string(testkube.RunningContextTypeTestWorkflow), Context: fmt.Sprintf("%s/executions/%s", config.WorkflowName(), config.ExecutionId()), }, IsVariablesFileUploaded: test.ExecutionRequest.IsVariablesFileUploaded, @@ -162,6 +163,11 @@ func buildWorkflowExecution(workflow testworkflowsv1.StepExecuteWorkflow, async tags := config.ExecutionTags() + parentIds := []string{config.ExecutionId()} + if config.Config().Execution.ParentIds != "" { + parentIds = append(strings.Split(config.Config().Execution.ParentIds, "/"), parentIds...) + } + var exec testkube.TestWorkflowExecution for i := 0; i < CreateExecutionRetryOnFailureMaxAttempts; i++ { exec, err = c.ExecuteTestWorkflow(workflow.Name, testkube.TestWorkflowExecutionRequest{ @@ -169,6 +175,18 @@ func buildWorkflowExecution(workflow testworkflowsv1.StepExecuteWorkflow, async Config: testworkflows.MapConfigValueKubeToAPI(workflow.Config), DisableWebhooks: config.ExecutionDisableWebhooks(), Tags: tags, + RunningContext: &testkube.TestWorkflowRunningContext{ + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Type_: common.Ptr(testkube.API_TestWorkflowRunningContextInterfaceType), + }, + Actor: &testkube.TestWorkflowRunningContextActor{ + Name: workflow.Name, + ExecutionId: config.ExecutionId(), + ExecutionPath: strings.Join(parentIds, "/"), + Type_: common.Ptr(testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType), + }, + }, + ParentExecutionIds: parentIds, }) if err == nil { break diff --git a/go.mod b/go.mod index 9d2fab49068..77012306b4e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/99designs/gqlgen v0.17.27 github.com/Masterminds/semver v1.5.0 github.com/adhocore/gronx v1.6.3 + github.com/avast/retry-go/v4 v4.6.0 github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/cdevents/sdk-go v0.3.0 github.com/cli/cli/v2 v2.20.2 @@ -38,7 +39,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kelseyhightower/envconfig v1.4.0 github.com/kubepug/kubepug v1.7.1 - github.com/kubeshop/testkube-operator v1.17.55-0.20241023094459-ac7c03dedade + github.com/kubeshop/testkube-operator v1.17.55-0.20241028132112-e2a9c1706edd github.com/minio/minio-go/v7 v7.0.47 github.com/montanaflynn/stats v0.6.6 github.com/moogar0880/problems v0.1.1 @@ -48,7 +49,6 @@ require ( github.com/olekukonko/tablewriter v0.0.6-0.20230925090304-df64c4bbad77 github.com/onsi/ginkgo/v2 v2.15.0 github.com/onsi/gomega v1.31.0 - github.com/opencontainers/image-spec v1.1.0-rc3 github.com/otiai10/copy v1.11.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 @@ -91,7 +91,6 @@ require ( github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alecthomas/chroma v0.10.0 // indirect github.com/andybalholm/brotli v1.0.5 // indirect - github.com/avast/retry-go/v4 v4.6.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect @@ -173,6 +172,7 @@ require ( github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc3 // indirect github.com/package-url/packageurl-go v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect diff --git a/go.sum b/go.sum index b3f4327d313..6e304ceb74e 100644 --- a/go.sum +++ b/go.sum @@ -402,8 +402,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubepug/kubepug v1.7.1 h1:LKhfSxS8Y5mXs50v+3Lpyec+cogErDLcV7CMUuiaisw= github.com/kubepug/kubepug v1.7.1/go.mod h1:lv+HxD0oTFL7ZWjj0u6HKhMbbTIId3eG7aWIW0gyF8g= -github.com/kubeshop/testkube-operator v1.17.55-0.20241023094459-ac7c03dedade h1:LQ1hkvNbjTE+VWyWoMVQYR+i4lKKmKwUoizifcvDI2k= -github.com/kubeshop/testkube-operator v1.17.55-0.20241023094459-ac7c03dedade/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk= +github.com/kubeshop/testkube-operator v1.17.55-0.20241028132112-e2a9c1706edd h1:xjoWglCbVkCfeNEAUF7RxLpT6SgyjQjbOC65baGDams= +github.com/kubeshop/testkube-operator v1.17.55-0.20241028132112-e2a9c1706edd/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= diff --git a/internal/app/api/v1/testworkflowexecutions.go b/internal/app/api/v1/testworkflowexecutions.go index d2272c8117e..637ee3609f8 100644 --- a/internal/app/api/v1/testworkflowexecutions.go +++ b/internal/app/api/v1/testworkflowexecutions.go @@ -496,5 +496,15 @@ func getWorkflowExecutionsFilterFromRequest(c *fiber.Ctx) testworkflow2.Filter { filter = filter.WithTagSelector(tagSelector) } + actorName := c.Query("actorName") + if actorName != "" { + filter = filter.WithActorName(actorName) + } + + actorType := c.Query("actorType") + if actorType != "" { + filter = filter.WithActorType(testkube.TestWorkflowRunningContextActorType(actorType)) + } + return filter } diff --git a/internal/app/api/v1/testworkflows.go b/internal/app/api/v1/testworkflows.go index df995257e14..4602eb555a3 100644 --- a/internal/app/api/v1/testworkflows.go +++ b/internal/app/api/v1/testworkflows.go @@ -361,6 +361,13 @@ func (s *TestkubeAPI) ExecuteTestWorkflowHandler() fiber.Handler { return s.BadRequest(c, errPrefix, "invalid body", err) } + // Pro edition only (tcl protected code) + if request.RunningContext != nil { + if s.proContext == nil || s.proContext.APIKey == "" { + request.RunningContext = nil + } + } + var results []testkube.TestWorkflowExecution var errs []error diff --git a/pkg/api/v1/client/interface.go b/pkg/api/v1/client/interface.go index 8957637ea4f..554a3faaf64 100644 --- a/pkg/api/v1/client/interface.go +++ b/pkg/api/v1/client/interface.go @@ -158,7 +158,7 @@ type TestWorkflowAPI interface { // TestWorkflowExecutionAPI describes test workflow api methods type TestWorkflowExecutionAPI interface { GetTestWorkflowExecution(executionID string) (execution testkube.TestWorkflowExecution, err error) - ListTestWorkflowExecutions(id string, limit int, selector, tagSelector string) (executions testkube.TestWorkflowExecutionsResult, err error) + ListTestWorkflowExecutions(id string, limit int, options FilterTestWorkflowExecutionOptions) (executions testkube.TestWorkflowExecutionsResult, err error) AbortTestWorkflowExecution(workflow string, id string) error AbortTestWorkflowExecutions(workflow string) error GetTestWorkflowExecutionArtifacts(executionID string) (artifacts testkube.Artifacts, err error) @@ -286,6 +286,14 @@ type ExecuteTestSuiteOptions struct { DisableWebhooks bool } +// FilterTestWorkflowExecutionOptions contains filter test workflow execution options +type FilterTestWorkflowExecutionOptions struct { + Selector string + TagSelector string + ActorName string + ActorType testkube.TestWorkflowRunningContextActorType +} + // Gettable is an interface of gettable objects type Gettable interface { testkube.Test | testkube.TestSuite | testkube.ExecutorDetails | diff --git a/pkg/api/v1/client/testworkflow.go b/pkg/api/v1/client/testworkflow.go index 7e3420eb073..3c929be6601 100644 --- a/pkg/api/v1/client/testworkflow.go +++ b/pkg/api/v1/client/testworkflow.go @@ -136,15 +136,17 @@ func (c TestWorkflowClient) GetTestWorkflowExecution(id string) (testkube.TestWo } // ListTestWorkflowExecutions list test workflow executions for selected workflow -func (c TestWorkflowClient) ListTestWorkflowExecutions(id string, limit int, selector, tagSelector string) (testkube.TestWorkflowExecutionsResult, error) { +func (c TestWorkflowClient) ListTestWorkflowExecutions(id string, limit int, options FilterTestWorkflowExecutionOptions) (testkube.TestWorkflowExecutionsResult, error) { uri := c.testWorkflowExecutionsResultTransport.GetURI("/test-workflow-executions/") if id != "" { uri = c.testWorkflowExecutionsResultTransport.GetURI(fmt.Sprintf("/test-workflows/%s/executions", id)) } params := map[string]string{ - "selector": selector, + "selector": options.Selector, "pageSize": fmt.Sprintf("%d", limit), - "tagSelector": tagSelector, + "tagSelector": options.TagSelector, + "actorName": options.ActorName, + "actorType": string(options.ActorType), } return c.testWorkflowExecutionsResultTransport.Execute(http.MethodGet, uri, nil, params) } diff --git a/pkg/api/v1/testkube/model_running_context_extended.go b/pkg/api/v1/testkube/model_running_context_extended.go index b1b4db2a0ae..26e90d52e99 100644 --- a/pkg/api/v1/testkube/model_running_context_extended.go +++ b/pkg/api/v1/testkube/model_running_context_extended.go @@ -3,12 +3,14 @@ package testkube type RunningContextType string const ( - RunningContextTypeUserCLI RunningContextType = "user-cli" - RunningContextTypeUserUI RunningContextType = "user-ui" - RunningContextTypeTestSuite RunningContextType = "testsuite" - RunningContextTypeTestTrigger RunningContextType = "testtrigger" - RunningContextTypeScheduler RunningContextType = "scheduler" - RunningContextTypeTestExecution RunningContextType = "testexecution" - RunningContextTypeTestSuiteExecution RunningContextType = "testsuiteexecution" - RunningContextTypeEmpty RunningContextType = "" + RunningContextTypeUserCLI RunningContextType = "user-cli" + RunningContextTypeUserUI RunningContextType = "user-ui" + RunningContextTypeTestSuite RunningContextType = "testsuite" + RunningContextTypeTestWorkflow RunningContextType = "testworkflow" + RunningContextTypeTestTrigger RunningContextType = "testtrigger" + RunningContextTypeScheduler RunningContextType = "scheduler" + RunningContextTypeTestExecution RunningContextType = "testexecution" + RunningContextTypeTestSuiteExecution RunningContextType = "testsuiteexecution" + RunningContextTypeTestWorkflowExecution RunningContextType = "testworkflowexecution" + RunningContextTypeEmpty RunningContextType = "" ) diff --git a/pkg/api/v1/testkube/model_test_workflow_execution.go b/pkg/api/v1/testkube/model_test_workflow_execution.go index 184916a342b..6fdea115b99 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution.go @@ -40,6 +40,7 @@ type TestWorkflowExecution struct { // test workflow execution name started the test workflow execution TestWorkflowExecutionName string `json:"testWorkflowExecutionName,omitempty"` // whether webhooks on the execution of this test workflow are disabled - DisableWebhooks bool `json:"disableWebhooks,omitempty"` - Tags map[string]string `json:"tags,omitempty"` + DisableWebhooks bool `json:"disableWebhooks,omitempty"` + Tags map[string]string `json:"tags,omitempty"` + RunningContext *TestWorkflowRunningContext `json:"runningContext,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_workflow_execution_request.go b/pkg/api/v1/testkube/model_test_workflow_execution_request.go index aa5840710f5..53d575edb56 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution_request.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution_request.go @@ -16,6 +16,9 @@ type TestWorkflowExecutionRequest struct { // test workflow execution name started the test workflow execution TestWorkflowExecutionName string `json:"testWorkflowExecutionName,omitempty"` // whether webhooks on the execution of this test workflow are disabled - DisableWebhooks bool `json:"disableWebhooks,omitempty"` - Tags map[string]string `json:"tags,omitempty"` + DisableWebhooks bool `json:"disableWebhooks,omitempty"` + Tags map[string]string `json:"tags,omitempty"` + RunningContext *TestWorkflowRunningContext `json:"runningContext,omitempty"` + // parent execution ids + ParentExecutionIds []string `json:"parentExecutionIds,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_workflow_execution_summary.go b/pkg/api/v1/testkube/model_test_workflow_execution_summary.go index f52ed9dc025..92f9868167b 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution_summary.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution_summary.go @@ -23,8 +23,9 @@ type TestWorkflowExecutionSummary struct { // when the execution has been scheduled to run ScheduledAt time.Time `json:"scheduledAt,omitempty"` // when the execution result's status has changed last time (queued, passed, failed) - StatusAt time.Time `json:"statusAt,omitempty"` - Result *TestWorkflowResultSummary `json:"result,omitempty"` - Workflow *TestWorkflowSummary `json:"workflow"` - Tags map[string]string `json:"tags,omitempty"` + StatusAt time.Time `json:"statusAt,omitempty"` + Result *TestWorkflowResultSummary `json:"result,omitempty"` + Workflow *TestWorkflowSummary `json:"workflow"` + Tags map[string]string `json:"tags,omitempty"` + RunningContext *TestWorkflowRunningContext `json:"runningContext,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_workflow_running_context.go b/pkg/api/v1/testkube/model_test_workflow_running_context.go new file mode 100644 index 00000000000..c93d4435d6c --- /dev/null +++ b/pkg/api/v1/testkube/model_test_workflow_running_context.go @@ -0,0 +1,16 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// running context for test workflow execution +type TestWorkflowRunningContext struct { + Interface_ *TestWorkflowRunningContextInterface `json:"interface"` + Actor *TestWorkflowRunningContextActor `json:"actor"` +} diff --git a/pkg/api/v1/testkube/model_test_workflow_running_context_actor.go b/pkg/api/v1/testkube/model_test_workflow_running_context_actor.go new file mode 100644 index 00000000000..4e0ae65fb3b --- /dev/null +++ b/pkg/api/v1/testkube/model_test_workflow_running_context_actor.go @@ -0,0 +1,23 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// running context actor for test workflow execution +type TestWorkflowRunningContextActor struct { + // actor name + Name string `json:"name,omitempty"` + // actor email + Email string `json:"email,omitempty"` + // test workflow execution id + ExecutionId string `json:"executionId,omitempty"` + // all test workflow execution ids starting from the root + ExecutionPath string `json:"executionPath,omitempty"` + Type_ *TestWorkflowRunningContextActorType `json:"type"` +} diff --git a/pkg/api/v1/testkube/model_test_workflow_running_context_actor_type.go b/pkg/api/v1/testkube/model_test_workflow_running_context_actor_type.go new file mode 100644 index 00000000000..2aae0146ccf --- /dev/null +++ b/pkg/api/v1/testkube/model_test_workflow_running_context_actor_type.go @@ -0,0 +1,23 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// TestWorkflowRunningContextActorType : supported actors for test workflow running context +type TestWorkflowRunningContextActorType string + +// List of TestWorkflowRunningContextActorType +const ( + CRON_TestWorkflowRunningContextActorType TestWorkflowRunningContextActorType = "cron" + TESTTRIGGER_TestWorkflowRunningContextActorType TestWorkflowRunningContextActorType = "testtrigger" + USER_TestWorkflowRunningContextActorType TestWorkflowRunningContextActorType = "user" + TESTWORKFLOW_TestWorkflowRunningContextActorType TestWorkflowRunningContextActorType = "testworkflow" + TESTWORKFLOWEXECUTION_TestWorkflowRunningContextActorType TestWorkflowRunningContextActorType = "testworkflowexecution" + PROGRAM_TestWorkflowRunningContextActorType TestWorkflowRunningContextActorType = "program" +) diff --git a/pkg/api/v1/testkube/model_test_workflow_running_context_interface.go b/pkg/api/v1/testkube/model_test_workflow_running_context_interface.go new file mode 100644 index 00000000000..03f3b66dfe5 --- /dev/null +++ b/pkg/api/v1/testkube/model_test_workflow_running_context_interface.go @@ -0,0 +1,17 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// running context interface for test workflow execution +type TestWorkflowRunningContextInterface struct { + // interface name + Name string `json:"name,omitempty"` + Type_ *TestWorkflowRunningContextInterfaceType `json:"type"` +} diff --git a/pkg/api/v1/testkube/model_test_workflow_running_context_interface_type.go b/pkg/api/v1/testkube/model_test_workflow_running_context_interface_type.go new file mode 100644 index 00000000000..ac0a711dbeb --- /dev/null +++ b/pkg/api/v1/testkube/model_test_workflow_running_context_interface_type.go @@ -0,0 +1,22 @@ +/* + * Testkube API + * + * Testkube provides a Kubernetes-native framework for test definition, execution and results + * + * API version: 1.0.0 + * Contact: testkube@kubeshop.io + * Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git) + */ +package testkube + +// TestWorkflowRunningContextInterfaceType : supported interfaces for test workflow running context +type TestWorkflowRunningContextInterfaceType string + +// List of TestWorkflowRunningContextInterfaceType +const ( + CLI_TestWorkflowRunningContextInterfaceType TestWorkflowRunningContextInterfaceType = "cli" + UI_TestWorkflowRunningContextInterfaceType TestWorkflowRunningContextInterfaceType = "ui" + API_TestWorkflowRunningContextInterfaceType TestWorkflowRunningContextInterfaceType = "api" + CICD_TestWorkflowRunningContextInterfaceType TestWorkflowRunningContextInterfaceType = "ci/cd" + INTERNAL_TestWorkflowRunningContextInterfaceType TestWorkflowRunningContextInterfaceType = "internal" +) diff --git a/pkg/mapper/cdevents/mapper.go b/pkg/mapper/cdevents/mapper.go index 27a166ab9bb..d61b4897d42 100644 --- a/pkg/mapper/cdevents/mapper.go +++ b/pkg/mapper/cdevents/mapper.go @@ -9,6 +9,7 @@ import ( cdevents "github.com/cdevents/sdk-go/pkg/api" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/mapper" ) // MapTestkubeEventToCDEvent maps OpenAPI spec Event to CDEvent CDEventReader @@ -414,7 +415,7 @@ func MapTestkubeRunningContextTypeToCDEventTiggerType(contextType string) string switch testkube.RunningContextType(contextType) { case testkube.RunningContextTypeUserCLI, testkube.RunningContextTypeUserUI: return "manual" - case testkube.RunningContextTypeTestTrigger, testkube.RunningContextTypeTestSuite: + case testkube.RunningContextTypeTestTrigger, testkube.RunningContextTypeTestSuite, testkube.RunningContextTypeTestWorkflow: return "event" case testkube.RunningContextTypeScheduler: return "schedule" @@ -522,6 +523,14 @@ func MapTestkubeEventQueuedTestWorkflowTestToCDEvent(event testkube.Event, clust Id: namespace, Source: clusterID, }) + + if event.TestWorkflowExecution.RunningContext != nil && event.TestWorkflowExecution.RunningContext.Actor != nil && + event.TestWorkflowExecution.RunningContext.Actor.Type_ != nil { + ev.SetSubjectTrigger(&cdevents.TestCaseRunQueuedSubjectContentTrigger{ + // Pro edition only (tcl protected code) + Type: mapper.MapTestkubeTestWorkflowRunningContextActorToCDEventTiggerType(*event.TestWorkflowExecution.RunningContext.Actor.Type_), + }) + } } return ev, nil @@ -561,6 +570,14 @@ func MapTestkubeEventQueuedTestWorkflowTestSuiteToCDEvent(event testkube.Event, Id: namespace, Source: clusterID, }) + + if event.TestWorkflowExecution.RunningContext != nil && event.TestWorkflowExecution.RunningContext.Actor != nil && + event.TestWorkflowExecution.RunningContext.Actor.Type_ != nil { + ev.SetSubjectTrigger(&cdevents.TestSuiteRunQueuedSubjectContentTrigger{ + // Pro edition only (tcl protected code) + Type: mapper.MapTestkubeTestWorkflowRunningContextActorToCDEventTiggerType(*event.TestWorkflowExecution.RunningContext.Actor.Type_), + }) + } } return ev, nil @@ -601,6 +618,14 @@ func MapTestkubeEventStartTestWorkflowTestToCDEvent(event testkube.Event, cluste Id: namespace, Source: clusterID, }) + + if event.TestWorkflowExecution.RunningContext != nil && event.TestWorkflowExecution.RunningContext.Actor != nil && + event.TestWorkflowExecution.RunningContext.Actor.Type_ != nil { + ev.SetSubjectTrigger(&cdevents.TestCaseRunStartedSubjectContentTrigger{ + // Pro edition only (tcl protected code) + Type: mapper.MapTestkubeTestWorkflowRunningContextActorToCDEventTiggerType(*event.TestWorkflowExecution.RunningContext.Actor.Type_), + }) + } } return ev, nil @@ -640,6 +665,14 @@ func MapTestkubeEventStartTestWorkflowTestSuiteToCDEvent(event testkube.Event, c Id: namespace, Source: clusterID, }) + + if event.TestWorkflowExecution.RunningContext != nil && event.TestWorkflowExecution.RunningContext.Actor != nil && + event.TestWorkflowExecution.RunningContext.Actor.Type_ != nil { + ev.SetSubjectTrigger(&cdevents.TestSuiteRunStartedSubjectContentTrigger{ + // Pro edition only (tcl protected code) + Type: mapper.MapTestkubeTestWorkflowRunningContextActorToCDEventTiggerType(*event.TestWorkflowExecution.RunningContext.Actor.Type_), + }) + } } return ev, nil diff --git a/pkg/mapper/cdevents/mapper_test.go b/pkg/mapper/cdevents/mapper_test.go index e66146e1ed7..76ce1bf265c 100644 --- a/pkg/mapper/cdevents/mapper_test.go +++ b/pkg/mapper/cdevents/mapper_test.go @@ -7,6 +7,7 @@ import ( cdevents "github.com/cdevents/sdk-go/pkg/api" "github.com/stretchr/testify/assert" + "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/testkube" ) @@ -511,6 +512,12 @@ func TestMapTestkubeEventQueuedTestWorkflowTestToCDEvent(t *testing.T) { }, }, }, + + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + }, }, } clusterID := "cluster-1" @@ -592,6 +599,11 @@ func TestMapTestkubeEventQueuedTestWorkflowTestSuiteToCDEvent(t *testing.T) { }, }, }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + }, }, } clusterID := "cluster-1" @@ -664,6 +676,11 @@ func TestMapTestkubeEventStartTestWorkflowTestToCDEvent(t *testing.T) { }, }, }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + }, }, } clusterID := "cluster-1" @@ -746,6 +763,11 @@ func TestMapTestkubeEventStartTestWorkflowTestSuiteToCDEvent(t *testing.T) { }, }, }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + }, }, } clusterID := "cluster-1" @@ -827,6 +849,11 @@ func TestMapTestkubeEventFinishTestWorkflowTestToCDEvent(t *testing.T) { }, }, }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + }, }, } clusterID := "cluster-1" @@ -927,6 +954,11 @@ func TestMapTestkubeEventFinishTestWorkflowTestSuiteToCDEvent(t *testing.T) { }, }, }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + }, }, } clusterID := "cluster-1" diff --git a/pkg/mapper/testworkflows/openapi_kube.go b/pkg/mapper/testworkflows/openapi_kube.go index e7b4195b9f4..012e1b3320a 100644 --- a/pkg/mapper/testworkflows/openapi_kube.go +++ b/pkg/mapper/testworkflows/openapi_kube.go @@ -13,6 +13,7 @@ import ( testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + mappertcl "github.com/kubeshop/testkube/pkg/tcl/mappertcl/testworkflows" ) func MapStringToIntOrString(i string) intstr.IntOrString { @@ -370,6 +371,7 @@ func MapCronJobConfigAPIToKube(v testkube.TestWorkflowCronJobConfig) testworkflo Annotations: v.Annotations, } } + func MapHostPathVolumeSourceAPIToKube(v testkube.HostPathVolumeSource) corev1.HostPathVolumeSource { return corev1.HostPathVolumeSource{ Path: v.Path, @@ -1368,6 +1370,7 @@ func MapTestWorkflowResultAPIToKube(v testkube.TestWorkflowResult) testworkflows Steps: common.MapMap(v.Steps, MapTestWorkflowStepResultAPIToKube), } } + func MapTestWorkflowSignatureAPIToKube(v testkube.TestWorkflowSignature) testworkflowsv1.TestWorkflowSignature { return testworkflowsv1.TestWorkflowSignature{ Ref: v.Ref, @@ -1396,6 +1399,8 @@ func MapTestWorkflowExecutionAPIToKube(v *testkube.TestWorkflowExecution) *testw TestWorkflowExecutionName: v.TestWorkflowExecutionName, DisableWebhooks: v.DisableWebhooks, Tags: v.Tags, + // Pro edition only (tcl protected code) + RunningContext: common.MapPtr(v.RunningContext, mappertcl.MapTestWorkflowRunningContextAPIToKube), } } @@ -1417,6 +1422,8 @@ func MapTestWorkflowExecutionAPIToKubeTestWorkflowStatusSummary(v *testkube.Test Result: common.MapPtr(v.Result, MapTestWorkflowResultAPIToKubeTestWorkflowResultSummary), Workflow: common.MapPtr(v.Workflow, MapTestWorkflowAPIToKubeTestWorkflowSummary), Tags: v.Tags, + // Pro edition only (tcl protected code) + RunningContext: common.MapPtr(v.RunningContext, mappertcl.MapTestWorkflowRunningContextAPIToKube), }, } } diff --git a/pkg/repository/testworkflow/filter.go b/pkg/repository/testworkflow/filter.go index 2f4c5c9d87f..23692a4387e 100644 --- a/pkg/repository/testworkflow/filter.go +++ b/pkg/repository/testworkflow/filter.go @@ -19,6 +19,8 @@ type FilterImpl struct { FSelector string FTagSelector string FLabelSelector *LabelSelector + FActorName string + FActorType testkube.TestWorkflowRunningContextActorType } func NewExecutionsFilter() *FilterImpl { @@ -84,6 +86,16 @@ func (f *FilterImpl) WithTagSelector(tagSelector string) *FilterImpl { return f } +func (f *FilterImpl) WithActorName(actorName string) *FilterImpl { + f.FActorName = actorName + return f +} + +func (f *FilterImpl) WithActorType(actorType testkube.TestWorkflowRunningContextActorType) *FilterImpl { + f.FActorType = actorType + return f +} + func (f *FilterImpl) WithLabelSelector(selector *LabelSelector) *FilterImpl { f.FLabelSelector = selector return f @@ -164,3 +176,19 @@ func (f FilterImpl) TagSelector() string { func (f FilterImpl) LabelSelector() *LabelSelector { return f.FLabelSelector } + +func (f FilterImpl) ActorName() string { + return f.FActorName +} + +func (f FilterImpl) ActorType() testkube.TestWorkflowRunningContextActorType { + return f.FActorType +} + +func (f FilterImpl) ActorNameDefined() bool { + return f.FActorName != "" +} + +func (f FilterImpl) ActorTypeDefined() bool { + return f.FActorType != "" +} diff --git a/pkg/repository/testworkflow/interface.go b/pkg/repository/testworkflow/interface.go index 05cf41a47f5..4d1d55bac8b 100644 --- a/pkg/repository/testworkflow/interface.go +++ b/pkg/repository/testworkflow/interface.go @@ -41,6 +41,10 @@ type Filter interface { Selector() string TagSelector() string LabelSelector() *LabelSelector + ActorName() string + ActorNameDefined() bool + ActorType() testkube.TestWorkflowRunningContextActorType + ActorTypeDefined() bool } //go:generate mockgen -destination=./mock_repository.go -package=testworkflow "github.com/kubeshop/testkube/pkg/repository/testworkflow" Repository diff --git a/pkg/repository/testworkflow/mongo.go b/pkg/repository/testworkflow/mongo.go index ab509596b04..b9cea5409ff 100644 --- a/pkg/repository/testworkflow/mongo.go +++ b/pkg/repository/testworkflow/mongo.go @@ -420,6 +420,14 @@ func composeQueryAndOpts(filter Filter) (bson.M, *options.FindOptions) { query["$or"] = subquery } + if filter.ActorNameDefined() { + query["runningcontext.actor.name"] = filter.ActorName() + } + + if filter.ActorTypeDefined() { + query["runningcontext.actor.type_"] = filter.ActorType() + } + opts.SetSkip(int64(filter.Page() * filter.PageSize())) opts.SetLimit(int64(filter.PageSize())) opts.SetSort(bson.D{{Key: "scheduledat", Value: -1}}) diff --git a/pkg/repository/testworkflow/mongo_integration_test.go b/pkg/repository/testworkflow/mongo_integration_test.go index 784c68069e8..fb56a02c73a 100644 --- a/pkg/repository/testworkflow/mongo_integration_test.go +++ b/pkg/repository/testworkflow/mongo_integration_test.go @@ -11,6 +11,7 @@ import ( "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" + "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/testkube" ) @@ -325,3 +326,108 @@ func TestNewMongoRepository_GetExecutions_Tags_Integration(t *testing.T) { assert.Len(t, res, 2) } + +func TestNewMongoRepository_GetExecutions_Actor_Integration(t *testing.T) { + test.IntegrationTest(t) + + ctx := context.Background() + + client, err := mongo.Connect(ctx, options.Client().ApplyURI(cfg.APIMongoDSN)) + if err != nil { + t.Fatalf("error connecting to mongo: %v", err) + } + db := client.Database("testworkflow-executions-actor-mongo-repository-test") + t.Cleanup(func() { + db.Drop(ctx) + }) + + repo := NewMongoRepository(db, false) + + execution := testkube.TestWorkflowExecution{ + Id: "test-id-1", + Name: "test-name-1", + Workflow: &testkube.TestWorkflow{ + Name: "test-name-1", + Spec: &testkube.TestWorkflowSpec{}, + }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Name: "user-1", + Type_: common.Ptr(testkube.USER_TestWorkflowRunningContextActorType), + }, + }, + } + if err := repo.Insert(ctx, execution); err != nil { + t.Fatalf("error inserting execution: %v", err) + } + + execution = testkube.TestWorkflowExecution{ + Id: "test-id-2", + Name: "test-name-2", + Workflow: &testkube.TestWorkflow{ + Name: "test-name-2", + Spec: &testkube.TestWorkflowSpec{}, + }, + RunningContext: &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Name: "user-2", + Type_: common.Ptr(testkube.USER_TestWorkflowRunningContextActorType), + }, + }, + } + if err := repo.Insert(ctx, execution); err != nil { + t.Fatalf("error inserting execution: %v", err) + } + + res, err := repo.GetExecutions(ctx, NewExecutionsFilter()) + if err != nil { + t.Fatalf("error getting executions: %v", err) + } + + assert.Len(t, res, 2) + + actorName := "user-1" + res, err = repo.GetExecutions(ctx, NewExecutionsFilter().WithActorName(actorName)) + if err != nil { + t.Fatalf("error getting executions: %v", err) + } + + assert.Len(t, res, 1) + assert.Equal(t, "test-name-1", res[0].Name) + + actorType := testkube.USER_TestWorkflowRunningContextActorType + res, err = repo.GetExecutions(ctx, NewExecutionsFilter().WithActorType(actorType)) + if err != nil { + t.Fatalf("error getting executions: %v", err) + } + + assert.Len(t, res, 2) + + actorName = "user-1" + actorType = testkube.USER_TestWorkflowRunningContextActorType + res, err = repo.GetExecutions(ctx, NewExecutionsFilter().WithActorName(actorName).WithActorType(actorType)) + if err != nil { + t.Fatalf("error getting executions: %v", err) + } + + assert.Len(t, res, 1) + assert.Equal(t, "test-name-1", res[0].Name) + + actorName = "user-1" + actorType = testkube.PROGRAM_TestWorkflowRunningContextActorType + res, err = repo.GetExecutions(ctx, NewExecutionsFilter().WithActorName(actorName).WithActorType(actorType)) + if err != nil { + t.Fatalf("error getting executions: %v", err) + } + + assert.Len(t, res, 0) + + actorName = "user-3" + actorType = testkube.USER_TestWorkflowRunningContextActorType + res, err = repo.GetExecutions(ctx, NewExecutionsFilter().WithActorName(actorName).WithActorType(actorType)) + if err != nil { + t.Fatalf("error getting executions: %v", err) + } + + assert.Len(t, res, 0) +} diff --git a/pkg/tcl/mappertcl/testworkflows/openapi_kube.go b/pkg/tcl/mappertcl/testworkflows/openapi_kube.go new file mode 100644 index 00000000000..26374486f32 --- /dev/null +++ b/pkg/tcl/mappertcl/testworkflows/openapi_kube.go @@ -0,0 +1,47 @@ +// Copyright 2024 Testkube. +// +// Licensed as a Testkube Pro file under the Testkube Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt + +package testworkflows + +import ( + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +func MapTestWorkflowRunningContextInterfaceTypeAPIToKube(v testkube.TestWorkflowRunningContextInterfaceType) testworkflowsv1.TestWorkflowRunningContextInterfaceType { + return testworkflowsv1.TestWorkflowRunningContextInterfaceType(v) +} + +func MapTestWorkflowRunningContextInterfaceAPIToKube(v testkube.TestWorkflowRunningContextInterface) testworkflowsv1.TestWorkflowRunningContextInterface { + return testworkflowsv1.TestWorkflowRunningContextInterface{ + Name: v.Name, + Type_: common.MapPtr(v.Type_, MapTestWorkflowRunningContextInterfaceTypeAPIToKube), + } +} + +func MapTestWorkflowRunningContextActorTypeAPIToKube(v testkube.TestWorkflowRunningContextActorType) testworkflowsv1.TestWorkflowRunningContextActorType { + return testworkflowsv1.TestWorkflowRunningContextActorType(v) +} + +func MapTestWorkflowRunningContextActorAPIToKube(v testkube.TestWorkflowRunningContextActor) testworkflowsv1.TestWorkflowRunningContextActor { + return testworkflowsv1.TestWorkflowRunningContextActor{ + Name: v.Name, + Email: v.Email, + ExecutionId: v.ExecutionId, + ExecutionPath: v.ExecutionPath, + Type_: common.MapPtr(v.Type_, MapTestWorkflowRunningContextActorTypeAPIToKube), + } +} + +func MapTestWorkflowRunningContextAPIToKube(v testkube.TestWorkflowRunningContext) testworkflowsv1.TestWorkflowRunningContext { + return testworkflowsv1.TestWorkflowRunningContext{ + Interface_: common.MapPtr(v.Interface_, MapTestWorkflowRunningContextInterfaceAPIToKube), + Actor: common.MapPtr(v.Actor, MapTestWorkflowRunningContextActorAPIToKube), + } +} diff --git a/pkg/tcl/testworkflowstcl/cmd/cmd.go b/pkg/tcl/testworkflowstcl/cmd/cmd.go new file mode 100644 index 00000000000..56b0bd878e2 --- /dev/null +++ b/pkg/tcl/testworkflowstcl/cmd/cmd.go @@ -0,0 +1,118 @@ +// Copyright 2024 Testkube. +// +// Licensed as a Testkube Pro file under the Testkube Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt + +package cmd + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" + + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/ui" +) + +func GetRunningContext(runContext, token string, interfaceType testkube.TestWorkflowRunningContextInterfaceType) *testkube.TestWorkflowRunningContext { + var name, email string + if token != "" { + payload, err := getJWTPayload(token) + if err == nil { + if value, ok := payload["name"]; ok { + name = fmt.Sprint(value) + } + + if value, ok := payload["email"]; ok { + email = fmt.Sprint(value) + } + } + } + + return &testkube.TestWorkflowRunningContext{ + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: runContext, + Type_: common.Ptr(interfaceType), + }, + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.USER_TestWorkflowRunningContextActorType), + Name: name, + Email: email, + }, + } +} + +func getJWTPayload(token string) (map[string]interface{}, error) { + parts := strings.Split(token, ".") + if len(parts) != 3 { + return nil, fmt.Errorf("invalid token format") + } + + // Decode the payload + payloadJSON, err := base64.RawURLEncoding.DecodeString((parts[1])) + if err != nil { + return nil, fmt.Errorf("failed to decode payload: %v", err) + } + + // Unmarshal the payload into maps + var payload map[string]interface{} + if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil { + return nil, fmt.Errorf("failed to parse payload JSON: %v", err) + } + + return payload, nil +} + +func PrintRunningContext(ui *ui.UI, execution testkube.TestWorkflowExecution) { + if execution.RunningContext != nil { + ui.Warn("Running context: ") + ctx := execution.RunningContext + if ctx.Interface_ != nil { + ui.Warn("Interface: ") + if ctx.Interface_.Name != "" { + ui.Warn(" Name: ", ctx.Interface_.Name) + } + if ctx.Interface_.Type_ != nil { + ui.Warn(" Type: ", string(*ctx.Interface_.Type_)) + } + } + if ctx.Actor != nil { + ui.Warn("Actor: ") + fields := []struct { + name string + value string + }{ + { + " Name: ", + ctx.Actor.Name, + }, + { + " Email: ", + ctx.Actor.Email, + }, + { + " Execution id: ", + ctx.Actor.ExecutionId, + }, + { + " Execution path: ", + ctx.Actor.ExecutionPath, + }, + } + + for _, field := range fields { + if field.value != "" { + ui.Warn(field.name, field.value) + } + } + if ctx.Actor.Type_ != nil { + ui.Warn(" Type: ", string(*ctx.Actor.Type_)) + } + } + } +} diff --git a/pkg/tcl/testworkflowstcl/cmd/cmd_test.go b/pkg/tcl/testworkflowstcl/cmd/cmd_test.go new file mode 100644 index 00000000000..ad08983d389 --- /dev/null +++ b/pkg/tcl/testworkflowstcl/cmd/cmd_test.go @@ -0,0 +1,94 @@ +// Copyright 2024 Testkube. +// +// Licensed as a Testkube Pro file under the Testkube Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt + +package cmd + +import ( + "encoding/base64" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCmd_GetJWTPayload(t *testing.T) { + t.Parallel() + + t.Run("valid jwt token", func(t *testing.T) { + t.Parallel() + + header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + payload := base64.RawURLEncoding.EncodeToString([]byte(`{"sub":"1234567890","name":"John Doe","email":"fake@email.com"}`)) + signature := "signature" + + token := fmt.Sprintf("%s.%s.%s", header, payload, signature) + + expectedPayload := map[string]interface{}{ + "sub": "1234567890", + "name": "John Doe", + "email": "fake@email.com", + } + + result, err := getJWTPayload(token) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + + assert.Equal(t, expectedPayload, result) + }) + + t.Run("invalid jwt token", func(t *testing.T) { + t.Parallel() + + token := "invalid.token" + + _, err := getJWTPayload(token) + if err == nil { + t.Fatal("expected error, got nil") + } + + expectedError := "invalid token format" + assert.EqualError(t, err, expectedError) + }) + + t.Run("invalid base64 payload", func(t *testing.T) { + t.Parallel() + + header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + payload := "invalidbase64" + signature := "signature" + + token := fmt.Sprintf("%s.%s.%s", header, payload, signature) + + _, err := getJWTPayload(token) + if err == nil { + t.Fatal("expected error, got nil") + } + + expectedError := "failed to decode payload" + assert.ErrorContains(t, err, expectedError) + }) + + t.Run("invalid json", func(t *testing.T) { + t.Parallel() + + header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"HS256","typ":"JWT"}`)) + payload := base64.RawURLEncoding.EncodeToString([]byte(`invalid-json`)) + signature := "signature" + + token := fmt.Sprintf("%s.%s.%s", header, payload, signature) + + _, err := getJWTPayload(token) + if err == nil { + t.Fatal("expected error, got nil") + } + + expectedError := "failed to parse payload JSON" + assert.ErrorContains(t, err, expectedError) + }) +} diff --git a/pkg/tcl/testworkflowstcl/mapper/cdevents.go b/pkg/tcl/testworkflowstcl/mapper/cdevents.go new file mode 100644 index 00000000000..d5e69053e6d --- /dev/null +++ b/pkg/tcl/testworkflowstcl/mapper/cdevents.go @@ -0,0 +1,26 @@ +// Copyright 2024 Testkube. +// +// Licensed as a Testkube Pro file under the Testkube Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt + +package mapper + +import "github.com/kubeshop/testkube/pkg/api/v1/testkube" + +// MapTestkubeTestWorkflowRunningContextActorToCDEventTiggerType maps OpenAPI spec Test Workflow Running Context Actor to CDEvent Trigger Type +func MapTestkubeTestWorkflowRunningContextActorToCDEventTiggerType(actor testkube.TestWorkflowRunningContextActorType) string { + switch actor { + case testkube.USER_TestWorkflowRunningContextActorType, testkube.PROGRAM_TestWorkflowRunningContextActorType: + return "manual" + case testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType, testkube.TESTWORKFLOWEXECUTION_TestWorkflowRunningContextActorType, + testkube.TESTTRIGGER_TestWorkflowRunningContextActorType: + return "event" + case testkube.CRON_TestWorkflowRunningContextActorType: + return "schedule" + } + + return "other" +} diff --git a/pkg/tcl/testworkflowstcl/triggers/executor.go b/pkg/tcl/testworkflowstcl/triggers/executor.go new file mode 100644 index 00000000000..65459377457 --- /dev/null +++ b/pkg/tcl/testworkflowstcl/triggers/executor.go @@ -0,0 +1,26 @@ +// Copyright 2024 Testkube. +// +// Licensed as a Testkube Pro file under the Testkube Community +// License (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// https://github.com/kubeshop/testkube/blob/main/licenses/TCL.txt + +package triggers + +import ( + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +func GetRunningContext(name string) *testkube.TestWorkflowRunningContext { + return &testkube.TestWorkflowRunningContext{ + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Type_: common.Ptr(testkube.INTERNAL_TestWorkflowRunningContextInterfaceType), + }, + Actor: &testkube.TestWorkflowRunningContextActor{ + Name: name, + Type_: common.Ptr(testkube.TESTTRIGGER_TestWorkflowRunningContextActorType), + }, + } +} diff --git a/pkg/testworkflows/testworkflowconfig/config.go b/pkg/testworkflows/testworkflowconfig/config.go index fb317f0443b..8f41f3c8cfe 100644 --- a/pkg/testworkflows/testworkflowconfig/config.go +++ b/pkg/testworkflows/testworkflowconfig/config.go @@ -21,6 +21,7 @@ type ExecutionConfig struct { Debug bool `json:"d,omitempty"` OrganizationId string `json:"o,omitempty"` EnvironmentId string `json:"e,omitempty"` + ParentIds string `json:"p,omitempty"` } type WorkflowConfig struct { diff --git a/pkg/testworkflows/testworkflowexecutor/executor.go b/pkg/testworkflows/testworkflowexecutor/executor.go index a1cc7f7c6f1..632baa7a095 100644 --- a/pkg/testworkflows/testworkflowexecutor/executor.go +++ b/pkg/testworkflows/testworkflowexecutor/executor.go @@ -470,6 +470,7 @@ func (e *executor) initialize(ctx context.Context, workflow *testworkflowsv1.Tes TestWorkflowExecutionName: request.TestWorkflowExecutionName, DisableWebhooks: request.DisableWebhooks, Tags: map[string]string{}, + RunningContext: request.RunningContext, } // Try to resolve tags initially diff --git a/pkg/triggers/executor.go b/pkg/triggers/executor.go index 8ebb106d5cc..ebb2ed222a9 100644 --- a/pkg/triggers/executor.go +++ b/pkg/triggers/executor.go @@ -15,6 +15,7 @@ import ( "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/scheduler" + triggerstcl "github.com/kubeshop/testkube/pkg/tcl/testworkflowstcl/triggers" "github.com/kubeshop/testkube/pkg/workerpool" ) @@ -137,6 +138,11 @@ func (s *Service) execute(ctx context.Context, e *watcherEvent, t *testtriggersv Config: make(map[string]string, len(variables)), } + // Pro edition only (tcl protected code) + if s.proContext != nil && s.proContext.APIKey != "" { + request.RunningContext = triggerstcl.GetRunningContext(t.Name) + } + for _, variable := range variables { request.Config[variable.Name] = variable.Value } diff --git a/pkg/triggers/service.go b/pkg/triggers/service.go index eb1a8a68c76..b049ff8de37 100644 --- a/pkg/triggers/service.go +++ b/pkg/triggers/service.go @@ -18,6 +18,7 @@ import ( testkubeclientsetv1 "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" "github.com/kubeshop/testkube/cmd/api-server/services" "github.com/kubeshop/testkube/internal/app/api/metrics" + intconfig "github.com/kubeshop/testkube/internal/config" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/event/bus" "github.com/kubeshop/testkube/pkg/http" @@ -72,6 +73,7 @@ type Service struct { watcherNamespaces []string disableSecretCreation bool deprecatedSystem *services.DeprecatedSystem + proContext *intconfig.ProContext } type Option func(*Service) @@ -199,6 +201,12 @@ func WithDisableSecretCreation(disableSecretCreation bool) Option { } } +func WithProContext(proContext *intconfig.ProContext) Option { + return func(s *Service) { + s.proContext = proContext + } +} + func (s *Service) Run(ctx context.Context) { leaseChan := make(chan bool)