From 21bf1e113b769992bc4aa883ad3a77cf6455238b Mon Sep 17 00:00:00 2001
From: Povilas Versockas
Date: Tue, 8 Oct 2024 00:12:42 -0700
Subject: [PATCH] feat: [TKC-2581] add testworkflow / template cloud client
(#5882)
---
cmd/api-server/main.go | 11 ++-
internal/app/api/v1/server.go | 4 +-
internal/config/config.go | 1 +
pkg/agent/agent.go | 3 +-
pkg/cloud/data/testworkflow/commands.go | 8 ++
.../data/testworkflow/execution_models.go | 24 ++++++
pkg/cloud/data/testworkflow/templates.go | 77 +++++++++++++++++++
pkg/cloud/data/testworkflow/workflows.go | 77 +++++++++++++++++++
8 files changed, 199 insertions(+), 6 deletions(-)
create mode 100644 pkg/cloud/data/testworkflow/templates.go
create mode 100644 pkg/cloud/data/testworkflow/workflows.go
diff --git a/cmd/api-server/main.go b/cmd/api-server/main.go
index 16d1c51baf7..8fa37a246db 100644
--- a/cmd/api-server/main.go
+++ b/cmd/api-server/main.go
@@ -235,8 +235,10 @@ func main() {
testsourcesClient := testsourcesclientv1.NewClient(kubeClient, cfg.TestkubeNamespace)
testExecutionsClient := testexecutionsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace)
testsuiteExecutionsClient := testsuiteexecutionsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace)
- testWorkflowsClient := testworkflowsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace)
- testWorkflowTemplatesClient := testworkflowsclientv1.NewTestWorkflowTemplatesClient(kubeClient, cfg.TestkubeNamespace)
+ var testWorkflowsClient testworkflowsclientv1.Interface
+ testWorkflowsClient = testworkflowsclientv1.NewClient(kubeClient, cfg.TestkubeNamespace)
+ var testWorkflowTemplatesClient testworkflowsclientv1.TestWorkflowTemplatesInterface
+ testWorkflowTemplatesClient = testworkflowsclientv1.NewTestWorkflowTemplatesClient(kubeClient, cfg.TestkubeNamespace)
testWorkflowExecutionsClient := testworkflowsclientv1.NewTestWorkflowExecutionsClient(kubeClient, cfg.TestkubeNamespace)
templatesClient := templatesclientv1.NewClient(kubeClient, cfg.TestkubeNamespace)
@@ -274,6 +276,11 @@ func main() {
resultsRepository = cloudresult.NewCloudResultRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey)
testResultsRepository = cloudtestresult.NewCloudRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey)
configRepository = cloudconfig.NewCloudResultRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey)
+
+ if cfg.WorkflowStorage == "control-plane" {
+ testWorkflowsClient = cloudtestworkflow.NewCloudTestWorkflowRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey)
+ testWorkflowTemplatesClient = cloudtestworkflow.NewCloudTestWorkflowTemplateRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey)
+ }
// Pro edition only (tcl protected code)
testWorkflowResultsRepository = cloudtestworkflow.NewCloudRepository(grpcClient, grpcConn, cfg.TestkubeProAPIKey)
var opts []cloudtestworkflow.Option
diff --git a/internal/app/api/v1/server.go b/internal/app/api/v1/server.go
index 688a6a0ab78..9f52d2b0646 100644
--- a/internal/app/api/v1/server.go
+++ b/internal/app/api/v1/server.go
@@ -82,8 +82,8 @@ func NewTestkubeAPI(
clientset kubernetes.Interface,
testkubeClientset testkubeclientset.Interface,
testsourcesClient *testsourcesclientv1.TestSourcesClient,
- testWorkflowsClient *testworkflowsv1.TestWorkflowsClient,
- testWorkflowTemplatesClient *testworkflowsv1.TestWorkflowTemplatesClient,
+ testWorkflowsClient testworkflowsv1.Interface,
+ testWorkflowTemplatesClient testworkflowsv1.TestWorkflowTemplatesInterface,
configMap repoConfig.Repository,
clusterId string,
eventsEmitter *event.Emitter,
diff --git a/internal/config/config.go b/internal/config/config.go
index 7f48a517350..01772cf1313 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -37,6 +37,7 @@ 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"`
diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go
index 9a15fcea8a0..5a8fe3d68c6 100644
--- a/pkg/agent/agent.go
+++ b/pkg/agent/agent.go
@@ -9,14 +9,13 @@ import (
"os"
"time"
- "google.golang.org/grpc/keepalive"
-
"github.com/kubeshop/testkube/pkg/executor/output"
"github.com/kubeshop/testkube/pkg/version"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
+ "google.golang.org/grpc/keepalive"
"github.com/pkg/errors"
"github.com/valyala/fasthttp"
diff --git a/pkg/cloud/data/testworkflow/commands.go b/pkg/cloud/data/testworkflow/commands.go
index 611e7dcf750..f75f35bf691 100644
--- a/pkg/cloud/data/testworkflow/commands.go
+++ b/pkg/cloud/data/testworkflow/commands.go
@@ -29,6 +29,9 @@ const (
CmdTestWorkflowOutputHasLog executor.Command = "workflow_output_has_log"
CmdTestWorkflowOutputDeleteByTestWorkflow executor.Command = "workflow_output_delete_by_test_workflow"
CmdTestworkflowOutputDeleteForTestWorkflows executor.Command = "workflow_output_delete_for_test_workflows"
+
+ CmdTestWorkflowGet executor.Command = "workflow_get"
+ CmdTestWorkflowTemplateGet executor.Command = "workflow_template_get"
)
func command(v interface{}) executor.Command {
@@ -82,6 +85,11 @@ func command(v interface{}) executor.Command {
return CmdTestWorkflowOutputDeleteByTestWorkflow
case ExecutionDeleteOutputForTestWorkflowsRequest:
return CmdTestworkflowOutputDeleteForTestWorkflows
+
+ case TestWorkflowGetRequest:
+ return CmdTestWorkflowGet
+ case TestWorkflowTemplateGetRequest:
+ return CmdTestWorkflowTemplateGet
}
panic("unknown test workflows Cloud request")
}
diff --git a/pkg/cloud/data/testworkflow/execution_models.go b/pkg/cloud/data/testworkflow/execution_models.go
index 38f04323033..58f170465d3 100644
--- a/pkg/cloud/data/testworkflow/execution_models.go
+++ b/pkg/cloud/data/testworkflow/execution_models.go
@@ -186,3 +186,27 @@ type ExecutionGetExecutionTagsRequest struct {
type ExecutionGetExecutionTagsResponse struct {
Tags map[string][]string `json:"tags"`
}
+
+type TestWorkflowListRequest struct {
+ Selector string `json:"selector"`
+}
+
+type TestWorkflowListResponse struct {
+ TestWorkflows []testkube.TestWorkflow `json:"testWorkflows"`
+}
+
+type TestWorkflowGetRequest struct {
+ Name string `json:"name"`
+}
+
+type TestWorkflowGetResponse struct {
+ TestWorkflow testkube.TestWorkflow `json:"testWorkflow"`
+}
+
+type TestWorkflowTemplateGetRequest struct {
+ Name string `json:"name"`
+}
+
+type TestWorkflowTemplateGetResponse struct {
+ TestWorkflowTemplate testkube.TestWorkflowTemplate `json:"testWorkflowTemplate"`
+}
diff --git a/pkg/cloud/data/testworkflow/templates.go b/pkg/cloud/data/testworkflow/templates.go
new file mode 100644
index 00000000000..cfdae086d46
--- /dev/null
+++ b/pkg/cloud/data/testworkflow/templates.go
@@ -0,0 +1,77 @@
+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
new file mode 100644
index 00000000000..c04474b2c5d
--- /dev/null
+++ b/pkg/cloud/data/testworkflow/workflows.go
@@ -0,0 +1,77 @@
+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
+}