Skip to content

Commit

Permalink
feat: [TKC-2765] run test workflows by selector (#6016)
Browse files Browse the repository at this point in the history
* feat: run test workflows by selector

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: log typo

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* feat: selector for execute op

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: dep update

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* feat: execute method with selector

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: check if name is not empty

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: exit on error code

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: update help

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: switch to error merhods

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: error text

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: join errors

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: use existing naming in cli

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: move common part

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: help

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* feat: add execute test workflows api method spec

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* Update pkg/api/v1/client/interface.go

Co-authored-by: Dawid Rusnak <dawid@drcode.pl>

* fix: remove concurrency level

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: use Label Selector

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: check for empty name and selector

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

* fix: dep update

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>

---------

Signed-off-by: Vladislav Sukhin <vladislav@kubeshop.io>
Co-authored-by: Dawid Rusnak <dawid@drcode.pl>
  • Loading branch information
vsukhin and rangoo94 authored Nov 18, 2024
1 parent 481c968 commit f920506
Show file tree
Hide file tree
Showing 12 changed files with 281 additions and 77 deletions.
54 changes: 54 additions & 0 deletions api/v1/testkube.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4065,6 +4065,57 @@ paths:
items:
$ref: "#/components/schemas/Problem"
/test-workflow-executions:
post:
parameters:
- $ref: "#/components/parameters/Selector"
- $ref: "#/components/parameters/ConcurrencyLevel"
tags:
- api
- test-workflows
- pro
summary: "Execute test workflows"
description: "Execute test workflows in the kubernetes cluster"
operationId: executeTestWorkflows
requestBody:
description: test workflow execution request
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TestWorkflowExecutionRequest"
responses:
200:
description: successful execution
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TestWorkflowExecution"
400:
description: "problem with body parsing - probably some bad input occurs"
content:
application/problem+json:
schema:
type: array
items:
$ref: "#/components/schemas/Problem"
402:
description: "missing Pro subscription for a commercial feature"
content:
application/problem+json:
schema:
type: array
items:
$ref: "#/components/schemas/Problem"
502:
description: problem communicating with kubernetes cluster
content:
application/problem+json:
schema:
type: array
items:
$ref: "#/components/schemas/Problem"
get:
tags:
- test-workflows
Expand Down Expand Up @@ -8990,6 +9041,9 @@ components:
$ref: "#/components/schemas/TestWorkflowTarballRequest"
config:
$ref: "#/components/schemas/TestWorkflowConfigValue"
selector:
$ref: "#/components/schemas/LabelSelector"
description: label selector for test workflow

TestWorkflowStepExecuteTestRef:
type: object
Expand Down
85 changes: 59 additions & 26 deletions cmd/kubectl-testkube/commands/testworkflows/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package testworkflows
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"os"
Expand Down Expand Up @@ -45,12 +46,12 @@ func NewRunTestWorkflowCmd() *cobra.Command {
format string
masks []string
tags map[string]string
selectors []string
)

cmd := &cobra.Command{
Use: "testworkflow [name]",
Aliases: []string{"testworkflows", "tw"},
Args: cobra.ExactArgs(1),
Short: "Starts test workflow execution",

Run: func(cmd *cobra.Command, args []string) {
Expand All @@ -65,7 +66,6 @@ func NewRunTestWorkflowCmd() *cobra.Command {
client, _, err := common.GetClient(cmd)
ui.ExitOnError("getting client", err)

name := args[0]
runContext := telemetry.GetCliRunContext()
interfaceType := testkube.CICD_TestWorkflowRunningContextInterfaceType
if runContext == "others|local" {
Expand All @@ -83,13 +83,29 @@ func NewRunTestWorkflowCmd() *cobra.Command {
runningContext = tclcmd.GetRunningContext(runContext, cfg.CloudContext.ApiKey, interfaceType)
}

execution, err := client.ExecuteTestWorkflow(name, testkube.TestWorkflowExecutionRequest{
request := testkube.TestWorkflowExecutionRequest{
Name: executionName,
Config: config,
DisableWebhooks: disableWebhooks,
Tags: tags,
RunningContext: runningContext,
})
}

var executions []testkube.TestWorkflowExecution
switch {
case len(args) > 0:
name := args[0]

var execution testkube.TestWorkflowExecution
execution, err = client.ExecuteTestWorkflow(name, request)
executions = append(executions, execution)
case len(selectors) != 0:
selector := strings.Join(selectors, ",")
executions, err = client.ExecuteTestWorkflows(selector, request)
default:
ui.Failf("Pass Test workflow name or labels to run by labels ")
}

if err != nil {
// User friendly Open Source operation error
errMessage := err.Error()
Expand All @@ -108,33 +124,49 @@ func NewRunTestWorkflowCmd() *cobra.Command {
}
}

ui.ExitOnError("execute test workflow "+name+" from namespace "+namespace, err)
err = renderer.PrintTestWorkflowExecution(cmd, os.Stdout, execution)
ui.ExitOnError("render test workflow execution", err)

var exitCode = 0
if outputPretty {
ui.NL()
if !execution.FailedToInitialize() {
if watchEnabled {
exitCode = uiWatch(execution, client)
ui.NL()
if downloadArtifactsEnabled {
tests.DownloadTestWorkflowArtifacts(execution.Id, downloadDir, format, masks, client, outputPretty)
if len(args) > 0 {
ui.ExitOnError("execute test workflow "+args[0]+" from namespace "+namespace, err)
} else {
ui.ExitOnError("execute test workflows "+strings.Join(selectors, ",")+" from namespace "+namespace, err)
}

go func() {
<-cmd.Context().Done()
if errors.Is(cmd.Context().Err(), context.Canceled) {
os.Exit(0)
}
}()

for _, execution := range executions {
err = renderer.PrintTestWorkflowExecution(cmd, os.Stdout, execution)
ui.ExitOnError("render test workflow execution", err)

var exitCode = 0
if outputPretty {
ui.NL()
if !execution.FailedToInitialize() {
if watchEnabled && len(args) > 0 {
exitCode = uiWatch(execution, client)
ui.NL()
if downloadArtifactsEnabled {
tests.DownloadTestWorkflowArtifacts(execution.Id, downloadDir, format, masks, client, outputPretty)
}
} else {
uiShellWatchExecution(execution.Id)
}
} else {
uiShellWatchExecution(execution.Id)
}
}

execution, err = client.GetTestWorkflowExecution(execution.Id)
ui.ExitOnError("get execution failed", err)
execution, err = client.GetTestWorkflowExecution(execution.Id)
ui.ExitOnError("get execution failed", err)

render.PrintTestWorkflowExecutionURIs(&execution)
uiShellGetExecution(execution.Id)
}
render.PrintTestWorkflowExecutionURIs(&execution)
uiShellGetExecution(execution.Id)
}

os.Exit(exitCode)
if exitCode != 0 {
os.Exit(exitCode)
}
}
},
}

Expand All @@ -148,6 +180,7 @@ func NewRunTestWorkflowCmd() *cobra.Command {
cmd.Flags().StringVar(&format, "format", "folder", "data format for storing files, one of folder|archive")
cmd.Flags().StringArrayVarP(&masks, "mask", "", []string{}, "regexp to filter downloaded files, single or comma separated, like report/.* or .*\\.json,.*\\.js$")
cmd.Flags().StringToStringVarP(&tags, "tag", "", map[string]string{}, "execution tags in a form of name1=val1 passed to executor")
cmd.Flags().StringSliceVarP(&selectors, "label", "l", nil, "label key value pair: --label key1=value1 or label expression")

return cmd
}
Expand Down
73 changes: 53 additions & 20 deletions cmd/tcl/testworkflow-toolkit/commands/execute.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (

"github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1"
commontcl "github.com/kubeshop/testkube/cmd/tcl/testworkflow-toolkit/common"
Expand Down Expand Up @@ -368,42 +369,74 @@ func NewExecuteCmd() *cobra.Command {
operations = append(operations, fn)
}
}

c := env.Testkube()
for _, s := range workflows {
var w testworkflowsv1.StepExecuteWorkflow
err := json.Unmarshal([]byte(s), &w)
if err != nil {
ui.Fail(errors.Wrap(err, "unmarshal workflow definition"))
}

// Resolve the params
params, err := commontcl.GetParamsSpec(w.Matrix, w.Shards, w.Count, w.MaxCount, baseMachine)
if err != nil {
ui.Fail(errors.Wrap(err, "matrix and sharding"))
if w.Name == "" && w.Selector == nil {
ui.Fail(errors.New("either workflow name or selector should be specified"))
}
fmt.Printf("%s: %s\n", commontcl.ServiceLabel(w.Name), params.Humanize())

// Create operations for each expected execution
for i := int64(0); i < params.Count; i++ {
// Clone the spec
spec := w.DeepCopy()
var testWorkflowNames []string
if w.Name != "" {
testWorkflowNames = []string{w.Name}
}

// Build files for transfer
tarballMachine, err := registerTransfer(transferSrv, spec.Tarball, baseMachine, params.MachineAt(i))
if w.Selector != nil {
selector, err := metav1.LabelSelectorAsSelector(w.Selector)
if err != nil {
ui.Fail(errors.Wrapf(err, "'%s' workflow", spec.Name))
ui.Fail(errors.Wrap(err, "error creating selector from test workflow selector"))
}
spec.Tarball = nil

// Prepare the operation to run
err = expressions.Finalize(&spec, baseMachine, tarballMachine, params.MachineAt(i))
stringifiedSelector := selector.String()
testWorkflowsList, err := c.ListTestWorkflows(stringifiedSelector)
if err != nil {
ui.Fail(errors.Wrapf(err, "'%s' workflow: computing execution", spec.Name))
ui.Fail(errors.Wrap(err, "error listing test workflows using selector"))
}
fn, err := buildWorkflowExecution(*spec, async)
if err != nil {
ui.Fail(err)

for _, item := range testWorkflowsList {
testWorkflowNames = append(testWorkflowNames, item.Name)
}
}

// Resolve the params
params, err := commontcl.GetParamsSpec(w.Matrix, w.Shards, w.Count, w.MaxCount, baseMachine)
if err != nil {
ui.Fail(errors.Wrap(err, "matrix and sharding"))
}

for _, testWorkflowName := range testWorkflowNames {
fmt.Printf("%s: %s\n", commontcl.ServiceLabel(testWorkflowName), params.Humanize())

// Create operations for each expected execution
for i := int64(0); i < params.Count; i++ {
// Clone the spec
spec := w.DeepCopy()
spec.Name = testWorkflowName

// Build files for transfer
tarballMachine, err := registerTransfer(transferSrv, spec.Tarball, baseMachine, params.MachineAt(i))
if err != nil {
ui.Fail(errors.Wrapf(err, "'%s' workflow", spec.Name))
}
spec.Tarball = nil

// Prepare the operation to run
err = expressions.Finalize(&spec, baseMachine, tarballMachine, params.MachineAt(i))
if err != nil {
ui.Fail(errors.Wrapf(err, "'%s' workflow: computing execution", spec.Name))
}
fn, err := buildWorkflowExecution(*spec, async)
if err != nil {
ui.Fail(err)
}
operations = append(operations, fn)
}
operations = append(operations, fn)
}
}

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,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.20241030092155-2a57f6e797e9
github.com/kubeshop/testkube-operator v1.17.55-0.20241118133003-70462ac10f4a
github.com/minio/minio-go/v7 v7.0.47
github.com/montanaflynn/stats v0.6.6
github.com/moogar0880/problems v0.1.1
Expand All @@ -55,6 +55,7 @@ require (
github.com/prometheus/client_golang v1.18.0
github.com/pterm/pterm v0.12.79
github.com/robfig/cron v1.2.0
github.com/savioxavier/termlink v1.4.1
github.com/segmentio/analytics-go/v3 v3.2.1
github.com/shirou/gopsutil/v3 v3.24.3
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
Expand Down Expand Up @@ -185,7 +186,6 @@ require (
github.com/rs/xid v1.4.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect
github.com/savioxavier/termlink v1.4.1 // indirect
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 // indirect
github.com/segmentio/backo-go v1.0.0 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.20241030092155-2a57f6e797e9 h1:0v4W4kPfuDBJxvfkgKhDFA71AgmV0B5Jdb8dR7n4bV4=
github.com/kubeshop/testkube-operator v1.17.55-0.20241030092155-2a57f6e797e9/go.mod h1:P47tw1nKQFufdsZndyq2HG2MSa0zK/lU0XpRfZtEmIk=
github.com/kubeshop/testkube-operator v1.17.55-0.20241118133003-70462ac10f4a h1:xget2cwwqOL+K2Op9FPbMgfzj9lSVJAzZ9p48yxuFrE=
github.com/kubeshop/testkube-operator v1.17.55-0.20241118133003-70462ac10f4a/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=
Expand Down
1 change: 1 addition & 0 deletions internal/app/api/v1/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func (s *TestkubeAPI) Init(server server.HTTPServer) {

testWorkflowExecutions := root.Group("/test-workflow-executions")
testWorkflowExecutions.Get("/", s.ListTestWorkflowExecutionsHandler())
testWorkflowExecutions.Post("/", s.ExecuteTestWorkflowHandler())
testWorkflowExecutions.Get("/:executionID", s.GetTestWorkflowExecutionHandler())
testWorkflowExecutions.Get("/:executionID/notifications", s.StreamTestWorkflowExecutionNotificationsHandler())
testWorkflowExecutions.Get("/:executionID/notifications/stream", s.StreamTestWorkflowExecutionNotificationsWebSocketHandler())
Expand Down
Loading

0 comments on commit f920506

Please sign in to comment.