diff --git a/api/v1/testkube.yaml b/api/v1/testkube.yaml index 383e526afb8..19a25514ebd 100644 --- a/api/v1/testkube.yaml +++ b/api/v1/testkube.yaml @@ -7385,6 +7385,8 @@ components: type: string example: WEBHOOK_PARAMETER: "any value" + external: + type: boolean EventResource: type: string @@ -8188,6 +8190,9 @@ components: description: identifier for group of correlated executions format: bson objectId example: "62f395e004109209b50edfc1" + runnerId: + type: string + description: identifier of the runner where it has been executed name: type: string description: execution name @@ -8258,6 +8263,11 @@ components: description: unique execution identifier format: bson objectId example: "62f395e004109209b50edfc1" + groupId: + type: string + description: identifier for group of correlated executions + format: bson objectId + example: "62f395e004109209b50edfc1" name: type: string description: execution name diff --git a/cmd/api-server/commons/commons.go b/cmd/api-server/commons/commons.go index e10813b5c64..bd861f87da3 100644 --- a/cmd/api-server/commons/commons.go +++ b/cmd/api-server/commons/commons.go @@ -23,6 +23,7 @@ import ( parser "github.com/kubeshop/testkube/internal/template" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/cache" + "github.com/kubeshop/testkube/pkg/capabilities" "github.com/kubeshop/testkube/pkg/cloud" "github.com/kubeshop/testkube/pkg/configmap" "github.com/kubeshop/testkube/pkg/dbmigrator" @@ -290,12 +291,14 @@ func ReadProContext(ctx context.Context, cfg *config.Config, grpcClient cloud.Te WorkflowNotificationsWorkerCount: cfg.TestkubeProWorkflowNotificationsWorkerCount, WorkflowServiceNotificationsWorkerCount: cfg.TestkubeProWorkflowServiceNotificationsWorkerCount, WorkflowParallelStepNotificationsWorkerCount: cfg.TestkubeProWorkflowParallelStepNotificationsWorkerCount, - SkipVerify: cfg.TestkubeProSkipVerify, - EnvID: cfg.TestkubeProEnvID, - OrgID: cfg.TestkubeProOrgID, - Migrate: cfg.TestkubeProMigrate, - ConnectionTimeout: cfg.TestkubeProConnectionTimeout, - DashboardURI: cfg.TestkubeDashboardURI, + SkipVerify: cfg.TestkubeProSkipVerify, + EnvID: cfg.TestkubeProEnvID, + OrgID: cfg.TestkubeProOrgID, + Migrate: cfg.TestkubeProMigrate, + ConnectionTimeout: cfg.TestkubeProConnectionTimeout, + DashboardURI: cfg.TestkubeDashboardURI, + NewExecutions: grpcClient == nil, + TestWorkflowStorage: grpcClient == nil, } if cfg.TestkubeProAPIKey == "" || grpcClient == nil { @@ -320,6 +323,14 @@ func ReadProContext(ctx context.Context, cfg *config.Config, grpcClient cloud.Te proContext.OrgID = foundProContext.OrgId } + if capabilities.Enabled(foundProContext.Capabilities, capabilities.CapabilityNewExecutions) { + proContext.NewExecutions = true + } + + if capabilities.Enabled(foundProContext.Capabilities, capabilities.CapabilityTestWorkflowStorage) { + proContext.TestWorkflowStorage = true + } + return proContext } diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go index de8c8bd41b4..1fde90757e6 100644 --- a/cmd/api-server/main.go +++ b/cmd/api-server/main.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "strings" + "time" "github.com/gofiber/fiber/v2/middleware/cors" "google.golang.org/grpc" @@ -19,12 +20,19 @@ import ( cloudtestworkflow "github.com/kubeshop/testkube/pkg/cloud/data/testworkflow" "github.com/kubeshop/testkube/pkg/event/kind/cdevent" "github.com/kubeshop/testkube/pkg/event/kind/k8sevent" + "github.com/kubeshop/testkube/pkg/event/kind/testworkflowexecutionmetrics" + "github.com/kubeshop/testkube/pkg/event/kind/testworkflowexecutions" + "github.com/kubeshop/testkube/pkg/event/kind/testworkflowexecutiontelemetry" "github.com/kubeshop/testkube/pkg/event/kind/webhook" ws "github.com/kubeshop/testkube/pkg/event/kind/websocket" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" + runner2 "github.com/kubeshop/testkube/pkg/runner" "github.com/kubeshop/testkube/pkg/secretmanager" "github.com/kubeshop/testkube/pkg/server" "github.com/kubeshop/testkube/pkg/tcl/checktcl" "github.com/kubeshop/testkube/pkg/tcl/schedulertcl" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/presets" "github.com/kubeshop/testkube/internal/common" @@ -78,9 +86,35 @@ func main() { configMapConfig := commons.MustGetConfigMapConfig(ctx, cfg.APIServerConfig, cfg.TestkubeNamespace, cfg.TestkubeAnalyticsEnabled) + // k8s + kubeClient, err := kubeclient.GetClient() + commons.ExitOnError("Getting kubernetes client", err) + clientset, err := k8sclient.ConnectToK8s() + commons.ExitOnError("Creating k8s clientset", err) + + var runner runner2.Runner + lazyRunner := runner2.Lazy(&runner) + + var eventsEmitter *event.Emitter + lazyEmitter := event.Lazy(&eventsEmitter) + + // TODO: Make granular environment variables, yet backwards compatible + secretConfig := testkube.SecretConfig{ + Prefix: cfg.SecretCreationPrefix, + List: cfg.EnableSecretsEndpoint, + ListAll: cfg.EnableSecretsEndpoint && cfg.EnableListingAllSecrets, + Create: cfg.EnableSecretsEndpoint && !cfg.DisableSecretCreation, + Modify: cfg.EnableSecretsEndpoint && !cfg.DisableSecretCreation, + Delete: cfg.EnableSecretsEndpoint && !cfg.DisableSecretCreation, + AutoCreate: !cfg.DisableSecretCreation, + } + secretManager := secretmanager.New(clientset, secretConfig) + + metrics := metrics.NewMetrics() + // Start local Control Plane if mode == common.ModeStandalone { - controlPlane := services.CreateControlPlane(ctx, cfg, features, configMapConfig) + controlPlane := services.CreateControlPlane(ctx, cfg, features, configMapConfig, secretManager, metrics, lazyRunner, lazyEmitter) g.Go(func() error { return controlPlane.Run(ctx) }) @@ -93,37 +127,18 @@ func main() { clusterId, _ := configMapConfig.GetUniqueClusterId(ctx) telemetryEnabled, _ := configMapConfig.GetTelemetryEnabled(ctx) - // k8s - kubeClient, err := kubeclient.GetClient() - commons.ExitOnError("Getting kubernetes client", err) - clientset, err := k8sclient.ConnectToK8s() - commons.ExitOnError("Creating k8s clientset", err) - // k8s clients secretClient := secret.NewClientFor(clientset, cfg.TestkubeNamespace) configMapClient := configmap.NewClientFor(clientset, cfg.TestkubeNamespace) webhooksClient := executorsclientv1.NewWebhooksClient(kubeClient, cfg.TestkubeNamespace) testTriggersClient := testtriggersclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) - testWorkflowExecutionsClient := testworkflowsclientv1.NewTestWorkflowExecutionsClient(kubeClient, cfg.TestkubeNamespace) - - // TODO: Make granular environment variables, yet backwards compatible - secretConfig := testkube.SecretConfig{ - Prefix: cfg.SecretCreationPrefix, - List: cfg.EnableSecretsEndpoint, - ListAll: cfg.EnableSecretsEndpoint && cfg.EnableListingAllSecrets, - Create: cfg.EnableSecretsEndpoint && !cfg.DisableSecretCreation, - Modify: cfg.EnableSecretsEndpoint && !cfg.DisableSecretCreation, - Delete: cfg.EnableSecretsEndpoint && !cfg.DisableSecretCreation, - AutoCreate: !cfg.DisableSecretCreation, - } - secretManager := secretmanager.New(clientset, secretConfig) envs := commons.GetEnvironmentVariables() inspector := commons.CreateImageInspector(cfg, configMapClient, secretClient) - var testWorkflowsClient testworkflowsclientv1.Interface - var testWorkflowTemplatesClient testworkflowsclientv1.TestWorkflowTemplatesInterface + var testWorkflowsClient testworkflowclient.TestWorkflowClient + var testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient var grpcClient cloud.TestKubeCloudAPIClient var grpcConn *grpc.ClientConn @@ -146,20 +161,8 @@ func main() { grpcClient = cloud.NewTestKubeCloudAPIClient(grpcConn) - if mode == common.ModeAgent && cfg.WorkflowStorage == "control-plane" { - testWorkflowsClient = cloudtestworkflow.NewCloudTestWorkflowRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey) - testWorkflowTemplatesClient = cloudtestworkflow.NewCloudTestWorkflowTemplateRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey) - } else { - testWorkflowsClient = testworkflowsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace) - testWorkflowTemplatesClient = testworkflowsclientv1.NewTestWorkflowTemplatesClient(kubeClient, cfg.TestkubeNamespace) - } - testWorkflowResultsRepository := cloudtestworkflow.NewCloudRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey) - var opts []cloudtestworkflow.Option - if cfg.StorageSkipVerify { - opts = append(opts, cloudtestworkflow.WithSkipVerify()) - } - testWorkflowOutputRepository := cloudtestworkflow.NewCloudOutputRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey, opts...) + testWorkflowOutputRepository := cloudtestworkflow.NewCloudOutputRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey, cfg.StorageSkipVerify) triggerLeaseBackend := triggers.NewAcquireAlwaysLeaseBackend() artifactStorage := cloudartifacts.NewCloudArtifactsStorage(grpcClient, grpcConn, cfg.TestkubeProAPIKey) @@ -168,13 +171,21 @@ func main() { if cfg.Trace { eventBus.TraceEvents() } - eventsEmitter := event.NewEmitter(eventBus, cfg.TestkubeClusterName) + eventsEmitter = event.NewEmitter(eventBus, cfg.TestkubeClusterName) // Check Pro/Enterprise subscription proContext := commons.ReadProContext(ctx, cfg, grpcClient) subscriptionChecker, err := checktcl.NewSubscriptionChecker(ctx, proContext, grpcClient, grpcConn) commons.ExitOnError("Failed creating subscription checker", err) + if proContext.TestWorkflowStorage && cfg.FeatureTestWorkflowCloudStorage { + testWorkflowsClient = testworkflowclient.NewCloudTestWorkflowClient(grpcConn, cfg.TestkubeProAPIKey) + testWorkflowTemplatesClient = testworkflowtemplateclient.NewCloudTestWorkflowTemplateClient(grpcConn, cfg.TestkubeProAPIKey) + } else { + testWorkflowsClient = testworkflowclient.NewKubernetesTestWorkflowClient(kubeClient, cfg.TestkubeNamespace) + testWorkflowTemplatesClient = testworkflowtemplateclient.NewKubernetesTestWorkflowTemplateClient(kubeClient, cfg.TestkubeNamespace) + } + serviceAccountNames := map[string]string{ cfg.TestkubeNamespace: cfg.JobServiceAccountName, } @@ -185,8 +196,6 @@ func main() { serviceAccountNames = schedulertcl.GetServiceAccountNamesFromConfig(serviceAccountNames, cfg.TestkubeExecutionNamespaces) } - metrics := metrics.NewMetrics() - var deprecatedSystem *services.DeprecatedSystem if !cfg.DisableDeprecatedTests { deprecatedSystem = services.CreateDeprecatedSystem( @@ -214,28 +223,71 @@ func main() { if mode == common.ModeAgent { testWorkflowProcessor = presets.NewPro(inspector) } - executionWorker := services.CreateExecutionWorker(clientset, cfg, clusterId, serviceAccountNames, testWorkflowProcessor) + executionWorker := services.CreateExecutionWorker(clientset, cfg, clusterId, serviceAccountNames, testWorkflowProcessor, map[string]string{ + testworkflowconfig.FeatureFlagNewExecutions: fmt.Sprintf("%v", cfg.FeatureNewExecutions), + testworkflowconfig.FeatureFlagTestWorkflowCloudStorage: fmt.Sprintf("%v", cfg.FeatureTestWorkflowCloudStorage), + }) + + // Build the runner + runner = runner2.New( + executionWorker, + testWorkflowOutputRepository, + testWorkflowResultsRepository, + configMapConfig, + eventsEmitter, + metrics, + cfg.TestkubeDashboardURI, + cfg.StorageSkipVerify, + ) + + // Recover control + func() { + var list []testkube.TestWorkflowExecution + for { + // TODO: it should get running only in the context of current runner (worker.List?) + list, err = testWorkflowResultsRepository.GetRunning(ctx) + if err != nil { + log.DefaultLogger.Errorw("failed to fetch running executions to recover", "error", err) + <-time.After(time.Second) + continue + } + break + } + + for i := range list { + if (list[i].RunnerId == "" && len(list[i].Signature) == 0) || (list[i].RunnerId != "" && list[i].RunnerId != proContext.EnvID) { + continue + } + + // TODO: Should it throw error at all? + // TODO: Pass hints (namespace, signature, scheduledAt) + go func(e *testkube.TestWorkflowExecution) { + err := runner.Monitor(ctx, e.Id) + if err != nil { + log.DefaultLogger.Errorw("failed to monitor execution", "id", e.Id, "error", err) + } + }(&list[i]) + } + }() testWorkflowExecutor := testworkflowexecutor.New( + grpcClient, + cfg.TestkubeProAPIKey, + cfg.CDEventsTarget, eventsEmitter, - executionWorker, - clientset, + runner, testWorkflowResultsRepository, testWorkflowOutputRepository, - configMapConfig, testWorkflowTemplatesClient, - testWorkflowExecutionsClient, testWorkflowsClient, metrics, secretManager, cfg.GlobalWorkflowTemplateName, cfg.TestkubeDashboardURI, - &proContext, + proContext.OrgID, + proContext.EnvID, + cfg.FeatureNewExecutions, ) - g.Go(func() error { - testWorkflowExecutor.Recover(ctx) - return nil - }) var deprecatedClients commons.DeprecatedClients var deprecatedRepositories commons.DeprecatedRepositories @@ -262,6 +314,17 @@ func main() { if cfg.EnableK8sEvents { eventsEmitter.Loader.Register(k8sevent.NewK8sEventLoader(clientset, cfg.TestkubeNamespace, testkube.AllEventTypes)) } + + // Update TestWorkflowExecution Kubernetes resource objects on status change + eventsEmitter.Loader.Register(testworkflowexecutions.NewLoader(ctx, cfg.TestkubeNamespace, kubeClient)) + + // Update the Prometheus metrics regarding the Test Workflow Execution + eventsEmitter.Loader.Register(testworkflowexecutionmetrics.NewLoader(ctx, metrics, cfg.TestkubeDashboardURI)) + + // Send the telemetry data regarding the Test Workflow Execution + // TODO: Disable it if Control Plane does that + eventsEmitter.Loader.Register(testworkflowexecutiontelemetry.NewLoader(ctx, configMapConfig)) + eventsEmitter.Listen(ctx) g.Go(func() error { eventsEmitter.Reconcile(ctx) @@ -286,11 +349,12 @@ func main() { webhooksClient, testTriggersClient, testWorkflowsClient, + testworkflowsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace), testWorkflowTemplatesClient, + testworkflowsclientv1.NewTestWorkflowTemplatesClient(kubeClient, cfg.TestkubeNamespace), configMapConfig, secretManager, secretConfig, - testWorkflowExecutor, executionWorker, eventsEmitter, websocketLoader, @@ -301,6 +365,7 @@ func main() { cfg.TestkubeHelmchartVersion, serviceAccountNames, cfg.TestkubeDockerImageVersion, + testWorkflowExecutor, ) api.Init(httpServer) @@ -323,6 +388,16 @@ func main() { features, &proContext, cfg.TestkubeDockerImageVersion, + eventsEmitter, + func(environmentId, executionId string) error { + execution, err := testWorkflowResultsRepository.Get(context.Background(), executionId) + if err != nil { + return err + } + return testWorkflowExecutor.Start(environmentId, &execution, nil) + }, + cfg.FeatureNewExecutions, + cfg.FeatureTestWorkflowCloudStorage, ) commons.ExitOnError("Starting agent", err) g.Go(func() error { diff --git a/cmd/api-server/services/controlplane.go b/cmd/api-server/services/controlplane.go index 269fec688e7..45ebcb5ab04 100644 --- a/cmd/api-server/services/controlplane.go +++ b/cmd/api-server/services/controlplane.go @@ -8,7 +8,9 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + kubeclient "github.com/kubeshop/testkube-operator/pkg/client" "github.com/kubeshop/testkube/cmd/api-server/commons" + "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/internal/config" cloudartifacts "github.com/kubeshop/testkube/pkg/cloud/data/artifact" cloudconfig "github.com/kubeshop/testkube/pkg/cloud/data/config" @@ -16,18 +18,24 @@ import ( cloudtestresult "github.com/kubeshop/testkube/pkg/cloud/data/testresult" cloudtestworkflow "github.com/kubeshop/testkube/pkg/cloud/data/testworkflow" "github.com/kubeshop/testkube/pkg/controlplane" + "github.com/kubeshop/testkube/pkg/event" "github.com/kubeshop/testkube/pkg/featureflags" "github.com/kubeshop/testkube/pkg/k8sclient" "github.com/kubeshop/testkube/pkg/log" logsclient "github.com/kubeshop/testkube/pkg/logs/client" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" configRepo "github.com/kubeshop/testkube/pkg/repository/config" "github.com/kubeshop/testkube/pkg/repository/result" "github.com/kubeshop/testkube/pkg/repository/sequence" "github.com/kubeshop/testkube/pkg/repository/testresult" "github.com/kubeshop/testkube/pkg/repository/testworkflow" + runner2 "github.com/kubeshop/testkube/pkg/runner" "github.com/kubeshop/testkube/pkg/secret" + "github.com/kubeshop/testkube/pkg/secretmanager" "github.com/kubeshop/testkube/pkg/storage/minio" "github.com/kubeshop/testkube/pkg/tcl/checktcl" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" ) func mapTestWorkflowFilters(s []*testworkflow.FilterImpl) []testworkflow.Filter { @@ -54,16 +62,21 @@ func mapTestSuiteFilters(s []*testresult.FilterImpl) []testresult.Filter { return v } -func CreateControlPlane(ctx context.Context, cfg *config.Config, features featureflags.FeatureFlags, configMapClient configRepo.Repository) *controlplane.Server { +func CreateControlPlane(ctx context.Context, cfg *config.Config, features featureflags.FeatureFlags, configMapClient configRepo.Repository, secretManager secretmanager.SecretManager, metrics metrics.Metrics, runner runner2.Runner, emitter event.Interface) *controlplane.Server { // Connect to the cluster clientset, err := k8sclient.ConnectToK8s() commons.ExitOnError("Creating k8s clientset", err) + kubeClient, err := kubeclient.GetClient() + commons.ExitOnError("Getting kubernetes client", err) // Connect to storages secretClient := secret.NewClientFor(clientset, cfg.TestkubeNamespace) db := commons.MustGetMongoDatabase(ctx, cfg, secretClient, !cfg.DisableMongoMigrations) storageClient := commons.MustGetMinioClient(cfg) + testWorkflowsClient := testworkflowclient.NewKubernetesTestWorkflowClient(kubeClient, cfg.TestkubeNamespace) + testWorkflowTemplatesClient := testworkflowtemplateclient.NewKubernetesTestWorkflowTemplateClient(kubeClient, cfg.TestkubeNamespace) + var logGrpcClient logsclient.StreamGetter if !cfg.DisableDeprecatedTests && features.LogsV2 { logGrpcClient = commons.MustGetLogsV2Client(cfg) @@ -383,9 +396,30 @@ func CreateControlPlane(ctx context.Context, cfg *config.Config, features featur } } + executor := testworkflowexecutor.New( + nil, + "", + cfg.CDEventsTarget, + emitter, + runner, + testWorkflowResultsRepository, + testWorkflowOutputRepository, + testWorkflowTemplatesClient, + testWorkflowsClient, + metrics, + secretManager, + cfg.GlobalWorkflowTemplateName, + cfg.TestkubeDashboardURI, + "", + "", + cfg.FeatureNewExecutions, + ) + return controlplane.New(controlplane.Config{ - Port: cfg.GRPCServerPort, - Logger: log.DefaultLogger, - Verbose: false, - }, commands...) + Port: cfg.GRPCServerPort, + Logger: log.DefaultLogger, + Verbose: false, + FeatureNewExecutions: cfg.FeatureNewExecutions, + FeatureTestWorkflowsCloudStorage: cfg.FeatureTestWorkflowCloudStorage, + }, executor, testWorkflowsClient, testWorkflowTemplatesClient, commands...) } diff --git a/cmd/api-server/services/executionworker.go b/cmd/api-server/services/executionworker.go index 4b5db6af44d..4f6a0c3c3a0 100644 --- a/cmd/api-server/services/executionworker.go +++ b/cmd/api-server/services/executionworker.go @@ -19,6 +19,7 @@ func CreateExecutionWorker( clusterId string, serviceAccountNames map[string]string, processor testworkflowprocessor.Processor, + featureFlags map[string]string, ) executionworkertypes.Worker { namespacesConfig := map[string]kubernetesworker.NamespaceConfig{} for n, s := range serviceAccountNames { @@ -45,5 +46,6 @@ func CreateExecutionWorker( // TODO: Prepare ControlPlane interface for OSS, so we may unify the communication LocalApiUrl: fmt.Sprintf("http://%s:%d", cfg.APIServerFullname, cfg.APIServerPort), }, + FeatureFlags: featureFlags, }) } diff --git a/cmd/kubectl-testkube/commands/testworkflows/executions.go b/cmd/kubectl-testkube/commands/testworkflows/executions.go index bdd328da32c..fd241219205 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/executions.go +++ b/cmd/kubectl-testkube/commands/testworkflows/executions.go @@ -79,12 +79,7 @@ func NewGetTestWorkflowExecutionsCmd() *cobra.Command { sigs := flattenSignatures(execution.Signature) - var results map[string]testkube.TestWorkflowStepResult - if execution.Result != nil { - results = execution.Result.Steps - } - - printRawLogLines(logs, sigs, results) + printRawLogLines(logs, sigs, execution) if !logsOnly { render.PrintTestWorkflowExecutionURIs(&execution) } diff --git a/cmd/kubectl-testkube/commands/testworkflows/run.go b/cmd/kubectl-testkube/commands/testworkflows/run.go index 65725fea50a..a4b07c60cce 100644 --- a/cmd/kubectl-testkube/commands/testworkflows/run.go +++ b/cmd/kubectl-testkube/commands/testworkflows/run.go @@ -202,8 +202,42 @@ func NewRunTestWorkflowCmd() *cobra.Command { return cmd } +func getIterationDelay(iteration int) time.Duration { + if iteration < 5 { + return 500 * time.Millisecond + } else if iteration < 100 { + return 1 * time.Second + } + return 5 * time.Second +} + func uiWatch(execution testkube.TestWorkflowExecution, serviceName *string, serviceIndex int, parallelStepName *string, parallelStepIndex int, client apiclientv1.Client) int { + // Wait until the execution will be assigned to some runner + iteration := 0 + for !execution.Assigned() { + var err error + iteration++ + time.Sleep(getIterationDelay(iteration)) + execution, err = client.GetTestWorkflowExecution(execution.Id) + if err != nil { + ui.Failf("get execution failed: %v", err) + } + } + + // Print final logs in case execution is already finished + if execution.Result.IsFinished() { + ui.Info("Getting logs for test workflow execution", execution.Id) + + logs, err := client.GetTestWorkflowExecutionLogs(execution.Id) + ui.ExitOnError("getting logs from executor", err) + + sigs := flattenSignatures(execution.Signature) + + printRawLogLines(logs, sigs, execution) + return 0 + } + var result *testkube.TestWorkflowResult var err error @@ -304,7 +338,7 @@ func printSingleResultDifference(r1 testkube.TestWorkflowStepResult, r2 testkube } took := r2.FinishedAt.Sub(r2.QueuedAt).Round(time.Millisecond) - printStatus(signature, r2Status, took, index, steps, name) + printStatus(signature, r2Status, took, index, steps, name, r2.ErrorMessage) return true } @@ -330,8 +364,7 @@ func getTimestampLength(line string) int { return 0 } -func printTestWorkflowLogs(signature []testkube.TestWorkflowSignature, - notifications chan testkube.TestWorkflowExecutionNotification) (result *testkube.TestWorkflowResult) { +func printTestWorkflowLogs(signature []testkube.TestWorkflowSignature, notifications chan testkube.TestWorkflowExecutionNotification) (result *testkube.TestWorkflowResult) { steps := flattenSignatures(signature) var isLineBeginning = true @@ -347,7 +380,7 @@ func printTestWorkflowLogs(signature []testkube.TestWorkflowSignature, continue } - printStructuredLogLines(l.Log, &isLineBeginning) + isLineBeginning = printStructuredLogLines(l.Log, isLineBeginning) } ui.NL() @@ -456,7 +489,10 @@ func printStatusHeader(i, n int, name string) { } func printStatus(s testkube.TestWorkflowSignature, rStatus testkube.TestWorkflowStepStatus, took time.Duration, - i, n int, name string) { + i, n int, name string, errorMessage string) { + if len(errorMessage) > 0 { + fmt.Printf("\n%s", ui.Red(errorMessage)) + } switch rStatus { case testkube.RUNNING_TestWorkflowStepStatus: printStatusHeader(i, n, name) @@ -487,17 +523,49 @@ func trimTimestamp(line string) string { return line } -func printStructuredLogLines(logs string, _ *bool) { +func printStructuredLogLines(logs string, isLineBeginning bool) bool { + if len(logs) == 0 { + return isLineBeginning + } + willBeLineBeginning := logs[len(logs)-1] == '\n' scanner := bufio.NewScanner(strings.NewReader(logs)) + next := false for scanner.Scan() { - fmt.Println(trimTimestamp(scanner.Text())) + if next { + fmt.Print("\n") + } + fmt.Print(trimTimestamp(scanner.Text())) + next = true + } + if isLineBeginning { + fmt.Print("\n") } + return willBeLineBeginning } -func printRawLogLines(logs []byte, steps []testkube.TestWorkflowSignature, results map[string]testkube.TestWorkflowStepResult) { +func printRawLogLines(logs []byte, steps []testkube.TestWorkflowSignature, execution testkube.TestWorkflowExecution) { currentRef := "" i := -1 + + // Process the results + results := make(map[string]testkube.TestWorkflowStepResult) + if execution.Result != nil { + if execution.Result.Steps != nil { + results = execution.Result.Steps + } + if execution.Result.Initialization != nil { + results[""] = *execution.Result.Initialization + } + } + + // Print error message if that's the only available thing + if len(results) < 2 && len(logs) == 0 && len(results[""].ErrorMessage) > 0 { + fmt.Printf("\n%s\n", ui.Red(results[""].ErrorMessage)) + return + } + printStatusHeader(-1, len(steps), "Initializing") + // Strip timestamp + space for all new lines in the log for len(logs) > 0 { newLineIndex := bytes.Index(logs, NL) @@ -525,7 +593,7 @@ func printRawLogLines(logs []byte, steps []testkube.TestWorkflowSignature, resul if ps, ok := results[currentRef]; ok && ps.Status != nil { took := ps.FinishedAt.Sub(ps.QueuedAt).Round(time.Millisecond) if i != -1 { - printStatus(steps[i], *ps.Status, took, i, len(steps), steps[i].Label()) + printStatus(steps[i], *ps.Status, took, i, len(steps), steps[i].Label(), ps.ErrorMessage) } } @@ -539,9 +607,9 @@ func printRawLogLines(logs []byte, steps []testkube.TestWorkflowSignature, resul if i != -1 && i < len(steps) { for _, step := range steps[i:] { - if ps, ok := results[currentRef]; ok && ps.Status != nil { + if ps, ok := results[step.Ref]; ok && ps.Status != nil { took := ps.FinishedAt.Sub(ps.QueuedAt).Round(time.Millisecond) - printStatus(step, *ps.Status, took, i, len(steps), steps[i].Label()) + printStatus(step, *ps.Status, took, i, len(steps), steps[i].Label(), ps.ErrorMessage) } i++ diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go b/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go index 41612dcfac8..e6bec9a1603 100644 --- a/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go @@ -52,6 +52,8 @@ func (r *Agent) Create(ctx context.Context, env *client.Environment) error { {Name: "TESTKUBE_IMAGE_DATA_PERSISTENT_CACHE_KEY", Value: "testkube-image-cache"}, {Name: "TESTKUBE_TW_TOOLKIT_IMAGE", Value: r.toolkitImage}, {Name: "TESTKUBE_TW_INIT_IMAGE", Value: r.initProcessImage}, + {Name: "FEATURE_NEW_EXECUTIONS", Value: "true"}, + {Name: "FEATURE_TESTWORKFLOW_CLOUD_STORAGE", Value: "true"}, } if env != nil { tlsInsecure := "false" diff --git a/cmd/tcl/testworkflow-toolkit/commands/execute.go b/cmd/tcl/testworkflow-toolkit/commands/execute.go index d85e069fb09..e2ca14872eb 100644 --- a/cmd/tcl/testworkflow-toolkit/commands/execute.go +++ b/cmd/tcl/testworkflow-toolkit/commands/execute.go @@ -9,19 +9,19 @@ package commands import ( + "context" "encoding/json" "fmt" "os" - "strings" "sync" "time" "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" + "github.com/kubeshop/testkube/cmd/tcl/testworkflow-toolkit/execute" "github.com/kubeshop/testkube/cmd/tcl/testworkflow-toolkit/spawn" "github.com/kubeshop/testkube/cmd/testworkflow-init/data" "github.com/kubeshop/testkube/cmd/testworkflow-init/instructions" @@ -31,6 +31,7 @@ import ( "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud/data/testworkflow" "github.com/kubeshop/testkube/pkg/expressions" "github.com/kubeshop/testkube/pkg/mapper/testworkflows" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/constants" @@ -38,11 +39,13 @@ import ( ) const ( - CreateExecutionRetryOnFailureMaxAttempts = 5 - CreateExecutionRetryOnFailureBaseDelay = 100 * time.Millisecond + CreateExecutionRetryOnFailureMaxAttempts = 10 + CreateExecutionRetryOnFailureBaseDelay = 500 * time.Millisecond - GetExecutionRetryOnFailureMaxAttempts = 10 - GetExecutionRetryOnFailureDelay = 300 * time.Millisecond + GetExecutionRetryOnFailureMaxAttempts = 30 + GetExecutionRetryOnFailureDelay = 500 * time.Millisecond + + ExecutionResultPollingTime = 200 * time.Millisecond ) type testExecutionDetails struct { @@ -160,34 +163,18 @@ func buildTestExecution(test testworkflowsv1.StepExecuteTest, async bool) (func( func buildWorkflowExecution(workflow testworkflowsv1.StepExecuteWorkflow, async bool) (func() error, error) { return func() (err error) { - c := env.Testkube() + _, c, _ := env.Cloud(context.Background()) 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 + // Schedule execution + var execs []testkube.TestWorkflowExecution for i := 0; i < CreateExecutionRetryOnFailureMaxAttempts; i++ { - exec, err = c.ExecuteTestWorkflow(workflow.Name, testkube.TestWorkflowExecutionRequest{ + execs, err = execute.ExecuteTestWorkflow(workflow.Name, testkube.TestWorkflowExecutionRequest{ Name: workflow.ExecutionName, 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: config.WorkflowName(), - ExecutionId: config.ExecutionId(), - ExecutionPath: strings.Join(parentIds, "/"), - Type_: common.Ptr(testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType), - }, - }, - ParentExecutionIds: parentIds, }) if err == nil { break @@ -202,69 +189,87 @@ func buildWorkflowExecution(workflow testworkflowsv1.StepExecuteWorkflow, async ui.Errf("failed to execute test workflow: %s: %s", workflow.Name, err.Error()) return } - execName := exec.Name - instructions.PrintOutput(config.Ref(), "testworkflow-start", &testWorkflowExecutionDetails{ - Id: exec.Id, - Name: exec.Name, - TestWorkflowName: exec.Workflow.Name, - Description: workflow.Description, - }) - description := "" - if workflow.Description != "" { - description = fmt.Sprintf(": %s", workflow.Description) + // Print information about scheduled execution + for _, exec := range execs { + instructions.PrintOutput(config.Ref(), "testworkflow-start", &testWorkflowExecutionDetails{ + Id: exec.Id, + Name: exec.Name, + TestWorkflowName: exec.Workflow.Name, + Description: workflow.Description, + }) + + description := "" + if workflow.Description != "" { + description = fmt.Sprintf(": %s", workflow.Description) + } + fmt.Printf("%s%s • scheduled %s\n", ui.LightCyan(exec.Name), description, ui.DarkGray("("+exec.Id+")")) } - fmt.Printf("%s%s • scheduled %s\n", ui.LightCyan(execName), description, ui.DarkGray("("+exec.Id+")")) if async { return } - prevStatus := testkube.QUEUED_TestWorkflowStatus - loop: - for { - time.Sleep(100 * time.Millisecond) - for i := 0; i < GetExecutionRetryOnFailureMaxAttempts; i++ { - var nextExec testkube.TestWorkflowExecution - nextExec, err = c.GetTestWorkflowExecution(exec.Id) - if err == nil { - exec = nextExec - break - } - if i+1 < GetExecutionRetryOnFailureMaxAttempts { - ui.Errf("error while getting execution result: retrying in %s (attempt %d/%d): %s: %s", GetExecutionRetryOnFailureDelay.String(), i+2, GetExecutionRetryOnFailureMaxAttempts, ui.LightCyan(execName), err.Error()) - time.Sleep(GetExecutionRetryOnFailureDelay) + // Monitor + var wg sync.WaitGroup + wg.Add(len(execs)) + for i := range execs { + go func(exec testkube.TestWorkflowExecution) { + defer wg.Done() + prevStatus := testkube.QUEUED_TestWorkflowStatus + loop: + for { + // TODO: Consider real-time Notifications without logs instead + time.Sleep(ExecutionResultPollingTime) + for i := 0; i < GetExecutionRetryOnFailureMaxAttempts; i++ { + var resp []byte + resp, err = c.Execute(context.Background(), testworkflow.CmdTestWorkflowExecutionGet, testworkflow.ExecutionGetRequest{ID: exec.Id}) + if err == nil { + var v testworkflow.ExecutionGetResponse + err = json.Unmarshal(resp, &v) + if err == nil { + exec = v.WorkflowExecution + } + break + } + if i+1 < GetExecutionRetryOnFailureMaxAttempts { + ui.Errf("error while getting execution result: retrying in %s (attempt %d/%d): %s: %s", GetExecutionRetryOnFailureDelay.String(), i+2, GetExecutionRetryOnFailureMaxAttempts, ui.LightCyan(exec.Name), err.Error()) + time.Sleep(GetExecutionRetryOnFailureDelay) + } + } + if err != nil { + ui.Errf("error while getting execution result: %s: %s", ui.LightCyan(exec.Name), err.Error()) + return + } + if exec.Result != nil && exec.Result.Status != nil { + status := *exec.Result.Status + switch status { + case testkube.QUEUED_TestWorkflowStatus, testkube.RUNNING_TestWorkflowStatus: + break + default: + break loop + } + if prevStatus != status { + instructions.PrintOutput(config.Ref(), "testworkflow-status", &executionResult{Id: exec.Id, Status: string(status)}) + } + prevStatus = status + } } - } - if err != nil { - ui.Errf("error while getting execution result: %s: %s", ui.LightCyan(execName), err.Error()) - return - } - if exec.Result != nil && exec.Result.Status != nil { + status := *exec.Result.Status - switch status { - case testkube.QUEUED_TestWorkflowStatus, testkube.RUNNING_TestWorkflowStatus: - break - default: - break loop - } - if prevStatus != status { - instructions.PrintOutput(config.Ref(), "testworkflow-status", &executionResult{Id: exec.Id, Status: string(status)}) - } - prevStatus = status - } - } + color := ui.Green - status := *exec.Result.Status - color := ui.Green + if status != testkube.PASSED_TestWorkflowStatus { + err = errors.New("test workflow failed") + color = ui.Red + } - if status != testkube.PASSED_TestWorkflowStatus { - err = errors.New("test workflow failed") - color = ui.Red + instructions.PrintOutput(config.Ref(), "testworkflow-end", &executionResult{Id: exec.Id, Status: string(status)}) + fmt.Printf("%s • %s\n", color(exec.Name), string(status)) + }(execs[i]) } + wg.Wait() - instructions.PrintOutput(config.Ref(), "testworkflow-end", &executionResult{Id: exec.Id, Status: string(status)}) - fmt.Printf("%s • %s\n", color(execName), string(status)) return }, nil } @@ -370,7 +375,6 @@ func NewExecuteCmd() *cobra.Command { } } - c := env.Testkube() for _, s := range workflows { var w testworkflowsv1.StepExecuteWorkflow err := json.Unmarshal([]byte(s), &w) @@ -388,13 +392,10 @@ func NewExecuteCmd() *cobra.Command { } if w.Selector != nil { - selector, err := metav1.LabelSelectorAsSelector(w.Selector) - if err != nil { - ui.Fail(errors.Wrap(err, "error creating selector from test workflow selector")) + if len(w.Selector.MatchExpressions) > 0 { + ui.Fail(errors.New("error creating selector from test workflow selector: matchExpressions is not supported")) } - - stringifiedSelector := selector.String() - testWorkflowsList, err := c.ListTestWorkflows(stringifiedSelector) + testWorkflowsList, err := execute.ListTestWorkflows(w.Selector.MatchLabels) if err != nil { ui.Fail(errors.Wrap(err, "error listing test workflows using selector")) } diff --git a/cmd/tcl/testworkflow-toolkit/execute/execute.go b/cmd/tcl/testworkflow-toolkit/execute/execute.go new file mode 100644 index 00000000000..3a4bb9f8d4e --- /dev/null +++ b/cmd/tcl/testworkflow-toolkit/execute/execute.go @@ -0,0 +1,197 @@ +// 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 execute + +import ( + "context" + "encoding/json" + "fmt" + "io" + "math" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + "google.golang.org/grpc" + "google.golang.org/grpc/encoding/gzip" + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/kubeshop/testkube/cmd/testworkflow-toolkit/env" + "github.com/kubeshop/testkube/cmd/testworkflow-toolkit/env/config" + "github.com/kubeshop/testkube/internal/common" + agentclient "github.com/kubeshop/testkube/pkg/agent/client" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/capabilities" + "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" +) + +var ( + isGrpcMu sync.Mutex + isGrpcExecuteCache *bool + isGrpcListCache *bool +) + +func loadCapabilities() { + isGrpcMu.Lock() + defer isGrpcMu.Unlock() + + // Block if the instance doesn't support that + cfg := config.Config() + if isGrpcExecuteCache == nil && cfg.Worker.FeatureFlags[testworkflowconfig.FeatureFlagNewExecutions] != "true" { + isGrpcExecuteCache = common.Ptr(false) + } + if isGrpcListCache == nil && cfg.Worker.FeatureFlags[testworkflowconfig.FeatureFlagTestWorkflowCloudStorage] != "true" { + isGrpcListCache = common.Ptr(false) + } + + // Do not check Cloud support if its already predefined + if isGrpcExecuteCache != nil && isGrpcListCache != nil { + return + } + + // Check support in the cloud + ctx := agentclient.AddAPIKeyMeta(context.Background(), cfg.Worker.Connection.ApiKey) + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + _, _, client := env.Cloud(ctx) + proContext, _ := client.GetProContext(ctx, &emptypb.Empty{}) + if proContext != nil { + if isGrpcExecuteCache == nil { + isGrpcExecuteCache = common.Ptr(capabilities.Enabled(proContext.Capabilities, capabilities.CapabilityNewExecutions)) + } + if isGrpcListCache == nil { + isGrpcListCache = common.Ptr(capabilities.Enabled(proContext.Capabilities, capabilities.CapabilityTestWorkflowStorage)) + } + } else { + isGrpcExecuteCache = common.Ptr(false) + isGrpcListCache = common.Ptr(false) + } +} + +func isGrpcExecute() bool { + loadCapabilities() + return *isGrpcExecuteCache +} + +func isGrpcList() bool { + loadCapabilities() + return *isGrpcListCache +} + +func ExecuteTestWorkflow(workflowName string, request testkube.TestWorkflowExecutionRequest) ([]testkube.TestWorkflowExecution, error) { + if isGrpcExecute() { + return executeTestWorkflowGrpc(workflowName, request) + } + return executeTestWorkflowApi(workflowName, request) +} + +func executeTestWorkflowApi(workflowName string, request testkube.TestWorkflowExecutionRequest) ([]testkube.TestWorkflowExecution, error) { + cfg := config.Config() + client := env.Testkube() + + parentIds := make([]string, 0) + if cfg.Execution.ParentIds != "" { + parentIds = strings.Split(cfg.Execution.ParentIds, "/") + } + parentIds = append(parentIds, cfg.Execution.Id) + + request.ParentExecutionIds = parentIds + request.RunningContext = &testkube.TestWorkflowRunningContext{ + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Type_: common.Ptr(testkube.API_TestWorkflowRunningContextInterfaceType), + }, + Actor: &testkube.TestWorkflowRunningContextActor{ + Name: cfg.Workflow.Name, + ExecutionId: cfg.Execution.Id, + ExecutionPath: strings.Join(parentIds, "/"), + Type_: common.Ptr(testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType), + }, + } + execution, err := client.ExecuteTestWorkflow(workflowName, request) + if err != nil { + return nil, err + } + return []testkube.TestWorkflowExecution{execution}, nil +} + +func executeTestWorkflowGrpc(workflowName string, request testkube.TestWorkflowExecutionRequest) ([]testkube.TestWorkflowExecution, error) { + cfg := config.Config() + ctx := agentclient.AddAPIKeyMeta(context.Background(), cfg.Worker.Connection.ApiKey) + _, _, client := env.Cloud(ctx) + + parentIds := make([]string, 0) + if cfg.Execution.ParentIds != "" { + parentIds = strings.Split(cfg.Execution.ParentIds, "/") + } + parentIds = append(parentIds, cfg.Execution.Id) + + opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} + resp, err := client.ScheduleExecution(ctx, &cloud.ScheduleRequest{ + EnvironmentId: cfg.Execution.EnvironmentId, + Executions: []*cloud.ScheduleExecution{{Selector: &cloud.ScheduleResourceSelector{Name: workflowName}, Config: request.Config}}, + DisableWebhooks: cfg.Execution.DisableWebhooks, + Tags: request.Tags, + RunningContext: &cloud.RunningContext{ + Name: cfg.Execution.Id, + Type: cloud.RunningContextType_EXECUTION, + }, + ParentExecutionIds: parentIds, + }, opts...) + + if err != nil { + return nil, err + } + + result := make([]testkube.TestWorkflowExecution, 0) + var item *cloud.ScheduleResponse + for { + item, err = resp.Recv() + if err != nil { + if !errors.Is(err, io.EOF) { + fmt.Printf("warn: %s\n", err) + } + break + } + var execution testkube.TestWorkflowExecution + err = json.Unmarshal(item.Execution, &execution) + if err != nil { + fmt.Printf("warn: %s\n", err) + break + } + result = append(result, execution) + } + + return result, nil +} + +func ListTestWorkflows(labels map[string]string) ([]testkube.TestWorkflow, error) { + if isGrpcList() { + return listTestWorkflowsGrpc(labels) + } + return listTestWorkflowsApi(labels) +} + +func listTestWorkflowsApi(labels map[string]string) ([]testkube.TestWorkflow, error) { + client := env.Testkube() + selectors := make([]string, 0, len(labels)) + for k, v := range labels { + selectors = append(selectors, fmt.Sprintf("%s=%s", k, v)) + } + return client.ListTestWorkflows(strings.Join(selectors, ",")) +} + +func listTestWorkflowsGrpc(labels map[string]string) ([]testkube.TestWorkflow, error) { + cfg := config.Config() + conn, _, _ := env.Cloud(context.Background()) + client := testworkflowclient.NewCloudTestWorkflowClient(conn, cfg.Worker.Connection.ApiKey) + return client.List(context.Background(), cfg.Execution.EnvironmentId, testworkflowclient.ListOptions{Labels: labels}) +} diff --git a/cmd/tcl/testworkflow-toolkit/spawn/utils.go b/cmd/tcl/testworkflow-toolkit/spawn/utils.go index f4be949ee6e..18a968d999d 100644 --- a/cmd/tcl/testworkflow-toolkit/spawn/utils.go +++ b/cmd/tcl/testworkflow-toolkit/spawn/utils.go @@ -72,7 +72,8 @@ func ExecutionWorker() executionworkertypes.Worker { CacheKey: cfg.Worker.ImageInspectorPersistenceCacheKey, CacheTTL: cfg.Worker.ImageInspectorPersistenceCacheTTL, }, - Connection: cfg.Worker.Connection, + Connection: cfg.Worker.Connection, + FeatureFlags: cfg.Worker.FeatureFlags, }) } return executionWorker diff --git a/cmd/testworkflow-toolkit/artifacts/internalartifactstorage.go b/cmd/testworkflow-toolkit/artifacts/internalartifactstorage.go index 1e5a8a753c8..123af374c0f 100644 --- a/cmd/testworkflow-toolkit/artifacts/internalartifactstorage.go +++ b/cmd/testworkflow-toolkit/artifacts/internalartifactstorage.go @@ -33,7 +33,7 @@ type internalArtifactStorage struct { func newArtifactUploader() Uploader { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - client, _ := env.Cloud(ctx) + _, client, _ := env.Cloud(ctx) return NewCloudUploader(client, WithParallelismCloud(30), CloudDetectMimetype) } diff --git a/cmd/testworkflow-toolkit/commands/artifacts.go b/cmd/testworkflow-toolkit/commands/artifacts.go index 8ecd33c4c45..80e558747ef 100644 --- a/cmd/testworkflow-toolkit/commands/artifacts.go +++ b/cmd/testworkflow-toolkit/commands/artifacts.go @@ -85,7 +85,7 @@ func NewArtifactsCmd() *cobra.Command { ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second) defer cancel() ctx = agentclient.AddAPIKeyMeta(ctx, config.Config().Worker.Connection.ApiKey) - executor, client := env.Cloud(ctx) + _, executor, client := env.Cloud(ctx) proContext, err := client.GetProContext(ctx, &emptypb.Empty{}) var supported []*cloud.Capability if err != nil && !strings.Contains(err.Error(), "not supported") { diff --git a/cmd/testworkflow-toolkit/env/client.go b/cmd/testworkflow-toolkit/env/client.go index debdb35b296..a12c181f676 100644 --- a/cmd/testworkflow-toolkit/env/client.go +++ b/cmd/testworkflow-toolkit/env/client.go @@ -8,6 +8,7 @@ import ( "strconv" "sync" + "google.golang.org/grpc" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" @@ -91,22 +92,24 @@ var ( cloudMu sync.Mutex cloudExecutor cloudexecutor.Executor cloudClient cloud.TestKubeCloudAPIClient + cloudConn *grpc.ClientConn ) -func Cloud(ctx context.Context) (cloudexecutor.Executor, cloud.TestKubeCloudAPIClient) { +func Cloud(ctx context.Context) (*grpc.ClientConn, cloudexecutor.Executor, cloud.TestKubeCloudAPIClient) { cloudMu.Lock() defer cloudMu.Unlock() + var err error if cloudExecutor == nil { cfg := config2.Config().Worker.Connection logger := log.NewSilent() - grpcConn, err := agentclient.NewGRPCConnection(ctx, cfg.TlsInsecure, cfg.SkipVerify, cfg.Url, "", "", "", logger) + cloudConn, err = agentclient.NewGRPCConnection(ctx, cfg.TlsInsecure, cfg.SkipVerify, cfg.Url, "", "", "", logger) if err != nil { ui.Fail(fmt.Errorf("failed to connect with Cloud: %w", err)) } - cloudClient = cloud.NewTestKubeCloudAPIClient(grpcConn) - cloudExecutor = cloudexecutor.NewCloudGRPCExecutor(cloudClient, grpcConn, cfg.ApiKey) + cloudClient = cloud.NewTestKubeCloudAPIClient(cloudConn) + cloudExecutor = cloudexecutor.NewCloudGRPCExecutor(cloudClient, cloudConn, cfg.ApiKey) } - return cloudExecutor, cloudClient + return cloudConn, cloudExecutor, cloudClient } diff --git a/internal/app/api/v1/labels_tags.go b/internal/app/api/v1/labels_tags.go index 3007bc2d4e8..ca7de349872 100644 --- a/internal/app/api/v1/labels_tags.go +++ b/internal/app/api/v1/labels_tags.go @@ -1,6 +1,7 @@ package v1 import ( + "context" "fmt" "net/http" @@ -11,10 +12,37 @@ type LabelSource interface { ListLabels() (map[string][]string, error) } +type extendedLabelSource interface { + ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) +} + +type simpleLabelSource struct { + source extendedLabelSource + environmentId string +} + +func (s simpleLabelSource) ListLabels() (map[string][]string, error) { + return s.source.ListLabels(context.Background(), s.environmentId) +} + +func getClientLabelSource(source extendedLabelSource, environmentId string) LabelSource { + return &simpleLabelSource{source: source, environmentId: environmentId} +} + +func (s *TestkubeAPI) getEnvironmentId() string { + if s.proContext != nil { + return s.proContext.EnvID + } + return "" +} + func (s *TestkubeAPI) ListLabelsHandler() fiber.Handler { return func(c *fiber.Ctx) error { labels := make(map[string][]string) - sources := []LabelSource{s.TestWorkflowsClient, s.TestWorkflowTemplatesClient} + sources := []LabelSource{ + getClientLabelSource(s.TestWorkflowsClient, s.getEnvironmentId()), + getClientLabelSource(s.TestWorkflowTemplatesClient, s.getEnvironmentId()), + } if s.DeprecatedClients != nil { sources = append(sources, s.DeprecatedClients.Tests(), s.DeprecatedClients.TestSuites()) } diff --git a/internal/app/api/v1/secret.go b/internal/app/api/v1/secret.go index 37ccf905db0..d1c306aa411 100644 --- a/internal/app/api/v1/secret.go +++ b/internal/app/api/v1/secret.go @@ -194,13 +194,13 @@ func (s *TestkubeAPI) GetSecretHandler() fiber.Handler { func (s *TestkubeAPI) fetchOwnerReference(kind, name string) (metav1.OwnerReference, error) { if kind == testworkflowsv1.Resource { - obj, err := s.TestWorkflowsClient.Get(name) + obj, err := s.TestWorkflowsK8SClient.Get(name) if err != nil { return metav1.OwnerReference{}, errors.Wrap(err, "fetching owner") } return metav1.OwnerReference{APIVersion: obj.GroupVersionKind().String(), Kind: obj.Kind, Name: obj.Name, UID: obj.UID}, nil } else if kind == testworkflowsv1.ResourceTemplate { - obj, err := s.TestWorkflowsClient.Get(name) + obj, err := s.TestWorkflowTemplatesK8SClient.Get(name) if err != nil { return metav1.OwnerReference{}, errors.Wrap(err, "fetching owner") } diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go index 93e4b0d79b3..6581d4f0bc1 100644 --- a/internal/app/api/v1/server.go +++ b/internal/app/api/v1/server.go @@ -3,26 +3,27 @@ package v1 import ( "go.uber.org/zap" + executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1" testtriggersclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testtriggers/v1" testworkflowsv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" "github.com/kubeshop/testkube/cmd/api-server/commons" + "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/internal/config" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/log" - repoConfig "github.com/kubeshop/testkube/pkg/repository/config" - "github.com/kubeshop/testkube/pkg/repository/testworkflow" - "github.com/kubeshop/testkube/pkg/secretmanager" - "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" - "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" - - executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1" - "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/event" ws "github.com/kubeshop/testkube/pkg/event/kind/websocket" "github.com/kubeshop/testkube/pkg/executor/client" "github.com/kubeshop/testkube/pkg/featureflags" + "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" + repoConfig "github.com/kubeshop/testkube/pkg/repository/config" + "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/secretmanager" "github.com/kubeshop/testkube/pkg/server" "github.com/kubeshop/testkube/pkg/storage" + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" ) func NewTestkubeAPI( @@ -34,12 +35,13 @@ func NewTestkubeAPI( artifactsStorage storage.ArtifactsStorage, webhookClient executorsclientv1.WebhooksInterface, testTriggersClient testtriggersclientv1.Interface, - testWorkflowsClient testworkflowsv1.Interface, - testWorkflowTemplatesClient testworkflowsv1.TestWorkflowTemplatesInterface, + testWorkflowsClient testworkflowclient.TestWorkflowClient, + testWorkflowsK8SClient testworkflowsv1.Interface, + testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient, + testWorkflowTemplatesK8SClient testworkflowsv1.TestWorkflowTemplatesInterface, configMap repoConfig.Repository, secretManager secretmanager.SecretManager, secretConfig testkube.SecretConfig, - testWorkflowExecutor testworkflowexecutor.TestWorkflowExecutor, executionWorkerClient executionworkertypes.Worker, eventsEmitter *event.Emitter, websocketLoader *ws.WebsocketLoader, @@ -50,65 +52,70 @@ func NewTestkubeAPI( helmchartVersion string, serviceAccountNames map[string]string, dockerImageVersion string, + testWorkflowExecutor testworkflowexecutor.TestWorkflowExecutor, ) TestkubeAPI { return TestkubeAPI{ - ClusterID: clusterId, - Log: log.DefaultLogger, - DeprecatedClients: deprecatedClients, - TestWorkflowResults: testWorkflowResults, - TestWorkflowOutput: testWorkflowOutput, - SecretManager: secretManager, - TestTriggersClient: testTriggersClient, - TestWorkflowsClient: testWorkflowsClient, - TestWorkflowTemplatesClient: testWorkflowTemplatesClient, - Metrics: metrics, - WebsocketLoader: websocketLoader, - Events: eventsEmitter, - WebhooksClient: webhookClient, - Namespace: namespace, - ConfigMap: configMap, - TestWorkflowExecutor: testWorkflowExecutor, - ExecutionWorkerClient: executionWorkerClient, - ArtifactsStorage: artifactsStorage, - dashboardURI: dashboardURI, - helmchartVersion: helmchartVersion, - secretConfig: secretConfig, - featureFlags: ff, - ServiceAccountNames: serviceAccountNames, - dockerImageVersion: dockerImageVersion, - proContext: proContext, + ClusterID: clusterId, + Log: log.DefaultLogger, + DeprecatedClients: deprecatedClients, + TestWorkflowResults: testWorkflowResults, + TestWorkflowOutput: testWorkflowOutput, + SecretManager: secretManager, + TestTriggersClient: testTriggersClient, + TestWorkflowsClient: testWorkflowsClient, + TestWorkflowTemplatesClient: testWorkflowTemplatesClient, + TestWorkflowsK8SClient: testWorkflowsK8SClient, + TestWorkflowTemplatesK8SClient: testWorkflowTemplatesK8SClient, + Metrics: metrics, + WebsocketLoader: websocketLoader, + Events: eventsEmitter, + WebhooksClient: webhookClient, + Namespace: namespace, + ConfigMap: configMap, + ExecutionWorkerClient: executionWorkerClient, + ArtifactsStorage: artifactsStorage, + dashboardURI: dashboardURI, + helmchartVersion: helmchartVersion, + secretConfig: secretConfig, + featureFlags: ff, + ServiceAccountNames: serviceAccountNames, + dockerImageVersion: dockerImageVersion, + proContext: proContext, + testWorkflowExecutor: testWorkflowExecutor, } } type TestkubeAPI struct { - ClusterID string - Log *zap.SugaredLogger - TestWorkflowResults testworkflow.Repository - TestWorkflowOutput testworkflow.OutputRepository - Executor client.Executor - ContainerExecutor client.Executor - TestWorkflowExecutor testworkflowexecutor.TestWorkflowExecutor - ExecutionWorkerClient executionworkertypes.Worker - DeprecatedClients commons.DeprecatedClients - SecretManager secretmanager.SecretManager - WebhooksClient executorsclientv1.WebhooksInterface - TestTriggersClient testtriggersclientv1.Interface - TestWorkflowsClient testworkflowsv1.Interface - TestWorkflowTemplatesClient testworkflowsv1.TestWorkflowTemplatesInterface - Metrics metrics.Metrics - Namespace string - WebsocketLoader *ws.WebsocketLoader - Events *event.Emitter - ConfigMap repoConfig.Repository - ArtifactsStorage storage.ArtifactsStorage - dashboardURI string - helmchartVersion string - secretConfig testkube.SecretConfig - featureFlags featureflags.FeatureFlags - proContext *config.ProContext - ServiceAccountNames map[string]string - dockerImageVersion string + ClusterID string + Log *zap.SugaredLogger + TestWorkflowResults testworkflow.Repository + TestWorkflowOutput testworkflow.OutputRepository + Executor client.Executor + ContainerExecutor client.Executor + ExecutionWorkerClient executionworkertypes.Worker + DeprecatedClients commons.DeprecatedClients + SecretManager secretmanager.SecretManager + WebhooksClient executorsclientv1.WebhooksInterface + TestTriggersClient testtriggersclientv1.Interface + TestWorkflowsClient testworkflowclient.TestWorkflowClient + TestWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient + TestWorkflowsK8SClient testworkflowsv1.Interface + TestWorkflowTemplatesK8SClient testworkflowsv1.TestWorkflowTemplatesInterface + Metrics metrics.Metrics + Namespace string + WebsocketLoader *ws.WebsocketLoader + Events *event.Emitter + ConfigMap repoConfig.Repository + ArtifactsStorage storage.ArtifactsStorage + dashboardURI string + helmchartVersion string + secretConfig testkube.SecretConfig + featureFlags featureflags.FeatureFlags + proContext *config.ProContext + ServiceAccountNames map[string]string + dockerImageVersion string + testWorkflowExecutor testworkflowexecutor.TestWorkflowExecutor } func (s *TestkubeAPI) Init(server server.HTTPServer) { diff --git a/internal/app/api/v1/testworkflowmetrics.go b/internal/app/api/v1/testworkflowmetrics.go index 51099ac722e..7fc8b14d41e 100644 --- a/internal/app/api/v1/testworkflowmetrics.go +++ b/internal/app/api/v1/testworkflowmetrics.go @@ -4,9 +4,9 @@ import ( "context" testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/event/kind/testworkflowexecutiontelemetry" "github.com/kubeshop/testkube/pkg/log" "github.com/kubeshop/testkube/pkg/telemetry" - "github.com/kubeshop/testkube/pkg/testworkflows" "github.com/kubeshop/testkube/pkg/version" ) @@ -26,17 +26,17 @@ func (s *TestkubeAPI) sendCreateWorkflowTelemetry(ctx context.Context, workflow out, err := telemetry.SendCreateWorkflowEvent("testkube_api_create_test_workflow", telemetry.CreateWorkflowParams{ CreateParams: telemetry.CreateParams{ AppVersion: version.Version, - DataSource: testworkflows.GetDataSource(workflow.Spec.Content), - Host: testworkflows.GetHostname(), - ClusterID: testworkflows.GetClusterID(ctx, s.ConfigMap), + DataSource: testworkflowexecutiontelemetry.GetDataSource(workflow.Spec.Content), + Host: testworkflowexecutiontelemetry.GetHostname(), + ClusterID: testworkflowexecutiontelemetry.GetClusterID(ctx, s.ConfigMap), }, WorkflowParams: telemetry.WorkflowParams{ TestWorkflowSteps: int32(len(workflow.Spec.Setup) + len(workflow.Spec.Steps) + len(workflow.Spec.After)), TestWorkflowTemplateUsed: len(workflow.Spec.Use) != 0, - TestWorkflowImage: testworkflows.GetImage(workflow.Spec.Container), - TestWorkflowArtifactUsed: testworkflows.HasWorkflowStepLike(workflow.Spec, testworkflows.HasArtifacts), - TestWorkflowKubeshopGitURI: testworkflows.IsKubeshopGitURI(workflow.Spec.Content) || - testworkflows.HasWorkflowStepLike(workflow.Spec, testworkflows.HasKubeshopGitURI), + TestWorkflowImage: testworkflowexecutiontelemetry.GetImage(workflow.Spec.Container), + TestWorkflowArtifactUsed: testworkflowexecutiontelemetry.HasWorkflowStepLike(workflow.Spec, testworkflowexecutiontelemetry.HasArtifacts), + TestWorkflowKubeshopGitURI: testworkflowexecutiontelemetry.IsKubeshopGitURI(workflow.Spec.Content) || + testworkflowexecutiontelemetry.HasWorkflowStepLike(workflow.Spec, testworkflowexecutiontelemetry.HasKubeshopGitURI), }, }) if err != nil { @@ -62,16 +62,16 @@ func (s *TestkubeAPI) sendCreateWorkflowTemplateTelemetry(ctx context.Context, t out, err := telemetry.SendCreateWorkflowEvent("testkube_api_create_test_workflow_template", telemetry.CreateWorkflowParams{ CreateParams: telemetry.CreateParams{ AppVersion: version.Version, - DataSource: testworkflows.GetDataSource(template.Spec.Content), - Host: testworkflows.GetHostname(), - ClusterID: testworkflows.GetClusterID(ctx, s.ConfigMap), + DataSource: testworkflowexecutiontelemetry.GetDataSource(template.Spec.Content), + Host: testworkflowexecutiontelemetry.GetHostname(), + ClusterID: testworkflowexecutiontelemetry.GetClusterID(ctx, s.ConfigMap), }, WorkflowParams: telemetry.WorkflowParams{ TestWorkflowSteps: int32(len(template.Spec.Setup) + len(template.Spec.Steps) + len(template.Spec.After)), - TestWorkflowImage: testworkflows.GetImage(template.Spec.Container), - TestWorkflowArtifactUsed: testworkflows.HasTemplateStepLike(template.Spec, testworkflows.HasTemplateArtifacts), - TestWorkflowKubeshopGitURI: testworkflows.IsKubeshopGitURI(template.Spec.Content) || - testworkflows.HasTemplateStepLike(template.Spec, testworkflows.HasTemplateKubeshopGitURI), + TestWorkflowImage: testworkflowexecutiontelemetry.GetImage(template.Spec.Container), + TestWorkflowArtifactUsed: testworkflowexecutiontelemetry.HasTemplateStepLike(template.Spec, testworkflowexecutiontelemetry.HasTemplateArtifacts), + TestWorkflowKubeshopGitURI: testworkflowexecutiontelemetry.IsKubeshopGitURI(template.Spec.Content) || + testworkflowexecutiontelemetry.HasTemplateStepLike(template.Spec, testworkflowexecutiontelemetry.HasTemplateKubeshopGitURI), }, }) if err != nil { diff --git a/internal/app/api/v1/testworkflows.go b/internal/app/api/v1/testworkflows.go index 4a741e24b35..7f697504eb8 100644 --- a/internal/app/api/v1/testworkflows.go +++ b/internal/app/api/v1/testworkflows.go @@ -14,10 +14,11 @@ import ( testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" "github.com/kubeshop/testkube/pkg/mapper/testworkflows" - "github.com/kubeshop/testkube/pkg/scheduler" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" - "github.com/kubeshop/testkube/pkg/workerpool" ) func (s *TestkubeAPI) ListTestWorkflowsHandler() fiber.Handler { @@ -27,7 +28,10 @@ func (s *TestkubeAPI) ListTestWorkflowsHandler() fiber.Handler { if err != nil { return s.BadGateway(c, errPrefix, "client problem", err) } - err = SendResourceList(c, "TestWorkflow", testworkflowsv1.GroupVersion, testworkflows.MapTestWorkflowKubeToAPI, workflows.Items...) + crWorkflows := common.MapSlice(workflows, func(w testkube.TestWorkflow) testworkflowsv1.TestWorkflow { + return *testworkflows.MapAPIToKube(&w) + }) + err = SendResourceList(c, "TestWorkflow", testworkflowsv1.GroupVersion, testworkflows.MapTestWorkflowKubeToAPI, crWorkflows...) if err != nil { return s.InternalError(c, errPrefix, "serialization problem", err) } @@ -39,11 +43,11 @@ func (s *TestkubeAPI) GetTestWorkflowHandler() fiber.Handler { return func(c *fiber.Ctx) (err error) { name := c.Params("id") errPrefix := fmt.Sprintf("failed to get test workflow '%s'", name) - workflow, err := s.TestWorkflowsClient.Get(name) + workflow, err := s.TestWorkflowsClient.Get(c.Context(), s.getEnvironmentId(), name) if err != nil { return s.ClientError(c, errPrefix, err) } - err = SendResource(c, "TestWorkflow", testworkflowsv1.GroupVersion, testworkflows.MapKubeToAPI, workflow) + err = SendResource(c, "TestWorkflow", testworkflowsv1.GroupVersion, testworkflows.MapKubeToAPI, testworkflows.MapAPIToKube(workflow)) if err != nil { return s.InternalError(c, errPrefix, "serialization problem", err) } @@ -53,11 +57,14 @@ func (s *TestkubeAPI) GetTestWorkflowHandler() fiber.Handler { func (s *TestkubeAPI) DeleteTestWorkflowHandler() fiber.Handler { return func(c *fiber.Ctx) error { + ctx := c.Context() + environmentId := s.getEnvironmentId() + name := c.Params("id") errPrefix := fmt.Sprintf("failed to delete test workflow '%s'", name) skipCRD := c.Query("skipDeleteCRD", "") if skipCRD != "true" { - err := s.TestWorkflowsClient.Delete(name) + err := s.TestWorkflowsClient.Delete(ctx, environmentId, name) s.Metrics.IncDeleteTestWorkflow(err) if err != nil { return s.ClientError(c, errPrefix, err) @@ -81,45 +88,53 @@ func (s *TestkubeAPI) DeleteTestWorkflowHandler() fiber.Handler { func (s *TestkubeAPI) DeleteTestWorkflowsHandler() fiber.Handler { errPrefix := "failed to delete test workflows" return func(c *fiber.Ctx) error { + ctx := c.Context() + environmentId := s.getEnvironmentId() + selector := c.Query("selector") + labelSelector, err := metav1.ParseToLabelSelector(selector) + if err != nil { + return s.ClientError(c, errPrefix, err) + } + if len(labelSelector.MatchExpressions) > 0 { + return s.ClientError(c, errPrefix, errors.New("matchExpressions are not supported")) + } - var ( - workflows *testworkflowsv1.TestWorkflowList - err error - ) + workflows := make([]testkube.TestWorkflow, 0) testWorkflowNames := c.Query("testWorkflowNames") if testWorkflowNames != "" { names := strings.Split(testWorkflowNames, ",") - workflows = &testworkflowsv1.TestWorkflowList{} for _, name := range names { - workflow, err := s.TestWorkflowsClient.Get(name) + workflow, err := s.TestWorkflowsClient.Get(ctx, environmentId, name) if err != nil { return s.ClientError(c, errPrefix, err) } - workflows.Items = append(workflows.Items, *workflow) + workflows = append(workflows, *workflow) } } else { - workflows, err = s.TestWorkflowsClient.List(selector) + workflows, err = s.TestWorkflowsClient.List(ctx, environmentId, testworkflowclient.ListOptions{ + Labels: labelSelector.MatchLabels, + }) if err != nil { return s.BadGateway(c, errPrefix, "client problem", err) } } // Delete - err = s.TestWorkflowsClient.DeleteByLabels(selector) + _, err = s.TestWorkflowsClient.DeleteByLabels(ctx, environmentId, labelSelector.MatchLabels) if err != nil { return s.ClientError(c, errPrefix, err) } // Mark as deleted - for range workflows.Items { + for range workflows { s.Metrics.IncDeleteTestWorkflow(err) } // Delete the executions skipExecutions := c.Query("skipDeleteExecutions", "") if skipExecutions != "true" { - names := common.MapSlice(workflows.Items, func(t testworkflowsv1.TestWorkflow) string { + names := common.MapSlice(workflows, func(t testkube.TestWorkflow) string { return t.Name }) @@ -140,6 +155,9 @@ func (s *TestkubeAPI) DeleteTestWorkflowsHandler() fiber.Handler { func (s *TestkubeAPI) CreateTestWorkflowHandler() fiber.Handler { errPrefix := "failed to create test workflow" return func(c *fiber.Ctx) (err error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + // Deserialize resource obj := new(testworkflowsv1.TestWorkflow) if HasYAML(c) { @@ -177,14 +195,14 @@ func (s *TestkubeAPI) CreateTestWorkflowHandler() fiber.Handler { } // Create the resource - obj, err = s.TestWorkflowsClient.Create(obj) + err = s.TestWorkflowsClient.Create(ctx, environmentId, *testworkflows.MapKubeToAPI(obj)) if err != nil { s.Metrics.IncCreateTestWorkflow(err) return s.BadRequest(c, errPrefix, "client error", err) } // Create secrets - err = s.SecretManager.InsertBatch(c.Context(), execNamespace, secrets, &metav1.OwnerReference{ + err = s.SecretManager.InsertBatch(ctx, execNamespace, secrets, &metav1.OwnerReference{ APIVersion: testworkflowsv1.GroupVersion.String(), Kind: testworkflowsv1.Resource, Name: obj.Name, @@ -192,11 +210,11 @@ func (s *TestkubeAPI) CreateTestWorkflowHandler() fiber.Handler { }) s.Metrics.IncCreateTestWorkflow(err) if err != nil { - _ = s.TestWorkflowsClient.Delete(obj.Name) + _ = s.TestWorkflowsClient.Delete(context.Background(), environmentId, obj.Name) return s.BadRequest(c, errPrefix, "auto-creating secrets", err) } - s.sendCreateWorkflowTelemetry(c.Context(), obj) + s.sendCreateWorkflowTelemetry(ctx, obj) err = SendResource(c, "TestWorkflow", testworkflowsv1.GroupVersion, testworkflows.MapKubeToAPI, obj) if err != nil { @@ -209,6 +227,9 @@ func (s *TestkubeAPI) CreateTestWorkflowHandler() fiber.Handler { func (s *TestkubeAPI) UpdateTestWorkflowHandler() fiber.Handler { errPrefix := "failed to update test workflow" return func(c *fiber.Ctx) (err error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + name := c.Params("id") // Deserialize resource @@ -228,7 +249,7 @@ func (s *TestkubeAPI) UpdateTestWorkflowHandler() fiber.Handler { } // Read existing resource - workflow, err := s.TestWorkflowsClient.Get(name) + workflow, err := s.TestWorkflowsClient.Get(ctx, environmentId, name) if err != nil { return s.ClientError(c, errPrefix, err) } @@ -240,7 +261,6 @@ func (s *TestkubeAPI) UpdateTestWorkflowHandler() fiber.Handler { } obj.Namespace = workflow.Namespace obj.Name = workflow.Name - obj.ResourceVersion = workflow.ResourceVersion // Get information about execution namespace // TODO: Think what to do when it is dynamic - create in all execution namespaces? @@ -257,7 +277,7 @@ func (s *TestkubeAPI) UpdateTestWorkflowHandler() fiber.Handler { } // Update the resource - obj, err = s.TestWorkflowsClient.Update(obj) + err = s.TestWorkflowsClient.Update(ctx, environmentId, *testworkflows.MapKubeToAPI(obj)) if err != nil { s.Metrics.IncUpdateTestWorkflow(err) return s.BadRequest(c, errPrefix, "client error", err) @@ -272,7 +292,7 @@ func (s *TestkubeAPI) UpdateTestWorkflowHandler() fiber.Handler { }) s.Metrics.IncUpdateTestWorkflow(err) if err != nil { - _, err = s.TestWorkflowsClient.Update(initial) + err = s.TestWorkflowsClient.Update(context.Background(), environmentId, *initial) if err != nil { s.Log.Errorf("failed to recover previous TestWorkflow state: %v", err) } @@ -290,6 +310,9 @@ func (s *TestkubeAPI) UpdateTestWorkflowHandler() fiber.Handler { func (s *TestkubeAPI) PreviewTestWorkflowHandler() fiber.Handler { errPrefix := "failed to resolve test workflow" return func(c *fiber.Ctx) (err error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + // Check if it should inline templates inline, _ := strconv.ParseBool(c.Query("inline")) @@ -318,18 +341,18 @@ func (s *TestkubeAPI) PreviewTestWorkflowHandler() fiber.Handler { if inline { // Fetch the templates tpls := testworkflowresolver.ListTemplates(obj) - tplsMap := make(map[string]testworkflowsv1.TestWorkflowTemplate, len(tpls)) + tplsMap := make(map[string]*testworkflowsv1.TestWorkflowTemplate, len(tpls)) for name := range tpls { - tpl, err := s.TestWorkflowTemplatesClient.Get(name) + tpl, err := s.TestWorkflowTemplatesClient.Get(ctx, environmentId, name) if err != nil { return s.BadRequest(c, errPrefix, "fetching error", err) } - tplsMap[name] = *tpl + tplsMap[name] = testworkflows.MapTemplateAPIToKube(tpl) } // Resolve the TestWorkflow secrets := s.SecretManager.Batch("tw-", obj.Name) - err = testworkflowresolver.ApplyTemplates(obj, tplsMap, secrets.Append) + err = testworkflowresolver.ApplyTemplates(obj, tplsMap, testworkflowresolver.EnvVarSourceToSecretExpression(secrets.Append)) if err != nil { return s.BadRequest(c, errPrefix, "resolving error", err) } @@ -353,24 +376,6 @@ func (s *TestkubeAPI) ExecuteTestWorkflowHandler() fiber.Handler { errPrefix := "failed to execute test workflow" - var testWorkflows []testworkflowsv1.TestWorkflow - if name != "" { - errPrefix = errPrefix + " " + name - testWorkflow, err := s.TestWorkflowsClient.Get(name) - if err != nil { - return s.ClientError(c, errPrefix, err) - } - - testWorkflows = append(testWorkflows, *testWorkflow) - } else { - testWorkflowList, err := s.TestWorkflowsClient.List(selector) - if err != nil { - return s.ClientError(c, errPrefix, err) - } - - testWorkflows = append(testWorkflows, testWorkflowList.Items...) - } - // Load the execution request var request testkube.TestWorkflowExecutionRequest err = c.BodyParser(&request) @@ -378,33 +383,42 @@ 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 - - if len(testWorkflows) != 0 { - request.TestWorkflowExecutionName = strings.Clone(c.Query("testWorkflowExecutionName")) - workerpoolService := workerpool.New[testworkflowsv1.TestWorkflow, testkube.TestWorkflowExecutionRequest, testkube.TestWorkflowExecution](scheduler.DefaultConcurrencyLevel) + runningContext, user := testworkflowexecutor.GetNewRunningContext(request.RunningContext, request.ParentExecutionIds) - go workerpoolService.SendRequests(s.prepareTestWorkflowRequests(testWorkflows, request)) - go workerpoolService.Run(ctx) - - for r := range workerpoolService.GetResponses() { - results = append(results, r.Result) - if r.Err != nil { - errs = append(errs, r.Err) - } + var scheduleExecution cloud.ScheduleExecution + if name != "" { + scheduleExecution.Selector = &cloud.ScheduleResourceSelector{Name: name} + scheduleExecution.Config = request.Config + } else if selector != "" { + sel, err := metav1.ParseToLabelSelector(selector) + if err != nil { + return s.InternalError(c, errPrefix, "invalid selector", err) + } + if len(sel.MatchExpressions) > 0 { + return s.InternalError(c, errPrefix, "invalid selector", errors.New("only simple selectors are allowed")) } + scheduleExecution.Selector = &cloud.ScheduleResourceSelector{Labels: sel.MatchLabels} + scheduleExecution.Config = request.Config + } + + resp := s.testWorkflowExecutor.Execute(ctx, &cloud.ScheduleRequest{ + EnvironmentId: "", // use default + Executions: []*cloud.ScheduleExecution{&scheduleExecution}, + DisableWebhooks: request.DisableWebhooks, + Tags: request.Tags, + RunningContext: runningContext, + ParentExecutionIds: request.ParentExecutionIds, + KubernetesObjectName: request.TestWorkflowExecutionName, + User: user, + }) + + results := make([]testkube.TestWorkflowExecution, 0) + for v := range resp.Channel() { + results = append(results, *v) } - if len(errs) != 0 { - return s.InternalError(c, errPrefix, "execution error", errors.Join(errs...)) + if resp.Error() != nil { + return s.InternalError(c, errPrefix, "execution error", resp.Error()) } s.Log.Debugw("executing test workflow", "name", name, "selector", selector) @@ -420,8 +434,22 @@ func (s *TestkubeAPI) ExecuteTestWorkflowHandler() fiber.Handler { } } -func (s *TestkubeAPI) getFilteredTestWorkflowList(c *fiber.Ctx) (*testworkflowsv1.TestWorkflowList, error) { - crWorkflows, err := s.TestWorkflowsClient.List(c.Query("selector")) +func (s *TestkubeAPI) getFilteredTestWorkflowList(c *fiber.Ctx) ([]testkube.TestWorkflow, error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + + selector := c.Query("selector") + labelSelector, err := metav1.ParseToLabelSelector(selector) + if err != nil { + return nil, err + } + if len(labelSelector.MatchExpressions) > 0 { + return nil, errors.New("MatchExpressions are not supported") + } + + workflows, err := s.TestWorkflowsClient.List(ctx, environmentId, testworkflowclient.ListOptions{ + Labels: labelSelector.MatchLabels, + }) if err != nil { return nil, err } @@ -429,29 +457,11 @@ func (s *TestkubeAPI) getFilteredTestWorkflowList(c *fiber.Ctx) (*testworkflowsv search := c.Query("textSearch") if search != "" { // filter items array - for i := len(crWorkflows.Items) - 1; i >= 0; i-- { - if !strings.Contains(crWorkflows.Items[i].Name, search) { - crWorkflows.Items = append(crWorkflows.Items[:i], crWorkflows.Items[i+1:]...) + for i := len(workflows) - 1; i >= 0; i-- { + if !strings.Contains(workflows[i].Name, search) { + workflows = append(workflows[:i], workflows[i+1:]...) } } } - - return crWorkflows, nil -} - -func (s *TestkubeAPI) prepareTestWorkflowRequests(work []testworkflowsv1.TestWorkflow, request testkube.TestWorkflowExecutionRequest) []workerpool.Request[ - testworkflowsv1.TestWorkflow, - testkube.TestWorkflowExecutionRequest, - testkube.TestWorkflowExecution, -] { - requests := make([]workerpool.Request[testworkflowsv1.TestWorkflow, testkube.TestWorkflowExecutionRequest, testkube.TestWorkflowExecution], len(work)) - for i := range work { - requests[i] = workerpool.Request[testworkflowsv1.TestWorkflow, testkube.TestWorkflowExecutionRequest, testkube.TestWorkflowExecution]{ - Object: work[i], - Options: request, - ExecFn: s.TestWorkflowExecutor.Execute, - } - } - - return requests + return workflows, nil } diff --git a/internal/app/api/v1/testworkflowtemplates.go b/internal/app/api/v1/testworkflowtemplates.go index 8cefdbbce0a..759159c2a62 100644 --- a/internal/app/api/v1/testworkflowtemplates.go +++ b/internal/app/api/v1/testworkflowtemplates.go @@ -13,6 +13,7 @@ import ( "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/mapper/testworkflows" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" ) @@ -23,7 +24,10 @@ func (s *TestkubeAPI) ListTestWorkflowTemplatesHandler() fiber.Handler { if err != nil { return s.BadGateway(c, errPrefix, "client problem", err) } - err = SendResourceList(c, "TestWorkflowTemplate", testworkflowsv1.GroupVersion, testworkflows.MapTestWorkflowTemplateKubeToAPI, templates.Items...) + crTemplates := common.MapSlice(templates, func(w testkube.TestWorkflowTemplate) testworkflowsv1.TestWorkflowTemplate { + return *testworkflows.MapTemplateAPIToKube(&w) + }) + err = SendResourceList(c, "TestWorkflowTemplate", testworkflowsv1.GroupVersion, testworkflows.MapTestWorkflowTemplateKubeToAPI, crTemplates...) if err != nil { return s.InternalError(c, errPrefix, "serialization problem", err) } @@ -33,13 +37,17 @@ func (s *TestkubeAPI) ListTestWorkflowTemplatesHandler() fiber.Handler { func (s *TestkubeAPI) GetTestWorkflowTemplateHandler() fiber.Handler { return func(c *fiber.Ctx) (err error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + name := c.Params("id") errPrefix := fmt.Sprintf("failed to get test workflow template '%s'", name) - template, err := s.TestWorkflowTemplatesClient.Get(name) + template, err := s.TestWorkflowTemplatesClient.Get(ctx, environmentId, name) if err != nil { return s.ClientError(c, errPrefix, err) } - err = SendResource(c, "TestWorkflowTemplate", testworkflowsv1.GroupVersion, testworkflows.MapTemplateKubeToAPI, template) + crTemplate := testworkflows.MapTemplateAPIToKube(template) + err = SendResource(c, "TestWorkflowTemplate", testworkflowsv1.GroupVersion, testworkflows.MapTemplateKubeToAPI, crTemplate) if err != nil { return s.InternalError(c, errPrefix, "serialization problem", err) } @@ -49,9 +57,12 @@ func (s *TestkubeAPI) GetTestWorkflowTemplateHandler() fiber.Handler { func (s *TestkubeAPI) DeleteTestWorkflowTemplateHandler() fiber.Handler { return func(c *fiber.Ctx) error { + ctx := c.Context() + environmentId := s.getEnvironmentId() + name := c.Params("id") errPrefix := fmt.Sprintf("failed to delete test workflow template '%s'", name) - err := s.TestWorkflowTemplatesClient.Delete(name) + err := s.TestWorkflowTemplatesClient.Delete(ctx, environmentId, name) s.Metrics.IncDeleteTestWorkflowTemplate(err) if err != nil { return s.ClientError(c, errPrefix, err) @@ -63,8 +74,19 @@ func (s *TestkubeAPI) DeleteTestWorkflowTemplateHandler() fiber.Handler { func (s *TestkubeAPI) DeleteTestWorkflowTemplatesHandler() fiber.Handler { errPrefix := "failed to delete test workflow templates" return func(c *fiber.Ctx) error { + ctx := c.Context() + environmentId := s.getEnvironmentId() + selector := c.Query("selector") - err := s.TestWorkflowTemplatesClient.DeleteByLabels(selector) + labelSelector, err := metav1.ParseToLabelSelector(selector) + if err != nil { + return s.ClientError(c, errPrefix, err) + } + if len(labelSelector.MatchExpressions) > 0 { + return s.ClientError(c, errPrefix, errors.New("matchExpressions are not supported")) + } + + _, err = s.TestWorkflowTemplatesClient.DeleteByLabels(ctx, environmentId, labelSelector.MatchLabels) if err != nil { return s.ClientError(c, errPrefix, err) } @@ -75,6 +97,9 @@ func (s *TestkubeAPI) DeleteTestWorkflowTemplatesHandler() fiber.Handler { func (s *TestkubeAPI) CreateTestWorkflowTemplateHandler() fiber.Handler { errPrefix := "failed to create test workflow template" return func(c *fiber.Ctx) (err error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + // Deserialize resource obj := new(testworkflowsv1.TestWorkflowTemplate) if HasYAML(c) { @@ -112,7 +137,7 @@ func (s *TestkubeAPI) CreateTestWorkflowTemplateHandler() fiber.Handler { } // Create the resource - obj, err = s.TestWorkflowTemplatesClient.Create(obj) + err = s.TestWorkflowTemplatesClient.Create(ctx, environmentId, *testworkflows.MapTemplateKubeToAPI(obj)) if err != nil { s.Metrics.IncCreateTestWorkflowTemplate(err) return s.BadRequest(c, errPrefix, "client error", err) @@ -127,7 +152,7 @@ func (s *TestkubeAPI) CreateTestWorkflowTemplateHandler() fiber.Handler { }) s.Metrics.IncCreateTestWorkflowTemplate(err) if err != nil { - _ = s.TestWorkflowTemplatesClient.Delete(obj.Name) + _ = s.TestWorkflowTemplatesClient.Delete(ctx, environmentId, obj.Name) return s.BadRequest(c, errPrefix, "auto-creating secrets", err) } s.sendCreateWorkflowTemplateTelemetry(c.Context(), obj) @@ -143,6 +168,9 @@ func (s *TestkubeAPI) CreateTestWorkflowTemplateHandler() fiber.Handler { func (s *TestkubeAPI) UpdateTestWorkflowTemplateHandler() fiber.Handler { errPrefix := "failed to update test workflow template" return func(c *fiber.Ctx) (err error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + name := c.Params("id") // Deserialize resource @@ -162,7 +190,7 @@ func (s *TestkubeAPI) UpdateTestWorkflowTemplateHandler() fiber.Handler { } // Read existing resource - template, err := s.TestWorkflowTemplatesClient.Get(name) + template, err := s.TestWorkflowTemplatesClient.Get(ctx, environmentId, name) if err != nil { return s.ClientError(c, errPrefix, err) } @@ -174,7 +202,6 @@ func (s *TestkubeAPI) UpdateTestWorkflowTemplateHandler() fiber.Handler { } obj.Namespace = template.Namespace obj.Name = template.Name - obj.ResourceVersion = template.ResourceVersion // Get information about execution namespace // TODO: Considering that the TestWorkflow may override it - should it create in all execution namespaces? @@ -191,7 +218,7 @@ func (s *TestkubeAPI) UpdateTestWorkflowTemplateHandler() fiber.Handler { } // Update the resource - obj, err = s.TestWorkflowTemplatesClient.Update(obj) + err = s.TestWorkflowTemplatesClient.Update(ctx, environmentId, *testworkflows.MapTemplateKubeToAPI(obj)) if err != nil { s.Metrics.IncUpdateTestWorkflowTemplate(err) return s.BadRequest(c, errPrefix, "client error", err) @@ -206,7 +233,7 @@ func (s *TestkubeAPI) UpdateTestWorkflowTemplateHandler() fiber.Handler { }) s.Metrics.IncUpdateTestWorkflowTemplate(err) if err != nil { - _, err = s.TestWorkflowTemplatesClient.Update(initial) + err = s.TestWorkflowTemplatesClient.Update(ctx, environmentId, *initial) if err != nil { s.Log.Errorf("failed to recover previous TestWorkflowTemplate state: %v", err) } @@ -221,8 +248,21 @@ func (s *TestkubeAPI) UpdateTestWorkflowTemplateHandler() fiber.Handler { } } -func (s *TestkubeAPI) getFilteredTestWorkflowTemplateList(c *fiber.Ctx) (*testworkflowsv1.TestWorkflowTemplateList, error) { - crTemplates, err := s.TestWorkflowTemplatesClient.List(c.Query("selector")) +func (s *TestkubeAPI) getFilteredTestWorkflowTemplateList(c *fiber.Ctx) ([]testkube.TestWorkflowTemplate, error) { + ctx := c.Context() + environmentId := s.getEnvironmentId() + selector := c.Query("selector") + labelSelector, err := metav1.ParseToLabelSelector(selector) + if err != nil { + return nil, err + } + if len(labelSelector.MatchExpressions) > 0 { + return nil, errors.New("MatchExpressions are not supported") + } + + templates, err := s.TestWorkflowTemplatesClient.List(ctx, environmentId, testworkflowtemplateclient.ListOptions{ + Labels: labelSelector.MatchLabels, + }) if err != nil { return nil, err } @@ -230,12 +270,12 @@ func (s *TestkubeAPI) getFilteredTestWorkflowTemplateList(c *fiber.Ctx) (*testwo search := c.Query("textSearch") if search != "" { search = strings.ReplaceAll(search, "/", "--") - for i := len(crTemplates.Items) - 1; i >= 0; i-- { - if !strings.Contains(crTemplates.Items[i].Name, search) { - crTemplates.Items = append(crTemplates.Items[:i], crTemplates.Items[i+1:]...) + for i := len(templates) - 1; i >= 0; i-- { + if !strings.Contains(templates[i].Name, search) { + templates = append(templates[:i], templates[i+1:]...) } } } - return crTemplates, nil + return templates, nil } diff --git a/internal/app/api/v1/testworkflowwithexecutions.go b/internal/app/api/v1/testworkflowwithexecutions.go index 2c2d3d691be..edd7417123f 100644 --- a/internal/app/api/v1/testworkflowwithexecutions.go +++ b/internal/app/api/v1/testworkflowwithexecutions.go @@ -11,25 +11,24 @@ import ( "github.com/kubeshop/testkube/internal/app/api/apiutils" "github.com/kubeshop/testkube/pkg/api/v1/testkube" - testworkflowmappers "github.com/kubeshop/testkube/pkg/mapper/testworkflows" "github.com/kubeshop/testkube/pkg/repository/result" ) func (s *TestkubeAPI) GetTestWorkflowWithExecutionHandler() fiber.Handler { return func(c *fiber.Ctx) error { + ctx := c.Context() + environmentId := s.getEnvironmentId() + name := c.Params("id") errPrefix := fmt.Sprintf("failed to get test workflow '%s' with execution", name) if name == "" { return s.Error(c, http.StatusBadRequest, errors.New(errPrefix+": id cannot be empty")) } - crWorkflow, err := s.TestWorkflowsClient.Get(name) + workflow, err := s.TestWorkflowsClient.Get(ctx, environmentId, name) if err != nil { return s.ClientError(c, errPrefix, err) } - workflow := testworkflowmappers.MapKubeToAPI(crWorkflow) - - ctx := c.Context() execution, err := s.TestWorkflowResults.GetLatestByTestWorkflow(ctx, name) if err != nil && !apiutils.IsNotFound(err) { return s.ClientError(c, errPrefix, err) @@ -45,12 +44,11 @@ func (s *TestkubeAPI) GetTestWorkflowWithExecutionHandler() fiber.Handler { func (s *TestkubeAPI) ListTestWorkflowWithExecutionsHandler() fiber.Handler { return func(c *fiber.Ctx) error { errPrefix := "failed to list test workflows with executions" - crWorkflows, err := s.getFilteredTestWorkflowList(c) + workflows, err := s.getFilteredTestWorkflowList(c) if err != nil { return s.ClientError(c, errPrefix+": get filtered workflows", err) } - workflows := testworkflowmappers.MapListKubeToAPI(crWorkflows) ctx := c.Context() results := make([]testkube.TestWorkflowWithExecutionSummary, 0, len(workflows)) workflowNames := make([]string, len(workflows)) diff --git a/internal/config/config.go b/internal/config/config.go index d3a4d14fbcf..d1c5a648bf1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -38,7 +38,6 @@ type Config struct { ScrapperEnabled bool `envconfig:"SCRAPPERENABLED" default:"false"` LogsBucket string `envconfig:"LOGS_BUCKET" default:""` LogsStorage string `envconfig:"LOGS_STORAGE" default:""` - WorkflowStorage string `envconfig:"WORKFLOW_STORAGE" default:"crd"` // WhitelistedContainers is a list of containers from which logs should be collected. WhitelistedContainers []string `envconfig:"WHITELISTED_CONTAINERS" default:"init,logs,scraper"` NatsEmbedded bool `envconfig:"NATS_EMBEDDED" default:"false"` @@ -119,6 +118,11 @@ type Config struct { DisableDeprecatedTests bool `envconfig:"DISABLE_DEPRECATED_TESTS" default:"false"` DisableWebhooks bool `envconfig:"DISABLE_WEBHOOKS" default:"false"` + FeatureNewExecutions bool `envconfig:"FEATURE_NEW_EXECUTIONS" default:"false"` + FeatureTestWorkflowCloudStorage bool `envconfig:"FEATURE_TESTWORKFLOW_CLOUD_STORAGE" default:"false"` + // DEPRECATED: Use FeatureTestWorkflowCloudStorage instead + WorkflowStorage string `envconfig:"WORKFLOW_STORAGE" default:"crd"` + // DEPRECATED: Use TestkubeProAPIKey instead TestkubeCloudAPIKey string `envconfig:"TESTKUBE_CLOUD_API_KEY" default:""` // DEPRECATED: Use TestkubeProURL instead diff --git a/internal/config/procontext.go b/internal/config/procontext.go index ed8cf5d03b9..f1ae749ee35 100644 --- a/internal/config/procontext.go +++ b/internal/config/procontext.go @@ -15,4 +15,6 @@ type ProContext struct { Migrate string ConnectionTimeout int DashboardURI string + NewExecutions bool + TestWorkflowStorage bool } diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 07178e4b5e7..0e15eef5241 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -2,6 +2,7 @@ package agent import ( "context" + "encoding/json" "fmt" "math" "time" @@ -11,25 +12,30 @@ import ( "go.uber.org/zap" "golang.org/x/sync/errgroup" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/encoding/gzip" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" - agentclient "github.com/kubeshop/testkube/pkg/agent/client" - "github.com/kubeshop/testkube/pkg/executor/output" - + "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/internal/config" + agentclient "github.com/kubeshop/testkube/pkg/agent/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/event" + "github.com/kubeshop/testkube/pkg/executor/output" "github.com/kubeshop/testkube/pkg/featureflags" ) const ( - clusterIDMeta = "cluster-id" - cloudMigrateMeta = "migrate" - orgIdMeta = "organization-id" - envIdMeta = "environment-id" - healthcheckCommand = "healthcheck" - dockerImageVersionMeta = "docker-image-version" + clusterIDMeta = "cluster-id" + cloudMigrateMeta = "migrate" + orgIdMeta = "organization-id" + envIdMeta = "environment-id" + healthcheckCommand = "healthcheck" + dockerImageVersionMeta = "docker-image-version" + newExecutionsMeta = "exec" + testWorkflowStorageMeta = "tw-storage" ) // buffer up to five messages per worker @@ -65,6 +71,8 @@ type Agent struct { testWorkflowParallelStepNotificationsResponseBuffer chan *cloud.TestWorkflowParallelStepNotificationsResponse testWorkflowParallelStepNotificationsFunc func(ctx context.Context, executionID, ref string, workerIndex int) (<-chan testkube.TestWorkflowExecutionNotification, error) + runTestWorkflow func(environmentId, executionId string) error + events chan testkube.Event sendTimeout time.Duration receiveTimeout time.Duration @@ -76,6 +84,11 @@ type Agent struct { dockerImageVersion string proContext *config.ProContext + + eventEmitter event.Interface + + featureNewExecutions bool + featureTestWorkflowCloudStorage bool } func NewAgent(logger *zap.SugaredLogger, @@ -90,6 +103,10 @@ func NewAgent(logger *zap.SugaredLogger, features featureflags.FeatureFlags, proContext *config.ProContext, dockerImageVersion string, + eventEmitter event.Interface, + runTestWorkflow func(environmentId, executionId string) error, + featureNewExecutions bool, + featureTestWorkflowCloudStorage bool, ) (*Agent, error) { return &Agent{ handler: handler, @@ -119,11 +136,15 @@ func NewAgent(logger *zap.SugaredLogger, testWorkflowParallelStepNotificationsRequestBuffer: make(chan *cloud.TestWorkflowParallelStepNotificationsRequest, bufferSizePerWorker*proContext.WorkflowParallelStepNotificationsWorkerCount), testWorkflowParallelStepNotificationsResponseBuffer: make(chan *cloud.TestWorkflowParallelStepNotificationsResponse, bufferSizePerWorker*proContext.WorkflowParallelStepNotificationsWorkerCount), testWorkflowParallelStepNotificationsFunc: workflowParallelStepNotificationsFunc, - clusterID: clusterID, - clusterName: clusterName, - features: features, - proContext: proContext, - dockerImageVersion: dockerImageVersion, + clusterID: clusterID, + clusterName: clusterName, + features: features, + proContext: proContext, + dockerImageVersion: dockerImageVersion, + eventEmitter: eventEmitter, + runTestWorkflow: runTestWorkflow, + featureNewExecutions: featureNewExecutions, + featureTestWorkflowCloudStorage: featureTestWorkflowCloudStorage, }, nil } @@ -185,11 +206,135 @@ func (ag *Agent) run(ctx context.Context) (err error) { return ag.runTestWorkflowParallelStepNotificationsWorker(groupCtx, ag.testWorkflowParallelStepNotificationsWorkerCount) }) + g.Go(func() error { + return ag.runEventsReaderLoop(groupCtx) + }) + + if ag.featureNewExecutions { + g.Go(func() error { + return ag.runRunnerRequestsLoop(groupCtx) + }) + } + err = g.Wait() return err } +// TODO: Move to another Agent instance (RunnerAgent) +func (ag *Agent) runRunnerRequestsLoop(ctx context.Context) (err error) { + // Ignore when Control Plane doesn't support new executions + if !ag.proContext.NewExecutions { + return nil + } + + if ag.proContext.APIKey != "" { + ctx = agentclient.AddAPIKeyMeta(ctx, ag.proContext.APIKey) + } + + ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, ag.proContext.OrgID) + + // creates a new Stream from the client side. ctx is used for the lifetime of the stream. + opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} + stream, err := ag.client.GetRunnerRequests(ctx, opts...) + if err != nil { + ag.logger.Errorf("failed to read runner requests stream from Control Plane: %w", err) + return errors.Wrap(err, "failed to setup runner requests stream") + } + + for { + if ctx.Err() != nil { + return ctx.Err() + } + req, err := stream.Recv() + if err != nil { + // Ignore if it's not implemented in the Control Plane + if e, ok := err.(interface{ GRPCStatus() *status.Status }); ok && e.GRPCStatus().Code() == codes.Unimplemented { + return nil + } + return err + } + + // Lock the execution for itself + resp, err := ag.client.ObtainExecution(ctx, &cloud.ObtainExecutionRequest{Id: req.Id, EnvironmentId: req.EnvironmentId}, opts...) + if err != nil { + ag.logger.Errorf("failed to obtain execution '%s/%s', from Control Plane: %v", req.EnvironmentId, req.Id, err) + continue + } + + // Ignore if the resource has been locked before + if !resp.Success { + continue + } + + // Continue + err = ag.runTestWorkflow(req.EnvironmentId, req.Id) + if err != nil { + ag.logger.Errorf("failed to run execution '%s/%s' from Control Plane: %v", req.EnvironmentId, req.Id, err) + continue + } + } +} + +func (ag *Agent) runEventsReaderLoop(ctx context.Context) (err error) { + // Ignore when Control Plane doesn't support new executions + if !ag.proContext.NewExecutions { + return nil + } + + if ag.proContext.APIKey != "" { + ctx = agentclient.AddAPIKeyMeta(ctx, ag.proContext.APIKey) + } + + ctx = metadata.AppendToOutgoingContext(ctx, clusterIDMeta, ag.clusterID) + ctx = metadata.AppendToOutgoingContext(ctx, cloudMigrateMeta, ag.proContext.Migrate) + ctx = metadata.AppendToOutgoingContext(ctx, envIdMeta, ag.proContext.EnvID) + ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, ag.proContext.OrgID) + + // creates a new Stream from the client side. ctx is used for the lifetime of the stream. + opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} + stream, err := ag.client.GetEventStream(ctx, &cloud.EventStreamRequest{ + EnvironmentId: ag.proContext.EnvID, + Accept: []*cloud.EventResource{{Id: "*", Type: "*"}}, + }, opts...) + if err != nil { + ag.logger.Errorf("failed to read events stream from Control Plane: %w", err) + return errors.Wrap(err, "failed to setup events stream") + } + + for { + if ctx.Err() != nil { + return ctx.Err() + } + ev, err := stream.Recv() + if err != nil { + // Ignore if it's not implemented in the Control Plane + if e, ok := err.(interface{ GRPCStatus() *status.Status }); ok && e.GRPCStatus().Code() == codes.Unimplemented { + return nil + } + return err + } + if ev.Resource == nil { + ev.Resource = &cloud.EventResource{} + } + tkEvent := testkube.Event{ + Id: ev.Id, + Resource: common.Ptr(testkube.EventResource(ev.Resource.Type)), + ResourceId: ev.Resource.Id, + Type_: common.Ptr(testkube.EventType(ev.Type)), + TestWorkflowExecution: nil, + External: true, + } + if ev.Resource.Type == string(testkube.TESTWORKFLOWEXECUTION_EventResource) { + var v testkube.TestWorkflowExecution + if err = json.Unmarshal(ev.Data, &v); err == nil { + tkEvent.TestWorkflowExecution = &v + } + } + ag.eventEmitter.Notify(tkEvent) + } +} + func (ag *Agent) sendResponse(ctx context.Context, stream cloud.TestKubeCloudAPI_ExecuteClient, resp *cloud.ExecuteResponse) error { errChan := make(chan error, 1) go func() { @@ -261,6 +406,13 @@ func (ag *Agent) runCommandLoop(ctx context.Context) error { ctx = metadata.AppendToOutgoingContext(ctx, orgIdMeta, ag.proContext.OrgID) ctx = metadata.AppendToOutgoingContext(ctx, dockerImageVersionMeta, ag.dockerImageVersion) + if ag.featureNewExecutions { + ctx = metadata.AppendToOutgoingContext(ctx, newExecutionsMeta, "true") + } + if ag.featureTestWorkflowCloudStorage { + ctx = metadata.AppendToOutgoingContext(ctx, testWorkflowStorageMeta, "true") + } + ag.logger.Infow("initiating streaming connection with control plane") // creates a new Stream from the client side. ctx is used for the lifetime of the stream. opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index 8df8f8e888d..d6f4af78193 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -65,7 +65,7 @@ func TestCommandExecution(t *testing.T) { logger, _ := zap.NewDevelopment() proContext := config.ProContext{APIKey: "api-key", WorkerCount: 5, LogStreamWorkerCount: 5, WorkflowNotificationsWorkerCount: 5} agent, err := agent.NewAgent(logger.Sugar(), m, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, - workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") + workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "", nil, nil, false, false) if err != nil { t.Fatal(err) } diff --git a/pkg/agent/events.go b/pkg/agent/events.go index c0ad490bd46..600a4d0e242 100644 --- a/pkg/agent/events.go +++ b/pkg/agent/events.go @@ -50,6 +50,11 @@ func (ag *Agent) Metadata() map[string]string { } func (ag *Agent) Notify(event testkube.Event) (result testkube.EventResult) { + // Avoid re-delivering Control Plane's event back to Control Plane + if event.External { + return testkube.NewSuccessEventResult(event.Id, "ignored external event") + } + event.ClusterName = ag.clusterName // Non blocking send select { diff --git a/pkg/agent/events_test.go b/pkg/agent/events_test.go index 603c72e07af..65260176e23 100644 --- a/pkg/agent/events_test.go +++ b/pkg/agent/events_test.go @@ -61,7 +61,7 @@ func TestEventLoop(t *testing.T) { proContext := config.ProContext{APIKey: "api-key", WorkerCount: 5, LogStreamWorkerCount: 5, WorkflowNotificationsWorkerCount: 5} agent, err := agent.NewAgent(logger.Sugar(), nil, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, - workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") + workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "", nil, nil, false, false) assert.NoError(t, err) go func() { l, err := agent.Load() diff --git a/pkg/agent/logs_test.go b/pkg/agent/logs_test.go index 4b66726f684..4c488e1f4c3 100644 --- a/pkg/agent/logs_test.go +++ b/pkg/agent/logs_test.go @@ -72,7 +72,7 @@ func TestLogStream(t *testing.T) { logger, _ := zap.NewDevelopment() proContext := config.ProContext{APIKey: "api-key", WorkerCount: 5, LogStreamWorkerCount: 5, WorkflowNotificationsWorkerCount: 5} agent, err := agent.NewAgent(logger.Sugar(), m, grpcClient, logStreamFunc, workflowNotificationsStreamFunc, - workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "") + workflowServiceNotificationsStreamFunc, workflowParallelStepNotificationsStreamFunc, "", "", featureflags.FeatureFlags{}, &proContext, "", nil, nil, false, false) if err != nil { t.Fatal(err) } diff --git a/pkg/api/v1/client/api.go b/pkg/api/v1/client/api.go index 265d49e0b78..01c542e343a 100644 --- a/pkg/api/v1/client/api.go +++ b/pkg/api/v1/client/api.go @@ -44,6 +44,7 @@ func NewProxyAPIClient(client kubernetes.Interface, config APIConfig) APIClient ), TestWorkflowTemplateClient: NewTestWorkflowTemplateClient(NewProxyClient[testkube.TestWorkflowTemplate](client, config)), TestTriggerClient: NewTestTriggerClient(NewProxyClient[testkube.TestTrigger](client, config)), + SharedClient: NewSharedClient(NewProxyClient[map[string][]string](client, config)), } } @@ -83,6 +84,7 @@ func NewDirectAPIClient(httpClient *http.Client, sseClient *http.Client, apiURI, ), TestWorkflowTemplateClient: NewTestWorkflowTemplateClient(NewDirectClient[testkube.TestWorkflowTemplate](httpClient, apiURI, apiPathPrefix)), TestTriggerClient: NewTestTriggerClient(NewDirectClient[testkube.TestTrigger](httpClient, apiURI, apiPathPrefix)), + SharedClient: NewSharedClient(NewDirectClient[map[string][]string](httpClient, apiURI, apiPathPrefix)), } } @@ -122,6 +124,7 @@ func NewCloudAPIClient(httpClient *http.Client, sseClient *http.Client, apiURI, ), TestWorkflowTemplateClient: NewTestWorkflowTemplateClient(NewCloudClient[testkube.TestWorkflowTemplate](httpClient, apiURI, apiPathPrefix)), TestTriggerClient: NewTestTriggerClient(NewCloudClient[testkube.TestTrigger](httpClient, apiURI, apiPathPrefix)), + SharedClient: NewSharedClient(NewCloudClient[map[string][]string](httpClient, apiURI, apiPathPrefix)), } } @@ -138,6 +141,7 @@ type APIClient struct { TestWorkflowClient TestWorkflowTemplateClient TestTriggerClient + SharedClient } // check in compile time if interface is implemented diff --git a/pkg/api/v1/client/interface.go b/pkg/api/v1/client/interface.go index 056844022e3..e2470e0f900 100644 --- a/pkg/api/v1/client/interface.go +++ b/pkg/api/v1/client/interface.go @@ -25,6 +25,7 @@ type Client interface { TestWorkflowExecutionAPI TestWorkflowTemplateAPI TestTriggerAPI + SharedAPI } // TestAPI describes test api methods @@ -140,6 +141,10 @@ type TestSourceAPI interface { DeleteTestSources(selector string) (err error) } +type SharedAPI interface { + ListLabels() (labels map[string][]string, err error) +} + // TestWorkflowAPI describes test workflow api methods type TestWorkflowAPI interface { GetTestWorkflow(id string) (testkube.TestWorkflow, error) @@ -304,7 +309,7 @@ type Gettable interface { testkube.TestSuiteWithExecutionSummary | testkube.Artifact | testkube.ServerInfo | testkube.Config | testkube.DebugInfo | testkube.TestSource | testkube.Template | testkube.TestWorkflow | testkube.TestWorkflowWithExecution | testkube.TestWorkflowTemplate | testkube.TestWorkflowExecution | - testkube.TestTrigger + testkube.TestTrigger | map[string][]string } // Executable is an interface of executable objects diff --git a/pkg/api/v1/client/shared.go b/pkg/api/v1/client/shared.go new file mode 100644 index 00000000000..b2daa382320 --- /dev/null +++ b/pkg/api/v1/client/shared.go @@ -0,0 +1,25 @@ +package client + +import ( + "net/http" +) + +// NewSharedClient creates new client for some common methods +func NewSharedClient( + labelsTransport Transport[map[string][]string], +) SharedClient { + return SharedClient{ + labelsTransport: labelsTransport, + } +} + +// SharedClient is a client for test workflows +type SharedClient struct { + labelsTransport Transport[map[string][]string] +} + +// ListLabels returns map of labels +func (c SharedClient) ListLabels() (map[string][]string, error) { + uri := c.labelsTransport.GetURI("/labels") + return c.labelsTransport.Execute(http.MethodGet, uri, nil, nil) +} diff --git a/pkg/api/v1/testkube/model_event.go b/pkg/api/v1/testkube/model_event.go index 1e6e58cc61f..1a520866083 100644 --- a/pkg/api/v1/testkube/model_event.go +++ b/pkg/api/v1/testkube/model_event.go @@ -25,5 +25,6 @@ type Event struct { // cluster name of event ClusterName string `json:"clusterName,omitempty"` // environment variables - Envs map[string]string `json:"envs,omitempty"` + Envs map[string]string `json:"envs,omitempty"` + External bool `json:"external,omitempty"` } diff --git a/pkg/api/v1/testkube/model_test_workflow_execution.go b/pkg/api/v1/testkube/model_test_workflow_execution.go index 0801dee29bc..bb6d150705d 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution.go @@ -18,6 +18,8 @@ type TestWorkflowExecution struct { Id string `json:"id"` // identifier for group of correlated executions GroupId string `json:"groupId,omitempty"` + // identifier of the runner where it has been executed + RunnerId string `json:"runnerId,omitempty"` // execution name Name string `json:"name"` // execution namespace diff --git a/pkg/api/v1/testkube/model_test_workflow_execution_extended.go b/pkg/api/v1/testkube/model_test_workflow_execution_extended.go index 4b758c5abdc..9b381fe580f 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution_extended.go @@ -1,6 +1,7 @@ package testkube import ( + "encoding/json" "fmt" "github.com/gookit/color" @@ -130,3 +131,17 @@ func (e *TestWorkflowExecution) GetParallelStepReference(nameOrReference string) return "" } + +func (e *TestWorkflowExecution) Assigned() bool { + return e.Result.IsFinished() || len(e.Signature) > 0 +} + +func (e *TestWorkflowExecution) Clone() *TestWorkflowExecution { + if e == nil { + return nil + } + v, _ := json.Marshal(e) + result := TestWorkflowExecution{} + _ = json.Unmarshal(v, &result) + return &result +} 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 7e3f92bd92a..9fdf6b8d0d7 100644 --- a/pkg/api/v1/testkube/model_test_workflow_execution_summary.go +++ b/pkg/api/v1/testkube/model_test_workflow_execution_summary.go @@ -16,6 +16,8 @@ import ( type TestWorkflowExecutionSummary struct { // unique execution identifier Id string `json:"id"` + // identifier for group of correlated executions + GroupId string `json:"groupId,omitempty"` // execution name Name string `json:"name"` // sequence number for the execution diff --git a/pkg/api/v1/testkube/model_test_workflow_extended.go b/pkg/api/v1/testkube/model_test_workflow_extended.go index 846218c83e2..cf251bc58c7 100644 --- a/pkg/api/v1/testkube/model_test_workflow_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_extended.go @@ -1,6 +1,10 @@ package testkube -import "github.com/kubeshop/testkube/pkg/utils" +import ( + "encoding/json" + + "github.com/kubeshop/testkube/pkg/utils" +) type TestWorkflows []TestWorkflow @@ -115,3 +119,13 @@ func (w TestWorkflow) HasService(name string) bool { return false } + +func (w *TestWorkflow) DeepCopy() *TestWorkflow { + if w == nil { + return nil + } + v, _ := json.Marshal(w) + var result TestWorkflow + _ = json.Unmarshal(v, &result) + return &result +} diff --git a/pkg/api/v1/testkube/model_test_workflow_template_extended.go b/pkg/api/v1/testkube/model_test_workflow_template_extended.go index 86a7652977c..3aec1d0bedc 100644 --- a/pkg/api/v1/testkube/model_test_workflow_template_extended.go +++ b/pkg/api/v1/testkube/model_test_workflow_template_extended.go @@ -1,6 +1,7 @@ package testkube import ( + "encoding/json" "strings" "github.com/kubeshop/testkube/pkg/utils" @@ -80,3 +81,13 @@ func (w *TestWorkflowTemplate) ConvertDots(fn func(string) string) *TestWorkflow return w } + +func (w *TestWorkflowTemplate) DeepCopy() *TestWorkflowTemplate { + if w == nil { + return nil + } + v, _ := json.Marshal(w) + var result TestWorkflowTemplate + _ = json.Unmarshal(v, &result) + return &result +} diff --git a/pkg/capabilities/capabilities.go b/pkg/capabilities/capabilities.go index e30d9613f65..39be7f95d0d 100644 --- a/pkg/capabilities/capabilities.go +++ b/pkg/capabilities/capabilities.go @@ -5,6 +5,8 @@ import "github.com/kubeshop/testkube/pkg/cloud" type Capability string const CapabilityJUnitReports Capability = "junit-reports" +const CapabilityNewExecutions Capability = "exec" +const CapabilityTestWorkflowStorage Capability = "tw-storage" func Enabled(capabilities []*cloud.Capability, capability Capability) bool { for _, c := range capabilities { diff --git a/pkg/cloud/data/testworkflow/execution.go b/pkg/cloud/data/testworkflow/execution.go index 46b0c17c6de..2a19114ee86 100644 --- a/pkg/cloud/data/testworkflow/execution.go +++ b/pkg/cloud/data/testworkflow/execution.go @@ -3,6 +3,7 @@ package testworkflow import ( "context" "encoding/json" + "errors" "time" "google.golang.org/grpc" @@ -177,3 +178,21 @@ func (r *CloudRepository) GetExecutionTags(ctx context.Context, testWorkflowName } return pass(r.executor, ctx, req, process) } + +// Init sets the initialization data from the runner +// Prefer scheduling directly with TestKubeCloudAPI/ScheduleExecution operation. +// This one is a workaround for older Control Planes. It's not recommended, as it may cause race conditions. +func (r *CloudRepository) Init(ctx context.Context, id string, data testworkflow2.InitData) (err error) { + execution, err := r.Get(ctx, id) + if err != nil { + return + } + execution.Namespace = data.Namespace + execution.Signature = data.Signature + execution.RunnerId = data.RunnerID + return r.Update(ctx, execution) +} + +func (r *CloudRepository) GetUnassigned(ctx context.Context) (result []testkube.TestWorkflowExecution, err error) { + return nil, errors.New("not supported") +} diff --git a/pkg/cloud/data/testworkflow/output.go b/pkg/cloud/data/testworkflow/output.go index 1ae07c6c968..13fe38ccd3c 100644 --- a/pkg/cloud/data/testworkflow/output.go +++ b/pkg/cloud/data/testworkflow/output.go @@ -23,20 +23,12 @@ type CloudOutputRepository struct { httpClient *http.Client } -type Option func(*CloudOutputRepository) - -func WithSkipVerify() Option { - return func(r *CloudOutputRepository) { +func NewCloudOutputRepository(client cloud.TestKubeCloudAPIClient, grpcConn *grpc.ClientConn, apiKey string, skipVerify bool) *CloudOutputRepository { + r := &CloudOutputRepository{executor: executor.NewCloudGRPCExecutor(client, grpcConn, apiKey), httpClient: http.DefaultClient} + if skipVerify { transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} r.httpClient.Transport = transport } -} - -func NewCloudOutputRepository(client cloud.TestKubeCloudAPIClient, grpcConn *grpc.ClientConn, apiKey string, opts ...Option) *CloudOutputRepository { - r := &CloudOutputRepository{executor: executor.NewCloudGRPCExecutor(client, grpcConn, apiKey), httpClient: http.DefaultClient} - for _, opt := range opts { - opt(r) - } return r } diff --git a/pkg/cloud/data/testworkflow/templates.go b/pkg/cloud/data/testworkflow/templates.go deleted file mode 100644 index cfdae086d46..00000000000 --- a/pkg/cloud/data/testworkflow/templates.go +++ /dev/null @@ -1,77 +0,0 @@ -package testworkflow - -import ( - "context" - "encoding/json" - - testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" - testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" - "github.com/kubeshop/testkube/pkg/cloud" - "github.com/kubeshop/testkube/pkg/cloud/data/executor" - testworkflowmappers "github.com/kubeshop/testkube/pkg/mapper/testworkflows" - - "github.com/pkg/errors" - "google.golang.org/grpc" -) - -var _ testworkflowsclientv1.TestWorkflowTemplatesInterface = (*CloudTestWorkflowTemplateRepository)(nil) - -type CloudTestWorkflowTemplateRepository struct { - executor executor.Executor -} - -func NewCloudTestWorkflowTemplateRepository(client cloud.TestKubeCloudAPIClient, grpcConn *grpc.ClientConn, apiKey string) *CloudTestWorkflowTemplateRepository { - return &CloudTestWorkflowTemplateRepository{executor: executor.NewCloudGRPCExecutor(client, grpcConn, apiKey)} -} - -func (r *CloudTestWorkflowTemplateRepository) List(selector string) (*testworkflowsv1.TestWorkflowTemplateList, error) { - return nil, errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) ListLabels() (map[string][]string, error) { - return make(map[string][]string), nil -} - -func (r *CloudTestWorkflowTemplateRepository) Get(name string) (*testworkflowsv1.TestWorkflowTemplate, error) { - req := TestWorkflowTemplateGetRequest{Name: name} - response, err := r.executor.Execute(context.Background(), CmdTestWorkflowTemplateGet, req) - if err != nil { - return nil, err - } - var commandResponse TestWorkflowTemplateGetResponse - if err := json.Unmarshal(response, &commandResponse); err != nil { - return nil, err - } - return testworkflowmappers.MapTemplateAPIToKube(&commandResponse.TestWorkflowTemplate), nil -} - -// Create creates new TestWorkflow -func (r *CloudTestWorkflowTemplateRepository) Create(workflow *testworkflowsv1.TestWorkflowTemplate) (*testworkflowsv1.TestWorkflowTemplate, error) { - return nil, errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) Update(workflow *testworkflowsv1.TestWorkflowTemplate) (*testworkflowsv1.TestWorkflowTemplate, error) { - return nil, errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) Apply(workflow *testworkflowsv1.TestWorkflowTemplate) error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) Delete(name string) error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) DeleteAll() error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) DeleteByLabels(selector string) error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowTemplateRepository) UpdateStatus(workflow *testworkflowsv1.TestWorkflowTemplate) error { - // This is the actual implementation, as update status - // should update k8s crd's status field, but we don't have it when stored in mongo - return nil -} diff --git a/pkg/cloud/data/testworkflow/workflows.go b/pkg/cloud/data/testworkflow/workflows.go deleted file mode 100644 index c04474b2c5d..00000000000 --- a/pkg/cloud/data/testworkflow/workflows.go +++ /dev/null @@ -1,77 +0,0 @@ -package testworkflow - -import ( - "context" - "encoding/json" - - testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" - testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" - "github.com/kubeshop/testkube/pkg/cloud" - "github.com/kubeshop/testkube/pkg/cloud/data/executor" - testworkflowmappers "github.com/kubeshop/testkube/pkg/mapper/testworkflows" - - "github.com/pkg/errors" - "google.golang.org/grpc" -) - -var _ testworkflowsclientv1.Interface = (*CloudTestWorkflowRepository)(nil) - -type CloudTestWorkflowRepository struct { - executor executor.Executor -} - -func NewCloudTestWorkflowRepository(client cloud.TestKubeCloudAPIClient, grpcConn *grpc.ClientConn, apiKey string) *CloudTestWorkflowRepository { - return &CloudTestWorkflowRepository{executor: executor.NewCloudGRPCExecutor(client, grpcConn, apiKey)} -} - -func (r *CloudTestWorkflowRepository) List(selector string) (*testworkflowsv1.TestWorkflowList, error) { - return nil, errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) ListLabels() (map[string][]string, error) { - return make(map[string][]string), errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) Get(name string) (*testworkflowsv1.TestWorkflow, error) { - req := TestWorkflowGetRequest{Name: name} - response, err := r.executor.Execute(context.Background(), CmdTestWorkflowGet, req) - if err != nil { - return nil, err - } - var commandResponse TestWorkflowGetResponse - if err := json.Unmarshal(response, &commandResponse); err != nil { - return nil, err - } - return testworkflowmappers.MapAPIToKube(&commandResponse.TestWorkflow), nil -} - -// Create creates new TestWorkflow -func (r *CloudTestWorkflowRepository) Create(workflow *testworkflowsv1.TestWorkflow) (*testworkflowsv1.TestWorkflow, error) { - return nil, errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) Update(workflow *testworkflowsv1.TestWorkflow) (*testworkflowsv1.TestWorkflow, error) { - return nil, errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) Apply(workflow *testworkflowsv1.TestWorkflow) error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) Delete(name string) error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) DeleteAll() error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) DeleteByLabels(selector string) error { - return errors.New("unimplemented") -} - -func (r *CloudTestWorkflowRepository) UpdateStatus(workflow *testworkflowsv1.TestWorkflow) error { - // This is the actual implementation, as update status - // should update k8s crd's status field, but we don't have it when stored in mongo - return nil -} diff --git a/pkg/cloud/service.pb.go b/pkg/cloud/service.pb.go index 5bb59b2773e..67e74614313 100644 --- a/pkg/cloud/service.pb.go +++ b/pkg/cloud/service.pb.go @@ -218,6 +218,70 @@ func (Opcode) EnumDescriptor() ([]byte, []int) { return file_proto_service_proto_rawDescGZIP(), []int{3} } +type RunningContextType int32 + +const ( + RunningContextType_UNKNOWN RunningContextType = 0 + RunningContextType_UI RunningContextType = 1 + RunningContextType_CLI RunningContextType = 2 + RunningContextType_CICD RunningContextType = 3 + RunningContextType_CRON RunningContextType = 4 + RunningContextType_TESTTRIGGER RunningContextType = 5 + RunningContextType_KUBERNETESOBJECT RunningContextType = 6 + RunningContextType_EXECUTION RunningContextType = 7 +) + +// Enum value maps for RunningContextType. +var ( + RunningContextType_name = map[int32]string{ + 0: "UNKNOWN", + 1: "UI", + 2: "CLI", + 3: "CICD", + 4: "CRON", + 5: "TESTTRIGGER", + 6: "KUBERNETESOBJECT", + 7: "EXECUTION", + } + RunningContextType_value = map[string]int32{ + "UNKNOWN": 0, + "UI": 1, + "CLI": 2, + "CICD": 3, + "CRON": 4, + "TESTTRIGGER": 5, + "KUBERNETESOBJECT": 6, + "EXECUTION": 7, + } +) + +func (x RunningContextType) Enum() *RunningContextType { + p := new(RunningContextType) + *p = x + return p +} + +func (x RunningContextType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (RunningContextType) Descriptor() protoreflect.EnumDescriptor { + return file_proto_service_proto_enumTypes[4].Descriptor() +} + +func (RunningContextType) Type() protoreflect.EnumType { + return &file_proto_service_proto_enumTypes[4] +} + +func (x RunningContextType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use RunningContextType.Descriptor instead. +func (RunningContextType) EnumDescriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{4} +} + type LogsStreamRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1408,359 +1472,3148 @@ func (x *TestWorkflowParallelStepNotificationsResponse) GetMessage() string { return "" } -var File_proto_service_proto protoreflect.FileDescriptor +type ScheduleResourceSelector struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields -var file_proto_service_proto_rawDesc = []byte{ - 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x1a, 0x1b, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, - 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x01, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x73, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, - 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3f, 0x0a, - 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x73, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x84, - 0x01, 0x0a, 0x12, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, - 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, - 0x6c, 0x6f, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, - 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x5d, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, - 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x12, 0x31, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x61, 0x79, - 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x2d, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x0e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, - 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, - 0x12, 0x3c, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, - 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, - 0x64, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, - 0x64, 0x1a, 0x4e, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0xb2, 0x01, 0x0a, 0x20, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x4e, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, - 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xda, 0x01, 0x0a, 0x21, 0x54, 0x65, 0x73, 0x74, 0x57, - 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, - 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, - 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, - 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, - 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x22, 0x79, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, - 0x12, 0x15, 0x0a, 0x06, 0x65, 0x6e, 0x76, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x05, 0x65, 0x6e, 0x76, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, - 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, - 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3a, - 0x0a, 0x0a, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x25, 0x0a, 0x0b, 0x48, 0x65, - 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x22, 0xeb, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3d, 0x0a, - 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, - 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, - 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x64, 0x1a, - 0x4e, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x4a, 0x0a, 0x0d, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, - 0x12, 0x25, 0x0a, 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x0d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x52, - 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x4a, 0x0a, 0x11, 0x43, - 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x64, 0x65, - 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x81, 0x02, 0x0a, 0x27, 0x54, 0x65, 0x73, 0x74, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, - 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, - 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4e, 0x0a, 0x0c, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xe1, 0x01, 0x0a, 0x28, - 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, - 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x6c, 0x6f, - 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, - 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, - 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, - 0xf3, 0x01, 0x0a, 0x2c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, - 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, - 0x65, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, - 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4e, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6c, - 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x2d, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, - 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, - 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2a, 0x48, - 0x0a, 0x15, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x54, 0x52, 0x45, 0x41, - 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, - 0x17, 0x0a, 0x13, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, - 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x01, 0x2a, 0x69, 0x0a, 0x24, 0x54, 0x65, 0x73, 0x74, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x1f, 0x0a, 0x1b, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, - 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, - 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, - 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, - 0x4b, 0x10, 0x01, 0x2a, 0x8a, 0x01, 0x0a, 0x1c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, - 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, - 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, - 0x17, 0x0a, 0x13, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, - 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x57, 0x4f, 0x52, 0x4b, - 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x55, - 0x4c, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, - 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4f, 0x55, 0x54, 0x50, 0x55, 0x54, 0x10, 0x03, - 0x2a, 0x4c, 0x0a, 0x06, 0x4f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, - 0x53, 0x50, 0x45, 0x43, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, - 0x58, 0x54, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, - 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, - 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x03, 0x32, 0x88, - 0x07, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, 0x4b, 0x75, 0x62, 0x65, 0x43, 0x6c, 0x6f, 0x75, 0x64, - 0x41, 0x50, 0x49, 0x12, 0x3c, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x16, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, - 0x01, 0x12, 0x36, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, 0x35, 0x0a, 0x04, 0x43, 0x61, 0x6c, - 0x6c, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, - 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x41, 0x0a, 0x0c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x41, 0x73, 0x79, 0x6e, 0x63, - 0x12, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, - 0x01, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, - 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, - 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x7b, 0x0a, - 0x22, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x12, 0x28, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, - 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x27, 0x2e, - 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x90, 0x01, 0x0a, 0x29, 0x47, - 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x2e, 0x2e, 0x63, 0x6c, 0x6f, 0x75, - 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x9f, 0x01, - 0x0a, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x12, 0x34, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, - 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, - 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x33, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, - 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, - 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, - 0x42, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, - 0x2e, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x61, 0x6c, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, - 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, - 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, - 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, 0x09, 0x70, 0x6b, 0x67, - 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` } -var ( - file_proto_service_proto_rawDescOnce sync.Once - file_proto_service_proto_rawDescData = file_proto_service_proto_rawDesc -) +func (x *ScheduleResourceSelector) Reset() { + *x = ScheduleResourceSelector{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} -func file_proto_service_proto_rawDescGZIP() []byte { - file_proto_service_proto_rawDescOnce.Do(func() { - file_proto_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_service_proto_rawDescData) - }) - return file_proto_service_proto_rawDescData +func (x *ScheduleResourceSelector) String() string { + return protoimpl.X.MessageStringOf(x) } -var file_proto_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4) -var file_proto_service_proto_msgTypes = make([]protoimpl.MessageInfo, 20) -var file_proto_service_proto_goTypes = []interface{}{ - (LogsStreamRequestType)(0), // 0: cloud.LogsStreamRequestType - (TestWorkflowNotificationsRequestType)(0), // 1: cloud.TestWorkflowNotificationsRequestType - (TestWorkflowNotificationType)(0), // 2: cloud.TestWorkflowNotificationType - (Opcode)(0), // 3: cloud.Opcode - (*LogsStreamRequest)(nil), // 4: cloud.LogsStreamRequest - (*LogsStreamResponse)(nil), // 5: cloud.LogsStreamResponse - (*CommandRequest)(nil), // 6: cloud.CommandRequest - (*CommandResponse)(nil), // 7: cloud.CommandResponse - (*ExecuteRequest)(nil), // 8: cloud.ExecuteRequest - (*TestWorkflowNotificationsRequest)(nil), // 9: cloud.TestWorkflowNotificationsRequest - (*TestWorkflowNotificationsResponse)(nil), // 10: cloud.TestWorkflowNotificationsResponse - (*ProContextResponse)(nil), // 11: cloud.ProContextResponse - (*Capability)(nil), // 12: cloud.Capability - (*HeaderValue)(nil), // 13: cloud.HeaderValue - (*ExecuteResponse)(nil), // 14: cloud.ExecuteResponse - (*WebsocketData)(nil), // 15: cloud.WebsocketData - (*CredentialRequest)(nil), // 16: cloud.CredentialRequest - (*CredentialResponse)(nil), // 17: cloud.CredentialResponse - (*TestWorkflowServiceNotificationsRequest)(nil), // 18: cloud.TestWorkflowServiceNotificationsRequest - (*TestWorkflowServiceNotificationsResponse)(nil), // 19: cloud.TestWorkflowServiceNotificationsResponse - (*TestWorkflowParallelStepNotificationsRequest)(nil), // 20: cloud.TestWorkflowParallelStepNotificationsRequest - (*TestWorkflowParallelStepNotificationsResponse)(nil), // 21: cloud.TestWorkflowParallelStepNotificationsResponse - nil, // 22: cloud.ExecuteRequest.HeadersEntry - nil, // 23: cloud.ExecuteResponse.HeadersEntry - (*structpb.Struct)(nil), // 24: google.protobuf.Struct - (*emptypb.Empty)(nil), // 25: google.protobuf.Empty +func (*ScheduleResourceSelector) ProtoMessage() {} + +func (x *ScheduleResourceSelector) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) } -var file_proto_service_proto_depIdxs = []int32{ - 0, // 0: cloud.LogsStreamRequest.request_type:type_name -> cloud.LogsStreamRequestType - 24, // 1: cloud.CommandRequest.payload:type_name -> google.protobuf.Struct - 22, // 2: cloud.ExecuteRequest.headers:type_name -> cloud.ExecuteRequest.HeadersEntry - 1, // 3: cloud.TestWorkflowNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType - 2, // 4: cloud.TestWorkflowNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType - 12, // 5: cloud.ProContextResponse.capabilities:type_name -> cloud.Capability - 23, // 6: cloud.ExecuteResponse.headers:type_name -> cloud.ExecuteResponse.HeadersEntry - 3, // 7: cloud.WebsocketData.opcode:type_name -> cloud.Opcode - 1, // 8: cloud.TestWorkflowServiceNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType - 2, // 9: cloud.TestWorkflowServiceNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType - 1, // 10: cloud.TestWorkflowParallelStepNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType - 2, // 11: cloud.TestWorkflowParallelStepNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType - 13, // 12: cloud.ExecuteRequest.HeadersEntry.value:type_name -> cloud.HeaderValue - 13, // 13: cloud.ExecuteResponse.HeadersEntry.value:type_name -> cloud.HeaderValue - 14, // 14: cloud.TestKubeCloudAPI.Execute:input_type -> cloud.ExecuteResponse - 15, // 15: cloud.TestKubeCloudAPI.Send:input_type -> cloud.WebsocketData - 6, // 16: cloud.TestKubeCloudAPI.Call:input_type -> cloud.CommandRequest - 14, // 17: cloud.TestKubeCloudAPI.ExecuteAsync:input_type -> cloud.ExecuteResponse - 5, // 18: cloud.TestKubeCloudAPI.GetLogsStream:input_type -> cloud.LogsStreamResponse - 10, // 19: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:input_type -> cloud.TestWorkflowNotificationsResponse - 19, // 20: cloud.TestKubeCloudAPI.GetTestWorkflowServiceNotificationsStream:input_type -> cloud.TestWorkflowServiceNotificationsResponse - 21, // 21: cloud.TestKubeCloudAPI.GetTestWorkflowParallelStepNotificationsStream:input_type -> cloud.TestWorkflowParallelStepNotificationsResponse - 25, // 22: cloud.TestKubeCloudAPI.GetProContext:input_type -> google.protobuf.Empty - 16, // 23: cloud.TestKubeCloudAPI.GetCredential:input_type -> cloud.CredentialRequest - 8, // 24: cloud.TestKubeCloudAPI.Execute:output_type -> cloud.ExecuteRequest - 25, // 25: cloud.TestKubeCloudAPI.Send:output_type -> google.protobuf.Empty - 7, // 26: cloud.TestKubeCloudAPI.Call:output_type -> cloud.CommandResponse - 8, // 27: cloud.TestKubeCloudAPI.ExecuteAsync:output_type -> cloud.ExecuteRequest - 4, // 28: cloud.TestKubeCloudAPI.GetLogsStream:output_type -> cloud.LogsStreamRequest - 9, // 29: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:output_type -> cloud.TestWorkflowNotificationsRequest - 18, // 30: cloud.TestKubeCloudAPI.GetTestWorkflowServiceNotificationsStream:output_type -> cloud.TestWorkflowServiceNotificationsRequest - 20, // 31: cloud.TestKubeCloudAPI.GetTestWorkflowParallelStepNotificationsStream:output_type -> cloud.TestWorkflowParallelStepNotificationsRequest - 11, // 32: cloud.TestKubeCloudAPI.GetProContext:output_type -> cloud.ProContextResponse - 17, // 33: cloud.TestKubeCloudAPI.GetCredential:output_type -> cloud.CredentialResponse - 24, // [24:34] is the sub-list for method output_type - 14, // [14:24] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + +// Deprecated: Use ScheduleResourceSelector.ProtoReflect.Descriptor instead. +func (*ScheduleResourceSelector) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{18} } -func init() { file_proto_service_proto_init() } -func file_proto_service_proto_init() { - if File_proto_service_proto != nil { - return +func (x *ScheduleResourceSelector) GetName() string { + if x != nil { + return x.Name } - if !protoimpl.UnsafeEnabled { + return "" +} + +func (x *ScheduleResourceSelector) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +type ScheduleExecution struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Selector *ScheduleResourceSelector `protobuf:"bytes,1,opt,name=selector,proto3" json:"selector,omitempty"` + Config map[string]string `protobuf:"bytes,2,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + ExecutionName string `protobuf:"bytes,3,opt,name=execution_name,json=executionName,proto3" json:"execution_name,omitempty"` + Tags map[string]string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ScheduleExecution) Reset() { + *x = ScheduleExecution{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScheduleExecution) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScheduleExecution) ProtoMessage() {} + +func (x *ScheduleExecution) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScheduleExecution.ProtoReflect.Descriptor instead. +func (*ScheduleExecution) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{19} +} + +func (x *ScheduleExecution) GetSelector() *ScheduleResourceSelector { + if x != nil { + return x.Selector + } + return nil +} + +func (x *ScheduleExecution) GetConfig() map[string]string { + if x != nil { + return x.Config + } + return nil +} + +func (x *ScheduleExecution) GetExecutionName() string { + if x != nil { + return x.ExecutionName + } + return "" +} + +func (x *ScheduleExecution) GetTags() map[string]string { + if x != nil { + return x.Tags + } + return nil +} + +type RunningContext struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type RunningContextType `protobuf:"varint,2,opt,name=type,proto3,enum=cloud.RunningContextType" json:"type,omitempty"` +} + +func (x *RunningContext) Reset() { + *x = RunningContext{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RunningContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunningContext) ProtoMessage() {} + +func (x *RunningContext) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunningContext.ProtoReflect.Descriptor instead. +func (*RunningContext) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{20} +} + +func (x *RunningContext) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *RunningContext) GetType() RunningContextType { + if x != nil { + return x.Type + } + return RunningContextType_UNKNOWN +} + +type UserSignature struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` +} + +func (x *UserSignature) Reset() { + *x = UserSignature{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UserSignature) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserSignature) ProtoMessage() {} + +func (x *UserSignature) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[21] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserSignature.ProtoReflect.Descriptor instead. +func (*UserSignature) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{21} +} + +func (x *UserSignature) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UserSignature) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +type ScheduleRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Target + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + // Test Workflow details + Executions []*ScheduleExecution `protobuf:"bytes,2,rep,name=executions,proto3" json:"executions,omitempty"` + // Execution details + DisableWebhooks bool `protobuf:"varint,3,opt,name=disable_webhooks,json=disableWebhooks,proto3" json:"disable_webhooks,omitempty"` + Tags map[string]string `protobuf:"bytes,4,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Running metadata + RunningContext *RunningContext `protobuf:"bytes,5,opt,name=running_context,json=runningContext,proto3" json:"running_context,omitempty"` + ParentExecutionIds []string `protobuf:"bytes,7,rep,name=parent_execution_ids,json=parentExecutionIds,proto3" json:"parent_execution_ids,omitempty"` + User *UserSignature `protobuf:"bytes,8,opt,name=user,proto3,oneof" json:"user,omitempty"` // keep in mind that it should not be trusted + // Kubernetes resource TODO: is it required? + KubernetesObjectName string `protobuf:"bytes,9,opt,name=kubernetes_object_name,json=kubernetesObjectName,proto3" json:"kubernetes_object_name,omitempty"` +} + +func (x *ScheduleRequest) Reset() { + *x = ScheduleRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScheduleRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScheduleRequest) ProtoMessage() {} + +func (x *ScheduleRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[22] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScheduleRequest.ProtoReflect.Descriptor instead. +func (*ScheduleRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{22} +} + +func (x *ScheduleRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *ScheduleRequest) GetExecutions() []*ScheduleExecution { + if x != nil { + return x.Executions + } + return nil +} + +func (x *ScheduleRequest) GetDisableWebhooks() bool { + if x != nil { + return x.DisableWebhooks + } + return false +} + +func (x *ScheduleRequest) GetTags() map[string]string { + if x != nil { + return x.Tags + } + return nil +} + +func (x *ScheduleRequest) GetRunningContext() *RunningContext { + if x != nil { + return x.RunningContext + } + return nil +} + +func (x *ScheduleRequest) GetParentExecutionIds() []string { + if x != nil { + return x.ParentExecutionIds + } + return nil +} + +func (x *ScheduleRequest) GetUser() *UserSignature { + if x != nil { + return x.User + } + return nil +} + +func (x *ScheduleRequest) GetKubernetesObjectName() string { + if x != nil { + return x.KubernetesObjectName + } + return "" +} + +type ScheduleResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Execution []byte `protobuf:"bytes,1,opt,name=execution,proto3" json:"execution,omitempty"` // TestWorkflowExecution +} + +func (x *ScheduleResponse) Reset() { + *x = ScheduleResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ScheduleResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScheduleResponse) ProtoMessage() {} + +func (x *ScheduleResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[23] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScheduleResponse.ProtoReflect.Descriptor instead. +func (*ScheduleResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{23} +} + +func (x *ScheduleResponse) GetExecution() []byte { + if x != nil { + return x.Execution + } + return nil +} + +type EventResource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *EventResource) Reset() { + *x = EventResource{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EventResource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventResource) ProtoMessage() {} + +func (x *EventResource) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[24] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventResource.ProtoReflect.Descriptor instead. +func (*EventResource) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{24} +} + +func (x *EventResource) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *EventResource) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type EventStreamRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Accept []*EventResource `protobuf:"bytes,2,rep,name=accept,proto3" json:"accept,omitempty"` +} + +func (x *EventStreamRequest) Reset() { + *x = EventStreamRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *EventStreamRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EventStreamRequest) ProtoMessage() {} + +func (x *EventStreamRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[25] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EventStreamRequest.ProtoReflect.Descriptor instead. +func (*EventStreamRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{25} +} + +func (x *EventStreamRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *EventStreamRequest) GetAccept() []*EventResource { + if x != nil { + return x.Accept + } + return nil +} + +type Event struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Resource *EventResource `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Event) Reset() { + *x = Event{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Event) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Event) ProtoMessage() {} + +func (x *Event) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[26] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Event.ProtoReflect.Descriptor instead. +func (*Event) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{26} +} + +func (x *Event) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Event) GetResource() *EventResource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *Event) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Event) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +type RunnerRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *RunnerRequest) Reset() { + *x = RunnerRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RunnerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunnerRequest) ProtoMessage() {} + +func (x *RunnerRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[27] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunnerRequest.ProtoReflect.Descriptor instead. +func (*RunnerRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{27} +} + +func (x *RunnerRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *RunnerRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type RunnerResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Obtained bool `protobuf:"varint,3,opt,name=obtained,proto3" json:"obtained,omitempty"` + Error *string `protobuf:"bytes,4,opt,name=error,proto3,oneof" json:"error,omitempty"` +} + +func (x *RunnerResponse) Reset() { + *x = RunnerResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RunnerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunnerResponse) ProtoMessage() {} + +func (x *RunnerResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[28] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunnerResponse.ProtoReflect.Descriptor instead. +func (*RunnerResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{28} +} + +func (x *RunnerResponse) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *RunnerResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *RunnerResponse) GetObtained() bool { + if x != nil { + return x.Obtained + } + return false +} + +func (x *RunnerResponse) GetError() string { + if x != nil && x.Error != nil { + return *x.Error + } + return "" +} + +type ObtainExecutionRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ObtainExecutionRequest) Reset() { + *x = ObtainExecutionRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ObtainExecutionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObtainExecutionRequest) ProtoMessage() {} + +func (x *ObtainExecutionRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[29] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ObtainExecutionRequest.ProtoReflect.Descriptor instead. +func (*ObtainExecutionRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{29} +} + +func (x *ObtainExecutionRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *ObtainExecutionRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type ObtainExecutionResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Success bool `protobuf:"varint,3,opt,name=success,proto3" json:"success,omitempty"` +} + +func (x *ObtainExecutionResponse) Reset() { + *x = ObtainExecutionResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ObtainExecutionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ObtainExecutionResponse) ProtoMessage() {} + +func (x *ObtainExecutionResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[30] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ObtainExecutionResponse.ProtoReflect.Descriptor instead. +func (*ObtainExecutionResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{30} +} + +func (x *ObtainExecutionResponse) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *ObtainExecutionResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ObtainExecutionResponse) GetSuccess() bool { + if x != nil { + return x.Success + } + return false +} + +type GetTestWorkflowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetTestWorkflowRequest) Reset() { + *x = GetTestWorkflowRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTestWorkflowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTestWorkflowRequest) ProtoMessage() {} + +func (x *GetTestWorkflowRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[31] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTestWorkflowRequest.ProtoReflect.Descriptor instead. +func (*GetTestWorkflowRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{31} +} + +func (x *GetTestWorkflowRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *GetTestWorkflowRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetTestWorkflowResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Workflow []byte `protobuf:"bytes,1,opt,name=workflow,proto3" json:"workflow,omitempty"` +} + +func (x *GetTestWorkflowResponse) Reset() { + *x = GetTestWorkflowResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTestWorkflowResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTestWorkflowResponse) ProtoMessage() {} + +func (x *GetTestWorkflowResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[32] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTestWorkflowResponse.ProtoReflect.Descriptor instead. +func (*GetTestWorkflowResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{32} +} + +func (x *GetTestWorkflowResponse) GetWorkflow() []byte { + if x != nil { + return x.Workflow + } + return nil +} + +type ListTestWorkflowsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Limit uint32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + TextSearch string `protobuf:"bytes,5,opt,name=textSearch,proto3" json:"textSearch,omitempty"` +} + +func (x *ListTestWorkflowsRequest) Reset() { + *x = ListTestWorkflowsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[33] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTestWorkflowsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTestWorkflowsRequest) ProtoMessage() {} + +func (x *ListTestWorkflowsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[33] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTestWorkflowsRequest.ProtoReflect.Descriptor instead. +func (*ListTestWorkflowsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{33} +} + +func (x *ListTestWorkflowsRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *ListTestWorkflowsRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListTestWorkflowsRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListTestWorkflowsRequest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *ListTestWorkflowsRequest) GetTextSearch() string { + if x != nil { + return x.TextSearch + } + return "" +} + +type TestWorkflowListItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Workflow []byte `protobuf:"bytes,1,opt,name=workflow,proto3" json:"workflow,omitempty"` +} + +func (x *TestWorkflowListItem) Reset() { + *x = TestWorkflowListItem{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[34] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestWorkflowListItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestWorkflowListItem) ProtoMessage() {} + +func (x *TestWorkflowListItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[34] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestWorkflowListItem.ProtoReflect.Descriptor instead. +func (*TestWorkflowListItem) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{34} +} + +func (x *TestWorkflowListItem) GetWorkflow() []byte { + if x != nil { + return x.Workflow + } + return nil +} + +type LabelListItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Value []string `protobuf:"bytes,2,rep,name=value,proto3" json:"value,omitempty"` +} + +func (x *LabelListItem) Reset() { + *x = LabelListItem{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LabelListItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LabelListItem) ProtoMessage() {} + +func (x *LabelListItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[35] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LabelListItem.ProtoReflect.Descriptor instead. +func (*LabelListItem) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{35} +} + +func (x *LabelListItem) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *LabelListItem) GetValue() []string { + if x != nil { + return x.Value + } + return nil +} + +type ListTestWorkflowLabelsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` +} + +func (x *ListTestWorkflowLabelsRequest) Reset() { + *x = ListTestWorkflowLabelsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTestWorkflowLabelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTestWorkflowLabelsRequest) ProtoMessage() {} + +func (x *ListTestWorkflowLabelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[36] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTestWorkflowLabelsRequest.ProtoReflect.Descriptor instead. +func (*ListTestWorkflowLabelsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{36} +} + +func (x *ListTestWorkflowLabelsRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +type ListTestWorkflowLabelsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Labels []*LabelListItem `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` +} + +func (x *ListTestWorkflowLabelsResponse) Reset() { + *x = ListTestWorkflowLabelsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[37] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTestWorkflowLabelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTestWorkflowLabelsResponse) ProtoMessage() {} + +func (x *ListTestWorkflowLabelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[37] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTestWorkflowLabelsResponse.ProtoReflect.Descriptor instead. +func (*ListTestWorkflowLabelsResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{37} +} + +func (x *ListTestWorkflowLabelsResponse) GetLabels() []*LabelListItem { + if x != nil { + return x.Labels + } + return nil +} + +type CreateTestWorkflowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Workflow []byte `protobuf:"bytes,2,opt,name=workflow,proto3" json:"workflow,omitempty"` +} + +func (x *CreateTestWorkflowRequest) Reset() { + *x = CreateTestWorkflowRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[38] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTestWorkflowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestWorkflowRequest) ProtoMessage() {} + +func (x *CreateTestWorkflowRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[38] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTestWorkflowRequest.ProtoReflect.Descriptor instead. +func (*CreateTestWorkflowRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{38} +} + +func (x *CreateTestWorkflowRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *CreateTestWorkflowRequest) GetWorkflow() []byte { + if x != nil { + return x.Workflow + } + return nil +} + +type CreateTestWorkflowResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CreateTestWorkflowResponse) Reset() { + *x = CreateTestWorkflowResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[39] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTestWorkflowResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestWorkflowResponse) ProtoMessage() {} + +func (x *CreateTestWorkflowResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[39] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTestWorkflowResponse.ProtoReflect.Descriptor instead. +func (*CreateTestWorkflowResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{39} +} + +type UpdateTestWorkflowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Workflow []byte `protobuf:"bytes,2,opt,name=workflow,proto3" json:"workflow,omitempty"` +} + +func (x *UpdateTestWorkflowRequest) Reset() { + *x = UpdateTestWorkflowRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[40] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTestWorkflowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTestWorkflowRequest) ProtoMessage() {} + +func (x *UpdateTestWorkflowRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[40] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTestWorkflowRequest.ProtoReflect.Descriptor instead. +func (*UpdateTestWorkflowRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{40} +} + +func (x *UpdateTestWorkflowRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *UpdateTestWorkflowRequest) GetWorkflow() []byte { + if x != nil { + return x.Workflow + } + return nil +} + +type UpdateTestWorkflowResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpdateTestWorkflowResponse) Reset() { + *x = UpdateTestWorkflowResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[41] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTestWorkflowResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTestWorkflowResponse) ProtoMessage() {} + +func (x *UpdateTestWorkflowResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[41] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTestWorkflowResponse.ProtoReflect.Descriptor instead. +func (*UpdateTestWorkflowResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{41} +} + +type DeleteTestWorkflowRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteTestWorkflowRequest) Reset() { + *x = DeleteTestWorkflowRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[42] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowRequest) ProtoMessage() {} + +func (x *DeleteTestWorkflowRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[42] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowRequest.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{42} +} + +func (x *DeleteTestWorkflowRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *DeleteTestWorkflowRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteTestWorkflowResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteTestWorkflowResponse) Reset() { + *x = DeleteTestWorkflowResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[43] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowResponse) ProtoMessage() {} + +func (x *DeleteTestWorkflowResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[43] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowResponse.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{43} +} + +type DeleteTestWorkflowsByLabelsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *DeleteTestWorkflowsByLabelsRequest) Reset() { + *x = DeleteTestWorkflowsByLabelsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowsByLabelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowsByLabelsRequest) ProtoMessage() {} + +func (x *DeleteTestWorkflowsByLabelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowsByLabelsRequest.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowsByLabelsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{44} +} + +func (x *DeleteTestWorkflowsByLabelsRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *DeleteTestWorkflowsByLabelsRequest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +type DeleteTestWorkflowsByLabelsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count uint32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *DeleteTestWorkflowsByLabelsResponse) Reset() { + *x = DeleteTestWorkflowsByLabelsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowsByLabelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowsByLabelsResponse) ProtoMessage() {} + +func (x *DeleteTestWorkflowsByLabelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowsByLabelsResponse.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowsByLabelsResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{45} +} + +func (x *DeleteTestWorkflowsByLabelsResponse) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +type GetTestWorkflowTemplateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *GetTestWorkflowTemplateRequest) Reset() { + *x = GetTestWorkflowTemplateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[46] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTestWorkflowTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTestWorkflowTemplateRequest) ProtoMessage() {} + +func (x *GetTestWorkflowTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[46] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTestWorkflowTemplateRequest.ProtoReflect.Descriptor instead. +func (*GetTestWorkflowTemplateRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{46} +} + +func (x *GetTestWorkflowTemplateRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *GetTestWorkflowTemplateRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type GetTestWorkflowTemplateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Template []byte `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` +} + +func (x *GetTestWorkflowTemplateResponse) Reset() { + *x = GetTestWorkflowTemplateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[47] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetTestWorkflowTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTestWorkflowTemplateResponse) ProtoMessage() {} + +func (x *GetTestWorkflowTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[47] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTestWorkflowTemplateResponse.ProtoReflect.Descriptor instead. +func (*GetTestWorkflowTemplateResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{47} +} + +func (x *GetTestWorkflowTemplateResponse) GetTemplate() []byte { + if x != nil { + return x.Template + } + return nil +} + +type ListTestWorkflowTemplatesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Offset uint32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Limit uint32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + TextSearch string `protobuf:"bytes,5,opt,name=textSearch,proto3" json:"textSearch,omitempty"` +} + +func (x *ListTestWorkflowTemplatesRequest) Reset() { + *x = ListTestWorkflowTemplatesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[48] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTestWorkflowTemplatesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTestWorkflowTemplatesRequest) ProtoMessage() {} + +func (x *ListTestWorkflowTemplatesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[48] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTestWorkflowTemplatesRequest.ProtoReflect.Descriptor instead. +func (*ListTestWorkflowTemplatesRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{48} +} + +func (x *ListTestWorkflowTemplatesRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *ListTestWorkflowTemplatesRequest) GetOffset() uint32 { + if x != nil { + return x.Offset + } + return 0 +} + +func (x *ListTestWorkflowTemplatesRequest) GetLimit() uint32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListTestWorkflowTemplatesRequest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *ListTestWorkflowTemplatesRequest) GetTextSearch() string { + if x != nil { + return x.TextSearch + } + return "" +} + +type TestWorkflowTemplateListItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Template []byte `protobuf:"bytes,1,opt,name=template,proto3" json:"template,omitempty"` +} + +func (x *TestWorkflowTemplateListItem) Reset() { + *x = TestWorkflowTemplateListItem{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[49] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TestWorkflowTemplateListItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TestWorkflowTemplateListItem) ProtoMessage() {} + +func (x *TestWorkflowTemplateListItem) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[49] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TestWorkflowTemplateListItem.ProtoReflect.Descriptor instead. +func (*TestWorkflowTemplateListItem) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{49} +} + +func (x *TestWorkflowTemplateListItem) GetTemplate() []byte { + if x != nil { + return x.Template + } + return nil +} + +type ListTestWorkflowTemplateLabelsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` +} + +func (x *ListTestWorkflowTemplateLabelsRequest) Reset() { + *x = ListTestWorkflowTemplateLabelsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[50] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTestWorkflowTemplateLabelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTestWorkflowTemplateLabelsRequest) ProtoMessage() {} + +func (x *ListTestWorkflowTemplateLabelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[50] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTestWorkflowTemplateLabelsRequest.ProtoReflect.Descriptor instead. +func (*ListTestWorkflowTemplateLabelsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{50} +} + +func (x *ListTestWorkflowTemplateLabelsRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +type ListTestWorkflowTemplateLabelsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Labels []*LabelListItem `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` +} + +func (x *ListTestWorkflowTemplateLabelsResponse) Reset() { + *x = ListTestWorkflowTemplateLabelsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[51] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListTestWorkflowTemplateLabelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTestWorkflowTemplateLabelsResponse) ProtoMessage() {} + +func (x *ListTestWorkflowTemplateLabelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[51] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTestWorkflowTemplateLabelsResponse.ProtoReflect.Descriptor instead. +func (*ListTestWorkflowTemplateLabelsResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{51} +} + +func (x *ListTestWorkflowTemplateLabelsResponse) GetLabels() []*LabelListItem { + if x != nil { + return x.Labels + } + return nil +} + +type CreateTestWorkflowTemplateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Template []byte `protobuf:"bytes,2,opt,name=template,proto3" json:"template,omitempty"` +} + +func (x *CreateTestWorkflowTemplateRequest) Reset() { + *x = CreateTestWorkflowTemplateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTestWorkflowTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestWorkflowTemplateRequest) ProtoMessage() {} + +func (x *CreateTestWorkflowTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[52] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTestWorkflowTemplateRequest.ProtoReflect.Descriptor instead. +func (*CreateTestWorkflowTemplateRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{52} +} + +func (x *CreateTestWorkflowTemplateRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *CreateTestWorkflowTemplateRequest) GetTemplate() []byte { + if x != nil { + return x.Template + } + return nil +} + +type CreateTestWorkflowTemplateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *CreateTestWorkflowTemplateResponse) Reset() { + *x = CreateTestWorkflowTemplateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateTestWorkflowTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTestWorkflowTemplateResponse) ProtoMessage() {} + +func (x *CreateTestWorkflowTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[53] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTestWorkflowTemplateResponse.ProtoReflect.Descriptor instead. +func (*CreateTestWorkflowTemplateResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{53} +} + +type UpdateTestWorkflowTemplateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Template []byte `protobuf:"bytes,2,opt,name=template,proto3" json:"template,omitempty"` +} + +func (x *UpdateTestWorkflowTemplateRequest) Reset() { + *x = UpdateTestWorkflowTemplateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[54] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTestWorkflowTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTestWorkflowTemplateRequest) ProtoMessage() {} + +func (x *UpdateTestWorkflowTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[54] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTestWorkflowTemplateRequest.ProtoReflect.Descriptor instead. +func (*UpdateTestWorkflowTemplateRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{54} +} + +func (x *UpdateTestWorkflowTemplateRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *UpdateTestWorkflowTemplateRequest) GetTemplate() []byte { + if x != nil { + return x.Template + } + return nil +} + +type UpdateTestWorkflowTemplateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UpdateTestWorkflowTemplateResponse) Reset() { + *x = UpdateTestWorkflowTemplateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[55] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateTestWorkflowTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateTestWorkflowTemplateResponse) ProtoMessage() {} + +func (x *UpdateTestWorkflowTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[55] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateTestWorkflowTemplateResponse.ProtoReflect.Descriptor instead. +func (*UpdateTestWorkflowTemplateResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{55} +} + +type DeleteTestWorkflowTemplateRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *DeleteTestWorkflowTemplateRequest) Reset() { + *x = DeleteTestWorkflowTemplateRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[56] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowTemplateRequest) ProtoMessage() {} + +func (x *DeleteTestWorkflowTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[56] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowTemplateRequest.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowTemplateRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{56} +} + +func (x *DeleteTestWorkflowTemplateRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *DeleteTestWorkflowTemplateRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type DeleteTestWorkflowTemplateResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteTestWorkflowTemplateResponse) Reset() { + *x = DeleteTestWorkflowTemplateResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[57] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowTemplateResponse) ProtoMessage() {} + +func (x *DeleteTestWorkflowTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[57] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowTemplateResponse.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowTemplateResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{57} +} + +type DeleteTestWorkflowTemplatesByLabelsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EnvironmentId string `protobuf:"bytes,1,opt,name=environment_id,json=environmentId,proto3" json:"environment_id,omitempty"` + Labels map[string]string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *DeleteTestWorkflowTemplatesByLabelsRequest) Reset() { + *x = DeleteTestWorkflowTemplatesByLabelsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowTemplatesByLabelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowTemplatesByLabelsRequest) ProtoMessage() {} + +func (x *DeleteTestWorkflowTemplatesByLabelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[58] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowTemplatesByLabelsRequest.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowTemplatesByLabelsRequest) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{58} +} + +func (x *DeleteTestWorkflowTemplatesByLabelsRequest) GetEnvironmentId() string { + if x != nil { + return x.EnvironmentId + } + return "" +} + +func (x *DeleteTestWorkflowTemplatesByLabelsRequest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +type DeleteTestWorkflowTemplatesByLabelsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Count uint32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` +} + +func (x *DeleteTestWorkflowTemplatesByLabelsResponse) Reset() { + *x = DeleteTestWorkflowTemplatesByLabelsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_service_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteTestWorkflowTemplatesByLabelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTestWorkflowTemplatesByLabelsResponse) ProtoMessage() {} + +func (x *DeleteTestWorkflowTemplatesByLabelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_service_proto_msgTypes[59] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTestWorkflowTemplatesByLabelsResponse.ProtoReflect.Descriptor instead. +func (*DeleteTestWorkflowTemplatesByLabelsResponse) Descriptor() ([]byte, []int) { + return file_proto_service_proto_rawDescGZIP(), []int{59} +} + +func (x *DeleteTestWorkflowTemplatesByLabelsResponse) GetCount() uint32 { + if x != nil { + return x.Count + } + return 0 +} + +var File_proto_service_proto protoreflect.FileDescriptor + +var file_proto_service_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x1a, 0x1b, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x01, 0x0a, 0x11, 0x4c, 0x6f, 0x67, 0x73, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, + 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x3f, 0x0a, + 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x73, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0x84, + 0x01, 0x0a, 0x12, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x6c, 0x6f, 0x67, + 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6c, 0x6f, 0x67, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x69, 0x73, + 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x69, 0x73, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x5d, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, + 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, + 0x64, 0x12, 0x31, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x07, 0x70, 0x61, 0x79, + 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x2d, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0xfb, 0x01, 0x0a, 0x0e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x3c, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, + 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, + 0x64, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, + 0x64, 0x1a, 0x4e, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0xb2, 0x01, 0x0a, 0x20, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x4e, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xda, 0x01, 0x0a, 0x21, 0x54, 0x65, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, + 0x5f, 0x6e, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, + 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, + 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, + 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x22, 0x79, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6f, 0x72, 0x67, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6f, 0x72, 0x67, 0x49, 0x64, + 0x12, 0x15, 0x0a, 0x06, 0x65, 0x6e, 0x76, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x6e, 0x76, 0x49, 0x64, 0x12, 0x35, 0x0a, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, + 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x52, 0x0c, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x22, 0x3a, + 0x0a, 0x0a, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x22, 0x25, 0x0a, 0x0b, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x22, 0xeb, 0x01, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x3d, 0x0a, + 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, + 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x49, 0x64, 0x1a, + 0x4e, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x28, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x4a, 0x0a, 0x0d, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x44, 0x61, 0x74, 0x61, + 0x12, 0x25, 0x0a, 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x0d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x52, + 0x06, 0x6f, 0x70, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0x4a, 0x0a, 0x11, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x2e, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0x81, 0x02, 0x0a, 0x27, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, + 0x12, 0x21, 0x0a, 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4e, 0x0a, 0x0c, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xe1, 0x01, 0x0a, 0x28, + 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, + 0x66, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, + 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0xf3, 0x01, 0x0a, 0x2c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, + 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, + 0x65, 0x66, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4e, 0x0a, 0x0c, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x22, 0xe6, 0x01, 0x0a, 0x2d, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, + 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x49, 0x64, 0x12, 0x15, 0x0a, 0x06, 0x73, 0x65, 0x71, 0x5f, 0x6e, 0x6f, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x73, 0x65, 0x71, 0x4e, 0x6f, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x37, 0x0a, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0xae, + 0x01, 0x0a, 0x18, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x43, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0xe1, 0x02, 0x0a, 0x11, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x08, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, + 0x6f, 0x72, 0x12, 0x3c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x53, 0x63, + 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, + 0x39, 0x0a, 0x0b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, + 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x53, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x39, 0x0a, 0x0d, 0x55, 0x73, 0x65, 0x72, + 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, + 0x61, 0x69, 0x6c, 0x22, 0xec, 0x03, 0x0a, 0x0f, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, + 0x0a, 0x0a, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, + 0x75, 0x6c, 0x65, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x61, + 0x62, 0x6c, 0x65, 0x5f, 0x77, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x57, 0x65, 0x62, 0x68, 0x6f, + 0x6f, 0x6b, 0x73, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x3e, 0x0a, 0x0f, 0x72, 0x75, 0x6e, + 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0e, 0x72, 0x75, 0x6e, 0x6e, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x45, + 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x04, 0x75, + 0x73, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x48, + 0x00, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x34, 0x0a, 0x16, 0x6b, 0x75, + 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x6b, 0x75, 0x62, 0x65, + 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, + 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x22, 0x30, 0x0a, 0x10, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x33, 0x0a, 0x0d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x69, 0x0a, 0x12, 0x45, 0x76, 0x65, + 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x06, 0x61, 0x63, + 0x63, 0x65, 0x70, 0x74, 0x22, 0x71, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x46, 0x0a, 0x0d, 0x52, 0x75, 0x6e, 0x6e, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, + 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x88, 0x01, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, + 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x62, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6f, 0x62, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, + 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x4f, 0x0a, 0x16, 0x4f, 0x62, + 0x74, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, + 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x6a, 0x0a, 0x17, 0x4f, + 0x62, 0x74, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x53, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x35, 0x0a, 0x17, + 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x22, 0x8f, 0x02, 0x0a, 0x18, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x12, + 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x43, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, + 0x78, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x74, 0x65, 0x78, 0x74, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x32, 0x0a, 0x14, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x1a, 0x0a, + 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x39, 0x0a, 0x0d, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x22, 0x46, 0x0a, 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x1e, + 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, + 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x22, 0x5e, 0x0a, 0x19, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, + 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x1c, 0x0a, 0x1a, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5e, 0x0a, 0x19, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, + 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1a, + 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x1c, 0x0a, 0x1a, 0x55, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x56, 0x0a, 0x19, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x1c, 0x0a, 0x1a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xd5, + 0x01, 0x0a, 0x22, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x4d, 0x0a, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x23, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x42, 0x79, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, + 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x22, 0x5b, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, + 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x3d, 0x0a, 0x1f, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, + 0x9f, 0x02, 0x0a, 0x20, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, + 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, + 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x66, 0x66, + 0x73, 0x65, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x4b, 0x0a, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x65, 0x78, 0x74, 0x53, 0x65, + 0x61, 0x72, 0x63, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x78, 0x74, + 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0x3a, 0x0a, 0x1c, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, + 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x4e, 0x0a, + 0x25, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x56, 0x0a, + 0x26, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x22, 0x66, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, + 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x24, 0x0a, + 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x66, 0x0a, 0x21, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, + 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, + 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x22, 0x24, 0x0a, 0x22, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x5e, 0x0a, 0x21, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x22, 0x24, 0x0a, 0x22, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0xe5, 0x01, 0x0a, 0x2a, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, + 0x65, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x55, 0x0a, + 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x73, 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x43, 0x0a, 0x2b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x42, 0x79, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x2a, 0x48, 0x0a, 0x15, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, + 0x12, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, 0x53, 0x53, + 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, + 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x01, 0x2a, 0x69, + 0x0a, 0x24, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x1b, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, + 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x5f, 0x4d, 0x45, + 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x57, 0x4f, 0x52, 0x4b, 0x46, + 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, + 0x48, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x10, 0x01, 0x2a, 0x8a, 0x01, 0x0a, 0x1c, 0x54, 0x65, + 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x57, 0x4f, + 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x45, 0x52, + 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, + 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x01, 0x12, 0x1a, + 0x0a, 0x16, 0x57, 0x4f, 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, + 0x4d, 0x5f, 0x52, 0x45, 0x53, 0x55, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x57, 0x4f, + 0x52, 0x4b, 0x46, 0x4c, 0x4f, 0x57, 0x5f, 0x53, 0x54, 0x52, 0x45, 0x41, 0x4d, 0x5f, 0x4f, 0x55, + 0x54, 0x50, 0x55, 0x54, 0x10, 0x03, 0x2a, 0x4c, 0x0a, 0x06, 0x4f, 0x70, 0x63, 0x6f, 0x64, 0x65, + 0x12, 0x0e, 0x0a, 0x0a, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, + 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, 0x10, 0x01, + 0x12, 0x10, 0x0a, 0x0c, 0x42, 0x49, 0x4e, 0x41, 0x52, 0x59, 0x5f, 0x46, 0x52, 0x41, 0x4d, 0x45, + 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f, 0x43, 0x48, 0x45, + 0x43, 0x4b, 0x10, 0x03, 0x2a, 0x7c, 0x0a, 0x12, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, + 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x06, 0x0a, 0x02, 0x55, 0x49, 0x10, 0x01, 0x12, + 0x07, 0x0a, 0x03, 0x43, 0x4c, 0x49, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x49, 0x43, 0x44, + 0x10, 0x03, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x52, 0x4f, 0x4e, 0x10, 0x04, 0x12, 0x0f, 0x0a, 0x0b, + 0x54, 0x45, 0x53, 0x54, 0x54, 0x52, 0x49, 0x47, 0x47, 0x45, 0x52, 0x10, 0x05, 0x12, 0x14, 0x0a, + 0x10, 0x4b, 0x55, 0x42, 0x45, 0x52, 0x4e, 0x45, 0x54, 0x45, 0x53, 0x4f, 0x42, 0x4a, 0x45, 0x43, + 0x54, 0x10, 0x06, 0x12, 0x0d, 0x0a, 0x09, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, 0x4f, 0x4e, + 0x10, 0x07, 0x32, 0xf8, 0x14, 0x0a, 0x10, 0x54, 0x65, 0x73, 0x74, 0x4b, 0x75, 0x62, 0x65, 0x43, + 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x50, 0x49, 0x12, 0x3c, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x12, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x15, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x36, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x14, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x44, + 0x61, 0x74, 0x61, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x28, 0x01, 0x12, 0x35, 0x0a, + 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x6f, + 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0c, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x41, + 0x73, 0x79, 0x6e, 0x63, 0x12, 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x15, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x48, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x4c, 0x6f, + 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x4c, 0x6f, 0x67, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x1a, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x6f, 0x67, 0x73, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x7b, 0x0a, 0x22, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x28, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x1a, 0x27, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x90, + 0x01, 0x0a, 0x29, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2f, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x2e, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x9f, 0x01, 0x0a, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, + 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x12, 0x34, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, + 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x33, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x50, + 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x53, 0x74, 0x65, 0x70, 0x4e, 0x6f, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x28, + 0x01, 0x30, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, + 0x74, 0x65, 0x78, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x50, 0x72, 0x6f, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x18, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x19, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0c, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x46, 0x0a, 0x11, 0x53, 0x63, + 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x12, 0x44, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, + 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x1a, 0x14, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x52, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x28, 0x01, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x0f, 0x4f, 0x62, 0x74, 0x61, + 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x4f, 0x62, 0x74, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x4f, 0x62, 0x74, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a, 0x0f, 0x47, 0x65, + 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1d, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x63, + 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x11, + 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, + 0x73, 0x12, 0x1f, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, + 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x30, + 0x01, 0x12, 0x65, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x25, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, + 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6c, + 0x6f, 0x75, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, + 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x20, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x21, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x74, 0x0a, 0x1b, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, + 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x29, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, + 0x6c, 0x6f, 0x77, 0x73, 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x73, 0x42, + 0x79, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x68, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x25, 0x2e, 0x63, 0x6c, 0x6f, + 0x75, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, + 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x26, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x65, 0x73, + 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x19, 0x4c, 0x69, 0x73, + 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x23, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6d, 0x30, 0x01, 0x12, 0x7d, 0x0a, 0x1e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, + 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, + 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x2c, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, + 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, + 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x12, 0x28, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, + 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, + 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x1a, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x28, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x29, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, + 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x71, 0x0a, 0x1a, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x12, 0x28, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x29, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, + 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, + 0x01, 0x0a, 0x23, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x42, 0x79, + 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x31, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2e, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, + 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x42, 0x79, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x65, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, + 0x66, 0x6c, 0x6f, 0x77, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x42, 0x79, 0x4c, + 0x61, 0x62, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, + 0x09, 0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_proto_service_proto_rawDescOnce sync.Once + file_proto_service_proto_rawDescData = file_proto_service_proto_rawDesc +) + +func file_proto_service_proto_rawDescGZIP() []byte { + file_proto_service_proto_rawDescOnce.Do(func() { + file_proto_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_service_proto_rawDescData) + }) + return file_proto_service_proto_rawDescData +} + +var file_proto_service_proto_enumTypes = make([]protoimpl.EnumInfo, 5) +var file_proto_service_proto_msgTypes = make([]protoimpl.MessageInfo, 70) +var file_proto_service_proto_goTypes = []interface{}{ + (LogsStreamRequestType)(0), // 0: cloud.LogsStreamRequestType + (TestWorkflowNotificationsRequestType)(0), // 1: cloud.TestWorkflowNotificationsRequestType + (TestWorkflowNotificationType)(0), // 2: cloud.TestWorkflowNotificationType + (Opcode)(0), // 3: cloud.Opcode + (RunningContextType)(0), // 4: cloud.RunningContextType + (*LogsStreamRequest)(nil), // 5: cloud.LogsStreamRequest + (*LogsStreamResponse)(nil), // 6: cloud.LogsStreamResponse + (*CommandRequest)(nil), // 7: cloud.CommandRequest + (*CommandResponse)(nil), // 8: cloud.CommandResponse + (*ExecuteRequest)(nil), // 9: cloud.ExecuteRequest + (*TestWorkflowNotificationsRequest)(nil), // 10: cloud.TestWorkflowNotificationsRequest + (*TestWorkflowNotificationsResponse)(nil), // 11: cloud.TestWorkflowNotificationsResponse + (*ProContextResponse)(nil), // 12: cloud.ProContextResponse + (*Capability)(nil), // 13: cloud.Capability + (*HeaderValue)(nil), // 14: cloud.HeaderValue + (*ExecuteResponse)(nil), // 15: cloud.ExecuteResponse + (*WebsocketData)(nil), // 16: cloud.WebsocketData + (*CredentialRequest)(nil), // 17: cloud.CredentialRequest + (*CredentialResponse)(nil), // 18: cloud.CredentialResponse + (*TestWorkflowServiceNotificationsRequest)(nil), // 19: cloud.TestWorkflowServiceNotificationsRequest + (*TestWorkflowServiceNotificationsResponse)(nil), // 20: cloud.TestWorkflowServiceNotificationsResponse + (*TestWorkflowParallelStepNotificationsRequest)(nil), // 21: cloud.TestWorkflowParallelStepNotificationsRequest + (*TestWorkflowParallelStepNotificationsResponse)(nil), // 22: cloud.TestWorkflowParallelStepNotificationsResponse + (*ScheduleResourceSelector)(nil), // 23: cloud.ScheduleResourceSelector + (*ScheduleExecution)(nil), // 24: cloud.ScheduleExecution + (*RunningContext)(nil), // 25: cloud.RunningContext + (*UserSignature)(nil), // 26: cloud.UserSignature + (*ScheduleRequest)(nil), // 27: cloud.ScheduleRequest + (*ScheduleResponse)(nil), // 28: cloud.ScheduleResponse + (*EventResource)(nil), // 29: cloud.EventResource + (*EventStreamRequest)(nil), // 30: cloud.EventStreamRequest + (*Event)(nil), // 31: cloud.Event + (*RunnerRequest)(nil), // 32: cloud.RunnerRequest + (*RunnerResponse)(nil), // 33: cloud.RunnerResponse + (*ObtainExecutionRequest)(nil), // 34: cloud.ObtainExecutionRequest + (*ObtainExecutionResponse)(nil), // 35: cloud.ObtainExecutionResponse + (*GetTestWorkflowRequest)(nil), // 36: cloud.GetTestWorkflowRequest + (*GetTestWorkflowResponse)(nil), // 37: cloud.GetTestWorkflowResponse + (*ListTestWorkflowsRequest)(nil), // 38: cloud.ListTestWorkflowsRequest + (*TestWorkflowListItem)(nil), // 39: cloud.TestWorkflowListItem + (*LabelListItem)(nil), // 40: cloud.LabelListItem + (*ListTestWorkflowLabelsRequest)(nil), // 41: cloud.ListTestWorkflowLabelsRequest + (*ListTestWorkflowLabelsResponse)(nil), // 42: cloud.ListTestWorkflowLabelsResponse + (*CreateTestWorkflowRequest)(nil), // 43: cloud.CreateTestWorkflowRequest + (*CreateTestWorkflowResponse)(nil), // 44: cloud.CreateTestWorkflowResponse + (*UpdateTestWorkflowRequest)(nil), // 45: cloud.UpdateTestWorkflowRequest + (*UpdateTestWorkflowResponse)(nil), // 46: cloud.UpdateTestWorkflowResponse + (*DeleteTestWorkflowRequest)(nil), // 47: cloud.DeleteTestWorkflowRequest + (*DeleteTestWorkflowResponse)(nil), // 48: cloud.DeleteTestWorkflowResponse + (*DeleteTestWorkflowsByLabelsRequest)(nil), // 49: cloud.DeleteTestWorkflowsByLabelsRequest + (*DeleteTestWorkflowsByLabelsResponse)(nil), // 50: cloud.DeleteTestWorkflowsByLabelsResponse + (*GetTestWorkflowTemplateRequest)(nil), // 51: cloud.GetTestWorkflowTemplateRequest + (*GetTestWorkflowTemplateResponse)(nil), // 52: cloud.GetTestWorkflowTemplateResponse + (*ListTestWorkflowTemplatesRequest)(nil), // 53: cloud.ListTestWorkflowTemplatesRequest + (*TestWorkflowTemplateListItem)(nil), // 54: cloud.TestWorkflowTemplateListItem + (*ListTestWorkflowTemplateLabelsRequest)(nil), // 55: cloud.ListTestWorkflowTemplateLabelsRequest + (*ListTestWorkflowTemplateLabelsResponse)(nil), // 56: cloud.ListTestWorkflowTemplateLabelsResponse + (*CreateTestWorkflowTemplateRequest)(nil), // 57: cloud.CreateTestWorkflowTemplateRequest + (*CreateTestWorkflowTemplateResponse)(nil), // 58: cloud.CreateTestWorkflowTemplateResponse + (*UpdateTestWorkflowTemplateRequest)(nil), // 59: cloud.UpdateTestWorkflowTemplateRequest + (*UpdateTestWorkflowTemplateResponse)(nil), // 60: cloud.UpdateTestWorkflowTemplateResponse + (*DeleteTestWorkflowTemplateRequest)(nil), // 61: cloud.DeleteTestWorkflowTemplateRequest + (*DeleteTestWorkflowTemplateResponse)(nil), // 62: cloud.DeleteTestWorkflowTemplateResponse + (*DeleteTestWorkflowTemplatesByLabelsRequest)(nil), // 63: cloud.DeleteTestWorkflowTemplatesByLabelsRequest + (*DeleteTestWorkflowTemplatesByLabelsResponse)(nil), // 64: cloud.DeleteTestWorkflowTemplatesByLabelsResponse + nil, // 65: cloud.ExecuteRequest.HeadersEntry + nil, // 66: cloud.ExecuteResponse.HeadersEntry + nil, // 67: cloud.ScheduleResourceSelector.LabelsEntry + nil, // 68: cloud.ScheduleExecution.ConfigEntry + nil, // 69: cloud.ScheduleExecution.TagsEntry + nil, // 70: cloud.ScheduleRequest.TagsEntry + nil, // 71: cloud.ListTestWorkflowsRequest.LabelsEntry + nil, // 72: cloud.DeleteTestWorkflowsByLabelsRequest.LabelsEntry + nil, // 73: cloud.ListTestWorkflowTemplatesRequest.LabelsEntry + nil, // 74: cloud.DeleteTestWorkflowTemplatesByLabelsRequest.LabelsEntry + (*structpb.Struct)(nil), // 75: google.protobuf.Struct + (*emptypb.Empty)(nil), // 76: google.protobuf.Empty +} +var file_proto_service_proto_depIdxs = []int32{ + 0, // 0: cloud.LogsStreamRequest.request_type:type_name -> cloud.LogsStreamRequestType + 75, // 1: cloud.CommandRequest.payload:type_name -> google.protobuf.Struct + 65, // 2: cloud.ExecuteRequest.headers:type_name -> cloud.ExecuteRequest.HeadersEntry + 1, // 3: cloud.TestWorkflowNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType + 2, // 4: cloud.TestWorkflowNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType + 13, // 5: cloud.ProContextResponse.capabilities:type_name -> cloud.Capability + 66, // 6: cloud.ExecuteResponse.headers:type_name -> cloud.ExecuteResponse.HeadersEntry + 3, // 7: cloud.WebsocketData.opcode:type_name -> cloud.Opcode + 1, // 8: cloud.TestWorkflowServiceNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType + 2, // 9: cloud.TestWorkflowServiceNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType + 1, // 10: cloud.TestWorkflowParallelStepNotificationsRequest.request_type:type_name -> cloud.TestWorkflowNotificationsRequestType + 2, // 11: cloud.TestWorkflowParallelStepNotificationsResponse.type:type_name -> cloud.TestWorkflowNotificationType + 67, // 12: cloud.ScheduleResourceSelector.labels:type_name -> cloud.ScheduleResourceSelector.LabelsEntry + 23, // 13: cloud.ScheduleExecution.selector:type_name -> cloud.ScheduleResourceSelector + 68, // 14: cloud.ScheduleExecution.config:type_name -> cloud.ScheduleExecution.ConfigEntry + 69, // 15: cloud.ScheduleExecution.tags:type_name -> cloud.ScheduleExecution.TagsEntry + 4, // 16: cloud.RunningContext.type:type_name -> cloud.RunningContextType + 24, // 17: cloud.ScheduleRequest.executions:type_name -> cloud.ScheduleExecution + 70, // 18: cloud.ScheduleRequest.tags:type_name -> cloud.ScheduleRequest.TagsEntry + 25, // 19: cloud.ScheduleRequest.running_context:type_name -> cloud.RunningContext + 26, // 20: cloud.ScheduleRequest.user:type_name -> cloud.UserSignature + 29, // 21: cloud.EventStreamRequest.accept:type_name -> cloud.EventResource + 29, // 22: cloud.Event.resource:type_name -> cloud.EventResource + 71, // 23: cloud.ListTestWorkflowsRequest.labels:type_name -> cloud.ListTestWorkflowsRequest.LabelsEntry + 40, // 24: cloud.ListTestWorkflowLabelsResponse.labels:type_name -> cloud.LabelListItem + 72, // 25: cloud.DeleteTestWorkflowsByLabelsRequest.labels:type_name -> cloud.DeleteTestWorkflowsByLabelsRequest.LabelsEntry + 73, // 26: cloud.ListTestWorkflowTemplatesRequest.labels:type_name -> cloud.ListTestWorkflowTemplatesRequest.LabelsEntry + 40, // 27: cloud.ListTestWorkflowTemplateLabelsResponse.labels:type_name -> cloud.LabelListItem + 74, // 28: cloud.DeleteTestWorkflowTemplatesByLabelsRequest.labels:type_name -> cloud.DeleteTestWorkflowTemplatesByLabelsRequest.LabelsEntry + 14, // 29: cloud.ExecuteRequest.HeadersEntry.value:type_name -> cloud.HeaderValue + 14, // 30: cloud.ExecuteResponse.HeadersEntry.value:type_name -> cloud.HeaderValue + 15, // 31: cloud.TestKubeCloudAPI.Execute:input_type -> cloud.ExecuteResponse + 16, // 32: cloud.TestKubeCloudAPI.Send:input_type -> cloud.WebsocketData + 7, // 33: cloud.TestKubeCloudAPI.Call:input_type -> cloud.CommandRequest + 15, // 34: cloud.TestKubeCloudAPI.ExecuteAsync:input_type -> cloud.ExecuteResponse + 6, // 35: cloud.TestKubeCloudAPI.GetLogsStream:input_type -> cloud.LogsStreamResponse + 11, // 36: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:input_type -> cloud.TestWorkflowNotificationsResponse + 20, // 37: cloud.TestKubeCloudAPI.GetTestWorkflowServiceNotificationsStream:input_type -> cloud.TestWorkflowServiceNotificationsResponse + 22, // 38: cloud.TestKubeCloudAPI.GetTestWorkflowParallelStepNotificationsStream:input_type -> cloud.TestWorkflowParallelStepNotificationsResponse + 76, // 39: cloud.TestKubeCloudAPI.GetProContext:input_type -> google.protobuf.Empty + 17, // 40: cloud.TestKubeCloudAPI.GetCredential:input_type -> cloud.CredentialRequest + 30, // 41: cloud.TestKubeCloudAPI.GetEventStream:input_type -> cloud.EventStreamRequest + 27, // 42: cloud.TestKubeCloudAPI.ScheduleExecution:input_type -> cloud.ScheduleRequest + 33, // 43: cloud.TestKubeCloudAPI.GetRunnerRequests:input_type -> cloud.RunnerResponse + 34, // 44: cloud.TestKubeCloudAPI.ObtainExecution:input_type -> cloud.ObtainExecutionRequest + 36, // 45: cloud.TestKubeCloudAPI.GetTestWorkflow:input_type -> cloud.GetTestWorkflowRequest + 38, // 46: cloud.TestKubeCloudAPI.ListTestWorkflows:input_type -> cloud.ListTestWorkflowsRequest + 41, // 47: cloud.TestKubeCloudAPI.ListTestWorkflowLabels:input_type -> cloud.ListTestWorkflowLabelsRequest + 43, // 48: cloud.TestKubeCloudAPI.CreateTestWorkflow:input_type -> cloud.CreateTestWorkflowRequest + 45, // 49: cloud.TestKubeCloudAPI.UpdateTestWorkflow:input_type -> cloud.UpdateTestWorkflowRequest + 47, // 50: cloud.TestKubeCloudAPI.DeleteTestWorkflow:input_type -> cloud.DeleteTestWorkflowRequest + 49, // 51: cloud.TestKubeCloudAPI.DeleteTestWorkflowsByLabels:input_type -> cloud.DeleteTestWorkflowsByLabelsRequest + 51, // 52: cloud.TestKubeCloudAPI.GetTestWorkflowTemplate:input_type -> cloud.GetTestWorkflowTemplateRequest + 53, // 53: cloud.TestKubeCloudAPI.ListTestWorkflowTemplates:input_type -> cloud.ListTestWorkflowTemplatesRequest + 55, // 54: cloud.TestKubeCloudAPI.ListTestWorkflowTemplateLabels:input_type -> cloud.ListTestWorkflowTemplateLabelsRequest + 57, // 55: cloud.TestKubeCloudAPI.CreateTestWorkflowTemplate:input_type -> cloud.CreateTestWorkflowTemplateRequest + 59, // 56: cloud.TestKubeCloudAPI.UpdateTestWorkflowTemplate:input_type -> cloud.UpdateTestWorkflowTemplateRequest + 61, // 57: cloud.TestKubeCloudAPI.DeleteTestWorkflowTemplate:input_type -> cloud.DeleteTestWorkflowTemplateRequest + 63, // 58: cloud.TestKubeCloudAPI.DeleteTestWorkflowTemplatesByLabels:input_type -> cloud.DeleteTestWorkflowTemplatesByLabelsRequest + 9, // 59: cloud.TestKubeCloudAPI.Execute:output_type -> cloud.ExecuteRequest + 76, // 60: cloud.TestKubeCloudAPI.Send:output_type -> google.protobuf.Empty + 8, // 61: cloud.TestKubeCloudAPI.Call:output_type -> cloud.CommandResponse + 9, // 62: cloud.TestKubeCloudAPI.ExecuteAsync:output_type -> cloud.ExecuteRequest + 5, // 63: cloud.TestKubeCloudAPI.GetLogsStream:output_type -> cloud.LogsStreamRequest + 10, // 64: cloud.TestKubeCloudAPI.GetTestWorkflowNotificationsStream:output_type -> cloud.TestWorkflowNotificationsRequest + 19, // 65: cloud.TestKubeCloudAPI.GetTestWorkflowServiceNotificationsStream:output_type -> cloud.TestWorkflowServiceNotificationsRequest + 21, // 66: cloud.TestKubeCloudAPI.GetTestWorkflowParallelStepNotificationsStream:output_type -> cloud.TestWorkflowParallelStepNotificationsRequest + 12, // 67: cloud.TestKubeCloudAPI.GetProContext:output_type -> cloud.ProContextResponse + 18, // 68: cloud.TestKubeCloudAPI.GetCredential:output_type -> cloud.CredentialResponse + 31, // 69: cloud.TestKubeCloudAPI.GetEventStream:output_type -> cloud.Event + 28, // 70: cloud.TestKubeCloudAPI.ScheduleExecution:output_type -> cloud.ScheduleResponse + 32, // 71: cloud.TestKubeCloudAPI.GetRunnerRequests:output_type -> cloud.RunnerRequest + 35, // 72: cloud.TestKubeCloudAPI.ObtainExecution:output_type -> cloud.ObtainExecutionResponse + 37, // 73: cloud.TestKubeCloudAPI.GetTestWorkflow:output_type -> cloud.GetTestWorkflowResponse + 39, // 74: cloud.TestKubeCloudAPI.ListTestWorkflows:output_type -> cloud.TestWorkflowListItem + 42, // 75: cloud.TestKubeCloudAPI.ListTestWorkflowLabels:output_type -> cloud.ListTestWorkflowLabelsResponse + 44, // 76: cloud.TestKubeCloudAPI.CreateTestWorkflow:output_type -> cloud.CreateTestWorkflowResponse + 46, // 77: cloud.TestKubeCloudAPI.UpdateTestWorkflow:output_type -> cloud.UpdateTestWorkflowResponse + 48, // 78: cloud.TestKubeCloudAPI.DeleteTestWorkflow:output_type -> cloud.DeleteTestWorkflowResponse + 50, // 79: cloud.TestKubeCloudAPI.DeleteTestWorkflowsByLabels:output_type -> cloud.DeleteTestWorkflowsByLabelsResponse + 52, // 80: cloud.TestKubeCloudAPI.GetTestWorkflowTemplate:output_type -> cloud.GetTestWorkflowTemplateResponse + 54, // 81: cloud.TestKubeCloudAPI.ListTestWorkflowTemplates:output_type -> cloud.TestWorkflowTemplateListItem + 56, // 82: cloud.TestKubeCloudAPI.ListTestWorkflowTemplateLabels:output_type -> cloud.ListTestWorkflowTemplateLabelsResponse + 58, // 83: cloud.TestKubeCloudAPI.CreateTestWorkflowTemplate:output_type -> cloud.CreateTestWorkflowTemplateResponse + 60, // 84: cloud.TestKubeCloudAPI.UpdateTestWorkflowTemplate:output_type -> cloud.UpdateTestWorkflowTemplateResponse + 62, // 85: cloud.TestKubeCloudAPI.DeleteTestWorkflowTemplate:output_type -> cloud.DeleteTestWorkflowTemplateResponse + 64, // 86: cloud.TestKubeCloudAPI.DeleteTestWorkflowTemplatesByLabels:output_type -> cloud.DeleteTestWorkflowTemplatesByLabelsResponse + 59, // [59:87] is the sub-list for method output_type + 31, // [31:59] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name +} + +func init() { file_proto_service_proto_init() } +func file_proto_service_proto_init() { + if File_proto_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { file_proto_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LogsStreamRequest); i { case 0: @@ -1773,8 +4626,248 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LogsStreamResponse); i { + file_proto_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LogsStreamResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CommandRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CommandResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowNotificationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ProContextResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Capability); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeaderValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ExecuteResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WebsocketData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowServiceNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowServiceNotificationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowParallelStepNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowParallelStepNotificationsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScheduleResourceSelector); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScheduleExecution); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RunningContext); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UserSignature); i { case 0: return &v.state case 1: @@ -1785,8 +4878,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommandRequest); i { + file_proto_service_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScheduleRequest); i { case 0: return &v.state case 1: @@ -1797,8 +4890,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CommandResponse); i { + file_proto_service_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ScheduleResponse); i { case 0: return &v.state case 1: @@ -1809,8 +4902,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecuteRequest); i { + file_proto_service_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EventResource); i { case 0: return &v.state case 1: @@ -1821,8 +4914,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestWorkflowNotificationsRequest); i { + file_proto_service_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*EventStreamRequest); i { case 0: return &v.state case 1: @@ -1833,8 +4926,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestWorkflowNotificationsResponse); i { + file_proto_service_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Event); i { case 0: return &v.state case 1: @@ -1845,8 +4938,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProContextResponse); i { + file_proto_service_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RunnerRequest); i { case 0: return &v.state case 1: @@ -1857,8 +4950,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Capability); i { + file_proto_service_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RunnerResponse); i { case 0: return &v.state case 1: @@ -1869,8 +4962,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HeaderValue); i { + file_proto_service_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ObtainExecutionRequest); i { case 0: return &v.state case 1: @@ -1881,8 +4974,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecuteResponse); i { + file_proto_service_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ObtainExecutionResponse); i { case 0: return &v.state case 1: @@ -1893,8 +4986,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WebsocketData); i { + file_proto_service_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTestWorkflowRequest); i { case 0: return &v.state case 1: @@ -1905,8 +4998,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CredentialRequest); i { + file_proto_service_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTestWorkflowResponse); i { case 0: return &v.state case 1: @@ -1917,8 +5010,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CredentialResponse); i { + file_proto_service_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTestWorkflowsRequest); i { case 0: return &v.state case 1: @@ -1929,8 +5022,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestWorkflowServiceNotificationsRequest); i { + file_proto_service_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowListItem); i { case 0: return &v.state case 1: @@ -1941,8 +5034,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestWorkflowServiceNotificationsResponse); i { + file_proto_service_proto_msgTypes[35].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LabelListItem); i { case 0: return &v.state case 1: @@ -1953,8 +5046,8 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestWorkflowParallelStepNotificationsRequest); i { + file_proto_service_proto_msgTypes[36].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTestWorkflowLabelsRequest); i { case 0: return &v.state case 1: @@ -1965,8 +5058,272 @@ func file_proto_service_proto_init() { return nil } } - file_proto_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TestWorkflowParallelStepNotificationsResponse); i { + file_proto_service_proto_msgTypes[37].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTestWorkflowLabelsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[38].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTestWorkflowRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[39].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTestWorkflowResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[40].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTestWorkflowRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[41].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTestWorkflowResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[42].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[43].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowsByLabelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowsByLabelsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTestWorkflowTemplateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[47].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetTestWorkflowTemplateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[48].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTestWorkflowTemplatesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[49].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TestWorkflowTemplateListItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[50].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTestWorkflowTemplateLabelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[51].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListTestWorkflowTemplateLabelsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[52].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTestWorkflowTemplateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[53].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateTestWorkflowTemplateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[54].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTestWorkflowTemplateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[55].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateTestWorkflowTemplateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[56].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowTemplateRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[57].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowTemplateResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowTemplatesByLabelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_service_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteTestWorkflowTemplatesByLabelsResponse); i { case 0: return &v.state case 1: @@ -1978,13 +5335,15 @@ func file_proto_service_proto_init() { } } } + file_proto_service_proto_msgTypes[22].OneofWrappers = []interface{}{} + file_proto_service_proto_msgTypes[28].OneofWrappers = []interface{}{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_service_proto_rawDesc, - NumEnums: 4, - NumMessages: 20, + NumEnums: 5, + NumMessages: 70, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/cloud/service_grpc.pb.go b/pkg/cloud/service_grpc.pb.go index 7efe7686715..4750cc93496 100644 --- a/pkg/cloud/service_grpc.pb.go +++ b/pkg/cloud/service_grpc.pb.go @@ -35,6 +35,28 @@ type TestKubeCloudAPIClient interface { GetTestWorkflowParallelStepNotificationsStream(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamClient, error) GetProContext(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ProContextResponse, error) GetCredential(ctx context.Context, in *CredentialRequest, opts ...grpc.CallOption) (*CredentialResponse, error) + GetEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_GetEventStreamClient, error) + ScheduleExecution(ctx context.Context, in *ScheduleRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_ScheduleExecutionClient, error) + // Runner + GetRunnerRequests(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetRunnerRequestsClient, error) + ObtainExecution(ctx context.Context, in *ObtainExecutionRequest, opts ...grpc.CallOption) (*ObtainExecutionResponse, error) + // CRD Synchronisation + // -- Test Workflows + GetTestWorkflow(ctx context.Context, in *GetTestWorkflowRequest, opts ...grpc.CallOption) (*GetTestWorkflowResponse, error) + ListTestWorkflows(ctx context.Context, in *ListTestWorkflowsRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_ListTestWorkflowsClient, error) + ListTestWorkflowLabels(ctx context.Context, in *ListTestWorkflowLabelsRequest, opts ...grpc.CallOption) (*ListTestWorkflowLabelsResponse, error) + CreateTestWorkflow(ctx context.Context, in *CreateTestWorkflowRequest, opts ...grpc.CallOption) (*CreateTestWorkflowResponse, error) + UpdateTestWorkflow(ctx context.Context, in *UpdateTestWorkflowRequest, opts ...grpc.CallOption) (*UpdateTestWorkflowResponse, error) + DeleteTestWorkflow(ctx context.Context, in *DeleteTestWorkflowRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowResponse, error) + DeleteTestWorkflowsByLabels(ctx context.Context, in *DeleteTestWorkflowsByLabelsRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowsByLabelsResponse, error) + // -- Test Workflow Templates + GetTestWorkflowTemplate(ctx context.Context, in *GetTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*GetTestWorkflowTemplateResponse, error) + ListTestWorkflowTemplates(ctx context.Context, in *ListTestWorkflowTemplatesRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_ListTestWorkflowTemplatesClient, error) + ListTestWorkflowTemplateLabels(ctx context.Context, in *ListTestWorkflowTemplateLabelsRequest, opts ...grpc.CallOption) (*ListTestWorkflowTemplateLabelsResponse, error) + CreateTestWorkflowTemplate(ctx context.Context, in *CreateTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*CreateTestWorkflowTemplateResponse, error) + UpdateTestWorkflowTemplate(ctx context.Context, in *UpdateTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*UpdateTestWorkflowTemplateResponse, error) + DeleteTestWorkflowTemplate(ctx context.Context, in *DeleteTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowTemplateResponse, error) + DeleteTestWorkflowTemplatesByLabels(ctx context.Context, in *DeleteTestWorkflowTemplatesByLabelsRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowTemplatesByLabelsResponse, error) } type testKubeCloudAPIClient struct { @@ -292,6 +314,282 @@ func (c *testKubeCloudAPIClient) GetCredential(ctx context.Context, in *Credenti return out, nil } +func (c *testKubeCloudAPIClient) GetEventStream(ctx context.Context, in *EventStreamRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_GetEventStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[7], "/cloud.TestKubeCloudAPI/GetEventStream", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIGetEventStreamClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type TestKubeCloudAPI_GetEventStreamClient interface { + Recv() (*Event, error) + grpc.ClientStream +} + +type testKubeCloudAPIGetEventStreamClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIGetEventStreamClient) Recv() (*Event, error) { + m := new(Event) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *testKubeCloudAPIClient) ScheduleExecution(ctx context.Context, in *ScheduleRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_ScheduleExecutionClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[8], "/cloud.TestKubeCloudAPI/ScheduleExecution", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIScheduleExecutionClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type TestKubeCloudAPI_ScheduleExecutionClient interface { + Recv() (*ScheduleResponse, error) + grpc.ClientStream +} + +type testKubeCloudAPIScheduleExecutionClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIScheduleExecutionClient) Recv() (*ScheduleResponse, error) { + m := new(ScheduleResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *testKubeCloudAPIClient) GetRunnerRequests(ctx context.Context, opts ...grpc.CallOption) (TestKubeCloudAPI_GetRunnerRequestsClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[9], "/cloud.TestKubeCloudAPI/GetRunnerRequests", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIGetRunnerRequestsClient{stream} + return x, nil +} + +type TestKubeCloudAPI_GetRunnerRequestsClient interface { + Send(*RunnerResponse) error + Recv() (*RunnerRequest, error) + grpc.ClientStream +} + +type testKubeCloudAPIGetRunnerRequestsClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIGetRunnerRequestsClient) Send(m *RunnerResponse) error { + return x.ClientStream.SendMsg(m) +} + +func (x *testKubeCloudAPIGetRunnerRequestsClient) Recv() (*RunnerRequest, error) { + m := new(RunnerRequest) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *testKubeCloudAPIClient) ObtainExecution(ctx context.Context, in *ObtainExecutionRequest, opts ...grpc.CallOption) (*ObtainExecutionResponse, error) { + out := new(ObtainExecutionResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/ObtainExecution", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) GetTestWorkflow(ctx context.Context, in *GetTestWorkflowRequest, opts ...grpc.CallOption) (*GetTestWorkflowResponse, error) { + out := new(GetTestWorkflowResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/GetTestWorkflow", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) ListTestWorkflows(ctx context.Context, in *ListTestWorkflowsRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_ListTestWorkflowsClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[10], "/cloud.TestKubeCloudAPI/ListTestWorkflows", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIListTestWorkflowsClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type TestKubeCloudAPI_ListTestWorkflowsClient interface { + Recv() (*TestWorkflowListItem, error) + grpc.ClientStream +} + +type testKubeCloudAPIListTestWorkflowsClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIListTestWorkflowsClient) Recv() (*TestWorkflowListItem, error) { + m := new(TestWorkflowListItem) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *testKubeCloudAPIClient) ListTestWorkflowLabels(ctx context.Context, in *ListTestWorkflowLabelsRequest, opts ...grpc.CallOption) (*ListTestWorkflowLabelsResponse, error) { + out := new(ListTestWorkflowLabelsResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/ListTestWorkflowLabels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) CreateTestWorkflow(ctx context.Context, in *CreateTestWorkflowRequest, opts ...grpc.CallOption) (*CreateTestWorkflowResponse, error) { + out := new(CreateTestWorkflowResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/CreateTestWorkflow", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) UpdateTestWorkflow(ctx context.Context, in *UpdateTestWorkflowRequest, opts ...grpc.CallOption) (*UpdateTestWorkflowResponse, error) { + out := new(UpdateTestWorkflowResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/UpdateTestWorkflow", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) DeleteTestWorkflow(ctx context.Context, in *DeleteTestWorkflowRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowResponse, error) { + out := new(DeleteTestWorkflowResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/DeleteTestWorkflow", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) DeleteTestWorkflowsByLabels(ctx context.Context, in *DeleteTestWorkflowsByLabelsRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowsByLabelsResponse, error) { + out := new(DeleteTestWorkflowsByLabelsResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/DeleteTestWorkflowsByLabels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) GetTestWorkflowTemplate(ctx context.Context, in *GetTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*GetTestWorkflowTemplateResponse, error) { + out := new(GetTestWorkflowTemplateResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/GetTestWorkflowTemplate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) ListTestWorkflowTemplates(ctx context.Context, in *ListTestWorkflowTemplatesRequest, opts ...grpc.CallOption) (TestKubeCloudAPI_ListTestWorkflowTemplatesClient, error) { + stream, err := c.cc.NewStream(ctx, &TestKubeCloudAPI_ServiceDesc.Streams[11], "/cloud.TestKubeCloudAPI/ListTestWorkflowTemplates", opts...) + if err != nil { + return nil, err + } + x := &testKubeCloudAPIListTestWorkflowTemplatesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type TestKubeCloudAPI_ListTestWorkflowTemplatesClient interface { + Recv() (*TestWorkflowTemplateListItem, error) + grpc.ClientStream +} + +type testKubeCloudAPIListTestWorkflowTemplatesClient struct { + grpc.ClientStream +} + +func (x *testKubeCloudAPIListTestWorkflowTemplatesClient) Recv() (*TestWorkflowTemplateListItem, error) { + m := new(TestWorkflowTemplateListItem) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *testKubeCloudAPIClient) ListTestWorkflowTemplateLabels(ctx context.Context, in *ListTestWorkflowTemplateLabelsRequest, opts ...grpc.CallOption) (*ListTestWorkflowTemplateLabelsResponse, error) { + out := new(ListTestWorkflowTemplateLabelsResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/ListTestWorkflowTemplateLabels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) CreateTestWorkflowTemplate(ctx context.Context, in *CreateTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*CreateTestWorkflowTemplateResponse, error) { + out := new(CreateTestWorkflowTemplateResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/CreateTestWorkflowTemplate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) UpdateTestWorkflowTemplate(ctx context.Context, in *UpdateTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*UpdateTestWorkflowTemplateResponse, error) { + out := new(UpdateTestWorkflowTemplateResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/UpdateTestWorkflowTemplate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) DeleteTestWorkflowTemplate(ctx context.Context, in *DeleteTestWorkflowTemplateRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowTemplateResponse, error) { + out := new(DeleteTestWorkflowTemplateResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/DeleteTestWorkflowTemplate", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *testKubeCloudAPIClient) DeleteTestWorkflowTemplatesByLabels(ctx context.Context, in *DeleteTestWorkflowTemplatesByLabelsRequest, opts ...grpc.CallOption) (*DeleteTestWorkflowTemplatesByLabelsResponse, error) { + out := new(DeleteTestWorkflowTemplatesByLabelsResponse) + err := c.cc.Invoke(ctx, "/cloud.TestKubeCloudAPI/DeleteTestWorkflowTemplatesByLabels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // TestKubeCloudAPIServer is the server API for TestKubeCloudAPI service. // All implementations must embed UnimplementedTestKubeCloudAPIServer // for forward compatibility @@ -308,6 +606,28 @@ type TestKubeCloudAPIServer interface { GetTestWorkflowParallelStepNotificationsStream(TestKubeCloudAPI_GetTestWorkflowParallelStepNotificationsStreamServer) error GetProContext(context.Context, *emptypb.Empty) (*ProContextResponse, error) GetCredential(context.Context, *CredentialRequest) (*CredentialResponse, error) + GetEventStream(*EventStreamRequest, TestKubeCloudAPI_GetEventStreamServer) error + ScheduleExecution(*ScheduleRequest, TestKubeCloudAPI_ScheduleExecutionServer) error + // Runner + GetRunnerRequests(TestKubeCloudAPI_GetRunnerRequestsServer) error + ObtainExecution(context.Context, *ObtainExecutionRequest) (*ObtainExecutionResponse, error) + // CRD Synchronisation + // -- Test Workflows + GetTestWorkflow(context.Context, *GetTestWorkflowRequest) (*GetTestWorkflowResponse, error) + ListTestWorkflows(*ListTestWorkflowsRequest, TestKubeCloudAPI_ListTestWorkflowsServer) error + ListTestWorkflowLabels(context.Context, *ListTestWorkflowLabelsRequest) (*ListTestWorkflowLabelsResponse, error) + CreateTestWorkflow(context.Context, *CreateTestWorkflowRequest) (*CreateTestWorkflowResponse, error) + UpdateTestWorkflow(context.Context, *UpdateTestWorkflowRequest) (*UpdateTestWorkflowResponse, error) + DeleteTestWorkflow(context.Context, *DeleteTestWorkflowRequest) (*DeleteTestWorkflowResponse, error) + DeleteTestWorkflowsByLabels(context.Context, *DeleteTestWorkflowsByLabelsRequest) (*DeleteTestWorkflowsByLabelsResponse, error) + // -- Test Workflow Templates + GetTestWorkflowTemplate(context.Context, *GetTestWorkflowTemplateRequest) (*GetTestWorkflowTemplateResponse, error) + ListTestWorkflowTemplates(*ListTestWorkflowTemplatesRequest, TestKubeCloudAPI_ListTestWorkflowTemplatesServer) error + ListTestWorkflowTemplateLabels(context.Context, *ListTestWorkflowTemplateLabelsRequest) (*ListTestWorkflowTemplateLabelsResponse, error) + CreateTestWorkflowTemplate(context.Context, *CreateTestWorkflowTemplateRequest) (*CreateTestWorkflowTemplateResponse, error) + UpdateTestWorkflowTemplate(context.Context, *UpdateTestWorkflowTemplateRequest) (*UpdateTestWorkflowTemplateResponse, error) + DeleteTestWorkflowTemplate(context.Context, *DeleteTestWorkflowTemplateRequest) (*DeleteTestWorkflowTemplateResponse, error) + DeleteTestWorkflowTemplatesByLabels(context.Context, *DeleteTestWorkflowTemplatesByLabelsRequest) (*DeleteTestWorkflowTemplatesByLabelsResponse, error) mustEmbedUnimplementedTestKubeCloudAPIServer() } @@ -345,6 +665,60 @@ func (UnimplementedTestKubeCloudAPIServer) GetProContext(context.Context, *empty func (UnimplementedTestKubeCloudAPIServer) GetCredential(context.Context, *CredentialRequest) (*CredentialResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetCredential not implemented") } +func (UnimplementedTestKubeCloudAPIServer) GetEventStream(*EventStreamRequest, TestKubeCloudAPI_GetEventStreamServer) error { + return status.Errorf(codes.Unimplemented, "method GetEventStream not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) ScheduleExecution(*ScheduleRequest, TestKubeCloudAPI_ScheduleExecutionServer) error { + return status.Errorf(codes.Unimplemented, "method ScheduleExecution not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) GetRunnerRequests(TestKubeCloudAPI_GetRunnerRequestsServer) error { + return status.Errorf(codes.Unimplemented, "method GetRunnerRequests not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) ObtainExecution(context.Context, *ObtainExecutionRequest) (*ObtainExecutionResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ObtainExecution not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) GetTestWorkflow(context.Context, *GetTestWorkflowRequest) (*GetTestWorkflowResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTestWorkflow not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) ListTestWorkflows(*ListTestWorkflowsRequest, TestKubeCloudAPI_ListTestWorkflowsServer) error { + return status.Errorf(codes.Unimplemented, "method ListTestWorkflows not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) ListTestWorkflowLabels(context.Context, *ListTestWorkflowLabelsRequest) (*ListTestWorkflowLabelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListTestWorkflowLabels not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) CreateTestWorkflow(context.Context, *CreateTestWorkflowRequest) (*CreateTestWorkflowResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateTestWorkflow not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) UpdateTestWorkflow(context.Context, *UpdateTestWorkflowRequest) (*UpdateTestWorkflowResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateTestWorkflow not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) DeleteTestWorkflow(context.Context, *DeleteTestWorkflowRequest) (*DeleteTestWorkflowResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteTestWorkflow not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) DeleteTestWorkflowsByLabels(context.Context, *DeleteTestWorkflowsByLabelsRequest) (*DeleteTestWorkflowsByLabelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteTestWorkflowsByLabels not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) GetTestWorkflowTemplate(context.Context, *GetTestWorkflowTemplateRequest) (*GetTestWorkflowTemplateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTestWorkflowTemplate not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) ListTestWorkflowTemplates(*ListTestWorkflowTemplatesRequest, TestKubeCloudAPI_ListTestWorkflowTemplatesServer) error { + return status.Errorf(codes.Unimplemented, "method ListTestWorkflowTemplates not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) ListTestWorkflowTemplateLabels(context.Context, *ListTestWorkflowTemplateLabelsRequest) (*ListTestWorkflowTemplateLabelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListTestWorkflowTemplateLabels not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) CreateTestWorkflowTemplate(context.Context, *CreateTestWorkflowTemplateRequest) (*CreateTestWorkflowTemplateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateTestWorkflowTemplate not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) UpdateTestWorkflowTemplate(context.Context, *UpdateTestWorkflowTemplateRequest) (*UpdateTestWorkflowTemplateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateTestWorkflowTemplate not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) DeleteTestWorkflowTemplate(context.Context, *DeleteTestWorkflowTemplateRequest) (*DeleteTestWorkflowTemplateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteTestWorkflowTemplate not implemented") +} +func (UnimplementedTestKubeCloudAPIServer) DeleteTestWorkflowTemplatesByLabels(context.Context, *DeleteTestWorkflowTemplatesByLabelsRequest) (*DeleteTestWorkflowTemplatesByLabelsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteTestWorkflowTemplatesByLabels not implemented") +} func (UnimplementedTestKubeCloudAPIServer) mustEmbedUnimplementedTestKubeCloudAPIServer() {} // UnsafeTestKubeCloudAPIServer may be embedded to opt out of forward compatibility for this service. @@ -594,6 +968,350 @@ func _TestKubeCloudAPI_GetCredential_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _TestKubeCloudAPI_GetEventStream_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(EventStreamRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(TestKubeCloudAPIServer).GetEventStream(m, &testKubeCloudAPIGetEventStreamServer{stream}) +} + +type TestKubeCloudAPI_GetEventStreamServer interface { + Send(*Event) error + grpc.ServerStream +} + +type testKubeCloudAPIGetEventStreamServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIGetEventStreamServer) Send(m *Event) error { + return x.ServerStream.SendMsg(m) +} + +func _TestKubeCloudAPI_ScheduleExecution_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ScheduleRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(TestKubeCloudAPIServer).ScheduleExecution(m, &testKubeCloudAPIScheduleExecutionServer{stream}) +} + +type TestKubeCloudAPI_ScheduleExecutionServer interface { + Send(*ScheduleResponse) error + grpc.ServerStream +} + +type testKubeCloudAPIScheduleExecutionServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIScheduleExecutionServer) Send(m *ScheduleResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _TestKubeCloudAPI_GetRunnerRequests_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TestKubeCloudAPIServer).GetRunnerRequests(&testKubeCloudAPIGetRunnerRequestsServer{stream}) +} + +type TestKubeCloudAPI_GetRunnerRequestsServer interface { + Send(*RunnerRequest) error + Recv() (*RunnerResponse, error) + grpc.ServerStream +} + +type testKubeCloudAPIGetRunnerRequestsServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIGetRunnerRequestsServer) Send(m *RunnerRequest) error { + return x.ServerStream.SendMsg(m) +} + +func (x *testKubeCloudAPIGetRunnerRequestsServer) Recv() (*RunnerResponse, error) { + m := new(RunnerResponse) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _TestKubeCloudAPI_ObtainExecution_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ObtainExecutionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).ObtainExecution(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/ObtainExecution", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).ObtainExecution(ctx, req.(*ObtainExecutionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_GetTestWorkflow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTestWorkflowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).GetTestWorkflow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/GetTestWorkflow", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).GetTestWorkflow(ctx, req.(*GetTestWorkflowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_ListTestWorkflows_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ListTestWorkflowsRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(TestKubeCloudAPIServer).ListTestWorkflows(m, &testKubeCloudAPIListTestWorkflowsServer{stream}) +} + +type TestKubeCloudAPI_ListTestWorkflowsServer interface { + Send(*TestWorkflowListItem) error + grpc.ServerStream +} + +type testKubeCloudAPIListTestWorkflowsServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIListTestWorkflowsServer) Send(m *TestWorkflowListItem) error { + return x.ServerStream.SendMsg(m) +} + +func _TestKubeCloudAPI_ListTestWorkflowLabels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTestWorkflowLabelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).ListTestWorkflowLabels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/ListTestWorkflowLabels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).ListTestWorkflowLabels(ctx, req.(*ListTestWorkflowLabelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_CreateTestWorkflow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTestWorkflowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).CreateTestWorkflow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/CreateTestWorkflow", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).CreateTestWorkflow(ctx, req.(*CreateTestWorkflowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_UpdateTestWorkflow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateTestWorkflowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).UpdateTestWorkflow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/UpdateTestWorkflow", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).UpdateTestWorkflow(ctx, req.(*UpdateTestWorkflowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_DeleteTestWorkflow_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTestWorkflowRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflow(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/DeleteTestWorkflow", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflow(ctx, req.(*DeleteTestWorkflowRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_DeleteTestWorkflowsByLabels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTestWorkflowsByLabelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflowsByLabels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/DeleteTestWorkflowsByLabels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflowsByLabels(ctx, req.(*DeleteTestWorkflowsByLabelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_GetTestWorkflowTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTestWorkflowTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).GetTestWorkflowTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/GetTestWorkflowTemplate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).GetTestWorkflowTemplate(ctx, req.(*GetTestWorkflowTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_ListTestWorkflowTemplates_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ListTestWorkflowTemplatesRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(TestKubeCloudAPIServer).ListTestWorkflowTemplates(m, &testKubeCloudAPIListTestWorkflowTemplatesServer{stream}) +} + +type TestKubeCloudAPI_ListTestWorkflowTemplatesServer interface { + Send(*TestWorkflowTemplateListItem) error + grpc.ServerStream +} + +type testKubeCloudAPIListTestWorkflowTemplatesServer struct { + grpc.ServerStream +} + +func (x *testKubeCloudAPIListTestWorkflowTemplatesServer) Send(m *TestWorkflowTemplateListItem) error { + return x.ServerStream.SendMsg(m) +} + +func _TestKubeCloudAPI_ListTestWorkflowTemplateLabels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTestWorkflowTemplateLabelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).ListTestWorkflowTemplateLabels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/ListTestWorkflowTemplateLabels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).ListTestWorkflowTemplateLabels(ctx, req.(*ListTestWorkflowTemplateLabelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_CreateTestWorkflowTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTestWorkflowTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).CreateTestWorkflowTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/CreateTestWorkflowTemplate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).CreateTestWorkflowTemplate(ctx, req.(*CreateTestWorkflowTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_UpdateTestWorkflowTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateTestWorkflowTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).UpdateTestWorkflowTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/UpdateTestWorkflowTemplate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).UpdateTestWorkflowTemplate(ctx, req.(*UpdateTestWorkflowTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_DeleteTestWorkflowTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTestWorkflowTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflowTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/DeleteTestWorkflowTemplate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflowTemplate(ctx, req.(*DeleteTestWorkflowTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TestKubeCloudAPI_DeleteTestWorkflowTemplatesByLabels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTestWorkflowTemplatesByLabelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflowTemplatesByLabels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cloud.TestKubeCloudAPI/DeleteTestWorkflowTemplatesByLabels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TestKubeCloudAPIServer).DeleteTestWorkflowTemplatesByLabels(ctx, req.(*DeleteTestWorkflowTemplatesByLabelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // TestKubeCloudAPI_ServiceDesc is the grpc.ServiceDesc for TestKubeCloudAPI service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -613,6 +1331,58 @@ var TestKubeCloudAPI_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetCredential", Handler: _TestKubeCloudAPI_GetCredential_Handler, }, + { + MethodName: "ObtainExecution", + Handler: _TestKubeCloudAPI_ObtainExecution_Handler, + }, + { + MethodName: "GetTestWorkflow", + Handler: _TestKubeCloudAPI_GetTestWorkflow_Handler, + }, + { + MethodName: "ListTestWorkflowLabels", + Handler: _TestKubeCloudAPI_ListTestWorkflowLabels_Handler, + }, + { + MethodName: "CreateTestWorkflow", + Handler: _TestKubeCloudAPI_CreateTestWorkflow_Handler, + }, + { + MethodName: "UpdateTestWorkflow", + Handler: _TestKubeCloudAPI_UpdateTestWorkflow_Handler, + }, + { + MethodName: "DeleteTestWorkflow", + Handler: _TestKubeCloudAPI_DeleteTestWorkflow_Handler, + }, + { + MethodName: "DeleteTestWorkflowsByLabels", + Handler: _TestKubeCloudAPI_DeleteTestWorkflowsByLabels_Handler, + }, + { + MethodName: "GetTestWorkflowTemplate", + Handler: _TestKubeCloudAPI_GetTestWorkflowTemplate_Handler, + }, + { + MethodName: "ListTestWorkflowTemplateLabels", + Handler: _TestKubeCloudAPI_ListTestWorkflowTemplateLabels_Handler, + }, + { + MethodName: "CreateTestWorkflowTemplate", + Handler: _TestKubeCloudAPI_CreateTestWorkflowTemplate_Handler, + }, + { + MethodName: "UpdateTestWorkflowTemplate", + Handler: _TestKubeCloudAPI_UpdateTestWorkflowTemplate_Handler, + }, + { + MethodName: "DeleteTestWorkflowTemplate", + Handler: _TestKubeCloudAPI_DeleteTestWorkflowTemplate_Handler, + }, + { + MethodName: "DeleteTestWorkflowTemplatesByLabels", + Handler: _TestKubeCloudAPI_DeleteTestWorkflowTemplatesByLabels_Handler, + }, }, Streams: []grpc.StreamDesc{ { @@ -656,6 +1426,32 @@ var TestKubeCloudAPI_ServiceDesc = grpc.ServiceDesc{ ServerStreams: true, ClientStreams: true, }, + { + StreamName: "GetEventStream", + Handler: _TestKubeCloudAPI_GetEventStream_Handler, + ServerStreams: true, + }, + { + StreamName: "ScheduleExecution", + Handler: _TestKubeCloudAPI_ScheduleExecution_Handler, + ServerStreams: true, + }, + { + StreamName: "GetRunnerRequests", + Handler: _TestKubeCloudAPI_GetRunnerRequests_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "ListTestWorkflows", + Handler: _TestKubeCloudAPI_ListTestWorkflows_Handler, + ServerStreams: true, + }, + { + StreamName: "ListTestWorkflowTemplates", + Handler: _TestKubeCloudAPI_ListTestWorkflowTemplates_Handler, + ServerStreams: true, + }, }, Metadata: "proto/service.proto", } diff --git a/pkg/controlplane/server.go b/pkg/controlplane/server.go index 5703ac400b6..d075a4fe834 100644 --- a/pkg/controlplane/server.go +++ b/pkg/controlplane/server.go @@ -2,6 +2,7 @@ package controlplane import ( "context" + "encoding/json" "fmt" "math" "net" @@ -21,8 +22,13 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/capabilities" "github.com/kubeshop/testkube/pkg/cloud" - "github.com/kubeshop/testkube/pkg/cloud/data/executor" + cloudexecutor "github.com/kubeshop/testkube/pkg/cloud/data/executor" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" ) const ( @@ -34,32 +40,53 @@ const ( type Server struct { cloud.UnimplementedTestKubeCloudAPIServer - cfg Config - server *grpc.Server - commands map[executor.Command]CommandHandler + cfg Config + server *grpc.Server + commands map[cloudexecutor.Command]CommandHandler + executor testworkflowexecutor.TestWorkflowExecutor + testWorkflowsClient testworkflowclient.TestWorkflowClient + testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient } type Config struct { - Port int - Verbose bool - Logger *zap.SugaredLogger + Port int + Verbose bool + Logger *zap.SugaredLogger + FeatureNewExecutions bool + FeatureTestWorkflowsCloudStorage bool } -func New(cfg Config, commandGroups ...CommandHandlers) *Server { - commands := make(map[executor.Command]CommandHandler) +func New( + cfg Config, + executor testworkflowexecutor.TestWorkflowExecutor, + testWorkflowsClient testworkflowclient.TestWorkflowClient, + testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient, + commandGroups ...CommandHandlers, +) *Server { + commands := make(map[cloudexecutor.Command]CommandHandler) for _, group := range commandGroups { for cmd, handler := range group { commands[cmd] = handler } } return &Server{ - cfg: cfg, - commands: commands, + cfg: cfg, + executor: executor, + commands: commands, + testWorkflowsClient: testWorkflowsClient, + testWorkflowTemplatesClient: testWorkflowTemplatesClient, } } func (s *Server) GetProContext(_ context.Context, _ *emptypb.Empty) (*cloud.ProContextResponse, error) { - return nil, status.Error(codes.Unimplemented, "not supported in the standalone version") + caps := make([]*cloud.Capability, 0) + if s.cfg.FeatureNewExecutions { + caps = append(caps, &cloud.Capability{Name: string(capabilities.CapabilityNewExecutions), Enabled: true}) + } + if s.cfg.FeatureTestWorkflowsCloudStorage { + caps = append(caps, &cloud.Capability{Name: string(capabilities.CapabilityTestWorkflowStorage), Enabled: true}) + } + return &cloud.ProContextResponse{Capabilities: caps}, nil } func (s *Server) GetCredential(_ context.Context, _ *cloud.CredentialRequest) (*cloud.CredentialResponse, error) { @@ -104,6 +131,22 @@ func (s *Server) ExecuteAsync(srv cloud.TestKubeCloudAPI_ExecuteAsyncServer) err return g.Wait() } +func (s *Server) GetEventStream(_ *cloud.EventStreamRequest, srv cloud.TestKubeCloudAPI_GetEventStreamServer) error { + // Do nothing - it doesn't need to pass events down + <-srv.Context().Done() + return nil +} + +func (s *Server) GetRunnerRequests(srv cloud.TestKubeCloudAPI_GetRunnerRequestsServer) error { + // Do nothing - it doesn't need to send runner requests + <-srv.Context().Done() + return nil +} + +func (s *Server) ObtainExecution(_ context.Context, req *cloud.ObtainExecutionRequest) (*cloud.ObtainExecutionResponse, error) { + return &cloud.ObtainExecutionResponse{Id: req.Id, EnvironmentId: req.EnvironmentId, Success: true}, nil +} + // TODO: Consider deleting that func (s *Server) GetTestWorkflowNotificationsStream(srv cloud.TestKubeCloudAPI_GetTestWorkflowNotificationsStreamServer) error { ctx, cancel := context.WithCancel(srv.Context()) @@ -276,7 +319,7 @@ func (s *Server) GetLogsStream(srv cloud.TestKubeCloudAPI_GetLogsStreamServer) e } func (s *Server) Call(ctx context.Context, request *cloud.CommandRequest) (*cloud.CommandResponse, error) { - if cmd, ok := s.commands[executor.Command(request.Command)]; ok { + if cmd, ok := s.commands[cloudexecutor.Command(request.Command)]; ok { return cmd(ctx, request) } return nil, errors.Errorf("command not implemented: %s", request.Command) @@ -336,9 +379,209 @@ func (s *Server) Run(ctx context.Context) error { return nil } +func (s *Server) ScheduleExecution(req *cloud.ScheduleRequest, srv cloud.TestKubeCloudAPI_ScheduleExecutionServer) error { + resp := s.executor.Execute(srv.Context(), req) + for execution := range resp.Channel() { + // Send the data + // TODO: Use protobuf struct? + v, err := json.Marshal(execution) + if err != nil { + return err + } + if err = srv.Send(&cloud.ScheduleResponse{Execution: v}); err != nil { + // TODO: retry? + return err + } + } + if resp.Error() != nil { + return resp.Error() + } + return nil +} + // TODO: Use this when context is down func (s *Server) Shutdown() { if s.server != nil { s.server.GracefulStop() } } + +func (s *Server) GetTestWorkflow(ctx context.Context, req *cloud.GetTestWorkflowRequest) (*cloud.GetTestWorkflowResponse, error) { + workflow, err := s.testWorkflowsClient.Get(ctx, "", req.Name) + if err != nil { + return nil, err + } + workflowBytes, err := json.Marshal(workflow) + if err != nil { + return nil, err + } + return &cloud.GetTestWorkflowResponse{Workflow: workflowBytes}, nil +} + +func (s *Server) ListTestWorkflows(req *cloud.ListTestWorkflowsRequest, srv cloud.TestKubeCloudAPI_ListTestWorkflowsServer) error { + workflows, err := s.testWorkflowsClient.List(srv.Context(), "", testworkflowclient.ListOptions{ + Labels: req.Labels, + TextSearch: req.TextSearch, + Offset: req.Offset, + Limit: req.Limit, + }) + if err != nil { + return err + } + var workflowBytes []byte + for _, workflow := range workflows { + workflowBytes, err = json.Marshal(workflow) + if err != nil { + return err + } + err = srv.Send(&cloud.TestWorkflowListItem{Workflow: workflowBytes}) + if err != nil { + return err + } + } + return nil +} + +func (s *Server) ListTestWorkflowLabels(ctx context.Context, req *cloud.ListTestWorkflowLabelsRequest) (*cloud.ListTestWorkflowLabelsResponse, error) { + labels, err := s.testWorkflowsClient.ListLabels(ctx, "") + if err != nil { + return nil, err + } + res := &cloud.ListTestWorkflowLabelsResponse{Labels: make([]*cloud.LabelListItem, 0, len(labels))} + for k, v := range labels { + res.Labels = append(res.Labels, &cloud.LabelListItem{Name: k, Value: v}) + } + return res, nil +} + +func (s *Server) CreateTestWorkflow(ctx context.Context, req *cloud.CreateTestWorkflowRequest) (*cloud.CreateTestWorkflowResponse, error) { + var workflow testkube.TestWorkflow + err := json.Unmarshal(req.Workflow, &workflow) + if err != nil { + return nil, err + } + err = s.testWorkflowsClient.Create(ctx, "", workflow) + if err != nil { + return nil, err + } + return &cloud.CreateTestWorkflowResponse{}, nil +} + +func (s *Server) UpdateTestWorkflow(ctx context.Context, req *cloud.UpdateTestWorkflowRequest) (*cloud.UpdateTestWorkflowResponse, error) { + var workflow testkube.TestWorkflow + err := json.Unmarshal(req.Workflow, &workflow) + if err != nil { + return nil, err + } + err = s.testWorkflowsClient.Update(ctx, "", workflow) + if err != nil { + return nil, err + } + return &cloud.UpdateTestWorkflowResponse{}, nil +} + +func (s *Server) DeleteTestWorkflow(ctx context.Context, req *cloud.DeleteTestWorkflowRequest) (*cloud.DeleteTestWorkflowResponse, error) { + err := s.testWorkflowsClient.Delete(ctx, "", req.Name) + if err != nil { + return nil, err + } + return &cloud.DeleteTestWorkflowResponse{}, nil +} + +func (s *Server) DeleteTestWorkflowsByLabels(ctx context.Context, req *cloud.DeleteTestWorkflowsByLabelsRequest) (*cloud.DeleteTestWorkflowsByLabelsResponse, error) { + count, err := s.testWorkflowsClient.DeleteByLabels(ctx, "", req.Labels) + if err != nil { + return nil, err + } + return &cloud.DeleteTestWorkflowsByLabelsResponse{Count: count}, nil +} + +func (s *Server) GetTestWorkflowTemplate(ctx context.Context, req *cloud.GetTestWorkflowTemplateRequest) (*cloud.GetTestWorkflowTemplateResponse, error) { + template, err := s.testWorkflowTemplatesClient.Get(ctx, "", req.Name) + if err != nil { + return nil, err + } + templateBytes, err := json.Marshal(template) + if err != nil { + return nil, err + } + return &cloud.GetTestWorkflowTemplateResponse{Template: templateBytes}, nil +} + +func (s *Server) ListTestWorkflowTemplates(req *cloud.ListTestWorkflowTemplatesRequest, srv cloud.TestKubeCloudAPI_ListTestWorkflowTemplatesServer) error { + templates, err := s.testWorkflowTemplatesClient.List(srv.Context(), "", testworkflowtemplateclient.ListOptions{ + Labels: req.Labels, + TextSearch: req.TextSearch, + Offset: req.Offset, + Limit: req.Limit, + }) + if err != nil { + return err + } + var templateBytes []byte + for _, template := range templates { + templateBytes, err = json.Marshal(template) + if err != nil { + return err + } + err = srv.Send(&cloud.TestWorkflowTemplateListItem{Template: templateBytes}) + if err != nil { + return err + } + } + return nil +} + +func (s *Server) ListTestWorkflowTemplateLabels(ctx context.Context, req *cloud.ListTestWorkflowTemplateLabelsRequest) (*cloud.ListTestWorkflowTemplateLabelsResponse, error) { + labels, err := s.testWorkflowTemplatesClient.ListLabels(ctx, "") + if err != nil { + return nil, err + } + res := &cloud.ListTestWorkflowTemplateLabelsResponse{Labels: make([]*cloud.LabelListItem, 0, len(labels))} + for k, v := range labels { + res.Labels = append(res.Labels, &cloud.LabelListItem{Name: k, Value: v}) + } + return res, nil +} + +func (s *Server) CreateTestWorkflowTemplate(ctx context.Context, req *cloud.CreateTestWorkflowTemplateRequest) (*cloud.CreateTestWorkflowTemplateResponse, error) { + var template testkube.TestWorkflowTemplate + err := json.Unmarshal(req.Template, &template) + if err != nil { + return nil, err + } + err = s.testWorkflowTemplatesClient.Create(ctx, "", template) + if err != nil { + return nil, err + } + return &cloud.CreateTestWorkflowTemplateResponse{}, nil +} + +func (s *Server) UpdateTestWorkflowTemplate(ctx context.Context, req *cloud.UpdateTestWorkflowTemplateRequest) (*cloud.UpdateTestWorkflowTemplateResponse, error) { + var template testkube.TestWorkflowTemplate + err := json.Unmarshal(req.Template, &template) + if err != nil { + return nil, err + } + err = s.testWorkflowTemplatesClient.Update(ctx, "", template) + if err != nil { + return nil, err + } + return &cloud.UpdateTestWorkflowTemplateResponse{}, nil +} + +func (s *Server) DeleteTestWorkflowTemplate(ctx context.Context, req *cloud.DeleteTestWorkflowTemplateRequest) (*cloud.DeleteTestWorkflowTemplateResponse, error) { + err := s.testWorkflowTemplatesClient.Delete(ctx, "", req.Name) + if err != nil { + return nil, err + } + return &cloud.DeleteTestWorkflowTemplateResponse{}, nil +} + +func (s *Server) DeleteTestWorkflowTemplatesByLabels(ctx context.Context, req *cloud.DeleteTestWorkflowTemplatesByLabelsRequest) (*cloud.DeleteTestWorkflowTemplatesByLabelsResponse, error) { + count, err := s.testWorkflowTemplatesClient.DeleteByLabels(ctx, "", req.Labels) + if err != nil { + return nil, err + } + return &cloud.DeleteTestWorkflowTemplatesByLabelsResponse{Count: count}, nil +} diff --git a/pkg/controlplane/utils.go b/pkg/controlplane/utils.go index 52907166c46..6d43ccded68 100644 --- a/pkg/controlplane/utils.go +++ b/pkg/controlplane/utils.go @@ -13,7 +13,7 @@ import ( "google.golang.org/protobuf/types/known/structpb" "github.com/kubeshop/testkube/pkg/cloud" - "github.com/kubeshop/testkube/pkg/cloud/data/executor" + cloudexecutor "github.com/kubeshop/testkube/pkg/cloud/data/executor" "github.com/kubeshop/testkube/pkg/log" ) @@ -22,7 +22,7 @@ type grpcstatus interface { } type CommandHandler func(ctx context.Context, req *cloud.CommandRequest) (*cloud.CommandResponse, error) -type CommandHandlers map[executor.Command]CommandHandler +type CommandHandlers map[cloudexecutor.Command]CommandHandler func Handler[T any, U any](fn func(ctx context.Context, payload T) (U, error)) func(ctx context.Context, req *cloud.CommandRequest) (*cloud.CommandResponse, error) { return func(ctx context.Context, req *cloud.CommandRequest) (*cloud.CommandResponse, error) { diff --git a/pkg/credentials/expressions.go b/pkg/credentials/expressions.go index 8ca52ada84d..99eba8fb23e 100644 --- a/pkg/credentials/expressions.go +++ b/pkg/credentials/expressions.go @@ -27,6 +27,13 @@ func NewCredentialMachine(repository CredentialRepository, observers ...func(nam } if computed { expr, err := expressions.CompileAndResolveTemplate(string(value)) + // TODO: consider obfuscating each static part, if it's not finalized yet + if expr.Static() != nil { + strValue, _ := expr.Static().StringValue() + for i := range observers { + observers[i](name, strValue) + } + } return expr, true, err } valueStr := string(value) diff --git a/pkg/credentials/repository.go b/pkg/credentials/repository.go index 4b3c8dd18d1..05692a0db8c 100644 --- a/pkg/credentials/repository.go +++ b/pkg/credentials/repository.go @@ -3,14 +3,34 @@ package credentials import ( "context" "math" + "time" "google.golang.org/grpc" "google.golang.org/grpc/encoding/gzip" + "google.golang.org/grpc/status" agentclient "github.com/kubeshop/testkube/pkg/agent/client" "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/log" ) +type grpcstatus interface { + GRPCStatus() *status.Status +} + +const ( + GetCredentialRetryCount = 30 +) + +func getIterationDelay(iteration int) time.Duration { + if iteration < 5 { + return 500 * time.Millisecond + } else if iteration < 100 { + return 1 * time.Second + } + return 5 * time.Second +} + //go:generate mockgen -destination=./mock_repository.go -package=credentials "github.com/kubeshop/testkube/pkg/credentials" CredentialRepository type CredentialRepository interface { Get(ctx context.Context, name string) ([]byte, error) @@ -29,9 +49,19 @@ func NewCredentialRepository(getClient func() cloud.TestKubeCloudAPIClient, apiK func (c *credentialRepository) Get(ctx context.Context, name string) ([]byte, error) { ctx = agentclient.AddAPIKeyMeta(ctx, c.apiKey) opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} - result, err := c.getClient().GetCredential(ctx, &cloud.CredentialRequest{Name: name, ExecutionId: c.executionId}, opts...) - if err != nil { - return nil, err + var err error + var result *cloud.CredentialResponse + for i := 0; i < GetCredentialRetryCount; i++ { + result, err = c.getClient().GetCredential(ctx, &cloud.CredentialRequest{Name: name, ExecutionId: c.executionId}, opts...) + if err == nil { + return result.Content, nil + } + if _, ok := err.(grpcstatus); ok { + return nil, err + } + // Try to get credentials again if it may be recoverable error + log.DefaultLogger.Warnw("failed to get credential", "error", err) + time.Sleep(getIterationDelay(i)) } - return result.Content, nil + return nil, err } diff --git a/pkg/event/emitter.go b/pkg/event/emitter.go index 232537c9834..17b8ddb5b7b 100644 --- a/pkg/event/emitter.go +++ b/pkg/event/emitter.go @@ -30,6 +30,10 @@ func NewEmitter(eventBus bus.Bus, clusterName string) *Emitter { } } +type Interface interface { + Notify(event testkube.Event) +} + // Emitter handles events emitting for webhooks type Emitter struct { Listeners common.Listeners diff --git a/pkg/event/kind/testworkflowexecutionmetrics/listener.go b/pkg/event/kind/testworkflowexecutionmetrics/listener.go new file mode 100644 index 00000000000..7ac88d920d2 --- /dev/null +++ b/pkg/event/kind/testworkflowexecutionmetrics/listener.go @@ -0,0 +1,63 @@ +package testworkflowexecutionmetrics + +import ( + "context" + "fmt" + + "github.com/kubeshop/testkube/internal/app/api/metrics" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/event/kind/common" +) + +var _ common.Listener = (*testWorkflowExecutionMetricsListener)(nil) + +// Send metrics based on the Test Workflow Execution status changes +func NewListener(ctx context.Context, metrics metrics.Metrics, dashboardURI string) *testWorkflowExecutionMetricsListener { + return &testWorkflowExecutionMetricsListener{ + ctx: ctx, + metrics: metrics, + dashboardURI: dashboardURI, + } +} + +type testWorkflowExecutionMetricsListener struct { + ctx context.Context + metrics metrics.Metrics + dashboardURI string +} + +func (l *testWorkflowExecutionMetricsListener) Name() string { + return "TestWorkflowExecutionMetrics" +} + +func (l *testWorkflowExecutionMetricsListener) Selector() string { + return "" +} + +func (l *testWorkflowExecutionMetricsListener) Kind() string { + return "TestWorkflowExecutionMetrics" +} + +func (l *testWorkflowExecutionMetricsListener) Events() []testkube.EventType { + return []testkube.EventType{ + testkube.END_TESTWORKFLOW_SUCCESS_EventType, + testkube.END_TESTWORKFLOW_FAILED_EventType, + testkube.END_TESTWORKFLOW_ABORTED_EventType, + } +} + +func (l *testWorkflowExecutionMetricsListener) Metadata() map[string]string { + return map[string]string{ + "name": l.Name(), + "events": fmt.Sprintf("%v", l.Events()), + "selector": l.Selector(), + } +} + +func (l *testWorkflowExecutionMetricsListener) Notify(event testkube.Event) testkube.EventResult { + if event.TestWorkflowExecution == nil { + return testkube.NewSuccessEventResult(event.Id, "ignored") + } + l.metrics.IncAndObserveExecuteTestWorkflow(*event.TestWorkflowExecution, l.dashboardURI) + return testkube.NewSuccessEventResult(event.Id, "monitored") +} diff --git a/pkg/event/kind/testworkflowexecutionmetrics/loader.go b/pkg/event/kind/testworkflowexecutionmetrics/loader.go new file mode 100644 index 00000000000..fb841f03c6b --- /dev/null +++ b/pkg/event/kind/testworkflowexecutionmetrics/loader.go @@ -0,0 +1,28 @@ +package testworkflowexecutionmetrics + +import ( + "context" + + "github.com/kubeshop/testkube/internal/app/api/metrics" + "github.com/kubeshop/testkube/pkg/event/kind/common" +) + +var _ common.ListenerLoader = (*testWorkflowExecutionMetricsLoader)(nil) + +func NewLoader(ctx context.Context, metrics metrics.Metrics, dashboardURI string) *testWorkflowExecutionMetricsLoader { + return &testWorkflowExecutionMetricsLoader{ + listener: NewListener(ctx, metrics, dashboardURI), + } +} + +type testWorkflowExecutionMetricsLoader struct { + listener *testWorkflowExecutionMetricsListener +} + +func (r *testWorkflowExecutionMetricsLoader) Kind() string { + return "TestWorkflowExecutionMetrics" +} + +func (r *testWorkflowExecutionMetricsLoader) Load() (listeners common.Listeners, err error) { + return common.Listeners{r.listener}, nil +} diff --git a/pkg/event/kind/testworkflowexecutions/listener.go b/pkg/event/kind/testworkflowexecutions/listener.go new file mode 100644 index 00000000000..748fe3c0a9e --- /dev/null +++ b/pkg/event/kind/testworkflowexecutions/listener.go @@ -0,0 +1,83 @@ +package testworkflowexecutions + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/event/kind/common" + "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" +) + +var _ common.Listener = (*testWorkflowExecutionListener)(nil) + +// Update TestWorkflowExecution object in Kubernetes after status change +func NewListener(ctx context.Context, namespace string, kubeClient client.Client) *testWorkflowExecutionListener { + return &testWorkflowExecutionListener{ + ctx: ctx, + namespace: namespace, + kubeClient: kubeClient, + } +} + +type testWorkflowExecutionListener struct { + ctx context.Context + namespace string + kubeClient client.Client +} + +func (l *testWorkflowExecutionListener) Name() string { + return "TestWorkflowExecution" +} + +func (l *testWorkflowExecutionListener) Selector() string { + return "" +} + +func (l *testWorkflowExecutionListener) Kind() string { + return "TestWorkflowExecution" +} + +func (l *testWorkflowExecutionListener) Events() []testkube.EventType { + return []testkube.EventType{ + testkube.QUEUE_TESTWORKFLOW_EventType, + testkube.START_TESTWORKFLOW_EventType, + testkube.END_TESTWORKFLOW_SUCCESS_EventType, + testkube.END_TESTWORKFLOW_FAILED_EventType, + testkube.END_TESTWORKFLOW_ABORTED_EventType, + } +} + +func (l *testWorkflowExecutionListener) Metadata() map[string]string { + return map[string]string{ + "name": l.Name(), + "events": fmt.Sprintf("%v", l.Events()), + "selector": l.Selector(), + } +} + +func (l *testWorkflowExecutionListener) Notify(event testkube.Event) testkube.EventResult { + if event.TestWorkflowExecution == nil || event.TestWorkflowExecution.TestWorkflowExecutionName == "" { + return testkube.NewSuccessEventResult(event.Id, "ignored") + } + l.update(event.TestWorkflowExecution) + return testkube.NewSuccessEventResult(event.Id, "monitored") +} + +func (l *testWorkflowExecutionListener) update(execution *testkube.TestWorkflowExecution) { + obj := &testworkflowsv1.TestWorkflowExecution{} + err := l.kubeClient.Get(l.ctx, client.ObjectKey{Name: execution.TestWorkflowExecutionName, Namespace: l.namespace}, obj) + if err != nil { + log.DefaultLogger.Errorw("failed to get TestWorkflowExecution resource", "id", execution.Id, "error", err) + return + } + obj.Status = testworkflows.MapTestWorkflowExecutionStatusAPIToKube(execution, obj.Generation) + err = l.kubeClient.Status().Update(l.ctx, obj) + if err != nil { + log.DefaultLogger.Errorw("failed to update TestWorkflowExecution resource", "id", execution.Id, "error", err) + } +} diff --git a/pkg/event/kind/testworkflowexecutions/loader.go b/pkg/event/kind/testworkflowexecutions/loader.go new file mode 100644 index 00000000000..4ad85f3960d --- /dev/null +++ b/pkg/event/kind/testworkflowexecutions/loader.go @@ -0,0 +1,29 @@ +package testworkflowexecutions + +import ( + "context" + + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kubeshop/testkube/pkg/event/kind/common" +) + +var _ common.ListenerLoader = (*testWorkflowExecutionLoader)(nil) + +func NewLoader(ctx context.Context, namespace string, kubeClient client.Client) *testWorkflowExecutionLoader { + return &testWorkflowExecutionLoader{ + listener: NewListener(ctx, namespace, kubeClient), + } +} + +type testWorkflowExecutionLoader struct { + listener *testWorkflowExecutionListener +} + +func (r *testWorkflowExecutionLoader) Kind() string { + return "TestWorkflowExecution" +} + +func (r *testWorkflowExecutionLoader) Load() (listeners common.Listeners, err error) { + return common.Listeners{r.listener}, nil +} diff --git a/pkg/event/kind/testworkflowexecutiontelemetry/listener.go b/pkg/event/kind/testworkflowexecutiontelemetry/listener.go new file mode 100644 index 00000000000..129e2a61808 --- /dev/null +++ b/pkg/event/kind/testworkflowexecutiontelemetry/listener.go @@ -0,0 +1,178 @@ +package testworkflowexecutiontelemetry + +import ( + "context" + "fmt" + "strings" + + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/event/kind/common" + "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" + "github.com/kubeshop/testkube/pkg/repository/config" + "github.com/kubeshop/testkube/pkg/telemetry" + "github.com/kubeshop/testkube/pkg/version" +) + +var _ common.Listener = (*testWorkflowExecutionTelemetryListener)(nil) + +// Send telemetry events based on the Test Workflow Execution status changes +func NewListener(ctx context.Context, configRepository config.Repository) *testWorkflowExecutionTelemetryListener { + return &testWorkflowExecutionTelemetryListener{ + ctx: ctx, + configRepository: configRepository, + } +} + +type testWorkflowExecutionTelemetryListener struct { + ctx context.Context + configRepository config.Repository +} + +func (l *testWorkflowExecutionTelemetryListener) Name() string { + return "TestWorkflowExecutionTelemetry" +} + +func (l *testWorkflowExecutionTelemetryListener) Selector() string { + return "" +} + +func (l *testWorkflowExecutionTelemetryListener) Kind() string { + return "TestWorkflowExecutionTelemetry" +} + +func (l *testWorkflowExecutionTelemetryListener) Events() []testkube.EventType { + return []testkube.EventType{ + testkube.QUEUE_TESTWORKFLOW_EventType, + testkube.START_TESTWORKFLOW_EventType, + testkube.END_TESTWORKFLOW_SUCCESS_EventType, + testkube.END_TESTWORKFLOW_FAILED_EventType, + testkube.END_TESTWORKFLOW_ABORTED_EventType, + } +} + +func (l *testWorkflowExecutionTelemetryListener) Metadata() map[string]string { + return map[string]string{ + "name": l.Name(), + "events": fmt.Sprintf("%v", l.Events()), + "selector": l.Selector(), + } +} + +func (l *testWorkflowExecutionTelemetryListener) Notify(event testkube.Event) testkube.EventResult { + if event.TestWorkflowExecution == nil { + return testkube.NewSuccessEventResult(event.Id, "ignored") + } + + if *event.Type_ == testkube.END_TESTWORKFLOW_ABORTED_EventType || + *event.Type_ == testkube.END_TESTWORKFLOW_FAILED_EventType || + *event.Type_ == testkube.END_TESTWORKFLOW_SUCCESS_EventType { + l.sendRunWorkflowTelemetry(context.Background(), testworkflows.MapAPIToKube(event.TestWorkflowExecution.ResolvedWorkflow), event.TestWorkflowExecution) + } + + return testkube.NewSuccessEventResult(event.Id, "monitored") +} + +func (l *testWorkflowExecutionTelemetryListener) sendRunWorkflowTelemetry(ctx context.Context, workflow *testworkflowsv1.TestWorkflow, execution *testkube.TestWorkflowExecution) { + if workflow == nil { + log.DefaultLogger.Debug("empty workflow passed to telemetry event") + return + } + telemetryEnabled, err := l.configRepository.GetTelemetryEnabled(ctx) + if err != nil { + log.DefaultLogger.Debugf("getting telemetry enabled error", "error", err) + } + if !telemetryEnabled { + return + } + + properties := make(map[string]any) + properties["name"] = workflow.Name + stats := stepStats{ + imagesUsed: make(map[string]struct{}), + templatesUsed: make(map[string]struct{}), + } + + var isSample bool + if workflow.Labels != nil && workflow.Labels["docs"] == "example" && strings.HasSuffix(workflow.Name, "-sample") { + isSample = true + } else { + isSample = false + } + + spec := workflow.Spec + for _, step := range spec.Steps { + stats.Merge(getStepInfo(step)) + } + if spec.Container != nil { + stats.imagesUsed[spec.Container.Image] = struct{}{} + } + if len(spec.Services) != 0 { + stats.hasServices = true + } + if len(spec.Use) > 0 { + stats.hasTemplate = true + for _, tmpl := range spec.Use { + stats.templatesUsed[tmpl.Name] = struct{}{} + } + } + + var images []string + for image := range stats.imagesUsed { + if image == "" { + continue + } + images = append(images, image) + } + + var templates []string + for t := range stats.templatesUsed { + if t == "" { + continue + } + templates = append(templates, t) + } + var ( + status string + durationMs int32 + ) + if execution.Result != nil { + if execution.Result.Status != nil { + status = string(*execution.Result.Status) + } + durationMs = execution.Result.DurationMs + } + + out, err := telemetry.SendRunWorkflowEvent("testkube_api_run_test_workflow", telemetry.RunWorkflowParams{ + RunParams: telemetry.RunParams{ + AppVersion: version.Version, + DataSource: GetDataSource(workflow.Spec.Content), + Host: GetHostname(), + ClusterID: GetClusterID(ctx, l.configRepository), + DurationMs: durationMs, + Status: status, + }, + WorkflowParams: telemetry.WorkflowParams{ + TestWorkflowSteps: int32(stats.numSteps), + TestWorkflowExecuteCount: int32(stats.numExecute), + TestWorkflowImage: GetImage(workflow.Spec.Container), + TestWorkflowArtifactUsed: stats.hasArtifacts, + TestWorkflowParallelUsed: stats.hasParallel, + TestWorkflowMatrixUsed: stats.hasMatrix, + TestWorkflowServicesUsed: stats.hasServices, + TestWorkflowTemplateUsed: stats.hasTemplate, + TestWorkflowIsSample: isSample, + TestWorkflowTemplates: templates, + TestWorkflowImages: images, + TestWorkflowKubeshopGitURI: IsKubeshopGitURI(workflow.Spec.Content) || + HasWorkflowStepLike(workflow.Spec, HasKubeshopGitURI), + }, + }) + + if err != nil { + log.DefaultLogger.Debugw("sending run test workflow telemetry event error", "error", err) + } else { + log.DefaultLogger.Debugw("sending run test workflow telemetry event", "output", out) + } +} diff --git a/pkg/event/kind/testworkflowexecutiontelemetry/loader.go b/pkg/event/kind/testworkflowexecutiontelemetry/loader.go new file mode 100644 index 00000000000..4b2f71945ae --- /dev/null +++ b/pkg/event/kind/testworkflowexecutiontelemetry/loader.go @@ -0,0 +1,28 @@ +package testworkflowexecutiontelemetry + +import ( + "context" + + "github.com/kubeshop/testkube/pkg/event/kind/common" + "github.com/kubeshop/testkube/pkg/repository/config" +) + +var _ common.ListenerLoader = (*testWorkflowExecutionTelemetryLoader)(nil) + +func NewLoader(ctx context.Context, configRepository config.Repository) *testWorkflowExecutionTelemetryLoader { + return &testWorkflowExecutionTelemetryLoader{ + listener: NewListener(ctx, configRepository), + } +} + +type testWorkflowExecutionTelemetryLoader struct { + listener *testWorkflowExecutionTelemetryListener +} + +func (r *testWorkflowExecutionTelemetryLoader) Kind() string { + return "TestWorkflowExecutionTelemetry" +} + +func (r *testWorkflowExecutionTelemetryLoader) Load() (listeners common.Listeners, err error) { + return common.Listeners{r.listener}, nil +} diff --git a/pkg/event/kind/testworkflowexecutiontelemetry/testworkflowtelemetry.go b/pkg/event/kind/testworkflowexecutiontelemetry/testworkflowtelemetry.go new file mode 100644 index 00000000000..b4e8f966d0a --- /dev/null +++ b/pkg/event/kind/testworkflowexecutiontelemetry/testworkflowtelemetry.go @@ -0,0 +1,122 @@ +package testworkflowexecutiontelemetry + +import ( + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" +) + +type stepStats struct { + numSteps int + numExecute int + hasArtifacts bool + hasMatrix bool + hasParallel bool + hasTemplate bool + hasServices bool + imagesUsed map[string]struct{} + templatesUsed map[string]struct{} +} + +func (ss *stepStats) Merge(stats *stepStats) { + ss.numSteps += stats.numSteps + ss.numExecute += stats.numExecute + + if stats.hasArtifacts { + ss.hasArtifacts = true + } + if stats.hasMatrix { + ss.hasMatrix = true + } + if stats.hasParallel { + ss.hasParallel = true + } + if stats.hasServices { + ss.hasServices = true + } + if stats.hasTemplate { + ss.hasTemplate = true + } + for image := range stats.imagesUsed { + ss.imagesUsed[image] = struct{}{} + } + for tmpl := range stats.templatesUsed { + ss.templatesUsed[tmpl] = struct{}{} + } +} + +func getStepInfo(step testworkflowsv1.Step) *stepStats { + res := &stepStats{ + imagesUsed: make(map[string]struct{}), + templatesUsed: make(map[string]struct{}), + } + if step.Execute != nil { + res.numExecute++ + } + if step.Artifacts != nil { + res.hasArtifacts = true + } + if len(step.Use) > 0 { + res.hasTemplate = true + for _, tmpl := range step.Use { + res.templatesUsed[tmpl.Name] = struct{}{} + } + } + if step.Template != nil { + res.hasTemplate = true + res.templatesUsed[step.Template.Name] = struct{}{} + } + if len(step.Services) > 0 { + res.hasServices = true + } + + if step.Run != nil && step.Run.Image != "" { + res.imagesUsed[step.Run.Image] = struct{}{} + } + if step.Container != nil && step.Container.Image != "" { + res.imagesUsed[step.Container.Image] = struct{}{} + } + + for _, step := range step.Steps { + res.Merge(getStepInfo(step)) + } + + if step.Parallel != nil { + res.hasParallel = true + + if len(step.Parallel.Matrix) != 0 { + res.hasMatrix = true + } + if step.Parallel.Artifacts != nil { + res.hasArtifacts = true + } + if step.Parallel.Execute != nil { + res.numExecute++ + } + if len(step.Parallel.Use) > 0 { + res.hasTemplate = true + for _, tmpl := range step.Parallel.Use { + res.templatesUsed[tmpl.Name] = struct{}{} + } + } + if step.Parallel.Template != nil { + res.hasTemplate = true + res.templatesUsed[step.Parallel.Template.Name] = struct{}{} + } + + if len(step.Parallel.Services) > 0 { + res.hasServices = true + } + + if step.Parallel.Run != nil && step.Parallel.Run.Image != "" { + res.imagesUsed[step.Parallel.Run.Image] = struct{}{} + } + if step.Parallel.Container != nil && step.Parallel.Container.Image != "" { + res.imagesUsed[step.Parallel.Container.Image] = struct{}{} + } + + for _, step := range step.Parallel.Steps { + res.Merge(getStepInfo(step)) + } + } + + return res +} diff --git a/pkg/event/kind/testworkflowexecutiontelemetry/utils.go b/pkg/event/kind/testworkflowexecutiontelemetry/utils.go new file mode 100644 index 00000000000..5bb639b2dfb --- /dev/null +++ b/pkg/event/kind/testworkflowexecutiontelemetry/utils.go @@ -0,0 +1,102 @@ +package testworkflowexecutiontelemetry + +import ( + "context" + "os" + "strings" + + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/log" + configRepo "github.com/kubeshop/testkube/pkg/repository/config" +) + +// GetImage returns the image of the container +func GetImage(container *testworkflowsv1.ContainerConfig) string { + if container != nil { + return container.Image + } + return "" +} + +func HasWorkflowStepLike(spec testworkflowsv1.TestWorkflowSpec, fn func(step testworkflowsv1.Step) bool) bool { + return HasStepLike(spec.Setup, fn) || HasStepLike(spec.Steps, fn) || HasStepLike(spec.After, fn) +} + +func HasTemplateStepLike(spec testworkflowsv1.TestWorkflowTemplateSpec, fn func(step testworkflowsv1.IndependentStep) bool) bool { + return HasIndependentStepLike(spec.Setup, fn) || HasIndependentStepLike(spec.Steps, fn) || HasIndependentStepLike(spec.After, fn) +} + +func HasStepLike(steps []testworkflowsv1.Step, fn func(step testworkflowsv1.Step) bool) bool { + for _, step := range steps { + if fn(step) || HasStepLike(step.Setup, fn) || HasStepLike(step.Steps, fn) { + return true + } + } + return false +} + +func HasIndependentStepLike(steps []testworkflowsv1.IndependentStep, fn func(step testworkflowsv1.IndependentStep) bool) bool { + for _, step := range steps { + if fn(step) || HasIndependentStepLike(step.Setup, fn) || HasIndependentStepLike(step.Steps, fn) { + return true + } + } + return false +} + +// HasArtifacts checks if the test workflow steps have artifacts +func HasArtifacts(step testworkflowsv1.Step) bool { + return step.Artifacts != nil +} + +// HasTemplateArtifacts checks if the test workflow steps have artifacts +func HasTemplateArtifacts(step testworkflowsv1.IndependentStep) bool { + return step.Artifacts != nil +} + +// HasKubeshopGitURI checks if the test workflow spec has a git URI that contains "kubeshop" +func HasKubeshopGitURI(step testworkflowsv1.Step) bool { + return IsKubeshopGitURI(step.Content) +} + +// HasTemplateKubeshopGitURI checks if the test workflow spec has a git URI that contains "kubeshop" +func HasTemplateKubeshopGitURI(step testworkflowsv1.IndependentStep) bool { + return IsKubeshopGitURI(step.Content) +} + +// IsKubeshopGitURI checks if the content has a git URI that contains "kubeshop" +func IsKubeshopGitURI(content *testworkflowsv1.Content) bool { + return content != nil && content.Git != nil && strings.Contains(content.Git.Uri, "kubeshop") +} + +// GetDataSource returns the data source of the content +func GetDataSource(content *testworkflowsv1.Content) string { + if content != nil { + if content.Git != nil { + return "git" + } else if len(content.Files) != 0 { + return "files" + } + } + return "" +} + +// GetHostname returns the hostname +func GetHostname() string { + host, err := os.Hostname() + if err != nil { + log.DefaultLogger.Debugf("getting hostname error", "hostname", host, "error", err) + return "" + } + return host +} + +// GetClusterID returns the cluster id +func GetClusterID(ctx context.Context, configMap configRepo.Repository) string { + clusterID, err := configMap.GetUniqueClusterId(ctx) + if err != nil { + log.DefaultLogger.Debugf("getting cluster id error", "error", err) + return "" + } + return clusterID +} diff --git a/pkg/testworkflows/utils_test.go b/pkg/event/kind/testworkflowexecutiontelemetry/utils_test.go similarity index 96% rename from pkg/testworkflows/utils_test.go rename to pkg/event/kind/testworkflowexecutiontelemetry/utils_test.go index f37685e4309..2eb55271582 100644 --- a/pkg/testworkflows/utils_test.go +++ b/pkg/event/kind/testworkflowexecutiontelemetry/utils_test.go @@ -1,12 +1,4 @@ -// 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 +package testworkflowexecutiontelemetry import ( "context" diff --git a/pkg/event/lazy.go b/pkg/event/lazy.go new file mode 100644 index 00000000000..36069b8b814 --- /dev/null +++ b/pkg/event/lazy.go @@ -0,0 +1,15 @@ +package event + +import "github.com/kubeshop/testkube/pkg/api/v1/testkube" + +type lazyEventEmitter[T Interface] struct { + accessor *T +} + +func Lazy[T Interface](accessor *T) Interface { + return &lazyEventEmitter[T]{accessor: accessor} +} + +func (l *lazyEventEmitter[T]) Notify(event testkube.Event) { + (*l.accessor).Notify(event) +} diff --git a/pkg/newclients/testworkflowclient/cloud.go b/pkg/newclients/testworkflowclient/cloud.go new file mode 100644 index 00000000000..733bec3a7bf --- /dev/null +++ b/pkg/newclients/testworkflowclient/cloud.go @@ -0,0 +1,151 @@ +package testworkflowclient + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" +) + +var _ TestWorkflowClient = &cloudTestWorkflowClient{} + +type cloudTestWorkflowClient struct { + conn *grpc.ClientConn + client cloud.TestKubeCloudAPIClient + apiKey string +} + +func NewCloudTestWorkflowClient(conn *grpc.ClientConn, apiKey string) TestWorkflowClient { + return &cloudTestWorkflowClient{ + conn: conn, + client: cloud.NewTestKubeCloudAPIClient(conn), + apiKey: apiKey, + } +} + +func (c *cloudTestWorkflowClient) Get(ctx context.Context, environmentId string, name string) (*testkube.TestWorkflow, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.GetTestWorkflow(ctx, &cloud.GetTestWorkflowRequest{ + EnvironmentId: environmentId, + Name: name, + }) + if err != nil { + return nil, err + } + + var workflow testkube.TestWorkflow + if err = json.Unmarshal(resp.Workflow, &workflow); err != nil { + return nil, err + } + return &workflow, nil +} + +func (c *cloudTestWorkflowClient) List(ctx context.Context, environmentId string, options ListOptions) ([]testkube.TestWorkflow, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.ListTestWorkflows(ctx, &cloud.ListTestWorkflowsRequest{ + EnvironmentId: environmentId, + Offset: options.Offset, + Limit: options.Limit, + Labels: options.Labels, + TextSearch: options.TextSearch, + }) + if err != nil { + return nil, err + } + + result := make([]testkube.TestWorkflow, 0) + var item *cloud.TestWorkflowListItem + for { + item, err = resp.Recv() + if err != nil { + break + } + var workflow testkube.TestWorkflow + err = json.Unmarshal(item.Workflow, &workflow) + if err != nil { + return nil, err + } + result = append(result, workflow) + } + return result, err +} + +func (c *cloudTestWorkflowClient) ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.ListTestWorkflowLabels(ctx, &cloud.ListTestWorkflowLabelsRequest{ + EnvironmentId: environmentId, + }) + if err != nil { + return nil, err + } + result := make(map[string][]string, len(resp.Labels)) + for _, label := range resp.Labels { + result[label.Name] = label.Value + } + return result, nil +} + +func (c *cloudTestWorkflowClient) Update(ctx context.Context, environmentId string, workflow testkube.TestWorkflow) error { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + workflowBytes, err := json.Marshal(workflow) + if err != nil { + return err + } + _, err = c.client.UpdateTestWorkflow(ctx, &cloud.UpdateTestWorkflowRequest{ + EnvironmentId: environmentId, + Workflow: workflowBytes, + }) + return err +} + +func (c *cloudTestWorkflowClient) Create(ctx context.Context, environmentId string, workflow testkube.TestWorkflow) error { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + workflowBytes, err := json.Marshal(workflow) + if err != nil { + return err + } + _, err = c.client.CreateTestWorkflow(ctx, &cloud.CreateTestWorkflowRequest{ + EnvironmentId: environmentId, + Workflow: workflowBytes, + }) + return err +} + +func (c *cloudTestWorkflowClient) Delete(ctx context.Context, environmentId string, name string) error { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + _, err := c.client.DeleteTestWorkflow(ctx, &cloud.DeleteTestWorkflowRequest{ + EnvironmentId: environmentId, + Name: name, + }) + return err +} + +func (c *cloudTestWorkflowClient) DeleteByLabels(ctx context.Context, environmentId string, labels map[string]string) (uint32, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.DeleteTestWorkflowsByLabels(ctx, &cloud.DeleteTestWorkflowsByLabelsRequest{ + EnvironmentId: environmentId, + Labels: labels, + }) + if err != nil { + return 0, err + } + return resp.Count, nil +} diff --git a/pkg/newclients/testworkflowclient/interface.go b/pkg/newclients/testworkflowclient/interface.go new file mode 100644 index 00000000000..13bdf573e3e --- /dev/null +++ b/pkg/newclients/testworkflowclient/interface.go @@ -0,0 +1,25 @@ +package testworkflowclient + +import ( + "context" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +type ListOptions struct { + Labels map[string]string + TextSearch string + Offset uint32 + Limit uint32 +} + +//go:generate mockgen -destination=./mock_interface.go -package=testworkflowclient "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" TestWorkflowClient +type TestWorkflowClient interface { + Get(ctx context.Context, environmentId string, name string) (*testkube.TestWorkflow, error) + List(ctx context.Context, environmentId string, options ListOptions) ([]testkube.TestWorkflow, error) + ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) + Update(ctx context.Context, environmentId string, workflow testkube.TestWorkflow) error + Create(ctx context.Context, environmentId string, workflow testkube.TestWorkflow) error + Delete(ctx context.Context, environmentId string, name string) error + DeleteByLabels(ctx context.Context, environmentId string, labels map[string]string) (uint32, error) +} diff --git a/pkg/newclients/testworkflowclient/kubernetes.go b/pkg/newclients/testworkflowclient/kubernetes.go new file mode 100644 index 00000000000..77a67a4ec2a --- /dev/null +++ b/pkg/newclients/testworkflowclient/kubernetes.go @@ -0,0 +1,152 @@ +package testworkflowclient + +import ( + "context" + "math" + "slices" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + labels2 "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "sigs.k8s.io/controller-runtime/pkg/client" + + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" +) + +var _ TestWorkflowClient = &k8sTestWorkflowClient{} + +type k8sTestWorkflowClient struct { + client client.Client + namespace string +} + +func NewKubernetesTestWorkflowClient(client client.Client, namespace string) TestWorkflowClient { + return &k8sTestWorkflowClient{ + client: client, + namespace: namespace, + } +} + +func (c *k8sTestWorkflowClient) get(ctx context.Context, name string) (*testworkflowsv1.TestWorkflow, error) { + workflow := testworkflowsv1.TestWorkflow{} + opts := client.ObjectKey{Namespace: c.namespace, Name: name} + if err := c.client.Get(ctx, opts, &workflow); err != nil { + return nil, err + } + return &workflow, nil +} + +func (c *k8sTestWorkflowClient) Get(ctx context.Context, environmentId string, name string) (*testkube.TestWorkflow, error) { + workflow, err := c.get(ctx, name) + if err != nil { + return nil, err + } + return testworkflows.MapKubeToAPI(workflow), nil +} + +func (c *k8sTestWorkflowClient) List(ctx context.Context, environmentId string, options ListOptions) ([]testkube.TestWorkflow, error) { + labelSelector := labels2.NewSelector() + for k, v := range options.Labels { + req, _ := labels2.NewRequirement(k, selection.Equals, []string{v}) + labelSelector = labelSelector.Add(*req) + } + + list := &testworkflowsv1.TestWorkflowList{} + opts := &client.ListOptions{Namespace: c.namespace, LabelSelector: labelSelector} + if options.Limit != 0 && options.TextSearch == "" { + opts.Limit = int64(options.Offset + options.Limit) + } + if err := c.client.List(ctx, list, opts); err != nil { + return nil, err + } + + offset := options.Offset + limit := options.Limit + if limit == 0 { + limit = math.MaxUint32 + } + options.TextSearch = strings.ToLower(options.TextSearch) + + result := make([]testkube.TestWorkflow, 0) + for i := range list.Items { + if options.TextSearch != "" && !strings.Contains(strings.ToLower(list.Items[i].Name), options.TextSearch) { + continue + } + if offset > 0 { + offset-- + continue + } + result = append(result, *testworkflows.MapKubeToAPI(&list.Items[i])) + limit-- + if limit == 0 { + break + } + } + return result, nil +} + +func (c *k8sTestWorkflowClient) ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) { + labels := map[string][]string{} + list := &testworkflowsv1.TestWorkflowList{} + err := c.client.List(ctx, list, &client.ListOptions{Namespace: c.namespace}) + if err != nil { + return labels, err + } + + for _, workflow := range list.Items { + for key, value := range workflow.Labels { + if !slices.Contains(labels[key], value) { + labels[key] = append(labels[key], value) + } + } + } + + return labels, nil +} + +func (c *k8sTestWorkflowClient) Update(ctx context.Context, environmentId string, workflow testkube.TestWorkflow) error { + original, err := c.get(ctx, workflow.Name) + if err != nil { + return err + } + next := testworkflows.MapAPIToKube(&workflow) + next.Name = original.Name + next.Namespace = c.namespace + next.ResourceVersion = original.ResourceVersion + return c.client.Update(ctx, next) +} + +func (c *k8sTestWorkflowClient) Create(ctx context.Context, environmentId string, workflow testkube.TestWorkflow) error { + next := testworkflows.MapAPIToKube(&workflow) + next.Namespace = c.namespace + return c.client.Create(ctx, next) +} + +func (c *k8sTestWorkflowClient) Delete(ctx context.Context, environmentId string, name string) error { + original, err := c.get(ctx, name) + if err != nil { + return err + } + original.Namespace = c.namespace + return c.client.Delete(ctx, original) +} + +func (c *k8sTestWorkflowClient) DeleteByLabels(ctx context.Context, environmentId string, labels map[string]string) (uint32, error) { + labelSelector := labels2.NewSelector() + for k, v := range labels { + req, _ := labels2.NewRequirement(k, selection.Equals, []string{v}) + labelSelector = labelSelector.Add(*req) + } + + u := &unstructured.Unstructured{} + u.SetKind("TestWorkflow") + u.SetAPIVersion(testworkflowsv1.GroupVersion.String()) + err := c.client.DeleteAllOf(ctx, u, + client.InNamespace(c.namespace), + client.MatchingLabelsSelector{Selector: labelSelector}) + // TODO: Consider if it's possible to return count + return math.MaxInt32, err +} diff --git a/pkg/newclients/testworkflowclient/mock_interface.go b/pkg/newclients/testworkflowclient/mock_interface.go new file mode 100644 index 00000000000..113a92f1d2a --- /dev/null +++ b/pkg/newclients/testworkflowclient/mock_interface.go @@ -0,0 +1,138 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kubeshop/testkube/pkg/newclients/testworkflowclient (interfaces: TestWorkflowClient) + +// Package testworkflowclient is a generated GoMock package. +package testworkflowclient + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + testkube "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +// MockTestWorkflowClient is a mock of TestWorkflowClient interface. +type MockTestWorkflowClient struct { + ctrl *gomock.Controller + recorder *MockTestWorkflowClientMockRecorder +} + +// MockTestWorkflowClientMockRecorder is the mock recorder for MockTestWorkflowClient. +type MockTestWorkflowClientMockRecorder struct { + mock *MockTestWorkflowClient +} + +// NewMockTestWorkflowClient creates a new mock instance. +func NewMockTestWorkflowClient(ctrl *gomock.Controller) *MockTestWorkflowClient { + mock := &MockTestWorkflowClient{ctrl: ctrl} + mock.recorder = &MockTestWorkflowClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTestWorkflowClient) EXPECT() *MockTestWorkflowClientMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockTestWorkflowClient) Create(arg0 context.Context, arg1 string, arg2 testkube.TestWorkflow) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockTestWorkflowClientMockRecorder) Create(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTestWorkflowClient)(nil).Create), arg0, arg1, arg2) +} + +// Delete mocks base method. +func (m *MockTestWorkflowClient) Delete(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockTestWorkflowClientMockRecorder) Delete(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTestWorkflowClient)(nil).Delete), arg0, arg1, arg2) +} + +// DeleteByLabels mocks base method. +func (m *MockTestWorkflowClient) DeleteByLabels(arg0 context.Context, arg1 string, arg2 map[string]string) (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteByLabels", arg0, arg1, arg2) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteByLabels indicates an expected call of DeleteByLabels. +func (mr *MockTestWorkflowClientMockRecorder) DeleteByLabels(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByLabels", reflect.TypeOf((*MockTestWorkflowClient)(nil).DeleteByLabels), arg0, arg1, arg2) +} + +// Get mocks base method. +func (m *MockTestWorkflowClient) Get(arg0 context.Context, arg1, arg2 string) (*testkube.TestWorkflow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].(*testkube.TestWorkflow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockTestWorkflowClientMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockTestWorkflowClient)(nil).Get), arg0, arg1, arg2) +} + +// List mocks base method. +func (m *MockTestWorkflowClient) List(arg0 context.Context, arg1 string, arg2 ListOptions) ([]testkube.TestWorkflow, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0, arg1, arg2) + ret0, _ := ret[0].([]testkube.TestWorkflow) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockTestWorkflowClientMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockTestWorkflowClient)(nil).List), arg0, arg1, arg2) +} + +// ListLabels mocks base method. +func (m *MockTestWorkflowClient) ListLabels(arg0 context.Context, arg1 string) (map[string][]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLabels", arg0, arg1) + ret0, _ := ret[0].(map[string][]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLabels indicates an expected call of ListLabels. +func (mr *MockTestWorkflowClientMockRecorder) ListLabels(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockTestWorkflowClient)(nil).ListLabels), arg0, arg1) +} + +// Update mocks base method. +func (m *MockTestWorkflowClient) Update(arg0 context.Context, arg1 string, arg2 testkube.TestWorkflow) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockTestWorkflowClientMockRecorder) Update(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTestWorkflowClient)(nil).Update), arg0, arg1, arg2) +} diff --git a/pkg/newclients/testworkflowtemplateclient/cloud.go b/pkg/newclients/testworkflowtemplateclient/cloud.go new file mode 100644 index 00000000000..f7bf8deea74 --- /dev/null +++ b/pkg/newclients/testworkflowtemplateclient/cloud.go @@ -0,0 +1,151 @@ +package testworkflowtemplateclient + +import ( + "context" + "encoding/json" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" +) + +var _ TestWorkflowTemplateClient = &cloudTestWorkflowTemplateClient{} + +type cloudTestWorkflowTemplateClient struct { + conn *grpc.ClientConn + client cloud.TestKubeCloudAPIClient + apiKey string +} + +func NewCloudTestWorkflowTemplateClient(conn *grpc.ClientConn, apiKey string) TestWorkflowTemplateClient { + return &cloudTestWorkflowTemplateClient{ + conn: conn, + client: cloud.NewTestKubeCloudAPIClient(conn), + apiKey: apiKey, + } +} + +func (c *cloudTestWorkflowTemplateClient) Get(ctx context.Context, environmentId string, name string) (*testkube.TestWorkflowTemplate, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.GetTestWorkflowTemplate(ctx, &cloud.GetTestWorkflowTemplateRequest{ + EnvironmentId: environmentId, + Name: name, + }) + if err != nil { + return nil, err + } + + var template testkube.TestWorkflowTemplate + if err = json.Unmarshal(resp.Template, &template); err != nil { + return nil, err + } + return &template, nil +} + +func (c *cloudTestWorkflowTemplateClient) List(ctx context.Context, environmentId string, options ListOptions) ([]testkube.TestWorkflowTemplate, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.ListTestWorkflowTemplates(ctx, &cloud.ListTestWorkflowTemplatesRequest{ + EnvironmentId: environmentId, + Offset: options.Offset, + Limit: options.Limit, + Labels: options.Labels, + TextSearch: options.TextSearch, + }) + if err != nil { + return nil, err + } + + result := make([]testkube.TestWorkflowTemplate, 0) + var item *cloud.TestWorkflowTemplateListItem + for { + item, err = resp.Recv() + if err != nil { + break + } + var template testkube.TestWorkflowTemplate + err = json.Unmarshal(item.Template, &template) + if err != nil { + return nil, err + } + result = append(result, template) + } + return result, err +} + +func (c *cloudTestWorkflowTemplateClient) ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.ListTestWorkflowTemplateLabels(ctx, &cloud.ListTestWorkflowTemplateLabelsRequest{ + EnvironmentId: environmentId, + }) + if err != nil { + return nil, err + } + result := make(map[string][]string, len(resp.Labels)) + for _, label := range resp.Labels { + result[label.Name] = label.Value + } + return result, nil +} + +func (c *cloudTestWorkflowTemplateClient) Update(ctx context.Context, environmentId string, template testkube.TestWorkflowTemplate) error { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + templateBytes, err := json.Marshal(template) + if err != nil { + return err + } + _, err = c.client.UpdateTestWorkflowTemplate(ctx, &cloud.UpdateTestWorkflowTemplateRequest{ + EnvironmentId: environmentId, + Template: templateBytes, + }) + return err +} + +func (c *cloudTestWorkflowTemplateClient) Create(ctx context.Context, environmentId string, template testkube.TestWorkflowTemplate) error { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + templateBytes, err := json.Marshal(template) + if err != nil { + return err + } + _, err = c.client.CreateTestWorkflowTemplate(ctx, &cloud.CreateTestWorkflowTemplateRequest{ + EnvironmentId: environmentId, + Template: templateBytes, + }) + return err +} + +func (c *cloudTestWorkflowTemplateClient) Delete(ctx context.Context, environmentId string, name string) error { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + _, err := c.client.DeleteTestWorkflowTemplate(ctx, &cloud.DeleteTestWorkflowTemplateRequest{ + EnvironmentId: environmentId, + Name: name, + }) + return err +} + +func (c *cloudTestWorkflowTemplateClient) DeleteByLabels(ctx context.Context, environmentId string, labels map[string]string) (uint32, error) { + // Pass the additional information + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("api-key", c.apiKey)) + + resp, err := c.client.DeleteTestWorkflowTemplatesByLabels(ctx, &cloud.DeleteTestWorkflowTemplatesByLabelsRequest{ + EnvironmentId: environmentId, + Labels: labels, + }) + if err != nil { + return 0, err + } + return resp.Count, nil +} diff --git a/pkg/newclients/testworkflowtemplateclient/interface.go b/pkg/newclients/testworkflowtemplateclient/interface.go new file mode 100644 index 00000000000..0232749f073 --- /dev/null +++ b/pkg/newclients/testworkflowtemplateclient/interface.go @@ -0,0 +1,25 @@ +package testworkflowtemplateclient + +import ( + "context" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +type ListOptions struct { + Labels map[string]string + TextSearch string + Offset uint32 + Limit uint32 +} + +//go:generate mockgen -destination=./mock_interface.go -package=testworkflowtemplateclient "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" TestWorkflowTemplateClient +type TestWorkflowTemplateClient interface { + Get(ctx context.Context, environmentId string, name string) (*testkube.TestWorkflowTemplate, error) + List(ctx context.Context, environmentId string, options ListOptions) ([]testkube.TestWorkflowTemplate, error) + ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) + Update(ctx context.Context, environmentId string, template testkube.TestWorkflowTemplate) error + Create(ctx context.Context, environmentId string, template testkube.TestWorkflowTemplate) error + Delete(ctx context.Context, environmentId string, name string) error + DeleteByLabels(ctx context.Context, environmentId string, labels map[string]string) (uint32, error) +} diff --git a/pkg/newclients/testworkflowtemplateclient/kubernetes.go b/pkg/newclients/testworkflowtemplateclient/kubernetes.go new file mode 100644 index 00000000000..9a4bdd3dde8 --- /dev/null +++ b/pkg/newclients/testworkflowtemplateclient/kubernetes.go @@ -0,0 +1,149 @@ +package testworkflowtemplateclient + +import ( + "context" + "math" + "slices" + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + labels2 "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + "sigs.k8s.io/controller-runtime/pkg/client" + + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" +) + +var _ TestWorkflowTemplateClient = &k8sTestWorkflowTemplateClient{} + +type k8sTestWorkflowTemplateClient struct { + client client.Client + namespace string +} + +func NewKubernetesTestWorkflowTemplateClient(client client.Client, namespace string) TestWorkflowTemplateClient { + return &k8sTestWorkflowTemplateClient{client: client, namespace: namespace} +} + +func (c *k8sTestWorkflowTemplateClient) get(ctx context.Context, name string) (*testworkflowsv1.TestWorkflowTemplate, error) { + template := testworkflowsv1.TestWorkflowTemplate{} + opts := client.ObjectKey{Namespace: c.namespace, Name: name} + if err := c.client.Get(ctx, opts, &template); err != nil { + return nil, err + } + return &template, nil +} + +func (c *k8sTestWorkflowTemplateClient) Get(ctx context.Context, environmentId string, name string) (*testkube.TestWorkflowTemplate, error) { + template, err := c.get(ctx, name) + if err != nil { + return nil, err + } + return testworkflows.MapTemplateKubeToAPI(template), nil +} + +func (c *k8sTestWorkflowTemplateClient) List(ctx context.Context, environmentId string, options ListOptions) ([]testkube.TestWorkflowTemplate, error) { + labelSelector := labels2.NewSelector() + for k, v := range options.Labels { + req, _ := labels2.NewRequirement(k, selection.Equals, []string{v}) + labelSelector = labelSelector.Add(*req) + } + + list := &testworkflowsv1.TestWorkflowTemplateList{} + opts := &client.ListOptions{Namespace: c.namespace, LabelSelector: labelSelector} + if options.Limit != 0 && options.TextSearch == "" { + opts.Limit = int64(options.Offset + options.Limit) + } + if err := c.client.List(ctx, list, opts); err != nil { + return nil, err + } + + offset := options.Offset + limit := options.Limit + if limit == 0 { + limit = math.MaxUint32 + } + options.TextSearch = strings.ToLower(options.TextSearch) + + result := make([]testkube.TestWorkflowTemplate, 0) + for i := range list.Items { + if options.TextSearch != "" && !strings.Contains(strings.ToLower(list.Items[i].Name), options.TextSearch) { + continue + } + if offset > 0 { + offset-- + continue + } + result = append(result, *testworkflows.MapTemplateKubeToAPI(&list.Items[i])) + limit-- + if limit == 0 { + break + } + } + return result, nil +} + +func (c *k8sTestWorkflowTemplateClient) ListLabels(ctx context.Context, environmentId string) (map[string][]string, error) { + labels := map[string][]string{} + list := &testworkflowsv1.TestWorkflowTemplateList{} + err := c.client.List(ctx, list, &client.ListOptions{Namespace: c.namespace}) + if err != nil { + return labels, err + } + + for _, template := range list.Items { + for key, value := range template.Labels { + if !slices.Contains(labels[key], value) { + labels[key] = append(labels[key], value) + } + } + } + + return labels, nil +} + +func (c *k8sTestWorkflowTemplateClient) Update(ctx context.Context, environmentId string, template testkube.TestWorkflowTemplate) error { + original, err := c.get(ctx, template.Name) + if err != nil { + return err + } + next := testworkflows.MapTemplateAPIToKube(&template) + next.Name = original.Name + next.Namespace = c.namespace + next.ResourceVersion = original.ResourceVersion + return c.client.Update(ctx, next) +} + +func (c *k8sTestWorkflowTemplateClient) Create(ctx context.Context, environmentId string, template testkube.TestWorkflowTemplate) error { + next := testworkflows.MapTemplateAPIToKube(&template) + next.Namespace = c.namespace + return c.client.Create(ctx, next) +} + +func (c *k8sTestWorkflowTemplateClient) Delete(ctx context.Context, environmentId string, name string) error { + original, err := c.get(ctx, name) + if err != nil { + return err + } + original.Namespace = c.namespace + return c.client.Delete(ctx, original) +} + +func (c *k8sTestWorkflowTemplateClient) DeleteByLabels(ctx context.Context, environmentId string, labels map[string]string) (uint32, error) { + labelSelector := labels2.NewSelector() + for k, v := range labels { + req, _ := labels2.NewRequirement(k, selection.Equals, []string{v}) + labelSelector = labelSelector.Add(*req) + } + + u := &unstructured.Unstructured{} + u.SetKind("TestWorkflowTemplate") + u.SetAPIVersion(testworkflowsv1.GroupVersion.String()) + err := c.client.DeleteAllOf(ctx, u, + client.InNamespace(c.namespace), + client.MatchingLabelsSelector{Selector: labelSelector}) + // TODO: Consider if it's possible to return count + return math.MaxInt32, err +} diff --git a/pkg/newclients/testworkflowtemplateclient/mock_interface.go b/pkg/newclients/testworkflowtemplateclient/mock_interface.go new file mode 100644 index 00000000000..f5ea99a24c1 --- /dev/null +++ b/pkg/newclients/testworkflowtemplateclient/mock_interface.go @@ -0,0 +1,138 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient (interfaces: TestWorkflowTemplateClient) + +// Package testworkflowtemplateclient is a generated GoMock package. +package testworkflowtemplateclient + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + testkube "github.com/kubeshop/testkube/pkg/api/v1/testkube" +) + +// MockTestWorkflowTemplateClient is a mock of TestWorkflowTemplateClient interface. +type MockTestWorkflowTemplateClient struct { + ctrl *gomock.Controller + recorder *MockTestWorkflowTemplateClientMockRecorder +} + +// MockTestWorkflowTemplateClientMockRecorder is the mock recorder for MockTestWorkflowTemplateClient. +type MockTestWorkflowTemplateClientMockRecorder struct { + mock *MockTestWorkflowTemplateClient +} + +// NewMockTestWorkflowTemplateClient creates a new mock instance. +func NewMockTestWorkflowTemplateClient(ctrl *gomock.Controller) *MockTestWorkflowTemplateClient { + mock := &MockTestWorkflowTemplateClient{ctrl: ctrl} + mock.recorder = &MockTestWorkflowTemplateClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTestWorkflowTemplateClient) EXPECT() *MockTestWorkflowTemplateClientMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockTestWorkflowTemplateClient) Create(arg0 context.Context, arg1 string, arg2 testkube.TestWorkflowTemplate) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockTestWorkflowTemplateClientMockRecorder) Create(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).Create), arg0, arg1, arg2) +} + +// Delete mocks base method. +func (m *MockTestWorkflowTemplateClient) Delete(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockTestWorkflowTemplateClientMockRecorder) Delete(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).Delete), arg0, arg1, arg2) +} + +// DeleteByLabels mocks base method. +func (m *MockTestWorkflowTemplateClient) DeleteByLabels(arg0 context.Context, arg1 string, arg2 map[string]string) (uint32, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteByLabels", arg0, arg1, arg2) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DeleteByLabels indicates an expected call of DeleteByLabels. +func (mr *MockTestWorkflowTemplateClientMockRecorder) DeleteByLabels(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteByLabels", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).DeleteByLabels), arg0, arg1, arg2) +} + +// Get mocks base method. +func (m *MockTestWorkflowTemplateClient) Get(arg0 context.Context, arg1, arg2 string) (*testkube.TestWorkflowTemplate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1, arg2) + ret0, _ := ret[0].(*testkube.TestWorkflowTemplate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockTestWorkflowTemplateClientMockRecorder) Get(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).Get), arg0, arg1, arg2) +} + +// List mocks base method. +func (m *MockTestWorkflowTemplateClient) List(arg0 context.Context, arg1 string, arg2 ListOptions) ([]testkube.TestWorkflowTemplate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0, arg1, arg2) + ret0, _ := ret[0].([]testkube.TestWorkflowTemplate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockTestWorkflowTemplateClientMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).List), arg0, arg1, arg2) +} + +// ListLabels mocks base method. +func (m *MockTestWorkflowTemplateClient) ListLabels(arg0 context.Context, arg1 string) (map[string][]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLabels", arg0, arg1) + ret0, _ := ret[0].(map[string][]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLabels indicates an expected call of ListLabels. +func (mr *MockTestWorkflowTemplateClientMockRecorder) ListLabels(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLabels", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).ListLabels), arg0, arg1) +} + +// Update mocks base method. +func (m *MockTestWorkflowTemplateClient) Update(arg0 context.Context, arg1 string, arg2 testkube.TestWorkflowTemplate) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockTestWorkflowTemplateClientMockRecorder) Update(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockTestWorkflowTemplateClient)(nil).Update), arg0, arg1, arg2) +} diff --git a/pkg/repository/testworkflow/interface.go b/pkg/repository/testworkflow/interface.go index 4d1d55bac8b..e8222f15c8c 100644 --- a/pkg/repository/testworkflow/interface.go +++ b/pkg/repository/testworkflow/interface.go @@ -19,6 +19,12 @@ type LabelSelector struct { Or []Label } +type InitData struct { + RunnerID string + Namespace string + Signature []testkube.TestWorkflowSignature +} + const PageDefaultLimit int = 100 type Filter interface { @@ -58,6 +64,8 @@ type Repository interface { GetLatestByTestWorkflow(ctx context.Context, workflowName string) (*testkube.TestWorkflowExecution, error) // GetRunning get list of executions that are still running GetRunning(ctx context.Context) ([]testkube.TestWorkflowExecution, error) + // GetUnassigned get list of executions that is waiting to be executed + GetUnassigned(ctx context.Context) ([]testkube.TestWorkflowExecution, error) // GetLatestByTestWorkflows gets latest execution results by workflow names GetLatestByTestWorkflows(ctx context.Context, workflowNames []string) (executions []testkube.TestWorkflowExecutionSummary, err error) // GetExecutionsTotals gets executions total stats using a filter, use filter with no data for all @@ -88,6 +96,8 @@ type Repository interface { GetTestWorkflowMetrics(ctx context.Context, name string, limit, last int) (metrics testkube.ExecutionsMetrics, err error) // GetExecutionTags gets execution tags GetExecutionTags(ctx context.Context, testWorkflowName string) (map[string][]string, error) + // Init sets the initialization data from the runner + Init(ctx context.Context, id string, data InitData) error } type Sequences interface { diff --git a/pkg/repository/testworkflow/mock_repository.go b/pkg/repository/testworkflow/mock_repository.go index cd269b43070..8b53e431671 100644 --- a/pkg/repository/testworkflow/mock_repository.go +++ b/pkg/repository/testworkflow/mock_repository.go @@ -233,6 +233,21 @@ func (mr *MockRepositoryMockRecorder) GetPreviousFinishedState(arg0, arg1, arg2 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPreviousFinishedState", reflect.TypeOf((*MockRepository)(nil).GetPreviousFinishedState), arg0, arg1, arg2) } +// GetUnassigned mocks base method. +func (m *MockRepository) GetUnassigned(arg0 context.Context) ([]testkube.TestWorkflowExecution, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnassigned", arg0) + ret0, _ := ret[0].([]testkube.TestWorkflowExecution) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUnassigned indicates an expected call of GetUnassigned. +func (mr *MockRepositoryMockRecorder) GetUnassigned(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnassigned", reflect.TypeOf((*MockRepository)(nil).GetUnassigned), arg0) +} + // GetRunning mocks base method. func (m *MockRepository) GetRunning(arg0 context.Context) ([]testkube.TestWorkflowExecution, error) { m.ctrl.T.Helper() @@ -263,6 +278,20 @@ func (mr *MockRepositoryMockRecorder) GetTestWorkflowMetrics(arg0, arg1, arg2, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTestWorkflowMetrics", reflect.TypeOf((*MockRepository)(nil).GetTestWorkflowMetrics), arg0, arg1, arg2, arg3) } +// Init mocks base method. +func (m *MockRepository) Init(arg0 context.Context, arg1 string, arg2 InitData) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Init", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Init indicates an expected call of Init. +func (mr *MockRepositoryMockRecorder) Init(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockRepository)(nil).Init), arg0, arg1, arg2) +} + // Insert mocks base method. func (m *MockRepository) Insert(arg0 context.Context, arg1 testkube.TestWorkflowExecution) error { m.ctrl.T.Helper() diff --git a/pkg/repository/testworkflow/mongo.go b/pkg/repository/testworkflow/mongo.go index 860715d6c67..1651cd3b78b 100644 --- a/pkg/repository/testworkflow/mongo.go +++ b/pkg/repository/testworkflow/mongo.go @@ -656,3 +656,39 @@ func (r *MongoRepository) GetExecutionTags(ctx context.Context, testWorkflowName return tags, nil } + +func (r *MongoRepository) Init(ctx context.Context, id string, data InitData) error { + _, err := r.Coll.UpdateOne(ctx, bson.M{"id": id}, bson.M{"$set": map[string]interface{}{ + "namespace": data.Namespace, + "signature": data.Signature, + "runnerId": data.RunnerID, + }}) + return err +} + +// TODO: Return IDs only +// TODO: Add indexes +func (r *MongoRepository) GetUnassigned(ctx context.Context) (result []testkube.TestWorkflowExecution, err error) { + result = make([]testkube.TestWorkflowExecution, 0) + opts := &options.FindOptions{} + opts.SetSort(bson.D{{Key: "_id", Value: -1}}) + if r.allowDiskUse { + opts.SetAllowDiskUse(r.allowDiskUse) + } + + cursor, err := r.Coll.Find(ctx, bson.M{ + "$and": []bson.M{ + {"result.status": testkube.QUEUED_TestWorkflowStatus}, + {"$or": []bson.M{{"runnerId": ""}, {"runnerId": nil}}}, + }, + }, opts) + if err != nil { + return result, err + } + err = cursor.All(ctx, &result) + + for i := range result { + result[i].UnscapeDots() + } + return +} diff --git a/pkg/runner/executionlogswriter.go b/pkg/runner/executionlogswriter.go new file mode 100644 index 00000000000..5cac030cc8d --- /dev/null +++ b/pkg/runner/executionlogswriter.go @@ -0,0 +1,139 @@ +package runner + +import ( + "bufio" + "context" + "io" + "net/http" + "sync" + + "github.com/pkg/errors" + + initconstants "github.com/kubeshop/testkube/cmd/testworkflow-init/constants" + "github.com/kubeshop/testkube/cmd/testworkflow-init/instructions" + "github.com/kubeshop/testkube/pkg/bufferedstream" +) + +type LogPresigner interface { + PresignSaveLog(ctx context.Context, id string, workflowName string) (string, error) +} + +type ExecutionLogsWriter interface { + io.Writer + WriteStart(ref string) error + Save(ctx context.Context) error + Saved() bool + Cleanup() + Reset() error +} + +type executionLogsWriter struct { + presigner LogPresigner + id string + workflowName string + skipVerify bool + + writer *io.PipeWriter + buffer bufferedstream.BufferedStream + mu sync.Mutex +} + +func NewExecutionLogsWriter(presigner LogPresigner, id string, workflowName string, skipVerify bool) (ExecutionLogsWriter, error) { + e := &executionLogsWriter{ + presigner: presigner, + id: id, + workflowName: workflowName, + skipVerify: skipVerify, + } + err := e.Reset() + if err != nil { + return nil, err + } + return e, nil +} + +func (e *executionLogsWriter) Write(p []byte) (n int, err error) { + return e.writer.Write(p) +} + +func (e *executionLogsWriter) WriteStart(ref string) error { + _, err := e.Write([]byte(instructions.SprintHint(ref, initconstants.InstructionStart))) + return err +} + +func (e *executionLogsWriter) Save(ctx context.Context) error { + e.mu.Lock() + defer e.mu.Unlock() + + if e.buffer == nil { + return errors.New("the writer is already cleaned up") + } + + e.writer.Close() + + url, err := e.presigner.PresignSaveLog(ctx, e.id, e.workflowName) + if err != nil { + return err + } + + content := e.buffer + contentLen := content.Len() + if contentLen == 0 { + content = nil + } + req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, content) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/octet-stream") + req.ContentLength = int64(contentLen) + res, err := http.DefaultClient.Do(req) + if err != nil { + return errors.Wrap(err, "failed to save file in the object storage") + } + if res.StatusCode != http.StatusOK { + return errors.Errorf("error saving file with presigned url: expected 200 OK response code, got %d", res.StatusCode) + } + e.cleanup() + return nil +} + +func (e *executionLogsWriter) cleanup() { + if e.writer != nil { + e.writer.Close() + } + if e.buffer != nil { + e.buffer.Cleanup() + } +} + +func (e *executionLogsWriter) Cleanup() { + e.mu.Lock() + defer e.mu.Unlock() + e.cleanup() +} + +func (e *executionLogsWriter) Saved() bool { + if v := e.mu.TryLock(); v { + defer e.mu.Unlock() + return e.buffer == nil + } + return false +} + +func (e *executionLogsWriter) Reset() error { + e.mu.Lock() + defer e.mu.Unlock() + e.cleanup() + + r, writer := io.Pipe() + reader := bufio.NewReader(r) + // TODO: consider how to choose temp dir + buffer, err := bufferedstream.NewBufferedStream("", "log", reader) + if err != nil { + return err + } + e.writer = writer + e.buffer = buffer + return nil +} diff --git a/pkg/runner/executionsaver.go b/pkg/runner/executionsaver.go new file mode 100644 index 00000000000..ea4fff1d2d5 --- /dev/null +++ b/pkg/runner/executionsaver.go @@ -0,0 +1,140 @@ +package runner + +import ( + "context" + "sync" + "sync/atomic" + "time" + + "golang.org/x/sync/errgroup" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/controller/store" +) + +const ( + ExecutionSaverUpdateRetryCount = 10 + ExecutionSaverUpdateRetryDelay = 300 * time.Millisecond +) + +//go:generate mockgen -destination=./mock_executionsaver.go -package=runner "github.com/kubeshop/testkube/pkg/runner" ExecutionSaver +type ExecutionSaver interface { + UpdateResult(result testkube.TestWorkflowResult) + AppendOutput(output ...testkube.TestWorkflowOutput) + End(ctx context.Context, result testkube.TestWorkflowResult) error +} + +type executionSaver struct { + id string + executionsRepository testworkflow.Repository + logs ExecutionLogsWriter + + // Intermediate data + output []testkube.TestWorkflowOutput + result *testkube.TestWorkflowResult + resultUpdate store.Update + resultMu sync.Mutex + + outputSaved *atomic.Bool + + ctx context.Context + ctxCancel context.CancelFunc +} + +func NewExecutionSaver(ctx context.Context, executionsRepository testworkflow.Repository, id string, logs ExecutionLogsWriter) (ExecutionSaver, error) { + ctx, cancel := context.WithCancel(ctx) + outputSaved := atomic.Bool{} + outputSaved.Store(true) + saver := &executionSaver{ + id: id, + executionsRepository: executionsRepository, + logs: logs, + resultUpdate: store.NewUpdate(), + outputSaved: &outputSaved, + ctx: ctx, + ctxCancel: cancel, + } + go saver.watchResultUpdates() + + return saver, nil +} + +func (s *executionSaver) watchResultUpdates() { + defer s.resultUpdate.Close() + ch := s.resultUpdate.Channel(s.ctx) + var prev *testkube.TestWorkflowResult + for { + select { + case <-s.ctx.Done(): + return + case _, ok := <-ch: + if !ok { + return + } + for i := 0; i < ExecutionSaverUpdateRetryCount; i++ { + s.resultMu.Lock() + next := s.result + s.resultMu.Unlock() + if prev == next { + break + } + err := s.executionsRepository.UpdateResult(s.ctx, s.id, next) + if err == nil { + break + } + select { + case <-s.ctx.Done(): + return + case <-time.After(ExecutionSaverUpdateRetryDelay): + } + } + } + } +} + +func (s *executionSaver) UpdateResult(result testkube.TestWorkflowResult) { + s.resultMu.Lock() + defer s.resultMu.Unlock() + s.result = &result + s.resultUpdate.Emit() +} + +func (s *executionSaver) AppendOutput(output ...testkube.TestWorkflowOutput) { + s.output = append(s.output, output...) + s.outputSaved.Store(false) +} + +func (s *executionSaver) End(ctx context.Context, result testkube.TestWorkflowResult) error { + s.ctxCancel() + s.resultMu.Lock() + defer s.resultMu.Unlock() + + // Save the logs and output + g, _ := errgroup.WithContext(ctx) + g.Go(func() error { + if s.outputSaved.Load() { + return nil + } + // TODO: Consider AppendOutput ($push) instead + return s.executionsRepository.UpdateOutput(ctx, s.id, s.output) + }) + g.Go(func() error { + if s.logs.Saved() { + return nil + } + return s.logs.Save(ctx) + }) + err := g.Wait() + if err != nil { + return err + } + + // Save the final result + err = s.executionsRepository.UpdateResult(ctx, s.id, &result) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/runner/lazy.go b/pkg/runner/lazy.go new file mode 100644 index 00000000000..e6dd110127d --- /dev/null +++ b/pkg/runner/lazy.go @@ -0,0 +1,39 @@ +package runner + +import ( + "context" + + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" +) + +type lazyRunner struct { + accessor *Runner +} + +func Lazy(accessor *Runner) Runner { + return &lazyRunner{accessor: accessor} +} + +func (r *lazyRunner) Monitor(ctx context.Context, id string) error { + return (*r.accessor).Monitor(ctx, id) +} + +func (r *lazyRunner) Notifications(ctx context.Context, id string) executionworkertypes.NotificationsWatcher { + return (*r.accessor).Notifications(ctx, id) +} + +func (r *lazyRunner) Execute(request executionworkertypes.ExecuteRequest) (*executionworkertypes.ExecuteResult, error) { + return (*r.accessor).Execute(request) +} + +func (r *lazyRunner) Pause(id string) error { + return (*r.accessor).Pause(id) +} + +func (r *lazyRunner) Resume(id string) error { + return (*r.accessor).Resume(id) +} + +func (r *lazyRunner) Abort(id string) error { + return (*r.accessor).Abort(id) +} diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go new file mode 100644 index 00000000000..44366883fee --- /dev/null +++ b/pkg/runner/runner.go @@ -0,0 +1,246 @@ +package runner + +import ( + "context" + "sync" + "time" + + "github.com/pkg/errors" + + "github.com/kubeshop/testkube/internal/app/api/metrics" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/event" + "github.com/kubeshop/testkube/pkg/log" + configRepo "github.com/kubeshop/testkube/pkg/repository/config" + "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" + "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/registry" +) + +const ( + GetNotificationsRetryCount = 10 + GetNotificationsRetryDelay = 500 * time.Millisecond + + SaveEndResultRetryCount = 100 + SaveEndResultRetryBaseDelay = 500 * time.Millisecond +) + +//go:generate mockgen -destination=./mock_runner.go -package=runner "github.com/kubeshop/testkube/pkg/runner" Runner +type Runner interface { + Monitor(ctx context.Context, id string) error + Notifications(ctx context.Context, id string) executionworkertypes.NotificationsWatcher + Execute(request executionworkertypes.ExecuteRequest) (*executionworkertypes.ExecuteResult, error) + Pause(id string) error + Resume(id string) error + Abort(id string) error +} + +type runner struct { + worker executionworkertypes.Worker + outputRepository testworkflow.OutputRepository + executionsRepository testworkflow.Repository + configRepository configRepo.Repository + emitter *event.Emitter + metrics metrics.Metrics + dashboardURI string + storageSkipVerify bool + + watching sync.Map +} + +func New( + worker executionworkertypes.Worker, + outputRepository testworkflow.OutputRepository, + executionsRepository testworkflow.Repository, + configRepository configRepo.Repository, + emitter *event.Emitter, + metrics metrics.Metrics, + dashboardURI string, + storageSkipVerify bool, +) Runner { + return &runner{ + worker: worker, + outputRepository: outputRepository, + executionsRepository: executionsRepository, + configRepository: configRepository, + emitter: emitter, + metrics: metrics, + dashboardURI: dashboardURI, + storageSkipVerify: storageSkipVerify, + } +} + +func (r *runner) monitor(ctx context.Context, execution testkube.TestWorkflowExecution) error { + defer r.watching.Delete(execution.Id) + + var notifications executionworkertypes.NotificationsWatcher + for i := 0; i < GetNotificationsRetryCount; i++ { + notifications = r.worker.Notifications(ctx, execution.Id, executionworkertypes.NotificationsOptions{}) + if notifications.Err() == nil { + break + } + if errors.Is(notifications.Err(), registry.ErrResourceNotFound) { + // TODO: should it mark as job was aborted then? + return registry.ErrResourceNotFound + } + time.Sleep(GetNotificationsRetryDelay) + } + if notifications.Err() != nil { + return errors.Wrapf(notifications.Err(), "failed to listen for '%s' execution notifications", execution.Id) + } + + logs, err := NewExecutionLogsWriter(r.outputRepository, execution.Id, execution.Workflow.Name, r.storageSkipVerify) + if err != nil { + return err + } + saver, err := NewExecutionSaver(ctx, r.executionsRepository, execution.Id, logs) + if err != nil { + return err + } + defer logs.Cleanup() + + currentRef := "" + var lastResult *testkube.TestWorkflowResult + for n := range notifications.Channel() { + if n.Temporary { + continue + } + + if n.Output != nil { + saver.AppendOutput(*n.Output) + } else if n.Result != nil { + lastResult = n.Result + // Don't send final result until everything is finished + if n.Result.IsFinished() { + continue + } + saver.UpdateResult(*n.Result) + } else { + if n.Ref != currentRef && n.Ref != "" { + currentRef = n.Ref + err = logs.WriteStart(n.Ref) + if err != nil { + log.DefaultLogger.Errorw("failed to write start logs", "id", execution.Id, "ref", n.Ref) + continue + } + } + _, err = logs.Write([]byte(n.Log)) + if err != nil { + log.DefaultLogger.Errorw("failed to write logs", "id", execution.Id, "ref", n.Ref) + continue + } + } + } + + // Ignore further monitoring if it has been canceled + if ctx.Err() != nil { + return ctx.Err() + } + + if notifications.Err() != nil { + log.DefaultLogger.Errorw("error from TestWorkflow watcher", "id", execution.Id, "error", notifications.Err()) + } + + if lastResult == nil || !lastResult.IsFinished() { + log.DefaultLogger.Errorw("not finished TestWorkflow result received, trying to recover...", "id", execution.Id) + watcher := r.worker.Notifications(ctx, execution.Id, executionworkertypes.NotificationsOptions{ + NoFollow: true, + }) + if watcher.Err() == nil { + for n := range watcher.Channel() { + if n.Result != nil { + lastResult = n.Result + } + } + } + + if lastResult == nil { + lastResult = execution.Result + } + if !lastResult.IsFinished() { + log.DefaultLogger.Errorw("failed to recover TestWorkflow result, marking as fatal error...", "id", execution.Id) + lastResult.Fatal(err, true, time.Now()) + } + } + + for i := 0; i < SaveEndResultRetryCount; i++ { + err = saver.End(ctx, *lastResult) + if err == nil { + break + } + log.DefaultLogger.Warnw("failed to save execution data", "id", execution.Id, "error", err) + time.Sleep(time.Duration(i/10) * SaveEndResultRetryBaseDelay) + } + + // Handle fatal error + if err != nil { + log.DefaultLogger.Errorw("failed to save execution data", "id", execution.Id, "error", err) + return errors.Wrapf(err, "failed to save execution '%s' data", execution.Id) + } + + // Try to substitute execution data + execution.Output = nil + execution.Result = lastResult + execution.StatusAt = lastResult.FinishedAt + + // Emit data + if lastResult.IsPassed() { + r.emitter.Notify(testkube.NewEventEndTestWorkflowSuccess(&execution)) + } else if lastResult.IsAborted() { + r.emitter.Notify(testkube.NewEventEndTestWorkflowAborted(&execution)) + } else { + r.emitter.Notify(testkube.NewEventEndTestWorkflowFailed(&execution)) + } + + err = r.worker.Destroy(context.Background(), execution.Id, executionworkertypes.DestroyOptions{}) + if err != nil { + // TODO: what to do on error? + log.DefaultLogger.Errorw("failed to cleanup TestWorkflow resources", "id", execution.Id, "error", err) + } + + return nil +} + +func (r *runner) Monitor(ctx context.Context, id string) error { + ctx, ctxCancel := context.WithCancel(ctx) + defer ctxCancel() + + // Check if there is any monitor attached already + r.watching.LoadOrStore(id, false) + if !r.watching.CompareAndSwap(id, false, true) { + return nil + } + + // Load the execution TODO: retry? + execution, err := r.executionsRepository.Get(ctx, id) + if err != nil { + return err + } + + return r.monitor(ctx, execution) +} + +func (r *runner) Notifications(ctx context.Context, id string) executionworkertypes.NotificationsWatcher { + return r.worker.Notifications(ctx, id, executionworkertypes.NotificationsOptions{}) +} + +func (r *runner) Execute(request executionworkertypes.ExecuteRequest) (*executionworkertypes.ExecuteResult, error) { + res, err := r.worker.Execute(context.Background(), request) + if err == nil { + // TODO: consider retry? + go r.Monitor(context.Background(), request.Execution.Id) + } + return res, err +} + +func (r *runner) Pause(id string) error { + return r.worker.Pause(context.Background(), id, executionworkertypes.ControlOptions{}) +} + +func (r *runner) Resume(id string) error { + return r.worker.Resume(context.Background(), id, executionworkertypes.ControlOptions{}) +} + +func (r *runner) Abort(id string) error { + return r.worker.Abort(context.Background(), id, executionworkertypes.DestroyOptions{}) +} diff --git a/pkg/testworkflows/executionworker/controller/controller.go b/pkg/testworkflows/executionworker/controller/controller.go index 61ca9acfbce..75b4948e4c6 100644 --- a/pkg/testworkflows/executionworker/controller/controller.go +++ b/pkg/testworkflows/executionworker/controller/controller.go @@ -12,14 +12,16 @@ import ( "github.com/kubeshop/testkube/cmd/testworkflow-init/instructions" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/controller/watchers" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/stage" ) var ( - ErrJobAborted = errors.New("job was aborted") - ErrJobTimeout = errors.New("timeout retrieving job") - ErrNoIPAssigned = errors.New("there is no IP assigned to this pod") - ErrNoNodeAssigned = errors.New("the pod is not assigned to a node yet") + ErrJobAborted = errors.New("job was aborted") + ErrJobTimeout = errors.New("timeout retrieving job") + ErrNoIPAssigned = errors.New("there is no IP assigned to this pod") + ErrNoNodeAssigned = errors.New("the pod is not assigned to a node yet") + ErrMissingEstimatedResult = errors.New("could not estimate the result") ) type ControllerOptions struct { @@ -46,6 +48,8 @@ type Controller interface { NodeName() (string, error) PodIP() (string, error) ContainersReady() (bool, error) + InternalConfig() (testworkflowconfig.InternalConfig, error) + EstimatedResult(parentCtx context.Context) (*testkube.TestWorkflowResult, error) Signature() []stage.Signature HasPod() bool ResourceID() string @@ -154,6 +158,10 @@ func (c *controller) PodIP() (string, error) { return podIP, nil } +func (c *controller) InternalConfig() (testworkflowconfig.InternalConfig, error) { + return c.watcher.State().InternalConfig() +} + func (c *controller) NodeName() (string, error) { nodeName := c.watcher.State().PodNodeName() if nodeName == "" { @@ -193,6 +201,19 @@ func (c *controller) StopController() { c.ctxCancel() } +func (c *controller) EstimatedResult(parentCtx context.Context) (*testkube.TestWorkflowResult, error) { + notifier := newNotifier(parentCtx, testkube.TestWorkflowResult{}, c.scheduledAt) + go notifier.Align(c.watcher.State()) + message := <-notifier.ch + if message.Error != nil { + return nil, message.Error + } + if message.Value.Result != nil { + return message.Value.Result, nil + } + return nil, ErrMissingEstimatedResult +} + func (c *controller) Watch(parentCtx context.Context, disableFollow bool) <-chan ChannelMessage[Notification] { ch, err := WatchInstrumentedPod(parentCtx, c.clientSet, c.signature, c.scheduledAt, c.watcher, WatchInstrumentedPodOptions{ DisableFollow: disableFollow, diff --git a/pkg/testworkflows/executionworker/controller/watchers/executionstate.go b/pkg/testworkflows/executionworker/controller/watchers/executionstate.go index ae541ce72bf..81d932d11b4 100644 --- a/pkg/testworkflows/executionworker/controller/watchers/executionstate.go +++ b/pkg/testworkflows/executionworker/controller/watchers/executionstate.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/action/actiontypes" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/stage" ) @@ -53,6 +54,7 @@ type ExecutionState interface { ContainerFailed(name string) bool Signature() ([]stage.Signature, error) ActionGroups() (actiontypes.ActionGroups, error) + InternalConfig() (testworkflowconfig.InternalConfig, error) ScheduledAt() time.Time ExecutionError() string @@ -253,6 +255,16 @@ func (e *executionState) Signature() ([]stage.Signature, error) { return nil, ErrMissingData } +func (e *executionState) InternalConfig() (testworkflowconfig.InternalConfig, error) { + if e.job != nil { + return e.job.InternalConfig() + } + if e.pod != nil { + return e.pod.InternalConfig() + } + return testworkflowconfig.InternalConfig{}, ErrMissingData +} + func (e *executionState) ScheduledAt() time.Time { if e.job != nil { v, err := e.job.ScheduledAt() diff --git a/pkg/testworkflows/executionworker/controller/watchers/job.go b/pkg/testworkflows/executionworker/controller/watchers/job.go index ae7206faeaa..99d9c30e0c9 100644 --- a/pkg/testworkflows/executionworker/controller/watchers/job.go +++ b/pkg/testworkflows/executionworker/controller/watchers/job.go @@ -6,6 +6,7 @@ import ( batchv1 "k8s.io/api/batch/v1" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/action/actiontypes" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/constants" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/stage" @@ -26,6 +27,7 @@ type Job interface { Finished() bool ActionGroups() (actiontypes.ActionGroups, error) Signature() ([]stage.Signature, error) + InternalConfig() (testworkflowconfig.InternalConfig, error) ScheduledAt() (time.Time, error) ExecutionError() string } @@ -74,6 +76,11 @@ func (j *job) ActionGroups() (actions actiontypes.ActionGroups, err error) { return } +func (j *job) InternalConfig() (cfg testworkflowconfig.InternalConfig, err error) { + err = json.Unmarshal([]byte(j.original.Spec.Template.Annotations[constants.InternalAnnotationName]), &cfg) + return +} + func (j *job) Signature() ([]stage.Signature, error) { return stage.GetSignatureFromJSON([]byte(j.original.Spec.Template.Annotations[constants.SignatureAnnotationName])) } diff --git a/pkg/testworkflows/executionworker/controller/watchers/pod.go b/pkg/testworkflows/executionworker/controller/watchers/pod.go index 90557426f6d..f4fed970cfa 100644 --- a/pkg/testworkflows/executionworker/controller/watchers/pod.go +++ b/pkg/testworkflows/executionworker/controller/watchers/pod.go @@ -7,6 +7,7 @@ import ( corev1 "k8s.io/api/core/v1" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/action/actiontypes" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/constants" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/stage" @@ -30,6 +31,7 @@ type Pod interface { Finished() bool ActionGroups() (actiontypes.ActionGroups, error) Signature() ([]stage.Signature, error) + InternalConfig() (testworkflowconfig.InternalConfig, error) ScheduledAt() (time.Time, error) ContainerStarted(name string) bool ContainerFinished(name string) bool @@ -117,6 +119,11 @@ func (p *pod) Signature() ([]stage.Signature, error) { return stage.GetSignatureFromJSON([]byte(p.original.Annotations[constants.SignatureAnnotationName])) } +func (p *pod) InternalConfig() (cfg testworkflowconfig.InternalConfig, err error) { + err = json.Unmarshal([]byte(p.original.Annotations[constants.InternalAnnotationName]), &cfg) + return +} + func (p *pod) ScheduledAt() (time.Time, error) { return time.Parse(time.RFC3339Nano, p.original.Annotations[constants.ScheduledAtAnnotationName]) } diff --git a/pkg/testworkflows/executionworker/kubernetesworker/interface.go b/pkg/testworkflows/executionworker/kubernetesworker/interface.go index adc070ebb54..fbf54b35f9f 100644 --- a/pkg/testworkflows/executionworker/kubernetesworker/interface.go +++ b/pkg/testworkflows/executionworker/kubernetesworker/interface.go @@ -27,4 +27,5 @@ type Config struct { Cluster ClusterConfig ImageInspector ImageInspectorConfig Connection testworkflowconfig.WorkerConnectionConfig + FeatureFlags map[string]string } diff --git a/pkg/testworkflows/executionworker/kubernetesworker/worker.go b/pkg/testworkflows/executionworker/kubernetesworker/worker.go index b8deca8ccf2..0a4584617e1 100644 --- a/pkg/testworkflows/executionworker/kubernetesworker/worker.go +++ b/pkg/testworkflows/executionworker/kubernetesworker/worker.go @@ -66,6 +66,7 @@ func NewWorker(clientSet kubernetes.Interface, processor testworkflowprocessor.P ImageInspectorPersistenceCacheKey: config.ImageInspector.CacheKey, ImageInspectorPersistenceCacheTTL: config.ImageInspector.CacheTTL, Connection: config.Connection, + FeatureFlags: config.FeatureFlags, }, } } @@ -81,6 +82,9 @@ func (w *worker) buildInternalConfig(resourceId, fsPrefix string, execution test if workflow.Spec.Job != nil && workflow.Spec.Job.Namespace != "" { cfg.Worker.Namespace = workflow.Spec.Job.Namespace } + if ns, ok := w.config.Cluster.Namespaces[cfg.Worker.Namespace]; ok && ns.DefaultServiceAccountName != "" { + cfg.Worker.DefaultServiceAccount = ns.DefaultServiceAccountName + } return cfg } @@ -115,6 +119,16 @@ func (w *worker) Execute(ctx context.Context, request executionworkertypes.Execu return nil, errors.New(fmt.Sprintf("namespace %s not supported", cfg.Worker.Namespace)) } + // Configure default service account + if request.Workflow.Spec.Pod == nil { + request.Workflow.Spec.Pod = &testworkflowsv1.PodConfig{ + ServiceAccountName: cfg.Worker.DefaultServiceAccount, + } + } else if cfg.Worker.DefaultServiceAccount == "" { + request.Workflow.Spec.Pod = request.Workflow.Spec.Pod.DeepCopy() + request.Workflow.Spec.Pod.ServiceAccountName = cfg.Worker.DefaultServiceAccount + } + // Process the Test Workflow bundle, err := w.processor.Bundle(ctx, &request.Workflow, testworkflowprocessor.BundleOptions{Config: cfg, Secrets: secrets, ScheduledAt: scheduledAt}) if err != nil { @@ -361,7 +375,33 @@ func (w *worker) Get(ctx context.Context, id string, options executionworkertype } func (w *worker) Summary(ctx context.Context, id string, options executionworkertypes.GetOptions) (*executionworkertypes.SummaryResult, error) { - panic("not implemented") + // Connect to the resource + // TODO: Move the implementation directly there + ctrl, err, recycle := w.registry.Connect(ctx, id, options.Hints) + if err != nil { + return nil, err + } + defer recycle() + + cfg, err := ctrl.InternalConfig() + if err != nil { + return nil, err + } + + estimatedResult, err := ctrl.EstimatedResult(ctx) + if err != nil { + log.DefaultLogger.Warnw("failed to estimate result", "id", id, "error", err) + estimatedResult = &testkube.TestWorkflowResult{} + } + + return &executionworkertypes.SummaryResult{ + Execution: cfg.Execution, + Workflow: cfg.Workflow, + Resource: cfg.Resource, + Signature: stage.MapSignatureListToInternal(ctrl.Signature()), + EstimatedResult: *estimatedResult, + Namespace: ctrl.Namespace(), + }, nil } func (w *worker) Finished(ctx context.Context, id string, options executionworkertypes.GetOptions) (bool, error) { diff --git a/pkg/testworkflows/executionworker/registry/controllers.go b/pkg/testworkflows/executionworker/registry/controllers.go index ed77fd7f0b9..00b233c6b92 100644 --- a/pkg/testworkflows/executionworker/registry/controllers.go +++ b/pkg/testworkflows/executionworker/registry/controllers.go @@ -15,6 +15,10 @@ import ( "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/stage" ) +const ( + DeregisterDelay = 1 * time.Second +) + type controllersRegistry struct { clientSet kubernetes.Interface namespaces NamespacesRegistry @@ -76,8 +80,17 @@ func (r *controllersRegistry) deregister(id string) { r.ips.Register(id, podIp) } r.namespaces.Register(id, r.controllers[id].Namespace()) - r.controllers[id].StopController() - delete(r.controllers, id) + + // Wait a moment to deregister - common case is that something may want to read it immediately afterwards + go func() { + time.Sleep(DeregisterDelay) + r.mu.Lock() + defer r.mu.Unlock() + if r.controllerReservations[id] == 0 && r.controllers[id] != nil { + r.controllers[id].StopController() + delete(r.controllers, id) + } + }() } r.mu.Unlock() } diff --git a/pkg/testworkflows/testworkflowconfig/config.go b/pkg/testworkflows/testworkflowconfig/config.go index c3efe42bb9a..9877555dd7e 100644 --- a/pkg/testworkflows/testworkflowconfig/config.go +++ b/pkg/testworkflows/testworkflowconfig/config.go @@ -2,6 +2,11 @@ package testworkflowconfig import "time" +const ( + FeatureFlagNewExecutions = "exec" + FeatureFlagTestWorkflowCloudStorage = "tw-storage" +) + type InternalConfig struct { Execution ExecutionConfig `json:"e,omitempty"` Workflow WorkflowConfig `json:"w,omitempty"` @@ -53,7 +58,8 @@ type WorkerConfig struct { ImageInspectorPersistenceCacheKey string `json:"P,omitempty"` ImageInspectorPersistenceCacheTTL time.Duration `json:"T,omitempty"` - Connection WorkerConnectionConfig `json:"C,omitempty"` + Connection WorkerConnectionConfig `json:"C,omitempty"` + FeatureFlags map[string]string `json:"f,omitempty"` } type WorkerConnectionConfig struct { diff --git a/pkg/testworkflows/testworkflowexecutor/executor.go b/pkg/testworkflows/testworkflowexecutor/executor.go index 69cc488fca1..e2820340382 100644 --- a/pkg/testworkflows/testworkflowexecutor/executor.go +++ b/pkg/testworkflows/testworkflowexecutor/executor.go @@ -1,757 +1,252 @@ package testworkflowexecutor import ( - "bufio" - "bytes" "context" + "encoding/json" errors2 "errors" - "fmt" "io" - "os" - "strings" - "sync" - "time" + "math" "github.com/pkg/errors" - "go.mongodb.org/mongo-driver/bson/primitive" - corev1 "k8s.io/api/core/v1" - "k8s.io/client-go/kubernetes" + "google.golang.org/grpc" + "google.golang.org/grpc/encoding/gzip" - testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" - testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" - initconstants "github.com/kubeshop/testkube/cmd/testworkflow-init/constants" - "github.com/kubeshop/testkube/cmd/testworkflow-init/instructions" v1 "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/internal/common" "github.com/kubeshop/testkube/internal/config" + agentclient "github.com/kubeshop/testkube/pkg/agent/client" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" "github.com/kubeshop/testkube/pkg/event" - "github.com/kubeshop/testkube/pkg/expressions" - "github.com/kubeshop/testkube/pkg/log" + log2 "github.com/kubeshop/testkube/pkg/log" testworkflowmappers "github.com/kubeshop/testkube/pkg/mapper/testworkflows" - configRepo "github.com/kubeshop/testkube/pkg/repository/config" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/runner" "github.com/kubeshop/testkube/pkg/secretmanager" - "github.com/kubeshop/testkube/pkg/testworkflows" - "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/controller" "github.com/kubeshop/testkube/pkg/testworkflows/executionworker/executionworkertypes" "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" - "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowprocessor/stage" - "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" ) const ( - SaveResultRetryMaxAttempts = 100 - SaveResultRetryBaseDelay = 300 * time.Millisecond - - SaveLogsRetryMaxAttempts = 10 - SaveLogsRetryBaseDelay = 300 * time.Millisecond - ConfigSizeLimit = 3 * 1024 * 1024 ) +type TestWorkflowExecutionStream Stream[*testkube.TestWorkflowExecution] + //go:generate mockgen -destination=./mock_executor.go -package=testworkflowexecutor "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" TestWorkflowExecutor type TestWorkflowExecutor interface { - Control(ctx context.Context, testWorkflow *testworkflowsv1.TestWorkflow, execution *testkube.TestWorkflowExecution) error - Recover(ctx context.Context) - Execute(ctx context.Context, workflow testworkflowsv1.TestWorkflow, request testkube.TestWorkflowExecutionRequest) ( - execution testkube.TestWorkflowExecution, err error) + Execute(ctx context.Context, req *cloud.ScheduleRequest) TestWorkflowExecutionStream + Start(environmentId string, execution *testkube.TestWorkflowExecution, secrets map[string]map[string]string) error } type executor struct { - emitter *event.Emitter - clientSet kubernetes.Interface - repository testworkflow.Repository - output testworkflow.OutputRepository - configMap configRepo.Repository - testWorkflowTemplatesClient testworkflowsclientv1.TestWorkflowTemplatesInterface - testWorkflowExecutionsClient testworkflowsclientv1.TestWorkflowExecutionsInterface - testWorkflowsClient testworkflowsclientv1.Interface - metrics v1.Metrics - secretManager secretmanager.SecretManager - globalTemplateName string - dashboardURI string - workerClient executionworkertypes.Worker - proContext *config.ProContext -} - -func New(emitter *event.Emitter, - workerClient executionworkertypes.Worker, - clientSet kubernetes.Interface, + grpcClient cloud.TestKubeCloudAPIClient + apiKey string + cdEventsTarget string + organizationId string + defaultEnvironmentId string + + emitter event.Interface + metrics v1.Metrics + secretManager secretmanager.SecretManager + dashboardURI string + runner runner.Runner + proContext *config.ProContext + scheduler Scheduler + featureNewExecutions bool +} + +func New( + grpClient cloud.TestKubeCloudAPIClient, + apiKey string, + cdEventsTarget string, + emitter event.Interface, + runner runner.Runner, repository testworkflow.Repository, output testworkflow.OutputRepository, - configMap configRepo.Repository, - testWorkflowTemplatesClient testworkflowsclientv1.TestWorkflowTemplatesInterface, - testWorkflowExecutionsClient testworkflowsclientv1.TestWorkflowExecutionsInterface, - testWorkflowsClient testworkflowsclientv1.Interface, + testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient, + testWorkflowsClient testworkflowclient.TestWorkflowClient, metrics v1.Metrics, secretManager secretmanager.SecretManager, globalTemplateName string, dashboardURI string, - proContext *config.ProContext) TestWorkflowExecutor { + organizationId string, + defaultEnvironmentId string, + featureNewExecutions bool) TestWorkflowExecutor { return &executor{ - emitter: emitter, - clientSet: clientSet, - repository: repository, - output: output, - configMap: configMap, - testWorkflowTemplatesClient: testWorkflowTemplatesClient, - testWorkflowExecutionsClient: testWorkflowExecutionsClient, - testWorkflowsClient: testWorkflowsClient, - metrics: metrics, - secretManager: secretManager, - globalTemplateName: globalTemplateName, - dashboardURI: dashboardURI, - workerClient: workerClient, - proContext: proContext, - } -} - -func (e *executor) handleFatalError(execution *testkube.TestWorkflowExecution, err error, ts time.Time) { - // Detect error type - isAborted := errors.Is(err, controller.ErrJobAborted) - - // Apply the expected result - execution.Result.Fatal(err, isAborted, ts) - err = e.repository.UpdateResult(context.Background(), execution.Id, execution.Result) - if err != nil { - log.DefaultLogger.Errorf("failed to save fatal error for execution %s: %v", execution.Id, err) - } - e.emitter.Notify(testkube.NewEventEndTestWorkflowFailed(execution)) - go e.workerClient.Destroy(context.Background(), execution.Id, executionworkertypes.DestroyOptions{ - Namespace: execution.Namespace, - }) -} - -func (e *executor) Recover(ctx context.Context) { - list, err := e.repository.GetRunning(ctx) + grpcClient: grpClient, + apiKey: apiKey, + cdEventsTarget: cdEventsTarget, + emitter: emitter, + metrics: metrics, + secretManager: secretManager, + dashboardURI: dashboardURI, + runner: runner, + organizationId: organizationId, + defaultEnvironmentId: defaultEnvironmentId, + featureNewExecutions: featureNewExecutions, + scheduler: NewScheduler( + testWorkflowsClient, + testWorkflowTemplatesClient, + repository, + output, + globalTemplateName, + organizationId, + defaultEnvironmentId, + ), + } +} + +func (e *executor) isDirect() bool { + return e.proContext == nil || !e.proContext.NewExecutions +} + +func (e *executor) Execute(ctx context.Context, req *cloud.ScheduleRequest) TestWorkflowExecutionStream { + if req != nil { + req = common.Ptr(*req) // nolint:govet + if req.EnvironmentId == "" { + req.EnvironmentId = e.defaultEnvironmentId + } + } + if e.isDirect() { + return e.executeDirect(ctx, req) + } + return e.execute(ctx, req) +} + +func (e *executor) execute(ctx context.Context, req *cloud.ScheduleRequest) TestWorkflowExecutionStream { + ch := make(chan *testkube.TestWorkflowExecution) + opts := []grpc.CallOption{grpc.UseCompressor(gzip.Name), grpc.MaxCallRecvMsgSize(math.MaxInt32)} + ctx = agentclient.AddAPIKeyMeta(ctx, e.apiKey) + resp, err := e.grpcClient.ScheduleExecution(ctx, req, opts...) + resultStream := NewStream(ch) if err != nil { - return + close(ch) + resultStream.addError(err) + return resultStream } - for i := range list { - go func(execution *testkube.TestWorkflowExecution) { - var testWorkflow *testworkflowsv1.TestWorkflow - var err error - if execution.Workflow != nil { - testWorkflow, err = e.testWorkflowsClient.Get(execution.Workflow.Name) - if err != nil { - e.handleFatalError(execution, err, time.Time{}) - return + go func() { + defer close(ch) + var item *cloud.ScheduleResponse + for { + item, err = resp.Recv() + if err != nil { + if !errors.Is(err, io.EOF) { + resultStream.addError(err) } + break } - - err = e.Control(context.Background(), testWorkflow, execution) + var r testkube.TestWorkflowExecution + err = json.Unmarshal(item.Execution, &r) if err != nil { - e.handleFatalError(execution, err, time.Time{}) + resultStream.addError(err) + break } - }(&list[i]) - } -} - -func (e *executor) updateStatus(execution *testkube.TestWorkflowExecution, - testWorkflowExecution *testworkflowsv1.TestWorkflowExecution) { - if testWorkflowExecution != nil { - testWorkflowExecution.Status = testworkflowmappers.MapTestWorkflowExecutionStatusAPIToKube(execution, testWorkflowExecution.Generation) - if err := e.testWorkflowExecutionsClient.UpdateStatus(testWorkflowExecution); err != nil { - log.DefaultLogger.Errorw("failed to update test workflow execution", "error", err) + ch <- &r } - } + }() + return resultStream } -func (e *executor) Control(ctx context.Context, testWorkflow *testworkflowsv1.TestWorkflow, execution *testkube.TestWorkflowExecution) error { - ctx, ctxCancel := context.WithCancel(ctx) - defer ctxCancel() +func (e *executor) executeDirect(ctx context.Context, req *cloud.ScheduleRequest) TestWorkflowExecutionStream { + // Prepare dependencies + sensitiveDataHandler := NewSecretHandler(e.secretManager) - // TODO: retry? - notifications := e.workerClient.Notifications(ctx, execution.Id, executionworkertypes.NotificationsOptions{ - Hints: executionworkertypes.Hints{ - Namespace: execution.Namespace, - Signature: execution.Signature, - ScheduledAt: common.Ptr(execution.ScheduledAt), - }, - }) - if notifications.Err() != nil { - log.DefaultLogger.Errorw("failed to control the TestWorkflow", "id", execution.Id, "error", notifications.Err()) - return notifications.Err() - } - - // Prepare stream for writing log - r, writer := io.Pipe() - reader := bufio.NewReader(r) - ref := "" - - var testWorkflowExecution *testworkflowsv1.TestWorkflowExecution - if execution.TestWorkflowExecutionName != "" { - var err error - testWorkflowExecution, err = e.testWorkflowExecutionsClient.Get(execution.TestWorkflowExecutionName) - if err != nil { - log.DefaultLogger.Errorw("failed to get test workflow execution", "error", err) - } + // Schedule execution + ch, err := e.scheduler.Schedule(ctx, sensitiveDataHandler, req) + if err != nil { + resultStream := NewStream(ch) + resultStream.addError(err) + return resultStream } - wg := sync.WaitGroup{} - wg.Add(1) + ch2 := make(chan *testkube.TestWorkflowExecution, 1) + resultStream := NewStream(ch2) go func() { - defer wg.Done() - - for v := range notifications.Channel() { - if v.Output != nil { - if !v.Temporary { - execution.Output = append(execution.Output, *v.Output) - } - } else if v.Result != nil { - execution.Result = v.Result - if execution.Result.IsFinished() { - execution.StatusAt = execution.Result.FinishedAt - } - var wg sync.WaitGroup - wg.Add(2) - go func() { - e.updateStatus(execution, testWorkflowExecution) - wg.Done() - }() - go func() { - err := e.repository.UpdateResult(ctx, execution.Id, execution.Result) - if err != nil { - log.DefaultLogger.Error(errors.Wrap(err, "error saving test workflow execution result")) - } - wg.Done() - }() - wg.Wait() - } else if !v.Temporary { - if ref != v.Ref && v.Ref != "" { - ref = v.Ref - _, err := writer.Write([]byte(instructions.SprintHint(ref, initconstants.InstructionStart))) - if err != nil { - log.DefaultLogger.Error(errors.Wrap(err, "saving log output signature")) - } - } - _, err := writer.Write([]byte(v.Log)) - if err != nil { - log.DefaultLogger.Error(errors.Wrap(err, "saving log output content")) - } - } - } - if notifications.Err() != nil && !errors.Is(notifications.Err(), context.Canceled) { - log.DefaultLogger.Errorw("error from TestWorkflow watcher", "id", execution.Id, "error", notifications.Err()) - } - - // Try to gracefully handle abort - if execution.Result.FinishedAt.IsZero() { - // Handle container failure - abortedAt := time.Time{} - for _, v := range execution.Result.Steps { - if v.Status != nil && *v.Status == testkube.ABORTED_TestWorkflowStepStatus { - abortedAt = v.FinishedAt - break - } - } - if !abortedAt.IsZero() { - e.handleFatalError(execution, controller.ErrJobAborted, abortedAt) - } else { - // Handle unknown state - notifications = e.workerClient.Notifications(ctx, execution.Id, executionworkertypes.NotificationsOptions{ - Hints: executionworkertypes.Hints{ - Namespace: execution.Namespace, - Signature: execution.Signature, - ScheduledAt: common.Ptr(execution.ScheduledAt), - }, - }) - if notifications.Err() == nil { - for v := range notifications.Channel() { - if v.Output == nil { - continue - } - - execution.Result = v.Result - if execution.Result.IsFinished() { - execution.StatusAt = execution.Result.FinishedAt - } - err := e.repository.UpdateResult(ctx, execution.Id, execution.Result) - if err != nil { - log.DefaultLogger.Error(errors.Wrap(err, "error saving test workflow execution result")) - } - } + defer close(ch2) + for execution := range ch { + e.emitter.Notify(testkube.NewEventQueueTestWorkflow(execution)) + + // Send the data + ch2 <- execution.Clone() + + // Finish early if it's immediately known to finish + if execution.Result.IsFinished() { + e.emitter.Notify(testkube.NewEventStartTestWorkflow(execution)) + if execution.Result.IsAborted() { + e.emitter.Notify(testkube.NewEventEndTestWorkflowAborted(execution)) + } else if execution.Result.IsFailed() { + e.emitter.Notify(testkube.NewEventEndTestWorkflowFailed(execution)) } else { - e.handleFatalError(execution, notifications.Err(), time.Time{}) + e.emitter.Notify(testkube.NewEventEndTestWorkflowSuccess(execution)) } + continue } - } - - err := writer.Close() - if err != nil { - log.DefaultLogger.Errorw("failed to close TestWorkflow log output stream", "id", execution.Id, "error", err) - } - // TODO: Consider AppendOutput ($push) instead - _ = e.repository.UpdateOutput(ctx, execution.Id, execution.Output) - if execution.Result.IsFinished() { - e.sendRunWorkflowTelemetry(ctx, testWorkflow, execution) - - if execution.Result.IsPassed() { - e.emitter.Notify(testkube.NewEventEndTestWorkflowSuccess(execution)) - } else if execution.Result.IsAborted() { - e.emitter.Notify(testkube.NewEventEndTestWorkflowAborted(execution)) - } else { - e.emitter.Notify(testkube.NewEventEndTestWorkflowFailed(execution)) + // Set the runner execution to environment ID as it's a legacy Agent + execution.RunnerId = req.EnvironmentId + if req.EnvironmentId == "" { + execution.RunnerId = "oss" } + + // Start the execution + _ = e.Start(req.EnvironmentId, execution, sensitiveDataHandler.Get(execution.Id)) } }() - // Stream the log into Minio - err := e.output.SaveLog(context.Background(), execution.Id, execution.Workflow.Name, reader) - - // Retry saving the logs to Minio if something goes wrong - for attempt := 1; err != nil && attempt <= SaveLogsRetryMaxAttempts; attempt++ { - log.DefaultLogger.Errorw("retrying save of TestWorkflow log output", "id", execution.Id, "error", err) - time.Sleep(SaveLogsRetryBaseDelay * time.Duration(attempt)) - err = e.output.SaveLog(context.Background(), execution.Id, execution.Workflow.Name, e.workerClient.Logs(context.Background(), execution.Id, executionworkertypes.LogsOptions{ - NoFollow: true, - Hints: executionworkertypes.Hints{ - Namespace: execution.Namespace, - ScheduledAt: common.Ptr(execution.ScheduledAt), - Signature: execution.Signature, - }, - })) - } - if err != nil { - log.DefaultLogger.Errorw("failed to save TestWorkflow log output", "id", execution.Id, "error", err) - } - - wg.Wait() - - e.metrics.IncAndObserveExecuteTestWorkflow(*execution, e.dashboardURI) - - e.updateStatus(execution, testWorkflowExecution) // TODO: Consider if it is needed - err = e.workerClient.Destroy(ctx, execution.Id, executionworkertypes.DestroyOptions{ - Namespace: execution.Namespace, - }) - if err != nil { - log.DefaultLogger.Errorw("failed to cleanup TestWorkflow resources", "id", execution.Id, "error", err) - } - - return nil -} - -func (e *executor) getPreExecutionMachine(workflow *testworkflowsv1.TestWorkflow, orgId, envId string) expressions.Machine { - controlPlaneConfig := e.buildControlPlaneConfig(orgId, envId) - workflowConfig := e.buildWorkflowConfig(workflow) - cloudMachine := testworkflowconfig.CreateCloudMachine(&controlPlaneConfig) - workflowMachine := testworkflowconfig.CreateWorkflowMachine(&workflowConfig) - return expressions.CombinedMachines(cloudMachine, workflowMachine) -} - -func (e *executor) getPostExecutionMachine(execution *testkube.TestWorkflowExecution, orgId, envId, parentIds string) expressions.Machine { - executionConfig := e.buildExecutionConfig(execution, orgId, envId, parentIds) - executionMachine := testworkflowconfig.CreateExecutionMachine(&executionConfig) - return expressions.CombinedMachines(executionMachine) + return resultStream } -func (e *executor) buildExecutionConfig(execution *testkube.TestWorkflowExecution, orgId, envId, parentIds string) testworkflowconfig.ExecutionConfig { - return testworkflowconfig.ExecutionConfig{ - Id: execution.Id, - GroupId: execution.GroupId, - Name: execution.Name, - Number: execution.Number, - ScheduledAt: execution.ScheduledAt, - DisableWebhooks: execution.DisableWebhooks, - Tags: execution.Tags, - Debug: false, - OrganizationId: orgId, - EnvironmentId: envId, - ParentIds: parentIds, +func (e *executor) Start(environmentId string, execution *testkube.TestWorkflowExecution, secrets map[string]map[string]string) error { + controlPlaneConfig := testworkflowconfig.ControlPlaneConfig{ + DashboardUrl: e.dashboardURI, + CDEventsTarget: e.cdEventsTarget, } -} - -func (e *executor) buildWorkflowConfig(workflow *testworkflowsv1.TestWorkflow) testworkflowconfig.WorkflowConfig { - return testworkflowconfig.WorkflowConfig{ - Name: workflow.Name, - Labels: workflow.Labels, - } -} -func (e *executor) buildControlPlaneConfig(orgId, envId string) testworkflowconfig.ControlPlaneConfig { - dashboardUrl := e.dashboardURI - if orgId != "" && envId != "" && dashboardUrl == "" { - cloudUiUrl := os.Getenv("TESTKUBE_PRO_UI_URL") - dashboardUrl = fmt.Sprintf("%s/organization/%s/environment/%s/dashboard", cloudUiUrl, orgId, envId) - } - return testworkflowconfig.ControlPlaneConfig{ - DashboardUrl: dashboardUrl, - CDEventsTarget: os.Getenv("CDEVENTS_TARGET"), + parentIds := "" + if execution.RunningContext != nil && execution.RunningContext.Actor != nil { + parentIds = execution.RunningContext.Actor.ExecutionPath } -} - -// TODO: Consider if we shouldn't make name unique across all TestWorkflows -func (e *executor) isExecutionNameReserved(ctx context.Context, name, workflowName string) (bool, error) { - // TODO: Detect errors other than 404? - next, _ := e.repository.GetByNameAndTestWorkflow(ctx, name, workflowName) - if next.Name == name { - return true, nil - } - return false, nil -} - -func (e *executor) initialize(ctx context.Context, workflow *testworkflowsv1.TestWorkflow, request *testkube.TestWorkflowExecutionRequest) (execution *testkube.TestWorkflowExecution, secrets []corev1.Secret, err error) { - // Delete unnecessary data - delete(workflow.Annotations, "kubectl.kubernetes.io/last-applied-configuration") - - // Build the initial execution entity - now := time.Now().UTC() - executionId := primitive.NewObjectIDFromTimestamp(now).Hex() - - var nameReserved *bool - - // Early check if the name is already provided (to avoid incrementing sequence number) - if request.Name != "" { - reserved, err := e.isExecutionNameReserved(ctx, request.Name, workflow.Name) - if err != nil { - return nil, nil, errors.Wrap(err, "checking for unique name") - } - if reserved { - return execution, nil, errors.New("execution name already exists") - } - nameReserved = &reserved - } - - // Load execution identifier data - number, err := e.repository.GetNextExecutionNumber(context.Background(), workflow.Name) - if err != nil { - return nil, nil, errors.Wrap(err, "registering next execution sequence number") - } - executionName := request.Name - if executionName == "" { - executionName = fmt.Sprintf("%s-%d", workflow.Name, number) - } - - // Ensure the execution name is unique - if nameReserved == nil { - reserved, err := e.isExecutionNameReserved(ctx, executionName, workflow.Name) - if err != nil { - return nil, nil, errors.Wrap(err, "checking for unique name") - } - if reserved { - return execution, nil, errors.New("execution name already exists") - } - } - - // Initialize the storage for dynamically created secrets - secretsBatch := e.secretManager.Batch("twe-", executionId).ForceEnable() - - // Preserve initial workflow - initialWorkflow := workflow.DeepCopy() - initialWorkflowApi := testworkflowmappers.MapKubeToAPI(initialWorkflow) - - // Simplify the workflow data initially - _ = expressions.Simplify(&workflow) - - // Create the execution entity - execution = &testkube.TestWorkflowExecution{ - Id: executionId, - Name: executionName, - Number: number, - ScheduledAt: now, - StatusAt: now, - Signature: []testkube.TestWorkflowSignature{}, - Result: &testkube.TestWorkflowResult{ - Status: common.Ptr(testkube.QUEUED_TestWorkflowStatus), - PredictedStatus: common.Ptr(testkube.PASSED_TestWorkflowStatus), - Initialization: &testkube.TestWorkflowStepResult{ - Status: common.Ptr(testkube.QUEUED_TestWorkflowStepStatus), - }, - Steps: map[string]testkube.TestWorkflowStepResult{}, + result, err := e.runner.Execute(executionworkertypes.ExecuteRequest{ + Execution: testworkflowconfig.ExecutionConfig{ + Id: execution.Id, + GroupId: execution.GroupId, + Name: execution.Name, + Number: execution.Number, + ScheduledAt: execution.ScheduledAt, + DisableWebhooks: execution.DisableWebhooks, + Debug: false, + OrganizationId: e.organizationId, + EnvironmentId: environmentId, + ParentIds: parentIds, }, - Output: []testkube.TestWorkflowOutput{}, - Workflow: initialWorkflowApi, - ResolvedWorkflow: initialWorkflowApi, - TestWorkflowExecutionName: request.TestWorkflowExecutionName, - DisableWebhooks: request.DisableWebhooks, - Tags: map[string]string{}, - RunningContext: request.RunningContext, - ConfigParams: make(map[string]testkube.TestWorkflowExecutionConfigValue), - } - - // Store the configuration if it is small and not sensitive - if testworkflows.CountMapBytes(request.Config) < ConfigSizeLimit { - storeConfig := true - schema := workflow.Spec.Config - for _, v := range schema { - if v.Sensitive { - storeConfig = false - execution.ConfigParams = nil - break - } - } - - if storeConfig { - for k, v := range request.Config { - if _, ok := schema[k]; ok { - execution.ConfigParams[k] = testkube.TestWorkflowExecutionConfigValue{ - Value: v, - } - } - } - } - } - - // Try to resolve tags initialily - if workflow.Spec.Execution != nil { - execution.Tags = workflow.Spec.Execution.Tags - } - execution.Tags = testworkflowresolver.MergeTags(execution.Tags, request.Tags) - - // Inject the global template - if e.globalTemplateName != "" { - testworkflowresolver.AddGlobalTemplateRef(workflow, testworkflowsv1.TemplateRef{ - Name: testworkflowresolver.GetDisplayTemplateName(e.globalTemplateName), - }) - } + Secrets: secrets, + Workflow: testworkflowmappers.MapTestWorkflowAPIToKube(*execution.ResolvedWorkflow), + ControlPlane: controlPlaneConfig, + }) - // Apply the configuration - _, err = testworkflowresolver.ApplyWorkflowConfig(workflow, testworkflowmappers.MapConfigValueAPIToKube(request.Config), secretsBatch.Append) + // TODO: define "revoke" error by runner (?) if err != nil { - execution.InitializationError("Failed to apply configuration.", err) - return execution, nil, err - } - - // Fetch all required templates - tpls := testworkflowresolver.ListTemplates(workflow) - tplsMap := make(map[string]testworkflowsv1.TestWorkflowTemplate, len(tpls)) - for tplName := range tpls { - tpl, err := e.testWorkflowTemplatesClient.Get(tplName) + err2 := e.scheduler.CriticalError(execution, "Failed to run execution", err) + err = errors2.Join(err, err2) if err != nil { - execution.InitializationError(fmt.Sprintf("Failed to fetch '%s' template.", testworkflowresolver.GetDisplayTemplateName(tplName)), err) - return execution, nil, err + log2.DefaultLogger.Errorw("failed to run and update execution", "executionId", execution.Id, "error", err) } - tplsMap[tplName] = *tpl - } - - // Resolve the TestWorkflow - err = testworkflowresolver.ApplyTemplates(workflow, tplsMap, secretsBatch.Append) - if err != nil { - execution.InitializationError("Failed to apply templates.", err) - return execution, nil, err - } - - // Preserve resolved TestWorkflow - resolvedWorkflow := workflow.DeepCopy() - - // Try to resolve the tags further - if workflow.Spec.Execution != nil { - execution.Tags = workflow.Spec.Execution.Tags - } - execution.Tags = testworkflowresolver.MergeTags(execution.Tags, request.Tags) - - // Apply more resolved data to the execution - execution.ResolvedWorkflow = testworkflowmappers.MapKubeToAPI(resolvedWorkflow) - - // Determine the organization/environment - organizationId := e.proContext.OrgID - environmentId := e.proContext.EnvID - if e.proContext.APIKey == "" { - organizationId = "" - environmentId = "" - } - - // Simplify the result - preMachine := e.getPreExecutionMachine(workflow, organizationId, environmentId) - postMachine := e.getPostExecutionMachine(execution, organizationId, environmentId, strings.Join(request.ParentExecutionIds, "/")) - _ = expressions.Simplify(&workflow, preMachine, postMachine) - - // Build the final tags - if workflow.Spec.Execution != nil { - execution.Tags = workflow.Spec.Execution.Tags - } - execution.Tags = testworkflowresolver.MergeTags(execution.Tags, request.Tags) - - return execution, secretsBatch.Get(), nil -} - -func (e *executor) notifyResult(execution *testkube.TestWorkflowExecution) { - if !execution.Result.IsFinished() { - return - } - if execution.Result.IsPassed() { - e.emitter.Notify(testkube.NewEventEndTestWorkflowSuccess(execution)) - } else if execution.Result.IsAborted() { + e.emitter.Notify(testkube.NewEventStartTestWorkflow(execution)) e.emitter.Notify(testkube.NewEventEndTestWorkflowAborted(execution)) - } else { - e.emitter.Notify(testkube.NewEventEndTestWorkflowFailed(execution)) - } -} - -func (e *executor) saveEmptyLogs(execution *testkube.TestWorkflowExecution) (err error) { - if !execution.Result.IsFinished() { - return nil - } - for i := 1; i <= SaveLogsRetryMaxAttempts; i++ { - err = e.output.SaveLog(context.Background(), execution.Id, execution.Workflow.Name, bytes.NewReader(nil)) - if err == nil { - return nil - } - log.DefaultLogger.Warnw("failed to save empty logs. retrying...", "id", execution.Id, "error", err) - time.Sleep(time.Duration(i) * SaveResultRetryBaseDelay) - } - log.DefaultLogger.Errorw("failed to save empty logs", "id", execution.Id, "error", err) - return err -} - -func (e *executor) updateInDatabase(ctx context.Context, execution *testkube.TestWorkflowExecution) (err error) { - for i := 1; i <= SaveResultRetryMaxAttempts; i++ { - err = e.repository.Update(ctx, *execution) - if err == nil { - return nil - } - log.DefaultLogger.Warnw("failed to update execution. retrying...", "id", execution.Id, "error", err) - time.Sleep(time.Duration(i) * SaveResultRetryBaseDelay) - } - log.DefaultLogger.Errorw("failed to update execution", "id", execution.Id, "error", err) - return errors.Wrap(err, fmt.Sprintf("updating execution in storage: %s", err.Error())) -} - -func (e *executor) updateInKubernetes(_ context.Context, execution *testkube.TestWorkflowExecution) (err error) { - if execution.TestWorkflowExecutionName == "" { return nil } - for i := 1; i <= SaveResultRetryMaxAttempts; i++ { - // Load current object - var cr *testworkflowsv1.TestWorkflowExecution - cr, err = e.testWorkflowExecutionsClient.Get(execution.TestWorkflowExecutionName) - if err == nil { - cr.Status = testworkflowmappers.MapTestWorkflowExecutionStatusAPIToKube(execution, cr.Generation) - if err := e.testWorkflowExecutionsClient.UpdateStatus(cr); err == nil { - return nil - } - } - log.DefaultLogger.Warnw("failed to update execution object in cluster. retrying...", "id", execution.Id, "error", err) - time.Sleep(time.Duration(i) * SaveResultRetryBaseDelay) - } - log.DefaultLogger.Errorw("failed to update execution object in cluster", "id", execution.Id, "error", err) - return errors.Wrap(err, fmt.Sprintf("updating execution object in cluster: %s", err.Error())) -} - -func (e *executor) update(ctx context.Context, execution *testkube.TestWorkflowExecution) error { - var wg sync.WaitGroup - wg.Add(2) - - // TODO: Update also TestWorkflow.Status in Kubernetes - var err1, err2 error - go func() { - err1 = e.updateInDatabase(ctx, execution) - wg.Done() - }() - go func() { - err2 = e.updateInKubernetes(ctx, execution) - wg.Done() - }() - wg.Wait() - - return errors2.Join(err1, err2) -} - -func (e *executor) insert(ctx context.Context, execution *testkube.TestWorkflowExecution) (err error) { - for i := 1; i <= SaveResultRetryMaxAttempts; i++ { - err = e.repository.Insert(ctx, *execution) - if err == nil { - return nil - } - log.DefaultLogger.Warnw("failed to insert execution. retrying...", "id", execution.Id, "error", err) - time.Sleep(time.Duration(i) * SaveResultRetryBaseDelay) - } - log.DefaultLogger.Errorw("failed to insert execution", "id", execution.Id, "error", err) - return errors.Wrap(err, fmt.Sprintf("inserting execution in storage: %s", err.Error())) -} - -func (e *executor) Execute(ctx context.Context, workflow testworkflowsv1.TestWorkflow, request testkube.TestWorkflowExecutionRequest) ( - testkube.TestWorkflowExecution, error) { - execution, secrets, err := e.initialize(ctx, &workflow, &request) - - // Handle error without execution built - if execution == nil { - return testkube.TestWorkflowExecution{}, err - } - - // Insert the execution - insertErr := e.insert(context.Background(), execution) - if insertErr != nil { - e.saveEmptyLogs(execution) - if err != nil { - return *execution, errors.Wrap(insertErr, fmt.Sprintf("initializing error: %s: saving", err.Error())) - } - return *execution, insertErr - } - e.emitter.Notify(testkube.NewEventQueueTestWorkflow(execution)) - // TODO: Check if we need to resolve the [control plane] secrets (?) + // Inform about execution start + e.emitter.Notify(testkube.NewEventStartTestWorkflow(execution)) - // Send events - defer e.notifyResult(execution) - - // Handle finished execution (i.e. initialization error) - if execution.Result.IsFinished() { - e.saveEmptyLogs(execution) - e.updateInKubernetes(ctx, execution) - return *execution, nil - } - - // Determine the organization/environment - cloudApiKey := common.GetOr(os.Getenv("TESTKUBE_PRO_API_KEY"), os.Getenv("TESTKUBE_CLOUD_API_KEY")) - environmentId := common.GetOr(os.Getenv("TESTKUBE_PRO_ENV_ID"), os.Getenv("TESTKUBE_CLOUD_ENV_ID")) - organizationId := common.GetOr(os.Getenv("TESTKUBE_PRO_ORG_ID"), os.Getenv("TESTKUBE_CLOUD_ORG_ID")) - if cloudApiKey == "" { - organizationId = "" - environmentId = "" - } - - // Apply default service account - if workflow.Spec.Pod == nil { - workflow.Spec.Pod = &testworkflowsv1.PodConfig{} - } - if workflow.Spec.Pod.ServiceAccountName == "" { - workflow.Spec.Pod.ServiceAccountName = "{{internal.serviceaccount.default}}" - } - - // Map secrets - secretsMap := map[string]map[string]string{} - for _, secret := range secrets { - secretsMap[secret.Name] = secret.StringData - } - - // Schedule the execution by the Execution Worker - result, err := e.workerClient.Execute(context.Background(), executionworkertypes.ExecuteRequest{ - Execution: e.buildExecutionConfig(execution, organizationId, environmentId, strings.Join(request.ParentExecutionIds, "/")), - Secrets: secretsMap, - Workflow: workflow, - ControlPlane: e.buildControlPlaneConfig(organizationId, environmentId), - }) - if err != nil { - defer e.saveEmptyLogs(execution) - execution.InitializationError("Failed to initialize the execution.", err) - return *execution, errors.Wrap(e.update(context.Background(), execution), fmt.Sprintf("processing error: %s: saving", err.Error())) - } - - // Apply the signature + // Apply the known data to temporary object. execution.Namespace = result.Namespace execution.Signature = result.Signature - execution.Result.Steps = stage.MapSignatureListToStepResults(stage.MapSignatureList(result.Signature)) - err = e.update(context.Background(), execution) - if err != nil { - e.saveEmptyLogs(execution) - return *execution, e.update(context.Background(), execution) + if err = e.scheduler.Start(execution); err != nil { + log2.DefaultLogger.Errorw("failed to mark execution as initialized", "executionId", execution.Id, "error", err) } - - // Inform about execution start TODO: Consider - //e.emitter.Notify(testkube.NewEventStartTestWorkflow(execution)) - - // Start to control the results - go func() { - // TODO: Use OpenAPI objects only - err = e.Control(context.Background(), testworkflowmappers.MapAPIToKube(execution.Workflow), execution) - if err != nil { - e.handleFatalError(execution, err, time.Time{}) - return - } - }() - - return *execution, nil + return nil } diff --git a/pkg/testworkflows/testworkflowexecutor/mock_executor.go b/pkg/testworkflows/testworkflowexecutor/mock_executor.go index 52d95026f39..7a83ece5ae2 100644 --- a/pkg/testworkflows/testworkflowexecutor/mock_executor.go +++ b/pkg/testworkflows/testworkflowexecutor/mock_executor.go @@ -9,8 +9,8 @@ import ( reflect "reflect" gomock "github.com/golang/mock/gomock" - v1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" testkube "github.com/kubeshop/testkube/pkg/api/v1/testkube" + cloud "github.com/kubeshop/testkube/pkg/cloud" ) // MockTestWorkflowExecutor is a mock of TestWorkflowExecutor interface. @@ -36,43 +36,30 @@ func (m *MockTestWorkflowExecutor) EXPECT() *MockTestWorkflowExecutorMockRecorde return m.recorder } -// Control mocks base method. -func (m *MockTestWorkflowExecutor) Control(arg0 context.Context, arg1 *v1.TestWorkflow, arg2 *testkube.TestWorkflowExecution) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Control", arg0, arg1, arg2) - ret0, _ := ret[0].(error) - return ret0 -} - -// Control indicates an expected call of Control. -func (mr *MockTestWorkflowExecutorMockRecorder) Control(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Control", reflect.TypeOf((*MockTestWorkflowExecutor)(nil).Control), arg0, arg1, arg2) -} - // Execute mocks base method. -func (m *MockTestWorkflowExecutor) Execute(arg0 context.Context, arg1 v1.TestWorkflow, arg2 testkube.TestWorkflowExecutionRequest) (testkube.TestWorkflowExecution, error) { +func (m *MockTestWorkflowExecutor) Execute(arg0 context.Context, arg1 *cloud.ScheduleRequest) TestWorkflowExecutionStream { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Execute", arg0, arg1, arg2) - ret0, _ := ret[0].(testkube.TestWorkflowExecution) - ret1, _ := ret[1].(error) - return ret0, ret1 + ret := m.ctrl.Call(m, "Execute", arg0, arg1) + ret0, _ := ret[0].(TestWorkflowExecutionStream) + return ret0 } // Execute indicates an expected call of Execute. -func (mr *MockTestWorkflowExecutorMockRecorder) Execute(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockTestWorkflowExecutorMockRecorder) Execute(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockTestWorkflowExecutor)(nil).Execute), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockTestWorkflowExecutor)(nil).Execute), arg0, arg1) } -// Recover mocks base method. -func (m *MockTestWorkflowExecutor) Recover(arg0 context.Context) { +// Start mocks base method. +func (m *MockTestWorkflowExecutor) Start(arg0 string, arg1 *testkube.TestWorkflowExecution, arg2 map[string]map[string]string) error { m.ctrl.T.Helper() - m.ctrl.Call(m, "Recover", arg0) + ret := m.ctrl.Call(m, "Start", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 } -// Recover indicates an expected call of Recover. -func (mr *MockTestWorkflowExecutorMockRecorder) Recover(arg0 interface{}) *gomock.Call { +// Start indicates an expected call of Start. +func (mr *MockTestWorkflowExecutorMockRecorder) Start(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Recover", reflect.TypeOf((*MockTestWorkflowExecutor)(nil).Recover), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockTestWorkflowExecutor)(nil).Start), arg0, arg1, arg2) } diff --git a/pkg/testworkflows/testworkflowexecutor/mock_scheduler.go b/pkg/testworkflows/testworkflowexecutor/mock_scheduler.go new file mode 100644 index 00000000000..63f419a978c --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/mock_scheduler.go @@ -0,0 +1,80 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor (interfaces: Scheduler) + +// Package testworkflowexecutor is a generated GoMock package. +package testworkflowexecutor + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + testkube "github.com/kubeshop/testkube/pkg/api/v1/testkube" + cloud "github.com/kubeshop/testkube/pkg/cloud" +) + +// MockScheduler is a mock of Scheduler interface. +type MockScheduler struct { + ctrl *gomock.Controller + recorder *MockSchedulerMockRecorder +} + +// MockSchedulerMockRecorder is the mock recorder for MockScheduler. +type MockSchedulerMockRecorder struct { + mock *MockScheduler +} + +// NewMockScheduler creates a new mock instance. +func NewMockScheduler(ctrl *gomock.Controller) *MockScheduler { + mock := &MockScheduler{ctrl: ctrl} + mock.recorder = &MockSchedulerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockScheduler) EXPECT() *MockSchedulerMockRecorder { + return m.recorder +} + +// CriticalError mocks base method. +func (m *MockScheduler) CriticalError(arg0 *testkube.TestWorkflowExecution, arg1 string, arg2 error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CriticalError", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// CriticalError indicates an expected call of CriticalError. +func (mr *MockSchedulerMockRecorder) CriticalError(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CriticalError", reflect.TypeOf((*MockScheduler)(nil).CriticalError), arg0, arg1, arg2) +} + +// Schedule mocks base method. +func (m *MockScheduler) Schedule(arg0 context.Context, arg1 SensitiveDataHandler, arg2 *cloud.ScheduleRequest) (<-chan *testkube.TestWorkflowExecution, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Schedule", arg0, arg1, arg2) + ret0, _ := ret[0].(<-chan *testkube.TestWorkflowExecution) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Schedule indicates an expected call of Schedule. +func (mr *MockSchedulerMockRecorder) Schedule(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Schedule", reflect.TypeOf((*MockScheduler)(nil).Schedule), arg0, arg1, arg2) +} + +// Start mocks base method. +func (m *MockScheduler) Start(arg0 *testkube.TestWorkflowExecution) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start. +func (mr *MockSchedulerMockRecorder) Start(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockScheduler)(nil).Start), arg0) +} diff --git a/pkg/testworkflows/testworkflowexecutor/prepared.go b/pkg/testworkflows/testworkflowexecutor/prepared.go new file mode 100644 index 00000000000..fd7ff96e331 --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/prepared.go @@ -0,0 +1,347 @@ +package testworkflowexecutor + +import ( + "fmt" + "maps" + "strings" + "time" + + errors2 "github.com/go-errors/errors" + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson/primitive" + + testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/expressions" + testworkflowmappers "github.com/kubeshop/testkube/pkg/mapper/testworkflows" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowconfig" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" +) + +// Handling sensitive data + +const ( + intermediateSensitiveDataFn = "interSensitiveData" +) + +type IntermediateExecutionSensitiveData struct { + Data map[string]string + ts time.Time +} + +func NewIntermediateExecutionSensitiveData() *IntermediateExecutionSensitiveData { + return &IntermediateExecutionSensitiveData{ + Data: make(map[string]string), + ts: time.Now(), + } +} + +func (s *IntermediateExecutionSensitiveData) Append(_, value string) (expressions.Expression, error) { + sensitiveId := primitive.NewObjectIDFromTimestamp(s.ts).Hex() + s.Data[sensitiveId] = value + return expressions.Compile(fmt.Sprintf(`%s("%s")`, intermediateSensitiveDataFn, sensitiveId)) +} + +func (s *IntermediateExecutionSensitiveData) Clone() *IntermediateExecutionSensitiveData { + return &IntermediateExecutionSensitiveData{ + Data: maps.Clone(s.Data), + ts: s.ts, + } +} + +type IntermediateExecution struct { + cr *testworkflowsv1.TestWorkflow + dirty bool + execution *testkube.TestWorkflowExecution + sensitiveData *IntermediateExecutionSensitiveData + prepended []string + tags map[string]string +} + +// Handling different execution state + +func NewIntermediateExecution() *IntermediateExecution { + return &IntermediateExecution{ + tags: make(map[string]string), + dirty: true, + execution: &testkube.TestWorkflowExecution{ + Signature: []testkube.TestWorkflowSignature{}, + Result: &testkube.TestWorkflowResult{ + Status: common.Ptr(testkube.QUEUED_TestWorkflowStatus), + PredictedStatus: common.Ptr(testkube.PASSED_TestWorkflowStatus), + Initialization: &testkube.TestWorkflowStepResult{ + Status: common.Ptr(testkube.QUEUED_TestWorkflowStepStatus), + }, + Steps: map[string]testkube.TestWorkflowStepResult{}, + }, + Output: []testkube.TestWorkflowOutput{}, + Tags: map[string]string{}, + }, + sensitiveData: NewIntermediateExecutionSensitiveData(), + } +} + +func (e *IntermediateExecution) SetScheduledAt(t time.Time) *IntermediateExecution { + e.execution.ScheduledAt = t + e.execution.StatusAt = t + if e.execution.Result.IsFinished() { + // TODO: set others too + e.execution.Result.FinishedAt = t + e.execution.Result.Initialization.FinishedAt = t + e.execution.Result.HealDuration(t) + } + return e +} + +func (e *IntermediateExecution) AutoGenerateID() *IntermediateExecution { + e.execution.Id = primitive.NewObjectIDFromTimestamp(time.Now()).Hex() + return e +} + +func (e *IntermediateExecution) ID() string { + return e.execution.Id +} + +func (e *IntermediateExecution) GroupID() string { + return e.execution.GroupId +} + +func (e *IntermediateExecution) SetName(name string) *IntermediateExecution { + e.execution.Name = name + return e +} + +func (e *IntermediateExecution) SetGroupID(groupID string) *IntermediateExecution { + if groupID == "" { + e.execution.GroupId = e.execution.Id + } else { + e.execution.GroupId = groupID + } + return e +} + +func (e *IntermediateExecution) AppendTags(tags map[string]string) *IntermediateExecution { + e.dirty = true + maps.Copy(e.tags, tags) + return e +} + +func (e *IntermediateExecution) SetKubernetesObjectName(name string) *IntermediateExecution { + e.execution.TestWorkflowExecutionName = name + return e +} + +func (e *IntermediateExecution) SetDisabledWebhooks(disabled bool) *IntermediateExecution { + e.execution.DisableWebhooks = disabled + return e +} + +func (e *IntermediateExecution) SetRunningContext(runningContext *testkube.TestWorkflowRunningContext) *IntermediateExecution { + e.execution.RunningContext = runningContext + return e +} + +func (e *IntermediateExecution) WorkflowName() string { + if e.cr == nil { + panic("workflow not set yet") + } + return e.cr.Name +} + +func (e *IntermediateExecution) Name() string { + return e.execution.Name +} + +func (e *IntermediateExecution) Execution() *testkube.TestWorkflowExecution { + if e.cr == nil { + panic("workflow not set yet") + } + if e.dirty { + e.dirty = false + e.execution.ResolvedWorkflow = testworkflowmappers.MapKubeToAPI(e.cr) + e.execution.Tags = make(map[string]string) + if e.cr.Spec.Execution != nil { + // TODO: Should resolve the expressions? (`{{"{{"}}` becomes `{{`) + maps.Copy(e.execution.Tags, e.cr.Spec.Execution.Tags) + maps.Copy(e.execution.Tags, e.tags) + } + } + return e.execution +} + +func (e *IntermediateExecution) PrependTemplate(name string) *IntermediateExecution { + if name == "" { + return e + } + if e.cr == nil { + e.prepended = append(e.prepended, name) + return e + } + e.dirty = true + testworkflowresolver.AddGlobalTemplateRef(e.cr, testworkflowsv1.TemplateRef{ + Name: testworkflowresolver.GetDisplayTemplateName(name), + }) + return e +} + +func (e *IntermediateExecution) SensitiveData() map[string]string { + return e.sensitiveData.Data +} + +func (e *IntermediateExecution) simplifyCr() error { + crMachine := testworkflowconfig.CreateWorkflowMachine(&testworkflowconfig.WorkflowConfig{Name: e.cr.Name, Labels: e.cr.Labels}) + err1 := expressions.Simplify(e.cr, crMachine) + err2 := expressions.SimplifyForce(&e.sensitiveData.Data, crMachine) + err := errors2.Join(err1, err2) + e.dirty = true + if err != nil { + return err + } + return nil +} + +func (e *IntermediateExecution) ApplyDynamicConfig(config map[string]string) error { + if e.cr == nil { + panic("workflow not set yet") + } + e.dirty = true + _, err := testworkflowresolver.ApplyWorkflowConfig(e.cr, testworkflowmappers.MapConfigValueAPIToKube(config), e.sensitiveData.Append) + if err != nil { + return err + } + return e.simplifyCr() +} + +func (e *IntermediateExecution) ApplyConfig(config map[string]string) error { + dynamicConfig := make(map[string]string) + for k, v := range config { + dynamicConfig[k] = expressions.NewStringValue(v).Template() + } + return e.ApplyDynamicConfig(dynamicConfig) +} + +func (e *IntermediateExecution) ApplyTemplates(templates map[string]*testkube.TestWorkflowTemplate) error { + if e.cr == nil { + panic("workflow not set yet") + } + e.dirty = true + // TODO: apply CRDs directly? + crTemplates := make(map[string]*testworkflowsv1.TestWorkflowTemplate, len(templates)) + for k, v := range templates { + crTemplates[k] = testworkflowmappers.MapTemplateAPIToKube(v) + } + err := testworkflowresolver.ApplyTemplates(e.cr, crTemplates, e.sensitiveData.Append) + if err != nil { + return err + } + return e.simplifyCr() +} + +func (e *IntermediateExecution) TemplateNames() map[string]struct{} { + if e.cr == nil { + panic("workflow not set yet") + } + return testworkflowresolver.ListTemplates(e.cr) +} + +func (e *IntermediateExecution) Resolve(organizationId, environmentId string, parentExecutionIds []string, debug bool) error { + if e.cr == nil { + panic("workflow not set yet") + } + if e.execution.Id == "" || e.execution.GroupId == "" || e.execution.Name == "" || e.execution.Number == 0 { + return errors.New("execution is not ready yet") + } + + executionMachine := testworkflowconfig.CreateExecutionMachine(&testworkflowconfig.ExecutionConfig{ + Id: e.execution.Id, + GroupId: e.execution.GroupId, + Name: e.execution.Name, + Number: e.execution.Number, + ScheduledAt: e.execution.ScheduledAt, + DisableWebhooks: e.execution.DisableWebhooks, + Debug: debug, + OrganizationId: organizationId, + EnvironmentId: environmentId, + ParentIds: strings.Join(parentExecutionIds, "/"), + }) + resourceMachine := testworkflowconfig.CreateResourceMachine(&testworkflowconfig.ResourceConfig{ + Id: e.execution.Id, + RootId: e.execution.Id, + }) + crMachine := testworkflowconfig.CreateWorkflowMachine(&testworkflowconfig.WorkflowConfig{ + Name: e.cr.Name, + Labels: e.cr.Labels, + }) + err1 := expressions.Simplify(e.cr, crMachine, resourceMachine, executionMachine) + err2 := expressions.SimplifyForce(&e.sensitiveData.Data, crMachine, resourceMachine, executionMachine) + err := errors2.Join(err1, err2) + e.dirty = true + if err != nil { + return err + } + return nil +} + +func (e *IntermediateExecution) SetWorkflow(workflow *testworkflowsv1.TestWorkflow) *IntermediateExecution { + e.cr = workflow.DeepCopy() + e.dirty = true + e.execution.Workflow = testworkflowmappers.MapKubeToAPI(e.cr) + for _, tpl := range e.prepended { + testworkflowresolver.AddGlobalTemplateRef(e.cr, testworkflowsv1.TemplateRef{ + Name: testworkflowresolver.GetDisplayTemplateName(tpl), + }) + } + e.prepended = nil + return e +} + +func (e *IntermediateExecution) SetSequenceNumber(number int32) *IntermediateExecution { + e.execution.Number = number + return e +} + +func (e *IntermediateExecution) SetError(header string, err error) *IntermediateExecution { + // Keep only the 1st error + if !e.execution.Result.IsFinished() { + e.execution.InitializationError(header, err) + } + return e +} + +func (e *IntermediateExecution) RewriteSensitiveDataCall(handler func(name string) (expressions.Expression, error)) error { + e.dirty = true + return expressions.Simplify(&e.cr, expressions.NewMachine().RegisterFunction(intermediateSensitiveDataFn, func(values ...expressions.StaticValue) (interface{}, bool, error) { + if len(values) != 1 { + return nil, true, fmt.Errorf(`"%s" function expects 1 argument, %d provided`, intermediateSensitiveDataFn, len(values)) + } + localCredentialName, _ := values[0].StringValue() + expr, err := handler(localCredentialName) + return expr, true, err + })) +} + +func (e *IntermediateExecution) StoreConfig(config map[string]string) *IntermediateExecution { + params := make(map[string]testkube.TestWorkflowExecutionConfigValue) + for k, v := range config { + if _, ok := e.cr.Spec.Config[k]; ok { + params[k] = testkube.TestWorkflowExecutionConfigValue{Value: v} + } + } + e.execution.ConfigParams = params + return e +} + +func (e *IntermediateExecution) Finished() bool { + return e.execution.Result.IsFinished() +} + +func (e *IntermediateExecution) Clone() *IntermediateExecution { + return &IntermediateExecution{ + cr: e.cr.DeepCopy(), + tags: maps.Clone(e.tags), + execution: e.execution.Clone(), + prepended: e.prepended, + sensitiveData: e.sensitiveData.Clone(), + } +} diff --git a/pkg/testworkflows/testworkflowexecutor/scheduler.go b/pkg/testworkflows/testworkflowexecutor/scheduler.go new file mode 100644 index 00000000000..6904275d043 --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/scheduler.go @@ -0,0 +1,338 @@ +package testworkflowexecutor + +import ( + "bytes" + "context" + "fmt" + "time" + + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/zap" + + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/log" + testworkflows2 "github.com/kubeshop/testkube/pkg/mapper/testworkflows" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" + "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/testworkflows" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" +) + +const ( + SaveResultRetryMaxAttempts = 100 + SaveResultRetryBaseDelay = 300 * time.Millisecond +) + +//go:generate mockgen -destination=./mock_scheduler.go -package=testworkflowexecutor "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" Scheduler +type Scheduler interface { + Schedule(ctx context.Context, sensitiveDataHandler SensitiveDataHandler, req *cloud.ScheduleRequest) (<-chan *testkube.TestWorkflowExecution, error) + CriticalError(execution *testkube.TestWorkflowExecution, name string, err error) error + Start(execution *testkube.TestWorkflowExecution) error +} + +type scheduler struct { + logger *zap.SugaredLogger + testWorkflowsClient testworkflowclient.TestWorkflowClient + testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient + resultsRepository testworkflow.Repository + outputRepository testworkflow.OutputRepository + globalTemplateName string + organizationId string + defaultEnvironmentId string +} + +func NewScheduler( + testWorkflowsClient testworkflowclient.TestWorkflowClient, + testWorkflowTemplatesClient testworkflowtemplateclient.TestWorkflowTemplateClient, + resultsRepository testworkflow.Repository, + outputRepository testworkflow.OutputRepository, + globalTemplateName string, + organizationId string, + defaultEnvironmentId string, +) Scheduler { + return &scheduler{ + logger: log.DefaultLogger, + testWorkflowsClient: testWorkflowsClient, + testWorkflowTemplatesClient: testWorkflowTemplatesClient, + resultsRepository: resultsRepository, + outputRepository: outputRepository, + globalTemplateName: globalTemplateName, + organizationId: organizationId, + defaultEnvironmentId: defaultEnvironmentId, + } +} + +func (s *scheduler) insert(ctx context.Context, execution *testkube.TestWorkflowExecution) error { + err := retry(SaveResultRetryMaxAttempts, SaveResultRetryBaseDelay, func() error { + err := s.resultsRepository.Insert(ctx, *execution) + if err != nil { + s.logger.Warnw("failed to update the TestWorkflow execution in database", "recoverable", true, "executionId", execution.Id, "error", err) + } + return err + }) + if err != nil { + s.logger.Errorw("failed to update the TestWorkflow execution in database", "recoverable", false, "executionId", execution.Id, "error", err) + } + return err +} + +func (s *scheduler) update(ctx context.Context, execution *testkube.TestWorkflowExecution) error { + err := retry(SaveResultRetryMaxAttempts, SaveResultRetryBaseDelay, func() error { + err := s.resultsRepository.Update(ctx, *execution) + if err != nil { + s.logger.Warnw("failed to update the TestWorkflow execution in database", "recoverable", true, "executionId", execution.Id, "error", err) + } + return err + }) + if err != nil { + s.logger.Errorw("failed to update the TestWorkflow execution in database", "recoverable", false, "executionId", execution.Id, "error", err) + } + return err +} + +func (s *scheduler) init(ctx context.Context, execution *testkube.TestWorkflowExecution) error { + err := retry(SaveResultRetryMaxAttempts, SaveResultRetryBaseDelay, func() error { + err := s.resultsRepository.Init(ctx, execution.Id, testworkflow.InitData{ + RunnerID: execution.RunnerId, + Namespace: execution.Namespace, + Signature: execution.Signature, + }) + if err != nil { + s.logger.Warnw("failed to initialize the TestWorkflow execution in database", "recoverable", true, "executionId", execution.Id, "error", err) + } + return err + }) + if err != nil { + s.logger.Errorw("failed to initialize the TestWorkflow execution in database", "recoverable", false, "executionId", execution.Id, "error", err) + } + return err +} + +func (s *scheduler) saveEmptyLogs(ctx context.Context, execution *testkube.TestWorkflowExecution) error { + err := retry(SaveResultRetryMaxAttempts, SaveResultRetryBaseDelay, func() error { + return s.outputRepository.SaveLog(ctx, execution.Id, execution.Workflow.Name, bytes.NewReader(nil)) + }) + if err != nil { + s.logger.Errorw("failed to save empty log", "executionId", execution.Id, "error", err) + } + return err +} + +func (s *scheduler) Schedule(ctx context.Context, sensitiveDataHandler SensitiveDataHandler, req *cloud.ScheduleRequest) (<-chan *testkube.TestWorkflowExecution, error) { + // Prepare the channel + ch := make(chan *testkube.TestWorkflowExecution, 1) + + // Set up context + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Validate the execution request + if err := ValidateExecutionRequest(req); err != nil { + close(ch) + return ch, err + } + + // Check if there is anything to run + if len(req.Executions) == 0 { + close(ch) + return ch, nil + } + + // Initialize execution template + now := time.Now().UTC() + base := NewIntermediateExecution(). + SetGroupID(primitive.NewObjectIDFromTimestamp(now).Hex()). + SetScheduledAt(now). + AppendTags(req.Tags). + SetDisabledWebhooks(req.DisableWebhooks). + SetKubernetesObjectName(req.KubernetesObjectName). + SetRunningContext(GetLegacyRunningContext(req)). + PrependTemplate(s.globalTemplateName) + + // Initialize fetchers + testWorkflows := NewTestWorkflowFetcher(s.testWorkflowsClient, req.EnvironmentId) + testWorkflowTemplates := NewTestWorkflowTemplateFetcher(s.testWorkflowTemplatesClient, req.EnvironmentId) + + // Prefetch all the Test Workflows + err := testWorkflows.PrefetchMany(common.MapSlice(req.Executions, func(t *cloud.ScheduleExecution) *cloud.ScheduleResourceSelector { + return t.Selector + })) + if err != nil { + close(ch) + return ch, err + } + + // Prefetch all the Test Workflow Templates. + // Don't fail immediately - it should be execution's error message if it's missing. + tplNames := testWorkflows.TemplateNames() + if s.globalTemplateName != "" { + tplNames[testworkflowresolver.GetInternalTemplateName(s.globalTemplateName)] = struct{}{} + } + _ = testWorkflowTemplates.PrefetchMany(tplNames) + + // Flatten selectors + selectors := make([]*cloud.ScheduleExecution, 0, len(req.Executions)) + for i := range req.Executions { + list, _ := testWorkflows.Get(req.Executions[i].Selector) + for _, w := range list { + selectors = append(selectors, &cloud.ScheduleExecution{ + Selector: &cloud.ScheduleResourceSelector{Name: w.Name}, + Config: req.Executions[i].Config, + ExecutionName: req.Executions[i].ExecutionName, // TODO: what to do when execution name is configured, but multiple requested? + Tags: req.Executions[i].Tags, + }) + } + } + + // Resolve executions for each selector + intermediate := make([]*IntermediateExecution, 0, len(selectors)) + for _, v := range selectors { + workflow, _ := testWorkflows.GetByName(v.Selector.Name) + current := base.Clone(). + AutoGenerateID(). + SetName(v.ExecutionName). + AppendTags(v.Tags). + SetWorkflow(testworkflows2.MapAPIToKube(workflow)) + intermediate = append(intermediate, current) + + // Inject configuration + storeConfig := true + schema := workflow.Spec.Config + for k := range v.Config { + if s, ok := schema[k]; ok && s.Sensitive { + storeConfig = false + } + } + if storeConfig && testworkflows.CountMapBytes(v.Config) < ConfigSizeLimit { + current.StoreConfig(v.Config) + } + + // Apply the configuration + if err := current.ApplyConfig(v.Config); err != nil { + current.SetError("Cannot inline Test Workflow configuration", err) + continue + } + + // Load the required Test Workflow Templates + tpls, err := testWorkflowTemplates.GetMany(current.TemplateNames()) + if err != nil { + current.SetError("Cannot fetch required Test Workflow Templates", err) + continue + } + + // Apply the Test Workflow Templates + if err = current.ApplyTemplates(tpls); err != nil { + current.SetError("Cannot inline Test Workflow Templates", err) + continue + } + } + + // Simplify group ID in case of single execution + if len(intermediate) == 1 { + intermediate[0].SetGroupID(intermediate[0].ID()) + } + + // Validate if there are no execution name duplicates initially + if err = ValidateExecutionNameDuplicates(intermediate); err != nil { + close(ch) + return ch, err + } + + // Validate if the static execution names are not reserved in the database already + for i := range intermediate { + if intermediate[i].Name() == "" { + continue + } + if err = ValidateExecutionNameRemoteDuplicate(ctx, s.resultsRepository, intermediate[i]); err != nil { + close(ch) + return ch, err + } + } + + // Ensure the rest of operations won't be stopped if started + if ctx.Err() != nil { + close(ch) + return ch, ctx.Err() + } + cancel() + + // Generate execution names and sequence numbers + for i := range intermediate { + // Load execution identifier data + number, err := s.resultsRepository.GetNextExecutionNumber(context.Background(), intermediate[i].WorkflowName()) + if err != nil { + close(ch) + return ch, errors.Wrap(err, "registering next execution sequence number") + } + intermediate[i].SetSequenceNumber(number) + + // Generating the execution name + if intermediate[i].Name() == "" { + name := fmt.Sprintf("%s-%d", intermediate[i].WorkflowName(), number) + intermediate[i].SetName(name) + + // Edge case: Check for local duplicates, if there is no clash between static and auto-generated one + if err = ValidateExecutionNameDuplicates(intermediate); err != nil { + return ch, err + } + + // Ensure the execution name is unique + if err = ValidateExecutionNameRemoteDuplicate(context.Background(), s.resultsRepository, intermediate[i]); err != nil { + close(ch) + return ch, err + } + } + + // Resolve it finally + environmentId := req.EnvironmentId + if environmentId == "" { + environmentId = s.defaultEnvironmentId + } + err = intermediate[i].Resolve(s.organizationId, environmentId, req.ParentExecutionIds, false) + if err != nil { + intermediate[i].SetError("Cannot process Test Workflow specification", err) + continue + } + } + + go func() { + defer close(ch) + for i := range intermediate { + // Prepare sensitive data + if err = sensitiveDataHandler.Process(intermediate[i]); err != nil { + intermediate[i].SetError("Cannot store the sensitive data", err) + } + + // Save empty logs if the execution is already finished + if intermediate[i].Finished() { + _ = s.saveEmptyLogs(context.Background(), intermediate[i].Execution()) + } + + // Insert the execution + if err = s.insert(context.Background(), intermediate[i].Execution()); err != nil { + sensitiveDataHandler.Rollback(intermediate[i].ID()) + // TODO: notify API about problem (?) + continue + } + + // Inform about the next execution + ch <- intermediate[i].Execution() + } + }() + + return ch, nil +} + +func (s *scheduler) CriticalError(execution *testkube.TestWorkflowExecution, name string, err error) error { + execution.InitializationError(name, err) + _ = s.saveEmptyLogs(context.Background(), execution) + return s.update(context.Background(), execution) +} + +func (s *scheduler) Start(execution *testkube.TestWorkflowExecution) error { + return s.init(context.Background(), execution) +} diff --git a/pkg/testworkflows/testworkflowexecutor/secrethandler.go b/pkg/testworkflows/testworkflowexecutor/secrethandler.go new file mode 100644 index 00000000000..9580766555f --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/secrethandler.go @@ -0,0 +1,66 @@ +package testworkflowexecutor + +import ( + "fmt" + + "github.com/kubeshop/testkube/pkg/expressions" + "github.com/kubeshop/testkube/pkg/secretmanager" +) + +type executionID = string +type secretName = string +type secretData = map[string]string + +type SensitiveDataHandler interface { + Process(execution *IntermediateExecution) error + Rollback(id string) error +} + +type secretHandler struct { + manager secretmanager.SecretManager + maps map[executionID]map[secretName]secretData +} + +func NewSecretHandler(manager secretmanager.SecretManager) *secretHandler { + return &secretHandler{ + manager: manager, + maps: make(map[executionID]map[secretName]secretData), + } +} + +func (s *secretHandler) Process(intermediate *IntermediateExecution) error { + id := intermediate.ID() + + // Pack the sensitive data into a secrets set + secretsBatch := s.manager.Batch("twe-", id).ForceEnable() + credentialExpressions := map[string]expressions.Expression{} + for k, v := range intermediate.SensitiveData() { + envVarSource, err := secretsBatch.Append(k, v) + if err != nil { + return err + } + credentialExpressions[k] = expressions.MustCompile(fmt.Sprintf(`secret("%s","%s",true)`, envVarSource.SecretKeyRef.Name, envVarSource.SecretKeyRef.Key)) + } + secrets := secretsBatch.Get() + s.maps[id] = make(map[string]map[string]string, len(secrets)) + for j := range secrets { + s.maps[id][secrets[j].Name] = secrets[j].StringData + } + + // Change the calls to access these secrets + return intermediate.RewriteSensitiveDataCall(func(name string) (expressions.Expression, error) { + if expr, ok := credentialExpressions[name]; ok { + return expr, nil + } + return nil, fmt.Errorf(`unknown sensitive data: '%s'`, name) + }) +} + +func (s *secretHandler) Get(id executionID) map[secretName]secretData { + return s.maps[id] +} + +func (s *secretHandler) Rollback(_ executionID) error { + // There are no actual resources created, so nothing needs to be rolled back + return nil +} diff --git a/pkg/testworkflows/testworkflowexecutor/testworkflowfetcher.go b/pkg/testworkflows/testworkflowexecutor/testworkflowfetcher.go new file mode 100644 index 00000000000..e53e91baba7 --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/testworkflowfetcher.go @@ -0,0 +1,171 @@ +package testworkflowexecutor + +import ( + "context" + "maps" + + "github.com/pkg/errors" + "golang.org/x/sync/errgroup" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" +) + +type testWorkflowFetcher struct { + client testworkflowclient.TestWorkflowClient + environmentId string + cache map[string]*testkube.TestWorkflow + prefetchedLabels []map[string]string +} + +func NewTestWorkflowFetcher(client testworkflowclient.TestWorkflowClient, environmentId string) *testWorkflowFetcher { + return &testWorkflowFetcher{ + client: client, + environmentId: environmentId, + cache: make(map[string]*testkube.TestWorkflow), + } +} + +func (r *testWorkflowFetcher) PrefetchByLabelSelector(labels map[string]string) error { + if containsSameMap(r.prefetchedLabels, labels) { + return nil + } + workflows, err := r.client.List(context.Background(), r.environmentId, testworkflowclient.ListOptions{Labels: labels}) + if err != nil { + return errors.Wrapf(err, "cannot fetch Test Workflows by label selector: %v", labels) + } + for i := range workflows { + r.cache[workflows[i].Name] = &workflows[i] + } + r.prefetchedLabels = append(r.prefetchedLabels, labels) + return nil +} + +func (r *testWorkflowFetcher) PrefetchByName(name string) error { + if _, ok := r.cache[name]; ok { + return nil + } + workflow, err := r.client.Get(context.Background(), r.environmentId, name) + if err != nil { + return errors.Wrapf(err, "cannot fetch Test Workflow by name: %s", name) + } + r.cache[name] = workflow + return nil +} + +func (r *testWorkflowFetcher) PrefetchMany(selectors []*cloud.ScheduleResourceSelector) error { + // Categorize selectors + names := make(map[string]struct{}) + labels := make([]map[string]string, 0) + for i := range selectors { + if selectors[i].Name == "" { + if !containsSameMap(labels, selectors[i].Labels) { + labels = append(labels, selectors[i].Labels) + } + } else { + names[selectors[i].Name] = struct{}{} + } + } + + // Fetch firstly by the label selector, as it is more likely to conflict with others + g := errgroup.Group{} + g.SetLimit(10) + for i := range labels { + func(m map[string]string) { + g.Go(func() error { + return r.PrefetchByLabelSelector(labels[i]) + }) + }(labels[i]) + } + err := g.Wait() + if err != nil { + return err + } + + // Fetch the rest by name + g = errgroup.Group{} + g.SetLimit(10) + for name := range names { + func(n string) { + g.Go(func() error { + return r.PrefetchByName(n) + }) + }(name) + } + return g.Wait() +} + +func (r *testWorkflowFetcher) GetByName(name string) (*testkube.TestWorkflow, error) { + if r.cache[name] == nil { + err := r.PrefetchByName(name) + if err != nil { + return nil, err + } + } + return r.cache[name], nil +} + +func (r *testWorkflowFetcher) GetByLabelSelector(labels map[string]string) ([]*testkube.TestWorkflow, error) { + if !containsSameMap(r.prefetchedLabels, labels) { + err := r.PrefetchByLabelSelector(labels) + if err != nil { + return nil, err + } + } + result := make([]*testkube.TestWorkflow, 0) +loop: + for name := range r.cache { + for k := range labels { + if r.cache[name].Labels[k] != labels[k] { + continue loop + } + } + result = append(result, r.cache[name]) + } + return result, nil +} + +func (r *testWorkflowFetcher) Get(selector *cloud.ScheduleResourceSelector) ([]*testkube.TestWorkflow, error) { + if selector.Name == "" { + return r.GetByLabelSelector(selector.Labels) + } + v, err := r.GetByName(selector.Name) + if err != nil { + return nil, err + } + return []*testkube.TestWorkflow{v}, nil +} + +func (r *testWorkflowFetcher) Names() []string { + names := make([]string, 0, len(r.cache)) + for k := range r.cache { + names = append(names, k) + } + return names +} + +func (r *testWorkflowFetcher) TemplateNames() map[string]struct{} { + result := make(map[string]struct{}) + for k := range r.cache { + // TODO: avoid converting to CRD + maps.Copy(result, testworkflowresolver.ListTemplates(testworkflows.MapAPIToKube(r.cache[k]))) + } + return result +} + +func containsSameMap[T comparable, U comparable](s []map[T]U, v map[T]U) bool { + for i := range s { + if len(s[i]) != len(v) { + continue + } + for k := range s[i] { + if x, ok := v[k]; !ok || x != s[i][k] { + return true + } + } + } + return false +} diff --git a/pkg/testworkflows/testworkflowexecutor/testworkflowmetrics.go b/pkg/testworkflows/testworkflowexecutor/testworkflowmetrics.go deleted file mode 100644 index dae361f581b..00000000000 --- a/pkg/testworkflows/testworkflowexecutor/testworkflowmetrics.go +++ /dev/null @@ -1,234 +0,0 @@ -package testworkflowexecutor - -import ( - "context" - "strings" - - testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" - - "github.com/kubeshop/testkube/pkg/api/v1/testkube" - "github.com/kubeshop/testkube/pkg/log" - "github.com/kubeshop/testkube/pkg/telemetry" - "github.com/kubeshop/testkube/pkg/testworkflows" - "github.com/kubeshop/testkube/pkg/version" -) - -type stepStats struct { - numSteps int - numExecute int - hasArtifacts bool - hasMatrix bool - hasParallel bool - hasTemplate bool - hasServices bool - imagesUsed map[string]struct{} - templatesUsed map[string]struct{} -} - -func (ss *stepStats) Merge(stats *stepStats) { - ss.numSteps += stats.numSteps - ss.numExecute += stats.numExecute - - if stats.hasArtifacts { - ss.hasArtifacts = true - } - if stats.hasMatrix { - ss.hasMatrix = true - } - if stats.hasParallel { - ss.hasParallel = true - } - if stats.hasServices { - ss.hasServices = true - } - if stats.hasTemplate { - ss.hasTemplate = true - } - for image := range stats.imagesUsed { - ss.imagesUsed[image] = struct{}{} - } - for tmpl := range stats.templatesUsed { - ss.templatesUsed[tmpl] = struct{}{} - } -} - -func getStepInfo(step testworkflowsv1.Step) *stepStats { - res := &stepStats{ - imagesUsed: make(map[string]struct{}), - templatesUsed: make(map[string]struct{}), - } - if step.Execute != nil { - res.numExecute++ - } - if step.Artifacts != nil { - res.hasArtifacts = true - } - if len(step.Use) > 0 { - res.hasTemplate = true - for _, tmpl := range step.Use { - res.templatesUsed[tmpl.Name] = struct{}{} - } - } - if step.Template != nil { - res.hasTemplate = true - res.templatesUsed[step.Template.Name] = struct{}{} - } - if len(step.Services) > 0 { - res.hasServices = true - } - - if step.Run != nil && step.Run.Image != "" { - res.imagesUsed[step.Run.Image] = struct{}{} - } - if step.Container != nil && step.Container.Image != "" { - res.imagesUsed[step.Container.Image] = struct{}{} - } - - for _, step := range step.Steps { - res.Merge(getStepInfo(step)) - } - - if step.Parallel != nil { - res.hasParallel = true - - if len(step.Parallel.Matrix) != 0 { - res.hasMatrix = true - } - if step.Parallel.Artifacts != nil { - res.hasArtifacts = true - } - if step.Parallel.Execute != nil { - res.numExecute++ - } - if len(step.Parallel.Use) > 0 { - res.hasTemplate = true - for _, tmpl := range step.Parallel.Use { - res.templatesUsed[tmpl.Name] = struct{}{} - } - } - if step.Parallel.Template != nil { - res.hasTemplate = true - res.templatesUsed[step.Parallel.Template.Name] = struct{}{} - } - - if len(step.Parallel.Services) > 0 { - res.hasServices = true - } - - if step.Parallel.Run != nil && step.Parallel.Run.Image != "" { - res.imagesUsed[step.Parallel.Run.Image] = struct{}{} - } - if step.Parallel.Container != nil && step.Parallel.Container.Image != "" { - res.imagesUsed[step.Parallel.Container.Image] = struct{}{} - } - - for _, step := range step.Parallel.Steps { - res.Merge(getStepInfo(step)) - } - } - - return res -} - -func (e *executor) sendRunWorkflowTelemetry(ctx context.Context, workflow *testworkflowsv1.TestWorkflow, execution *testkube.TestWorkflowExecution) { - if workflow == nil { - log.DefaultLogger.Debug("empty workflow passed to telemetry event") - return - } - telemetryEnabled, err := e.configMap.GetTelemetryEnabled(ctx) - if err != nil { - log.DefaultLogger.Debugf("getting telemetry enabled error", "error", err) - } - if !telemetryEnabled { - return - } - - properties := make(map[string]any) - properties["name"] = workflow.Name - stats := stepStats{ - imagesUsed: make(map[string]struct{}), - templatesUsed: make(map[string]struct{}), - } - - var isSample bool - if workflow.Labels != nil && workflow.Labels["docs"] == "example" && strings.HasSuffix(workflow.Name, "-sample") { - isSample = true - } else { - isSample = false - } - - spec := workflow.Spec - for _, step := range spec.Steps { - stats.Merge(getStepInfo(step)) - } - if spec.Container != nil { - stats.imagesUsed[spec.Container.Image] = struct{}{} - } - if len(spec.Services) != 0 { - stats.hasServices = true - } - if len(spec.Use) > 0 { - stats.hasTemplate = true - for _, tmpl := range spec.Use { - stats.templatesUsed[tmpl.Name] = struct{}{} - } - } - - var images []string - for image := range stats.imagesUsed { - if image == "" { - continue - } - images = append(images, image) - } - - var templates []string - for t := range stats.templatesUsed { - if t == "" { - continue - } - templates = append(templates, t) - } - var ( - status string - durationMs int32 - ) - if execution.Result != nil { - if execution.Result.Status != nil { - status = string(*execution.Result.Status) - } - durationMs = execution.Result.DurationMs - } - - out, err := telemetry.SendRunWorkflowEvent("testkube_api_run_test_workflow", telemetry.RunWorkflowParams{ - RunParams: telemetry.RunParams{ - AppVersion: version.Version, - DataSource: testworkflows.GetDataSource(workflow.Spec.Content), - Host: testworkflows.GetHostname(), - ClusterID: testworkflows.GetClusterID(ctx, e.configMap), - DurationMs: durationMs, - Status: status, - }, - WorkflowParams: telemetry.WorkflowParams{ - TestWorkflowSteps: int32(stats.numSteps), - TestWorkflowExecuteCount: int32(stats.numExecute), - TestWorkflowImage: testworkflows.GetImage(workflow.Spec.Container), - TestWorkflowArtifactUsed: stats.hasArtifacts, - TestWorkflowParallelUsed: stats.hasParallel, - TestWorkflowMatrixUsed: stats.hasMatrix, - TestWorkflowServicesUsed: stats.hasServices, - TestWorkflowTemplateUsed: stats.hasTemplate, - TestWorkflowIsSample: isSample, - TestWorkflowTemplates: templates, - TestWorkflowImages: images, - TestWorkflowKubeshopGitURI: testworkflows.IsKubeshopGitURI(workflow.Spec.Content) || - testworkflows.HasWorkflowStepLike(workflow.Spec, testworkflows.HasKubeshopGitURI), - }, - }) - - if err != nil { - log.DefaultLogger.Debugf("sending run test workflow telemetry event error", "error", err) - } else { - log.DefaultLogger.Debugf("sending run test workflow telemetry event", "output", out) - } -} diff --git a/pkg/testworkflows/testworkflowexecutor/testworkflowtemplatefetcher.go b/pkg/testworkflows/testworkflowexecutor/testworkflowtemplatefetcher.go new file mode 100644 index 00000000000..0767b322aa5 --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/testworkflowtemplatefetcher.go @@ -0,0 +1,99 @@ +package testworkflowexecutor + +import ( + "context" + "sync" + + "github.com/pkg/errors" + "golang.org/x/sync/errgroup" + + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowtemplateclient" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowresolver" +) + +type testWorkflowTemplateFetcher struct { + client testworkflowtemplateclient.TestWorkflowTemplateClient + environmentId string + cache map[string]*testkube.TestWorkflowTemplate +} + +func NewTestWorkflowTemplateFetcher( + client testworkflowtemplateclient.TestWorkflowTemplateClient, + environmentId string, +) *testWorkflowTemplateFetcher { + return &testWorkflowTemplateFetcher{ + client: client, + environmentId: environmentId, + cache: make(map[string]*testkube.TestWorkflowTemplate), + } +} + +func (r *testWorkflowTemplateFetcher) Prefetch(name string) error { + name = testworkflowresolver.GetInternalTemplateName(name) + if _, ok := r.cache[name]; ok { + return nil + } + workflow, err := r.client.Get(context.Background(), r.environmentId, name) + if err != nil { + return errors.Wrapf(err, "cannot fetch Test Workflow Template by name: %s", name) + } + r.cache[name] = workflow + return nil +} + +func (r *testWorkflowTemplateFetcher) PrefetchMany(namesSet map[string]struct{}) error { + // Internalize and dedupe names + internalNames := make(map[string]struct{}, len(namesSet)) + for name := range namesSet { + internalNames[testworkflowresolver.GetInternalTemplateName(name)] = struct{}{} + } + + // Fetch all the requested templates + var g errgroup.Group + g.SetLimit(10) + for name := range internalNames { + func(n string) { + g.Go(func() error { + return r.Prefetch(n) + }) + }(name) + } + return g.Wait() +} + +func (r *testWorkflowTemplateFetcher) Get(name string) (*testkube.TestWorkflowTemplate, error) { + if r.cache[name] == nil { + err := r.Prefetch(name) + if err != nil { + return nil, err + } + } + return r.cache[name], nil +} + +func (r *testWorkflowTemplateFetcher) GetMany(names map[string]struct{}) (map[string]*testkube.TestWorkflowTemplate, error) { + results := make(map[string]*testkube.TestWorkflowTemplate, len(names)) + resultsMu := &sync.Mutex{} + + // Fetch all the requested templates + var g errgroup.Group + g.SetLimit(10) + for name := range names { + func(n string) { + g.Go(func() error { + v, err := r.Get(n) + if err != nil { + return err + } + resultsMu.Lock() + defer resultsMu.Unlock() + results[v.Name] = v + return nil + }) + }(name) + } + err := g.Wait() + + return results, err +} diff --git a/pkg/testworkflows/testworkflowexecutor/utils.go b/pkg/testworkflows/testworkflowexecutor/utils.go new file mode 100644 index 00000000000..8c6acf64438 --- /dev/null +++ b/pkg/testworkflows/testworkflowexecutor/utils.go @@ -0,0 +1,261 @@ +package testworkflowexecutor + +import ( + "context" + errors2 "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + "go.mongodb.org/mongo-driver/mongo" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/repository/testworkflow" + "github.com/kubeshop/testkube/pkg/secretmanager" +) + +type Stream[T any] interface { + Error() error + Channel() <-chan T +} + +type stream[T any] struct { + errs []error + ch <-chan T + mu sync.RWMutex +} + +func NewStream[T any](ch <-chan T) *stream[T] { + return &stream[T]{ch: ch} +} + +func (s *stream[T]) addError(err error) { + s.mu.Lock() + defer s.mu.Unlock() + s.errs = append(s.errs, err) +} + +func (s *stream[T]) Error() error { + s.mu.RLock() + defer s.mu.RUnlock() + return errors2.Join(s.errs...) +} + +func (s *stream[T]) Channel() <-chan T { + return s.ch +} + +func retry(count int, delayBase time.Duration, fn func() error) (err error) { + for i := 0; i < count; i++ { + err = fn() + if err == nil { + return nil + } + time.Sleep(time.Duration(i) * delayBase) + } + return err +} + +func GetNewRunningContext(legacy *testkube.TestWorkflowRunningContext, parentExecutionIds []string) (runningContext *cloud.RunningContext, untrustedUser *cloud.UserSignature) { + if legacy != nil { + if legacy.Actor != nil && legacy.Actor.Type_ != nil { + switch *legacy.Actor.Type_ { + case testkube.CRON_TestWorkflowRunningContextActorType: + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_CRON, Name: legacy.Actor.Name} + case testkube.TESTTRIGGER_TestWorkflowRunningContextActorType: + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_TESTTRIGGER, Name: legacy.Actor.Name} + case testkube.TESTWORKFLOWEXECUTION_TestWorkflowRunningContextActorType: + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_KUBERNETESOBJECT, Name: legacy.Actor.Name} + case testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType: + if len(parentExecutionIds) > 0 { + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_EXECUTION, Name: parentExecutionIds[len(parentExecutionIds)-1]} + } + case testkube.USER_TestWorkflowRunningContextActorType: + if legacy.Actor.Name != "" && legacy.Actor.Email != "" { + untrustedUser = &cloud.UserSignature{Name: legacy.Actor.Name, Email: legacy.Actor.Email} + } + } + } + if runningContext == nil && legacy.Interface_ != nil && legacy.Interface_.Type_ != nil { + switch *legacy.Interface_.Type_ { + case testkube.CLI_TestWorkflowRunningContextInterfaceType: + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_CLI, Name: legacy.Interface_.Name} + case testkube.UI_TestWorkflowRunningContextInterfaceType: + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_UI, Name: legacy.Interface_.Name} + case testkube.CICD_TestWorkflowRunningContextInterfaceType: + runningContext = &cloud.RunningContext{Type: cloud.RunningContextType_CICD, Name: legacy.Interface_.Name} + } + } + } + return +} + +func GetLegacyRunningContext(req *cloud.ScheduleRequest) (runningContext *testkube.TestWorkflowRunningContext) { + if req.RunningContext == nil { + return nil + } + + userActor := &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.USER_TestWorkflowRunningContextActorType), + } + if req.User != nil { + userActor.Name = req.User.Name + userActor.Email = req.User.Email + } + + switch req.RunningContext.Type { + case cloud.RunningContextType_UI: + return &testkube.TestWorkflowRunningContext{ + Actor: userActor, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.RunningContext.Name, + Type_: common.Ptr(testkube.UI_TestWorkflowRunningContextInterfaceType), + }, + } + case cloud.RunningContextType_CLI: + return &testkube.TestWorkflowRunningContext{ + Actor: userActor, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.RunningContext.Name, + Type_: common.Ptr(testkube.CLI_TestWorkflowRunningContextInterfaceType), + }, + } + case cloud.RunningContextType_CICD: + return &testkube.TestWorkflowRunningContext{ + Actor: userActor, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.RunningContext.Name, + Type_: common.Ptr(testkube.CICD_TestWorkflowRunningContextInterfaceType), + }, + } + case cloud.RunningContextType_CRON: + return &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.CRON_TestWorkflowRunningContextActorType), + }, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.RunningContext.Name, + Type_: common.Ptr(testkube.INTERNAL_TestWorkflowRunningContextInterfaceType), + }, + } + case cloud.RunningContextType_TESTTRIGGER: + return &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.TESTTRIGGER_TestWorkflowRunningContextActorType), + }, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.RunningContext.Name, + Type_: common.Ptr(testkube.INTERNAL_TestWorkflowRunningContextInterfaceType), + }, + } + case cloud.RunningContextType_KUBERNETESOBJECT: + return &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + Type_: common.Ptr(testkube.TESTWORKFLOWEXECUTION_TestWorkflowRunningContextActorType), + }, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.RunningContext.Name, + Type_: common.Ptr(testkube.INTERNAL_TestWorkflowRunningContextInterfaceType), + }, + } + case cloud.RunningContextType_EXECUTION: + if len(req.ParentExecutionIds) == 0 { + break + } + return &testkube.TestWorkflowRunningContext{ + Actor: &testkube.TestWorkflowRunningContextActor{ + ExecutionId: req.ParentExecutionIds[len(req.ParentExecutionIds)-1], + ExecutionPath: strings.Join(req.ParentExecutionIds, "/"), + Type_: common.Ptr(testkube.TESTWORKFLOW_TestWorkflowRunningContextActorType), + }, + Interface_: &testkube.TestWorkflowRunningContextInterface{ + Name: req.ParentExecutionIds[len(req.ParentExecutionIds)-1], + Type_: common.Ptr(testkube.INTERNAL_TestWorkflowRunningContextInterfaceType), + }, + } + } + return nil +} + +// TODO: Limit selectors or maximum executions to avoid huge load? +func ValidateExecutionRequest(req *cloud.ScheduleRequest) error { + // Validate if the selectors have exclusively name or label selector + nameSelectorsCount := 0 + labelSelectorsCount := 0 + for i := range req.Executions { + if req.Executions[i] == nil { + return errors.New("invalid selector provided") + } + if req.Executions[i].Selector.Name != "" && len(req.Executions[i].Selector.Labels) > 0 { + return errors.New("invalid selector provided") + } + if req.Executions[i].Selector.Name == "" && len(req.Executions[i].Selector.Labels) == 0 { + return errors.New("invalid selector provided") + } + if req.Executions[i].Selector.Name != "" { + nameSelectorsCount++ + } else { + labelSelectorsCount++ + } + } + + // Validate if that could be Kubernetes object + if req.KubernetesObjectName != "" && (nameSelectorsCount != 1 || labelSelectorsCount != 0) { + return errors.New("kubernetes object can trigger only execution of a single named TestWorkflow") + } + + return nil +} + +func ValidateExecutionNameDuplicates(intermediate []*IntermediateExecution) error { + type namePair struct { + Workflow string + Execution string + } + localDuplicatesCheck := make(map[namePair]struct{}) + for i := range intermediate { + if intermediate[i].Name() == "" { + continue + } + key := namePair{Workflow: intermediate[i].WorkflowName(), Execution: intermediate[i].Name()} + if _, ok := localDuplicatesCheck[key]; ok { + return fmt.Errorf("duplicated execution name: '%s' for workflow '%s'", intermediate[i].Name(), intermediate[i].WorkflowName()) + } + localDuplicatesCheck[key] = struct{}{} + } + return nil +} + +func IsNotFound(err error) bool { + if err == nil { + return false + } + if errors.Is(err, mongo.ErrNoDocuments) || k8serrors.IsNotFound(err) || errors.Is(err, secretmanager.ErrNotFound) { + return true + } + if e, ok := status.FromError(err); ok { + return e.Code() == codes.NotFound + } + return false +} + +func ValidateExecutionNameRemoteDuplicate(ctx context.Context, resultsRepository testworkflow.Repository, intermediate *IntermediateExecution) error { + next, err := resultsRepository.GetByNameAndTestWorkflow(ctx, intermediate.Name(), intermediate.WorkflowName()) + if IsNotFound(err) { + return nil + } + if err != nil { + return errors.Wrapf(err, "failed to verify unique name: '%s' in '%s' workflow", intermediate.Name(), intermediate.WorkflowName()) + } + if next.Name == intermediate.Name() { + return fmt.Errorf("execution name already exists: '%s' for workflow '%s'", intermediate.Name(), intermediate.WorkflowName()) + } + return nil +} diff --git a/pkg/testworkflows/testworkflowresolver/apply.go b/pkg/testworkflows/testworkflowresolver/apply.go index a5ab59b3463..29c13e1d1a3 100644 --- a/pkg/testworkflows/testworkflowresolver/apply.go +++ b/pkg/testworkflows/testworkflowresolver/apply.go @@ -13,7 +13,6 @@ import ( "reflect" "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" @@ -22,16 +21,16 @@ import ( "github.com/kubeshop/testkube/pkg/rand" ) -func buildTemplate(template testworkflowsv1.TestWorkflowTemplate, cfg map[string]intstr.IntOrString, - externalize func(key, value string) (*corev1.EnvVarSource, error)) (testworkflowsv1.TestWorkflowTemplate, error) { +func buildTemplate(template *testworkflowsv1.TestWorkflowTemplate, cfg map[string]intstr.IntOrString, + externalize func(key, value string) (expressions.Expression, error)) (*testworkflowsv1.TestWorkflowTemplate, error) { v, err := ApplyWorkflowTemplateConfig(template.DeepCopy(), cfg, externalize) if err != nil { return template, err } - return *v, err + return v, err } -func getTemplate(name string, templates map[string]testworkflowsv1.TestWorkflowTemplate) (tpl testworkflowsv1.TestWorkflowTemplate, err error) { +func getTemplate(name string, templates map[string]*testworkflowsv1.TestWorkflowTemplate) (tpl *testworkflowsv1.TestWorkflowTemplate, err error) { key := GetInternalTemplateName(name) tpl, ok := templates[key] if ok { @@ -45,8 +44,8 @@ func getTemplate(name string, templates map[string]testworkflowsv1.TestWorkflowT return tpl, fmt.Errorf(`template "%s" not found`, name) } -func getConfiguredTemplate(name string, cfg map[string]intstr.IntOrString, templates map[string]testworkflowsv1.TestWorkflowTemplate, - externalize func(key, value string) (*corev1.EnvVarSource, error)) (tpl testworkflowsv1.TestWorkflowTemplate, err error) { +func getConfiguredTemplate(name string, cfg map[string]intstr.IntOrString, templates map[string]*testworkflowsv1.TestWorkflowTemplate, + externalize func(key, value string) (expressions.Expression, error)) (tpl *testworkflowsv1.TestWorkflowTemplate, err error) { tpl, err = getTemplate(name, templates) if err != nil { return tpl, err @@ -54,7 +53,7 @@ func getConfiguredTemplate(name string, cfg map[string]intstr.IntOrString, templ return buildTemplate(tpl, cfg, externalize) } -func injectTemplateToSpec(spec *testworkflowsv1.TestWorkflowSpec, template testworkflowsv1.TestWorkflowTemplate) error { +func injectTemplateToSpec(spec *testworkflowsv1.TestWorkflowSpec, template *testworkflowsv1.TestWorkflowTemplate) error { if spec == nil { return nil } @@ -81,14 +80,7 @@ func injectTemplateToSpec(spec *testworkflowsv1.TestWorkflowSpec, template testw return nil } -func InjectTemplate(workflow *testworkflowsv1.TestWorkflow, template testworkflowsv1.TestWorkflowTemplate) error { - if workflow == nil { - return nil - } - return injectTemplateToSpec(&workflow.Spec, template) -} - -func InjectStepTemplate(step *testworkflowsv1.Step, template testworkflowsv1.TestWorkflowTemplate) error { +func InjectStepTemplate(step *testworkflowsv1.Step, template *testworkflowsv1.TestWorkflowTemplate) error { if step == nil { return nil } @@ -119,7 +111,7 @@ func InjectStepTemplate(step *testworkflowsv1.Step, template testworkflowsv1.Tes return nil } -func InjectServiceTemplate(svc *testworkflowsv1.ServiceSpec, template testworkflowsv1.TestWorkflowTemplate) error { +func InjectServiceTemplate(svc *testworkflowsv1.ServiceSpec, template *testworkflowsv1.TestWorkflowTemplate) error { if svc == nil { return nil } @@ -130,8 +122,8 @@ func InjectServiceTemplate(svc *testworkflowsv1.ServiceSpec, template testworkfl return nil } -func applyTemplatesToStep(step testworkflowsv1.Step, templates map[string]testworkflowsv1.TestWorkflowTemplate, - externalize func(key, value string) (*corev1.EnvVarSource, error)) (testworkflowsv1.Step, error) { +func applyTemplatesToStep(step testworkflowsv1.Step, templates map[string]*testworkflowsv1.TestWorkflowTemplate, + externalize func(key, value string) (expressions.Expression, error)) (testworkflowsv1.Step, error) { // Apply regular templates for i := len(step.Use) - 1; i >= 0; i-- { ref := step.Use[i] @@ -255,8 +247,8 @@ func FlattenStepList(steps []testworkflowsv1.Step) []testworkflowsv1.Step { return result } -func applyTemplatesToSpec(spec *testworkflowsv1.TestWorkflowSpec, templates map[string]testworkflowsv1.TestWorkflowTemplate, - externalize func(key, value string) (*corev1.EnvVarSource, error)) error { +func applyTemplatesToSpec(spec *testworkflowsv1.TestWorkflowSpec, templates map[string]*testworkflowsv1.TestWorkflowTemplate, + externalize func(key, value string) (expressions.Expression, error)) error { if spec == nil { return nil } @@ -334,8 +326,8 @@ func applyTemplatesToSpec(spec *testworkflowsv1.TestWorkflowSpec, templates map[ return nil } -func ApplyTemplates(workflow *testworkflowsv1.TestWorkflow, templates map[string]testworkflowsv1.TestWorkflowTemplate, - externalize func(key, value string) (*corev1.EnvVarSource, error)) error { +func ApplyTemplates(workflow *testworkflowsv1.TestWorkflow, templates map[string]*testworkflowsv1.TestWorkflowTemplate, + externalize func(key, value string) (expressions.Expression, error)) error { if workflow == nil { return nil } diff --git a/pkg/testworkflows/testworkflowresolver/apply_test.go b/pkg/testworkflows/testworkflowresolver/apply_test.go index e0617373ae4..a7a4a2dac58 100644 --- a/pkg/testworkflows/testworkflowresolver/apply_test.go +++ b/pkg/testworkflows/testworkflowresolver/apply_test.go @@ -107,13 +107,13 @@ var ( }, }, } - templates = map[string]testworkflowsv1.TestWorkflowTemplate{ - "pod": tplPod, - "podConfig": tplPodConfig, - "env": tplEnv, - "steps": tplSteps, - "stepsEnv": tplStepsEnv, - "stepsConfig": tplStepsConfig, + templates = map[string]*testworkflowsv1.TestWorkflowTemplate{ + "pod": &tplPod, + "podConfig": &tplPodConfig, + "env": &tplEnv, + "steps": &tplSteps, + "stepsEnv": &tplStepsEnv, + "stepsConfig": &tplStepsConfig, } tplPodRef = testworkflowsv1.TemplateRef{Name: "pod"} tplPodConfigRef = testworkflowsv1.TemplateRef{ @@ -605,7 +605,7 @@ func TestApplyTemplatesConfigOverflow(t *testing.T) { } func TestApplyTemplates_ConditionAlways(t *testing.T) { - tpls := map[string]testworkflowsv1.TestWorkflowTemplate{ + tpls := map[string]*testworkflowsv1.TestWorkflowTemplate{ "example": { Spec: testworkflowsv1.TestWorkflowTemplateSpec{ TestWorkflowSpecBase: testworkflowsv1.TestWorkflowSpecBase{ @@ -675,7 +675,7 @@ func TestApplyTemplates_ConditionAlways(t *testing.T) { } func TestApplyTemplates_MergePodValues(t *testing.T) { - tpls := map[string]testworkflowsv1.TestWorkflowTemplate{ + tpls := map[string]*testworkflowsv1.TestWorkflowTemplate{ "top": { Spec: testworkflowsv1.TestWorkflowTemplateSpec{ TestWorkflowSpecBase: testworkflowsv1.TestWorkflowSpecBase{ diff --git a/pkg/testworkflows/testworkflowresolver/config.go b/pkg/testworkflows/testworkflowresolver/config.go index 66aa54aaa93..cc59f9895a1 100644 --- a/pkg/testworkflows/testworkflowresolver/config.go +++ b/pkg/testworkflows/testworkflowresolver/config.go @@ -43,7 +43,7 @@ func castParameter(value intstr.IntOrString, schema testworkflowsv1.ParameterSch } func createConfigMachine(cfg map[string]intstr.IntOrString, schema map[string]testworkflowsv1.ParameterSchema, - externalize func(key, value string) (*corev1.EnvVarSource, error)) (expressions.Machine, error) { + externalize func(key, value string) (expressions.Expression, error)) (expressions.Machine, error) { machine := expressions.NewMachine() for k, v := range cfg { expr, err := castParameter(v, schema[k]) @@ -51,7 +51,7 @@ func createConfigMachine(cfg map[string]intstr.IntOrString, schema map[string]te return nil, errors.Wrap(err, "config."+k) } if schema[k].Sensitive && externalize != nil { - expr, err = getSecretCallExpression(expr, k, externalize) + expr, err = externalize(k, expr.Template()) if err != nil { return nil, err } @@ -65,7 +65,7 @@ func createConfigMachine(cfg map[string]intstr.IntOrString, schema map[string]te return nil, errors.Wrap(err, "config."+k) } if schema[k].Sensitive && externalize != nil { - expr, err = getSecretCallExpression(expr, k, externalize) + expr, err = externalize(k, expr.Template()) if err != nil { return nil, err } @@ -76,22 +76,23 @@ func createConfigMachine(cfg map[string]intstr.IntOrString, schema map[string]te return machine, nil } -func getSecretCallExpression(expr expressions.Expression, k string, externalize func(key, value string) (*corev1.EnvVarSource, error)) ( - expressions.Expression, error) { - envVar, err := externalize(k, expr.Template()) - if err != nil { - return nil, errors.Wrap(err, "config."+k) - } - if envVar.SecretKeyRef != nil { - return expressions.Compile(fmt.Sprintf("secret(\"%s\", \"%s\", true)", - envVar.SecretKeyRef.Name, envVar.SecretKeyRef.Key)) +func EnvVarSourceToSecretExpression(fn func(key, value string) (*corev1.EnvVarSource, error)) func(key, value string) (expressions.Expression, error) { + return func(key, value string) (expressions.Expression, error) { + envVar, err := fn(key, value) + if err != nil { + return nil, err + } + if envVar.SecretKeyRef != nil { + return expressions.Compile(fmt.Sprintf("secret(%s,%s,true)", + expressions.NewStringValue(envVar.SecretKeyRef.Name).String(), + expressions.NewStringValue(envVar.SecretKeyRef.Key).String())) + } + return nil, nil } - - return expr, nil } func ApplyWorkflowConfig(t *testworkflowsv1.TestWorkflow, cfg map[string]intstr.IntOrString, - externalize func(key, value string) (*corev1.EnvVarSource, error)) (*testworkflowsv1.TestWorkflow, error) { + externalize func(key, value string) (expressions.Expression, error)) (*testworkflowsv1.TestWorkflow, error) { if t == nil { return t, nil } @@ -104,7 +105,7 @@ func ApplyWorkflowConfig(t *testworkflowsv1.TestWorkflow, cfg map[string]intstr. } func ApplyWorkflowTemplateConfig(t *testworkflowsv1.TestWorkflowTemplate, cfg map[string]intstr.IntOrString, - externalize func(key, value string) (*corev1.EnvVarSource, error)) (*testworkflowsv1.TestWorkflowTemplate, error) { + externalize func(key, value string) (expressions.Expression, error)) (*testworkflowsv1.TestWorkflowTemplate, error) { if t == nil { return t, nil } diff --git a/pkg/testworkflows/utils.go b/pkg/testworkflows/utils.go index 7df6ad52dba..65d008d4ce2 100644 --- a/pkg/testworkflows/utils.go +++ b/pkg/testworkflows/utils.go @@ -8,107 +8,6 @@ package testworkflows -import ( - "context" - "os" - "strings" - - testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" - "github.com/kubeshop/testkube/pkg/log" - configRepo "github.com/kubeshop/testkube/pkg/repository/config" -) - -// GetImage returns the image of the container -func GetImage(container *testworkflowsv1.ContainerConfig) string { - if container != nil { - return container.Image - } - return "" -} - -func HasWorkflowStepLike(spec testworkflowsv1.TestWorkflowSpec, fn func(step testworkflowsv1.Step) bool) bool { - return HasStepLike(spec.Setup, fn) || HasStepLike(spec.Steps, fn) || HasStepLike(spec.After, fn) -} - -func HasTemplateStepLike(spec testworkflowsv1.TestWorkflowTemplateSpec, fn func(step testworkflowsv1.IndependentStep) bool) bool { - return HasIndependentStepLike(spec.Setup, fn) || HasIndependentStepLike(spec.Steps, fn) || HasIndependentStepLike(spec.After, fn) -} - -func HasStepLike(steps []testworkflowsv1.Step, fn func(step testworkflowsv1.Step) bool) bool { - for _, step := range steps { - if fn(step) || HasStepLike(step.Setup, fn) || HasStepLike(step.Steps, fn) { - return true - } - } - return false -} - -func HasIndependentStepLike(steps []testworkflowsv1.IndependentStep, fn func(step testworkflowsv1.IndependentStep) bool) bool { - for _, step := range steps { - if fn(step) || HasIndependentStepLike(step.Setup, fn) || HasIndependentStepLike(step.Steps, fn) { - return true - } - } - return false -} - -// HasArtifacts checks if the test workflow steps have artifacts -func HasArtifacts(step testworkflowsv1.Step) bool { - return step.Artifacts != nil -} - -// HasTemplateArtifacts checks if the test workflow steps have artifacts -func HasTemplateArtifacts(step testworkflowsv1.IndependentStep) bool { - return step.Artifacts != nil -} - -// HasKubeshopGitURI checks if the test workflow spec has a git URI that contains "kubeshop" -func HasKubeshopGitURI(step testworkflowsv1.Step) bool { - return IsKubeshopGitURI(step.Content) -} - -// HasTemplateKubeshopGitURI checks if the test workflow spec has a git URI that contains "kubeshop" -func HasTemplateKubeshopGitURI(step testworkflowsv1.IndependentStep) bool { - return IsKubeshopGitURI(step.Content) -} - -// IsKubeshopGitURI checks if the content has a git URI that contains "kubeshop" -func IsKubeshopGitURI(content *testworkflowsv1.Content) bool { - return content != nil && content.Git != nil && strings.Contains(content.Git.Uri, "kubeshop") -} - -// GetDataSource returns the data source of the content -func GetDataSource(content *testworkflowsv1.Content) string { - if content != nil { - if content.Git != nil { - return "git" - } else if len(content.Files) != 0 { - return "files" - } - } - return "" -} - -// GetHostname returns the hostname -func GetHostname() string { - host, err := os.Hostname() - if err != nil { - log.DefaultLogger.Debugf("getting hostname error", "hostname", host, "error", err) - return "" - } - return host -} - -// GetClusterID returns the cluster id -func GetClusterID(ctx context.Context, configMap configRepo.Repository) string { - clusterID, err := configMap.GetUniqueClusterId(ctx) - if err != nil { - log.DefaultLogger.Debugf("getting cluster id error", "error", err) - return "" - } - return clusterID -} - // CountMapBytes returns the total bytes of the map func CountMapBytes(m map[string]string) int { totalBytes := 0 diff --git a/pkg/triggers/executor.go b/pkg/triggers/executor.go index ebb2ed222a9..9c16d5bb37f 100644 --- a/pkg/triggers/executor.go +++ b/pkg/triggers/executor.go @@ -2,6 +2,7 @@ package triggers import ( "context" + "fmt" "regexp" "time" @@ -12,6 +13,12 @@ import ( testsuitesv3 "github.com/kubeshop/testkube-operator/api/testsuite/v3" testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1" testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" + "github.com/kubeshop/testkube/internal/common" + "github.com/kubeshop/testkube/pkg/cloud" + "github.com/kubeshop/testkube/pkg/log" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" + "github.com/kubeshop/testkube/pkg/testworkflows/testworkflowexecutor" "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/scheduler" @@ -134,52 +141,44 @@ func (s *Service) execute(ctx context.Context, e *watcherEvent, t *testtriggersv return err } - request := testkube.TestWorkflowExecutionRequest{ - Config: make(map[string]string, len(variables)), + request := &cloud.ScheduleRequest{ + Executions: common.MapSlice(testWorkflows, func(w testworkflowsv1.TestWorkflow) *cloud.ScheduleExecution { + execution := &cloud.ScheduleExecution{ + Selector: &cloud.ScheduleResourceSelector{Name: w.Name}, + Config: make(map[string]string, len(variables)), + } + for _, variable := range variables { + execution.Config[variable.Name] = variable.Value + } + return execution + }), } // 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 + request.RunningContext, _ = testworkflowexecutor.GetNewRunningContext(triggerstcl.GetRunningContext(t.Name), nil) } - wp := workerpool.New[testworkflowsv1.TestWorkflow, testkube.TestWorkflowExecutionRequest, testkube.TestWorkflowExecution](concurrencyLevel) - go func() { - isDelayDefined := t.Spec.Delay != nil - if isDelayDefined { - s.logger.Infof( - "trigger service: executor component: trigger %s/%s has delayed testworkflow execution configured for %f seconds", - t.Namespace, t.Name, t.Spec.Delay.Seconds(), - ) - time.Sleep(t.Spec.Delay.Duration) - } + isDelayDefined := t.Spec.Delay != nil + if isDelayDefined { s.logger.Infof( - "trigger service: executor component: scheduling testworkflow executions for trigger %s/%s", - t.Namespace, t.Name, + "trigger service: executor component: trigger %s/%s has delayed testworkflow execution configured for %f seconds", + t.Namespace, t.Name, t.Spec.Delay.Seconds(), ) - - requests := make([]workerpool.Request[testworkflowsv1.TestWorkflow, testkube.TestWorkflowExecutionRequest, - testkube.TestWorkflowExecution], len(testWorkflows)) - for i := range testWorkflows { - requests[i] = workerpool.Request[testworkflowsv1.TestWorkflow, testkube.TestWorkflowExecutionRequest, - testkube.TestWorkflowExecution]{ - Object: testWorkflows[i], - Options: request, - // Pro edition only (tcl protected code) - ExecFn: s.testWorkflowExecutor.Execute, - } - } - - go wp.SendRequests(requests) - go wp.Run(ctx) - }() - - for r := range wp.GetResponses() { - status.addTestWorkflowExecutionID(r.Result.Id) + time.Sleep(t.Spec.Delay.Duration) + } + s.logger.Infof( + "trigger service: executor component: scheduling testworkflow executions for trigger %s/%s", + t.Namespace, t.Name, + ) + + resp := s.testWorkflowExecutor.Execute(ctx, request) + for exec := range resp.Channel() { + status.addTestWorkflowExecutionID(exec.Id) + } + if resp.Error() != nil { + log.DefaultLogger.Errorw(fmt.Sprintf("trigger service: error executing testworkflow for trigger %s/%s", t.Namespace, t.Name), "error", resp.Error()) + return nil } default: @@ -284,20 +283,27 @@ func (s *Service) getTestSuites(t *testtriggersv1.TestTrigger) ([]testsuitesv3.T return testSuites, nil } +func (s *Service) getEnvironmentId() string { + if s.proContext != nil { + return s.proContext.EnvID + } + return "" +} + func (s *Service) getTestWorkflows(t *testtriggersv1.TestTrigger) ([]testworkflowsv1.TestWorkflow, error) { var testWorkflows []testworkflowsv1.TestWorkflow if t.Spec.TestSelector.Name != "" { s.logger.Debugf("trigger service: executor component: fetching testworkflowsv3.TestWorkflow with name %s", t.Spec.TestSelector.Name) - testWorkflow, err := s.testWorkflowsClient.Get(t.Spec.TestSelector.Name) + testWorkflow, err := s.testWorkflowsClient.Get(context.Background(), s.getEnvironmentId(), t.Spec.TestSelector.Name) if err != nil { return nil, err } - testWorkflows = append(testWorkflows, *testWorkflow) + testWorkflows = append(testWorkflows, *testworkflows.MapAPIToKube(testWorkflow)) } if t.Spec.TestSelector.NameRegex != "" { s.logger.Debugf("trigger service: executor component: fetching testworkflosv1.TestWorkflow with name regex %s", t.Spec.TestSelector.NameRegex) - testWorkflowsList, err := s.testWorkflowsClient.List("") + testWorkflowsList, err := s.testWorkflowsClient.List(context.Background(), s.getEnvironmentId(), testworkflowclient.ListOptions{}) if err != nil { return nil, err } @@ -307,25 +313,27 @@ func (s *Service) getTestWorkflows(t *testtriggersv1.TestTrigger) ([]testworkflo return nil, err } - for i := range testWorkflowsList.Items { - if re.MatchString(testWorkflowsList.Items[i].Name) { - testWorkflows = append(testWorkflows, testWorkflowsList.Items[i]) + for i := range testWorkflowsList { + if re.MatchString(testWorkflowsList[i].Name) { + testWorkflows = append(testWorkflows, *testworkflows.MapAPIToKube(&testWorkflowsList[i])) } } } if t.Spec.TestSelector.LabelSelector != nil { - selector, err := metav1.LabelSelectorAsSelector(t.Spec.TestSelector.LabelSelector) - if err != nil { - return nil, errors.WithMessagef(err, "error creating selector from test resource label selector") + if len(t.Spec.TestSelector.LabelSelector.MatchExpressions) > 0 { + return nil, errors.New("error creating selector from test resource label selector: MatchExpressions not supported") } - stringifiedSelector := selector.String() - s.logger.Debugf("trigger service: executor component: fetching testworkflowsv1.TestWorkflow with label %s", stringifiedSelector) - testWorkflowsList, err := s.testWorkflowsClient.List(stringifiedSelector) + s.logger.Debugf("trigger service: executor component: fetching testworkflowsv1.TestWorkflow with label %s", t.Spec.TestSelector.LabelSelector.MatchLabels) + testWorkflowsList, err := s.testWorkflowsClient.List(context.Background(), s.getEnvironmentId(), testworkflowclient.ListOptions{ + Labels: t.Spec.TestSelector.LabelSelector.MatchLabels, + }) if err != nil { return nil, err } - testWorkflows = append(testWorkflows, testWorkflowsList.Items...) + for i := range testWorkflowsList { + testWorkflows = append(testWorkflows, *testworkflows.MapAPIToKube(&testWorkflowsList[i])) + } } return testWorkflows, nil } diff --git a/pkg/triggers/executor_test.go b/pkg/triggers/executor_test.go index 9c1e7fda26c..51bec7d22d9 100644 --- a/pkg/triggers/executor_test.go +++ b/pkg/triggers/executor_test.go @@ -11,17 +11,16 @@ import ( v1 "github.com/kubeshop/testkube-operator/api/executor/v1" testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3" testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1" - testworkflowsv1 "github.com/kubeshop/testkube-operator/api/testworkflows/v1" executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1" testsclientv3 "github.com/kubeshop/testkube-operator/pkg/client/tests/v3" testsourcesv1 "github.com/kubeshop/testkube-operator/pkg/client/testsources/v1" testsuiteexecutionsv1 "github.com/kubeshop/testkube-operator/pkg/client/testsuiteexecutions/v1" testsuitesv3 "github.com/kubeshop/testkube-operator/pkg/client/testsuites/v3" - testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" "github.com/kubeshop/testkube/cmd/api-server/commons" "github.com/kubeshop/testkube/cmd/api-server/services" "github.com/kubeshop/testkube/internal/app/api/metrics" "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/cloud" "github.com/kubeshop/testkube/pkg/configmap" "github.com/kubeshop/testkube/pkg/event" "github.com/kubeshop/testkube/pkg/event/bus" @@ -29,6 +28,7 @@ import ( "github.com/kubeshop/testkube/pkg/featureflags" "github.com/kubeshop/testkube/pkg/log" logsclient "github.com/kubeshop/testkube/pkg/logs/client" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" "github.com/kubeshop/testkube/pkg/repository/config" "github.com/kubeshop/testkube/pkg/repository/result" "github.com/kubeshop/testkube/pkg/repository/testresult" @@ -201,21 +201,26 @@ func TestWorkflowExecute(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - mockTestWorkflowsClient := testworkflowsclientv1.NewMockInterface(mockCtrl) + mockTestWorkflowsClient := testworkflowclient.NewMockTestWorkflowClient(mockCtrl) mockTestWorkflowExecutor := testworkflowexecutor.NewMockTestWorkflowExecutor(mockCtrl) - mockTestWorkflow := testworkflowsv1.TestWorkflow{ObjectMeta: metav1.ObjectMeta{Namespace: "testkube", Name: "some-test"}} - mockTestWorkflowsClient.EXPECT().Get("some-test").Return(&mockTestWorkflow, nil).AnyTimes() - mockTestWorkflowExecutionRequest := testkube.TestWorkflowExecutionRequest{ - Config: map[string]string{ - "WATCHER_EVENT_EVENT_TYPE": "", - "WATCHER_EVENT_NAME": "", - "WATCHER_EVENT_NAMESPACE": "", - "WATCHER_EVENT_RESOURCE": "", + mockTestWorkflow := testkube.TestWorkflow{Namespace: "testkube", Name: "some-test"} + mockTestWorkflowsClient.EXPECT().Get(gomock.Any(), gomock.Any(), "some-test").Return(&mockTestWorkflow, nil).AnyTimes() + mockTestWorkflowExecutionRequest := &cloud.ScheduleRequest{ + Executions: []*cloud.ScheduleExecution{ + {Selector: &cloud.ScheduleResourceSelector{Name: mockTestWorkflow.Name}, Config: map[string]string{ + "WATCHER_EVENT_EVENT_TYPE": "", + "WATCHER_EVENT_NAME": "", + "WATCHER_EVENT_NAMESPACE": "", + "WATCHER_EVENT_RESOURCE": "", + }}, }, } - mockTestWorkflowExecution := testkube.TestWorkflowExecution{} - mockTestWorkflowExecutor.EXPECT().Execute(gomock.Any(), mockTestWorkflow, mockTestWorkflowExecutionRequest).Return(mockTestWorkflowExecution, nil) + executionsCh := make(chan *testkube.TestWorkflowExecution, 1) + executionsCh <- &testkube.TestWorkflowExecution{} + close(executionsCh) + executionsStream := testworkflowexecutor.NewStream(executionsCh) + mockTestWorkflowExecutor.EXPECT().Execute(gomock.Any(), mockTestWorkflowExecutionRequest).Return(executionsStream) s := &Service{ triggerStatus: make(map[statusKey]*triggerStatus), diff --git a/pkg/triggers/service.go b/pkg/triggers/service.go index b049ff8de37..fa976bdb822 100644 --- a/pkg/triggers/service.go +++ b/pkg/triggers/service.go @@ -14,7 +14,6 @@ import ( testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3" testsuitev3 "github.com/kubeshop/testkube-operator/api/testsuite/v3" testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1" - testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" 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" @@ -22,6 +21,7 @@ import ( "github.com/kubeshop/testkube/pkg/api/v1/testkube" "github.com/kubeshop/testkube/pkg/event/bus" "github.com/kubeshop/testkube/pkg/http" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" "github.com/kubeshop/testkube/pkg/repository/config" "github.com/kubeshop/testkube/pkg/repository/testworkflow" "github.com/kubeshop/testkube/pkg/telemetry" @@ -60,7 +60,7 @@ type Service struct { triggerStatus map[statusKey]*triggerStatus clientset kubernetes.Interface testKubeClientset testkubeclientsetv1.Interface - testWorkflowsClient testworkflowsclientv1.Interface + testWorkflowsClient testworkflowclient.TestWorkflowClient logger *zap.SugaredLogger configMap config.Repository httpClient http.HttpClient @@ -82,7 +82,7 @@ func NewService( deprecatedSystem *services.DeprecatedSystem, clientset kubernetes.Interface, testKubeClientset testkubeclientsetv1.Interface, - testWorkflowsClient testworkflowsclientv1.Interface, + testWorkflowsClient testworkflowclient.TestWorkflowClient, leaseBackend LeaseBackend, logger *zap.SugaredLogger, configMap config.Repository, diff --git a/pkg/triggers/service_test.go b/pkg/triggers/service_test.go index d3037e07a67..d93f0a40291 100644 --- a/pkg/triggers/service_test.go +++ b/pkg/triggers/service_test.go @@ -19,7 +19,6 @@ import ( testsourcesv1 "github.com/kubeshop/testkube-operator/pkg/client/testsources/v1" testsuiteexecutionsv1 "github.com/kubeshop/testkube-operator/pkg/client/testsuiteexecutions/v1" testsuitesv3 "github.com/kubeshop/testkube-operator/pkg/client/testsuites/v3" - testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" faketestkube "github.com/kubeshop/testkube-operator/pkg/clientset/versioned/fake" "github.com/kubeshop/testkube/cmd/api-server/commons" "github.com/kubeshop/testkube/cmd/api-server/services" @@ -32,6 +31,7 @@ import ( "github.com/kubeshop/testkube/pkg/featureflags" "github.com/kubeshop/testkube/pkg/log" logsclient "github.com/kubeshop/testkube/pkg/logs/client" + "github.com/kubeshop/testkube/pkg/newclients/testworkflowclient" "github.com/kubeshop/testkube/pkg/repository/config" "github.com/kubeshop/testkube/pkg/repository/result" "github.com/kubeshop/testkube/pkg/repository/testresult" @@ -65,7 +65,7 @@ func TestService_Run(t *testing.T) { configMapConfig := config.NewMockRepository(mockCtrl) mockConfigMapClient := configmap.NewMockInterface(mockCtrl) mockTestSuiteExecutionsClient := testsuiteexecutionsv1.NewMockInterface(mockCtrl) - mockTestWorkflowsClient := testworkflowsclientv1.NewMockInterface(mockCtrl) + mockTestWorkflowsClient := testworkflowclient.NewMockTestWorkflowClient(mockCtrl) mockTestWorkflowExecutor := testworkflowexecutor.NewMockTestWorkflowExecutor(mockCtrl) mockTestWorkflowRepository := testworkflow.NewMockRepository(mockCtrl) mockExecutionWorkerClient := executionworkertypes.NewMockWorker(mockCtrl) diff --git a/proto/service.proto b/proto/service.proto index b9ca13dafd1..dc0a79ede1b 100644 --- a/proto/service.proto +++ b/proto/service.proto @@ -17,9 +17,33 @@ service TestKubeCloudAPI { rpc GetLogsStream(stream LogsStreamResponse) returns (stream LogsStreamRequest); rpc GetTestWorkflowNotificationsStream(stream TestWorkflowNotificationsResponse) returns (stream TestWorkflowNotificationsRequest); rpc GetTestWorkflowServiceNotificationsStream(stream TestWorkflowServiceNotificationsResponse) returns (stream TestWorkflowServiceNotificationsRequest); - rpc GetTestWorkflowParallelStepNotificationsStream(stream TestWorkflowParallelStepNotificationsResponse) returns (stream TestWorkflowParallelStepNotificationsRequest); + rpc GetTestWorkflowParallelStepNotificationsStream(stream TestWorkflowParallelStepNotificationsResponse) returns (stream TestWorkflowParallelStepNotificationsRequest); rpc GetProContext(google.protobuf.Empty) returns (ProContextResponse); rpc GetCredential(CredentialRequest) returns (CredentialResponse); + rpc GetEventStream(EventStreamRequest) returns (stream Event); // TODO: Healthcheck (?) + rpc ScheduleExecution(ScheduleRequest) returns (stream ScheduleResponse); + + // Runner + rpc GetRunnerRequests(stream RunnerResponse) returns (stream RunnerRequest); // TODO: Healthcheck (?) + rpc ObtainExecution(ObtainExecutionRequest) returns (ObtainExecutionResponse); + + // CRD Synchronisation + // -- Test Workflows + rpc GetTestWorkflow(GetTestWorkflowRequest) returns (GetTestWorkflowResponse); + rpc ListTestWorkflows(ListTestWorkflowsRequest) returns (stream TestWorkflowListItem); + rpc ListTestWorkflowLabels(ListTestWorkflowLabelsRequest) returns (ListTestWorkflowLabelsResponse); + rpc CreateTestWorkflow(CreateTestWorkflowRequest) returns (CreateTestWorkflowResponse); + rpc UpdateTestWorkflow(UpdateTestWorkflowRequest) returns (UpdateTestWorkflowResponse); + rpc DeleteTestWorkflow(DeleteTestWorkflowRequest) returns (DeleteTestWorkflowResponse); + rpc DeleteTestWorkflowsByLabels(DeleteTestWorkflowsByLabelsRequest) returns (DeleteTestWorkflowsByLabelsResponse); + // -- Test Workflow Templates + rpc GetTestWorkflowTemplate(GetTestWorkflowTemplateRequest) returns (GetTestWorkflowTemplateResponse); + rpc ListTestWorkflowTemplates(ListTestWorkflowTemplatesRequest) returns (stream TestWorkflowTemplateListItem); + rpc ListTestWorkflowTemplateLabels(ListTestWorkflowTemplateLabelsRequest) returns (ListTestWorkflowTemplateLabelsResponse); + rpc CreateTestWorkflowTemplate(CreateTestWorkflowTemplateRequest) returns (CreateTestWorkflowTemplateResponse); + rpc UpdateTestWorkflowTemplate(UpdateTestWorkflowTemplateRequest) returns (UpdateTestWorkflowTemplateResponse); + rpc DeleteTestWorkflowTemplate(DeleteTestWorkflowTemplateRequest) returns (DeleteTestWorkflowTemplateResponse); + rpc DeleteTestWorkflowTemplatesByLabels(DeleteTestWorkflowTemplatesByLabelsRequest) returns (DeleteTestWorkflowTemplatesByLabelsResponse); } enum LogsStreamRequestType { @@ -131,7 +155,7 @@ message TestWorkflowServiceNotificationsRequest { string stream_id = 1; string execution_id = 2; string service_name = 3; - int32 service_index = 4; + int32 service_index = 4; TestWorkflowNotificationsRequestType request_type = 5; } @@ -148,7 +172,7 @@ message TestWorkflowParallelStepNotificationsRequest { string stream_id = 1; string execution_id = 2; string ref = 3; - int32 worker_index = 4; + int32 worker_index = 4; TestWorkflowNotificationsRequestType request_type = 5; } @@ -160,3 +184,227 @@ message TestWorkflowParallelStepNotificationsResponse { TestWorkflowNotificationType type = 5; string message = 6; // based on type: log/error = inline, others = serialized to JSON } + +message ScheduleResourceSelector { + string name = 1; + map labels = 2; +} + +message ScheduleExecution { + ScheduleResourceSelector selector = 1; + map config = 2; + string execution_name = 3; + map tags = 4; +} + +enum RunningContextType { + UNKNOWN = 0; + UI = 1; + CLI = 2; + CICD = 3; + CRON = 4; + TESTTRIGGER = 5; + KUBERNETESOBJECT = 6; + EXECUTION = 7; +} + +message RunningContext { + string name = 1; + RunningContextType type = 2; +} + +message UserSignature { + string name = 1; + string email = 2; +} + +message ScheduleRequest { + // Target + string environment_id = 1; + + // Test Workflow details + repeated ScheduleExecution executions = 2; + + // Execution details + bool disable_webhooks = 3; + map tags = 4; + + // Running metadata + RunningContext running_context = 5; + repeated string parent_execution_ids = 7; + optional UserSignature user = 8; // keep in mind that it should not be trusted + + // Kubernetes resource TODO: is it required? + string kubernetes_object_name = 9; +} + +message ScheduleResponse { + bytes execution = 1; // TestWorkflowExecution +} + +message EventResource { + string type = 1; + string id = 2; +} + +message EventStreamRequest { + string environment_id = 1; + repeated EventResource accept = 2; +} + +message Event { + string id = 1; + EventResource resource = 3; + string type = 4; + bytes data = 5; +} + +message RunnerRequest { + string environment_id = 1; + string id = 2; +} + +message RunnerResponse { + string environment_id = 1; + string id = 2; + bool obtained = 3; + optional string error = 4; +} + +message ObtainExecutionRequest { + string environment_id = 1; + string id = 2; +} + +message ObtainExecutionResponse { + string environment_id = 1; + string id = 2; + bool success = 3; +} + +// CRD Synchronisation -- Test Workflows + +message GetTestWorkflowRequest { + string environment_id = 1; + string name = 2; +} + +message GetTestWorkflowResponse { + bytes workflow = 1; +} + +message ListTestWorkflowsRequest { + string environment_id = 1; + uint32 offset = 2; + uint32 limit = 3; + map labels = 4; + string textSearch = 5; +} + +message TestWorkflowListItem { + bytes workflow = 1; +} + +message LabelListItem { + string name = 1; + repeated string value = 2; +} + +message ListTestWorkflowLabelsRequest { + string environment_id = 1; +} + +message ListTestWorkflowLabelsResponse { + repeated LabelListItem labels = 1; +} + +message CreateTestWorkflowRequest { + string environment_id = 1; + bytes workflow = 2; +} + +message CreateTestWorkflowResponse {} + +message UpdateTestWorkflowRequest { + string environment_id = 1; + bytes workflow = 2; +} + +message UpdateTestWorkflowResponse {} + +message DeleteTestWorkflowRequest { + string environment_id = 1; + string name = 2; +} + +message DeleteTestWorkflowResponse {} + +message DeleteTestWorkflowsByLabelsRequest { + string environment_id = 1; + map labels = 2; +} + +message DeleteTestWorkflowsByLabelsResponse { + uint32 count = 1; +} + +// CRD Synchronisation -- Test Workflow Templates + +message GetTestWorkflowTemplateRequest { + string environment_id = 1; + string name = 2; +} + +message GetTestWorkflowTemplateResponse { + bytes template = 1; +} + +message ListTestWorkflowTemplatesRequest { + string environment_id = 1; + uint32 offset = 2; + uint32 limit = 3; + map labels = 4; + string textSearch = 5; +} + +message TestWorkflowTemplateListItem { + bytes template = 1; +} + +message ListTestWorkflowTemplateLabelsRequest { + string environment_id = 1; +} + +message ListTestWorkflowTemplateLabelsResponse { + repeated LabelListItem labels = 1; +} + +message CreateTestWorkflowTemplateRequest { + string environment_id = 1; + bytes template = 2; +} + +message CreateTestWorkflowTemplateResponse {} + +message UpdateTestWorkflowTemplateRequest { + string environment_id = 1; + bytes template = 2; +} + +message UpdateTestWorkflowTemplateResponse {} + +message DeleteTestWorkflowTemplateRequest { + string environment_id = 1; + string name = 2; +} + +message DeleteTestWorkflowTemplateResponse {} + +message DeleteTestWorkflowTemplatesByLabelsRequest { + string environment_id = 1; + map labels = 2; +} + +message DeleteTestWorkflowTemplatesByLabelsResponse { + uint32 count = 1; +}