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: [TKC-2765] run test workflows by selector #6016

Merged
merged 22 commits into from
Nov 18, 2024
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
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:
vsukhin marked this conversation as resolved.
Show resolved Hide resolved
$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
rangoo94 marked this conversation as resolved.
Show resolved Hide resolved
)

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
vsukhin marked this conversation as resolved.
Show resolved Hide resolved
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)
vsukhin marked this conversation as resolved.
Show resolved Hide resolved
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
Loading