diff --git a/cmd/api-server/services/controlplane.go b/cmd/api-server/services/controlplane.go index 933d39f931..269fec688e 100644 --- a/cmd/api-server/services/controlplane.go +++ b/cmd/api-server/services/controlplane.go @@ -372,7 +372,7 @@ func CreateControlPlane(ctx context.Context, cfg *config.Config, features featur } } if cfg.LogsBucket != "" { - exists, err := storageClient.BucketExists(ctx, cfg.StorageBucket) + exists, err := storageClient.BucketExists(ctx, cfg.LogsBucket) if err != nil { log.DefaultLogger.Errorw("Failed to check if the storage bucket exists", "error", err) } else if !exists { diff --git a/cmd/tcl/kubectl-testkube/devbox/README.md b/cmd/tcl/kubectl-testkube/devbox/README.md index b002349818..75d7556f18 100644 --- a/cmd/tcl/kubectl-testkube/devbox/README.md +++ b/cmd/tcl/kubectl-testkube/devbox/README.md @@ -21,6 +21,7 @@ This utility is used to help with development of the Agent features (like Test W * For local development Testkube Enterprise (Skaffold), consider `testkube login --api-uri-override=http://localhost:8099 --agent-uri-override=http://testkube-enterprise-api.tk-dev.svc.local:8089 --auth-uri-override=http://localhost:5556 --custom-auth` * It's worth to create alias for that in own `.bashrc` or `.bash_profile` * It's worth to pass a devbox name, like `-n dawid`, so it's not using random name +* For OSS version - run with `--oss` parameter The CLI will print a dashboard link for the selected environment. @@ -44,12 +45,13 @@ Aliases: devbox, dev Flags: - --agent-image string base agent image (default "kubeshop/testkube-api-server:latest") - --init-image string base init image (default "kubeshop/testkube-tw-init:latest") -n, --name string devbox name (default "1730107481990508000") -s, --sync strings synchronise resources at paths - --toolkit-image string base toolkit image (default "kubeshop/testkube-tw-toolkit:latest") -o, --open open dashboard in browser + -O, --oss run open source version + --agent-image string base agent image (default "kubeshop/testkube-api-server:latest") + --init-image string base init image (default "kubeshop/testkube-tw-init:latest") + --toolkit-image string base toolkit image (default "kubeshop/testkube-tw-toolkit:latest") ``` ## Example diff --git a/cmd/tcl/kubectl-testkube/devbox/command.go b/cmd/tcl/kubectl-testkube/devbox/command.go index b32f9aa542..80c19e06e1 100644 --- a/cmd/tcl/kubectl-testkube/devbox/command.go +++ b/cmd/tcl/kubectl-testkube/devbox/command.go @@ -45,6 +45,7 @@ const ( func NewDevBoxCommand() *cobra.Command { var ( + oss bool rawDevboxName string open bool baseAgentImage string @@ -74,6 +75,8 @@ func NewDevBoxCommand() *cobra.Command { ui.Fail(errors.New("testkube repository not found")) } + var err error + // Connect to cluster cluster, err := devutils.NewCluster() if err != nil { @@ -86,26 +89,30 @@ func NewDevBoxCommand() *cobra.Command { pterm.Error.Printfln("Failed to load config file: %s", err.Error()) return } - cfg.CloudContext.AgentUri = "https://agent-dev.testkube.dev" - cloud, err := devutils.NewCloud(cfg.CloudContext, cmd) - if err != nil { - pterm.Error.Printfln("Failed to connect to Cloud: %s", err.Error()) - return + var cloud *devutils.CloudObject + if !oss { + cloud, err = devutils.NewCloud(cfg.CloudContext, cmd) + if err != nil { + pterm.Error.Printfln("Failed to connect to Cloud: %s", err.Error()) + return + } } // Detect obsolete devbox environments - if obsolete := cloud.ListObsolete(); len(obsolete) > 0 { - count := 0 - for _, env := range obsolete { - err := cloud.DeleteEnvironment(env.Id) - if err != nil { - fmt.Printf("Failed to delete obsolete devbox environment (%s): %s\n", env.Name, err.Error()) - continue + if !oss { + if obsolete := cloud.ListObsolete(); len(obsolete) > 0 { + count := 0 + for _, env := range obsolete { + err := cloud.DeleteEnvironment(env.Id) + if err != nil { + fmt.Printf("Failed to delete obsolete devbox environment (%s): %s\n", env.Name, err.Error()) + continue + } + cluster.Namespace(env.Name).Destroy() + count++ } - cluster.Namespace(env.Name).Destroy() - count++ + fmt.Printf("Deleted %d/%d obsolete devbox environments\n", count, len(obsolete)) } - fmt.Printf("Deleted %d/%d obsolete devbox environments\n", count, len(obsolete)) } // Initialize bare cluster resources @@ -113,6 +120,8 @@ func NewDevBoxCommand() *cobra.Command { interceptorPod := namespace.Pod("devbox-interceptor") agentPod := namespace.Pod("devbox-agent") binaryStoragePod := namespace.Pod("devbox-binary") + mongoPod := namespace.Pod("devbox-mongodb") + minioPod := namespace.Pod("devbox-minio") // Initialize binaries interceptorBin := devutils.NewBinary(InterceptorMainPath, cluster.OperatingSystem(), cluster.Architecture()) @@ -132,6 +141,8 @@ func NewDevBoxCommand() *cobra.Command { interceptor := devutils.NewInterceptor(interceptorPod, baseInitImage, baseToolkitImage, interceptorBin) agent := devutils.NewAgent(agentPod, cloud, baseAgentImage, baseInitImage, baseToolkitImage) binaryStorage := devutils.NewBinaryStorage(binaryStoragePod, binaryStorageBin) + mongo := devutils.NewMongo(mongoPod) + minio := devutils.NewMinio(minioPod) var env *client.Environment // Cleanup @@ -170,10 +181,12 @@ func NewDevBoxCommand() *cobra.Command { } // Create environment in the Cloud - fmt.Println("Creating environment in Cloud...") - env, err = cloud.CreateEnvironment(namespace.Name()) - if err != nil { - fail(errors.Wrap(err, "failed to create Cloud environment")) + if !oss { + fmt.Println("Creating environment in Cloud...") + env, err = cloud.CreateEnvironment(namespace.Name()) + if err != nil { + fail(errors.Wrap(err, "failed to create Cloud environment")) + } } // Create namespace @@ -182,6 +195,18 @@ func NewDevBoxCommand() *cobra.Command { fail(errors.Wrap(err, "failed to create namespace")) } + // Create resources accessor + var resources devutils.ResourcesClient + if oss { + resources = devutils.NewDirectResourcesClient(cluster.KubeClient(), namespace.Name()) + } else { + client, err := cloud.Client(env.Id) + if err != nil { + fail(errors.Wrap(err, "failed to create cloud client")) + } + resources = client + } + g, _ := errgroup.WithContext(ctx) binaryStorageReadiness := make(chan struct{}) @@ -234,6 +259,36 @@ func NewDevBoxCommand() *cobra.Command { return nil }) + if oss { + // Deploying Minio + g.Go(func() error { + fmt.Println("[Minio] Deploying...") + if err = minio.Create(ctx); err != nil { + fail(errors.Wrap(err, "failed to create Minio service")) + } + fmt.Println("[Minio] Waiting for readiness...") + if err = minio.WaitForReady(ctx); err != nil { + fail(errors.Wrap(err, "failed to create Minio service")) + } + fmt.Println("[Minio] Ready") + return nil + }) + + // Deploying Mongo + g.Go(func() error { + fmt.Println("[Mongo] Deploying...") + if err = mongo.Create(ctx); err != nil { + fail(errors.Wrap(err, "failed to create Mongo service")) + } + fmt.Println("[Mongo] Waiting for readiness...") + if err = mongo.WaitForReady(ctx); err != nil { + fail(errors.Wrap(err, "failed to create Mongo service")) + } + fmt.Println("[Mongo] Ready") + return nil + }) + } + // Deploying the Agent g.Go(func() error { fmt.Println("[Agent] Building...") @@ -349,14 +404,14 @@ func NewDevBoxCommand() *cobra.Command { }() workflowLabel := func(name string) string { - if !termlink.SupportsHyperlinks() { + if !termlink.SupportsHyperlinks() || oss { return name } return name + " " + termlink.ColorLink("(open)", cloud.DashboardUrl(env.Slug, fmt.Sprintf("dashboard/test-workflows/%s", name)), "magenta") } templateLabel := func(name string) string { - if !termlink.SupportsHyperlinks() { + if !termlink.SupportsHyperlinks() || oss { return name } return name + " " + termlink.ColorLink("(open)", cloud.DashboardUrl(env.Slug, fmt.Sprintf("dashboard/test-workflow-templates/%s", name)), "magenta") @@ -369,13 +424,9 @@ func NewDevBoxCommand() *cobra.Command { parallel <- struct{}{} switch update.Op { case devutils.CRDSyncUpdateOpCreate: - client, err := cloud.Client(env.Id) - if err != nil { - fail(errors.Wrap(err, "failed to create cloud client")) - } if update.Template != nil { update.Template.Spec.Events = nil // ignore Cronjobs - _, err := client.CreateTestWorkflowTemplate(*testworkflows.MapTemplateKubeToAPI(update.Template)) + _, err := resources.CreateTestWorkflowTemplate(*testworkflows.MapTemplateKubeToAPI(update.Template)) if err != nil { fmt.Printf("CRD Sync: creating template: %s: error: %s\n", templateLabel(update.Template.Name), err.Error()) } else { @@ -383,7 +434,7 @@ func NewDevBoxCommand() *cobra.Command { } } else { update.Workflow.Spec.Events = nil // ignore Cronjobs - _, err := client.CreateTestWorkflow(*testworkflows.MapKubeToAPI(update.Workflow)) + _, err := resources.CreateTestWorkflow(*testworkflows.MapKubeToAPI(update.Workflow)) if err != nil { fmt.Printf("CRD Sync: creating workflow: %s: error: %s\n", workflowLabel(update.Workflow.Name), err.Error()) } else { @@ -391,13 +442,9 @@ func NewDevBoxCommand() *cobra.Command { } } case devutils.CRDSyncUpdateOpUpdate: - client, err := cloud.Client(env.Id) - if err != nil { - fail(errors.Wrap(err, "failed to create cloud client")) - } if update.Template != nil { update.Template.Spec.Events = nil // ignore Cronjobs - _, err := client.UpdateTestWorkflowTemplate(*testworkflows.MapTemplateKubeToAPI(update.Template)) + _, err := resources.UpdateTestWorkflowTemplate(*testworkflows.MapTemplateKubeToAPI(update.Template)) if err != nil { fmt.Printf("CRD Sync: updating template: %s: error: %s\n", templateLabel(update.Template.Name), err.Error()) } else { @@ -405,7 +452,7 @@ func NewDevBoxCommand() *cobra.Command { } } else { update.Workflow.Spec.Events = nil - _, err := client.UpdateTestWorkflow(*testworkflows.MapKubeToAPI(update.Workflow)) + _, err := resources.UpdateTestWorkflow(*testworkflows.MapKubeToAPI(update.Workflow)) if err != nil { fmt.Printf("CRD Sync: updating workflow: %s: error: %s\n", workflowLabel(update.Workflow.Name), err.Error()) } else { @@ -413,19 +460,15 @@ func NewDevBoxCommand() *cobra.Command { } } case devutils.CRDSyncUpdateOpDelete: - client, err := cloud.Client(env.Id) - if err != nil { - fail(errors.Wrap(err, "failed to create cloud client")) - } if update.Template != nil { - err := client.DeleteTestWorkflowTemplate(update.Template.Name) + err := resources.DeleteTestWorkflowTemplate(update.Template.Name) if err != nil { fmt.Printf("CRD Sync: deleting template: %s: error: %s\n", templateLabel(update.Template.Name), err.Error()) } else { fmt.Println("CRD Sync: deleted template:", templateLabel(update.Template.Name)) } } else { - err := client.DeleteTestWorkflow(update.Workflow.Name) + err := resources.DeleteTestWorkflow(update.Workflow.Name) if err != nil { fmt.Printf("CRD Sync: deleting workflow: %s: error: %s\n", workflowLabel(update.Workflow.Name), err.Error()) } else { @@ -571,10 +614,13 @@ func NewDevBoxCommand() *cobra.Command { } color.Green.Println("Development box is ready. Took", time.Since(startTs).Truncate(time.Millisecond)) - if termlink.SupportsHyperlinks() { - fmt.Println("Dashboard:", termlink.Link(cloud.DashboardUrl(env.Slug, "dashboard/test-workflows"), cloud.DashboardUrl(env.Slug, "dashboard/test-workflows"))) - } else { - fmt.Println("Dashboard:", cloud.DashboardUrl(env.Slug, "dashboard/test-workflows")) + fmt.Println("Namespace:", namespace.Name()) + if !oss { + if termlink.SupportsHyperlinks() { + fmt.Println("Dashboard:", termlink.Link(cloud.DashboardUrl(env.Slug, "dashboard/test-workflows"), cloud.DashboardUrl(env.Slug, "dashboard/test-workflows"))) + } else { + fmt.Println("Dashboard:", cloud.DashboardUrl(env.Slug, "dashboard/test-workflows")) + } } if open { openurl.Run(cloud.DashboardUrl(env.Slug, "dashboard/test-workflows")) @@ -609,6 +655,7 @@ func NewDevBoxCommand() *cobra.Command { cmd.Flags().StringVarP(&rawDevboxName, "name", "n", fmt.Sprintf("%d", time.Now().UnixNano()), "devbox name") cmd.Flags().StringSliceVarP(&syncResources, "sync", "s", nil, "synchronise resources at paths") cmd.Flags().BoolVarP(&open, "open", "o", false, "open dashboard in browser") + cmd.Flags().BoolVarP(&oss, "oss", "O", false, "run open source version") cmd.Flags().StringVar(&baseInitImage, "init-image", "kubeshop/testkube-tw-init:latest", "base init image") cmd.Flags().StringVar(&baseToolkitImage, "toolkit-image", "kubeshop/testkube-tw-toolkit:latest", "base toolkit image") cmd.Flags().StringVar(&baseAgentImage, "agent-image", "kubeshop/testkube-api-server:latest", "base agent image") diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go b/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go index c0440f33c6..41612dcfac 100644 --- a/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/agent.go @@ -20,13 +20,13 @@ import ( type Agent struct { pod *PodObject - cloud *cloudObj + cloud *CloudObject agentImage string initProcessImage string toolkitImage string } -func NewAgent(pod *PodObject, cloud *cloudObj, agentImage, initProcessImage, toolkitImage string) *Agent { +func NewAgent(pod *PodObject, cloud *CloudObject, agentImage, initProcessImage, toolkitImage string) *Agent { return &Agent{ pod: pod, cloud: cloud, @@ -37,9 +37,45 @@ func NewAgent(pod *PodObject, cloud *cloudObj, agentImage, initProcessImage, too } func (r *Agent) Create(ctx context.Context, env *client.Environment) error { - tlsInsecure := "false" - if r.cloud.AgentInsecure() { - tlsInsecure = "true" + envVariables := []corev1.EnvVar{ + {Name: "NATS_EMBEDDED", Value: "true"}, + {Name: "APISERVER_PORT", Value: "8088"}, + {Name: "GRPC_PORT", Value: "8089"}, + {Name: "APISERVER_FULLNAME", Value: "devbox-agent"}, + {Name: "DISABLE_TEST_TRIGGERS", Value: "true"}, + {Name: "DISABLE_WEBHOOKS", Value: "true"}, + {Name: "DISABLE_DEPRECATED_TESTS", Value: "true"}, + {Name: "TESTKUBE_ANALYTICS_ENABLED", Value: "false"}, + {Name: "TESTKUBE_NAMESPACE", Value: r.pod.Namespace()}, + {Name: "JOB_SERVICE_ACCOUNT_NAME", Value: "devbox-account"}, + {Name: "TESTKUBE_ENABLE_IMAGE_DATA_PERSISTENT_CACHE", Value: "true"}, + {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}, + } + if env != nil { + tlsInsecure := "false" + if r.cloud.AgentInsecure() { + tlsInsecure = "true" + } + envVariables = append(envVariables, []corev1.EnvVar{ + {Name: "TESTKUBE_PRO_API_KEY", Value: env.AgentToken}, + {Name: "TESTKUBE_PRO_ORG_ID", Value: env.OrganizationId}, + {Name: "TESTKUBE_PRO_ENV_ID", Value: env.Id}, + {Name: "TESTKUBE_PRO_URL", Value: r.cloud.AgentURI()}, + {Name: "TESTKUBE_PRO_TLS_INSECURE", Value: tlsInsecure}, + {Name: "TESTKUBE_PRO_TLS_SKIP_VERIFY", Value: "true"}, + }...) + } else { + envVariables = append(envVariables, []corev1.EnvVar{ + {Name: "API_MONGO_DSN", Value: "mongodb://devbox-mongodb:27017"}, + {Name: "API_MONGO_ALLOW_DISK_USE", Value: "true"}, + {Name: "STORAGE_ENDPOINT", Value: "devbox-minio:9000"}, + {Name: "STORAGE_BUCKET", Value: "testkube-artifacts"}, + {Name: "STORAGE_ACCESSKEYID", Value: "minioadmin"}, + {Name: "STORAGE_SECRETACCESSKEY", Value: "minioadmin"}, + {Name: "LOGS_BUCKET", Value: "testkube-logs"}, + }...) } err := r.pod.Create(ctx, &corev1.Pod{ Spec: corev1.PodSpec{ @@ -60,27 +96,7 @@ func (r *Agent) Create(ctx context.Context, env *client.Environment) error { wget -O /.tk-devbox/testkube-api-server http://devbox-binary:8080/testkube-api-server || exit 1 chmod 777 /.tk-devbox/testkube-api-server exec /.tk-devbox/testkube-api-server`}, - Env: []corev1.EnvVar{ - {Name: "NATS_EMBEDDED", Value: "true"}, - {Name: "APISERVER_PORT", Value: "8088"}, - {Name: "APISERVER_FULLNAME", Value: "devbox-agent"}, - {Name: "DISABLE_TEST_TRIGGERS", Value: "true"}, - {Name: "DISABLE_WEBHOOKS", Value: "true"}, - {Name: "DISABLE_DEPRECATED_TESTS", Value: "true"}, - {Name: "TESTKUBE_ANALYTICS_ENABLED", Value: "false"}, - {Name: "TESTKUBE_NAMESPACE", Value: r.pod.Namespace()}, - {Name: "JOB_SERVICE_ACCOUNT_NAME", Value: "devbox-account"}, - {Name: "TESTKUBE_ENABLE_IMAGE_DATA_PERSISTENT_CACHE", Value: "true"}, - {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: "TESTKUBE_PRO_API_KEY", Value: env.AgentToken}, - {Name: "TESTKUBE_PRO_ORG_ID", Value: env.OrganizationId}, - {Name: "TESTKUBE_PRO_ENV_ID", Value: env.Id}, - {Name: "TESTKUBE_PRO_URL", Value: r.cloud.AgentURI()}, - {Name: "TESTKUBE_PRO_TLS_INSECURE", Value: tlsInsecure}, - {Name: "TESTKUBE_PRO_TLS_SKIP_VERIFY", Value: "true"}, - }, + Env: envVariables, VolumeMounts: []corev1.VolumeMount{ {Name: "tmp", MountPath: "/tmp"}, {Name: "nats", MountPath: "/app/nats"}, @@ -107,11 +123,25 @@ func (r *Agent) Create(ctx context.Context, env *client.Environment) error { if err != nil { return err } + err = r.pod.CreateNamedService(ctx, "testkube-api-server", corev1.ServicePort{ + Name: "api", + Protocol: "TCP", + Port: 8088, + TargetPort: intstr.FromInt32(8088), + }) + if err != nil { + return err + } return r.pod.CreateService(ctx, corev1.ServicePort{ Name: "api", Protocol: "TCP", Port: 8088, TargetPort: intstr.FromInt32(8088), + }, corev1.ServicePort{ + Name: "grpc", + Protocol: "TCP", + Port: 8089, + TargetPort: intstr.FromInt32(8089), }) } diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/cloud.go b/cmd/tcl/kubectl-testkube/devbox/devutils/cloud.go index 69b6f77a17..2914369229 100644 --- a/cmd/tcl/kubectl-testkube/devbox/devutils/cloud.go +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/cloud.go @@ -24,7 +24,7 @@ import ( "github.com/kubeshop/testkube/pkg/cloud/client" ) -type cloudObj struct { +type CloudObject struct { cfg config.CloudContext envClient *client.EnvironmentsClient list []client.Environment @@ -36,7 +36,7 @@ type cloudObj struct { cmd *cobra.Command } -func NewCloud(cfg config.CloudContext, cmd *cobra.Command) (*cloudObj, error) { +func NewCloud(cfg config.CloudContext, cmd *cobra.Command) (*CloudObject, error) { if cfg.ApiKey == "" || cfg.OrganizationId == "" || cfg.OrganizationName == "" { return nil, errors.New("login to the organization first") } @@ -56,7 +56,7 @@ func NewCloud(cfg config.CloudContext, cmd *cobra.Command) (*cloudObj, error) { cfg.AgentUri = "agent." + strings.TrimPrefix(cfg.AgentUri, "api.") } envClient := client.NewEnvironmentsClient(cfg.ApiUri, cfg.ApiKey, cfg.OrganizationId) - obj := &cloudObj{ + obj := &CloudObject{ cfg: cfg, envClient: envClient, cmd: cmd, @@ -69,11 +69,11 @@ func NewCloud(cfg config.CloudContext, cmd *cobra.Command) (*cloudObj, error) { return obj, nil } -func (c *cloudObj) List() []client.Environment { +func (c *CloudObject) List() []client.Environment { return c.list } -func (c *cloudObj) ListObsolete() []client.Environment { +func (c *CloudObject) ListObsolete() []client.Environment { obsolete := make([]client.Environment, 0) for _, env := range c.list { if !env.Connected { @@ -83,7 +83,7 @@ func (c *cloudObj) ListObsolete() []client.Environment { return obsolete } -func (c *cloudObj) UpdateList() error { +func (c *CloudObject) UpdateList() error { list, err := c.envClient.List() if err != nil { return err @@ -98,7 +98,7 @@ func (c *cloudObj) UpdateList() error { return nil } -func (c *cloudObj) Client(environmentId string) (client2.Client, error) { +func (c *CloudObject) Client(environmentId string) (client2.Client, error) { c.clientMu.Lock() defer c.clientMu.Unlock() @@ -121,31 +121,31 @@ func (c *cloudObj) Client(environmentId string) (client2.Client, error) { return c.client, nil } -func (c *cloudObj) AgentURI() string { +func (c *CloudObject) AgentURI() string { return c.cfg.AgentUri } -func (c *cloudObj) AgentInsecure() bool { +func (c *CloudObject) AgentInsecure() bool { return strings.HasPrefix(c.cfg.ApiUri, "http://") } -func (c *cloudObj) ApiURI() string { +func (c *CloudObject) ApiURI() string { return c.cfg.ApiUri } -func (c *cloudObj) ApiKey() string { +func (c *CloudObject) ApiKey() string { return c.cfg.ApiKey } -func (c *cloudObj) ApiInsecure() bool { +func (c *CloudObject) ApiInsecure() bool { return strings.HasPrefix(c.cfg.ApiUri, "http://") } -func (c *cloudObj) DashboardUrl(id, path string) string { +func (c *CloudObject) DashboardUrl(id, path string) string { return strings.TrimSuffix(fmt.Sprintf("%s/organization/%s/environment/%s/", c.cfg.UiUri, c.cfg.OrganizationId, id)+strings.TrimPrefix(path, "/"), "/") } -func (c *cloudObj) CreateEnvironment(name string) (*client.Environment, error) { +func (c *CloudObject) CreateEnvironment(name string) (*client.Environment, error) { env, err := c.envClient.Create(client.Environment{ Name: name, Owner: c.cfg.OrganizationId, @@ -176,6 +176,6 @@ func (c *cloudObj) CreateEnvironment(name string) (*client.Environment, error) { return &env, nil } -func (c *cloudObj) DeleteEnvironment(id string) error { +func (c *CloudObject) DeleteEnvironment(id string) error { return c.envClient.Delete(id) } diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/cluster.go b/cmd/tcl/kubectl-testkube/devbox/devutils/cluster.go index 19450acd94..9c6ee66c6b 100644 --- a/cmd/tcl/kubectl-testkube/devbox/devutils/cluster.go +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/cluster.go @@ -15,13 +15,16 @@ import ( "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + kubeclient "github.com/kubeshop/testkube-operator/pkg/client" "github.com/kubeshop/testkube/pkg/k8sclient" ) type ClusterObject struct { cfg *rest.Config clientSet *kubernetes.Clientset + kubeClient client.Client versionInfo *version.Info } @@ -41,9 +44,14 @@ func NewCluster() (*ClusterObject, error) { if err != nil { return nil, errors.Wrap(err, "failed to get Kubernetes cluster details") } + kubeClient, err := kubeclient.GetClient() + if err != nil { + return nil, errors.Wrap(err, "failed to create Kubernetes client wrapper") + } return &ClusterObject{ clientSet: clientSet, + kubeClient: kubeClient, versionInfo: info, cfg: config, }, nil @@ -53,6 +61,10 @@ func (c *ClusterObject) ClientSet() *kubernetes.Clientset { return c.clientSet } +func (c *ClusterObject) KubeClient() client.Client { + return c.kubeClient +} + func (c *ClusterObject) Config() *rest.Config { return c.cfg } diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/minio.go b/cmd/tcl/kubectl-testkube/devbox/devutils/minio.go new file mode 100644 index 0000000000..b2c5fe15f0 --- /dev/null +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/minio.go @@ -0,0 +1,65 @@ +// 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 devutils + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/kubeshop/testkube/internal/common" +) + +type Minio struct { + pod *PodObject +} + +func NewMinio(pod *PodObject) *Minio { + return &Minio{ + pod: pod, + } +} + +func (r *Minio) Create(ctx context.Context) error { + err := r.pod.Create(ctx, &corev1.Pod{ + Spec: corev1.PodSpec{ + TerminationGracePeriodSeconds: common.Ptr(int64(1)), + Containers: []corev1.Container{ + { + Name: "minio", + Image: "minio/minio:RELEASE.2024-10-13T13-34-11Z", + ImagePullPolicy: corev1.PullIfNotPresent, + Args: []string{"server", "/data", "--console-address", ":9090"}, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt32(9000), + }, + }, + PeriodSeconds: 1, + }, + }, + }, + }, + }) + if err != nil { + return err + } + return r.pod.CreateService(ctx, corev1.ServicePort{ + Name: "api", + Protocol: "TCP", + Port: 9000, + TargetPort: intstr.FromInt32(9000), + }) +} + +func (r *Minio) WaitForReady(ctx context.Context) error { + return r.pod.WaitForReady(ctx) +} diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/mongo.go b/cmd/tcl/kubectl-testkube/devbox/devutils/mongo.go new file mode 100644 index 0000000000..9b70a63b62 --- /dev/null +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/mongo.go @@ -0,0 +1,64 @@ +// 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 devutils + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "github.com/kubeshop/testkube/internal/common" +) + +type Mongo struct { + pod *PodObject +} + +func NewMongo(pod *PodObject) *Mongo { + return &Mongo{ + pod: pod, + } +} + +func (r *Mongo) Create(ctx context.Context) error { + err := r.pod.Create(ctx, &corev1.Pod{ + Spec: corev1.PodSpec{ + TerminationGracePeriodSeconds: common.Ptr(int64(1)), + Containers: []corev1.Container{ + { + Name: "mongo", + Image: "zcube/bitnami-compat-mongodb:6.0.5-debian-11-r64", + ImagePullPolicy: corev1.PullIfNotPresent, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + TCPSocket: &corev1.TCPSocketAction{ + Port: intstr.FromInt32(27017), + }, + }, + PeriodSeconds: 1, + }, + }, + }, + }, + }) + if err != nil { + return err + } + return r.pod.CreateService(ctx, corev1.ServicePort{ + Name: "api", + Protocol: "TCP", + Port: 27017, + TargetPort: intstr.FromInt32(27017), + }) +} + +func (r *Mongo) WaitForReady(ctx context.Context) error { + return r.pod.WaitForReady(ctx) +} diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/pods.go b/cmd/tcl/kubectl-testkube/devbox/devutils/pods.go index c923083d6c..4dc15e2633 100644 --- a/cmd/tcl/kubectl-testkube/devbox/devutils/pods.go +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/pods.go @@ -191,10 +191,10 @@ func (p *PodObject) ClusterAddress() string { return fmt.Sprintf("%s.%s.svc", p.service.Name, p.service.Namespace) } -func (p *PodObject) CreateService(ctx context.Context, ports ...corev1.ServicePort) error { +func (p *PodObject) CreateNamedService(ctx context.Context, name string, ports ...corev1.ServicePort) error { request := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ - Name: p.name, + Name: name, }, Spec: corev1.ServiceSpec{ Type: corev1.ServiceTypeClusterIP, @@ -220,6 +220,10 @@ func (p *PodObject) CreateService(ctx context.Context, ports ...corev1.ServicePo return nil } +func (p *PodObject) CreateService(ctx context.Context, ports ...corev1.ServicePort) error { + return p.CreateNamedService(ctx, p.name, ports...) +} + func (p *PodObject) Forward(_ context.Context, clusterPort, localPort int, ping bool) error { if p.pod == nil { return ErrPodNotFound diff --git a/cmd/tcl/kubectl-testkube/devbox/devutils/resources.go b/cmd/tcl/kubectl-testkube/devbox/devutils/resources.go new file mode 100644 index 0000000000..33249ef32f --- /dev/null +++ b/cmd/tcl/kubectl-testkube/devbox/devutils/resources.go @@ -0,0 +1,85 @@ +// 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 devutils + +import ( + "sigs.k8s.io/controller-runtime/pkg/client" + + testworkflowsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/testworkflows/v1" + "github.com/kubeshop/testkube/pkg/api/v1/testkube" + "github.com/kubeshop/testkube/pkg/mapper/testworkflows" +) + +type ResourcesClient interface { + CreateTestWorkflow(workflow testkube.TestWorkflow) (testkube.TestWorkflow, error) + UpdateTestWorkflow(workflow testkube.TestWorkflow) (testkube.TestWorkflow, error) + DeleteTestWorkflow(name string) error + + CreateTestWorkflowTemplate(workflow testkube.TestWorkflowTemplate) (testkube.TestWorkflowTemplate, error) + UpdateTestWorkflowTemplate(workflow testkube.TestWorkflowTemplate) (testkube.TestWorkflowTemplate, error) + DeleteTestWorkflowTemplate(name string) error +} + +type ossResourcesClient struct { + namespace string + testWorkflows *testworkflowsclientv1.TestWorkflowsClient + testWorkflowTemplates *testworkflowsclientv1.TestWorkflowTemplatesClient +} + +func NewDirectResourcesClient(kubeClient client.Client, namespace string) ResourcesClient { + return &ossResourcesClient{ + namespace: namespace, + testWorkflows: testworkflowsclientv1.NewClient(kubeClient, namespace), + testWorkflowTemplates: testworkflowsclientv1.NewTestWorkflowTemplatesClient(kubeClient, namespace), + } +} + +func (r *ossResourcesClient) CreateTestWorkflow(workflow testkube.TestWorkflow) (result testkube.TestWorkflow, err error) { + workflow.Namespace = r.namespace + v, err := r.testWorkflows.Create(testworkflows.MapAPIToKube(&workflow)) + if err != nil { + return + } + return *testworkflows.MapKubeToAPI(v), nil +} + +func (r *ossResourcesClient) UpdateTestWorkflow(workflow testkube.TestWorkflow) (result testkube.TestWorkflow, err error) { + workflow.Namespace = r.namespace + v, err := r.testWorkflows.Update(testworkflows.MapAPIToKube(&workflow)) + if err != nil { + return + } + return *testworkflows.MapKubeToAPI(v), nil +} + +func (r *ossResourcesClient) DeleteTestWorkflow(name string) error { + return r.testWorkflows.Delete(name) +} + +func (r *ossResourcesClient) CreateTestWorkflowTemplate(template testkube.TestWorkflowTemplate) (result testkube.TestWorkflowTemplate, err error) { + template.Namespace = r.namespace + v, err := r.testWorkflowTemplates.Create(testworkflows.MapTemplateAPIToKube(&template)) + if err != nil { + return + } + return *testworkflows.MapTemplateKubeToAPI(v), nil +} + +func (r *ossResourcesClient) UpdateTestWorkflowTemplate(template testkube.TestWorkflowTemplate) (result testkube.TestWorkflowTemplate, err error) { + template.Namespace = r.namespace + v, err := r.testWorkflowTemplates.Update(testworkflows.MapTemplateAPIToKube(&template)) + if err != nil { + return + } + return *testworkflows.MapTemplateKubeToAPI(v), nil +} + +func (r *ossResourcesClient) DeleteTestWorkflowTemplate(name string) error { + return r.testWorkflowTemplates.Delete(name) +} diff --git a/cmd/testworkflow-toolkit/commands/artifacts.go b/cmd/testworkflow-toolkit/commands/artifacts.go index 657c3e2293..d9f9e4926c 100644 --- a/cmd/testworkflow-toolkit/commands/artifacts.go +++ b/cmd/testworkflow-toolkit/commands/artifacts.go @@ -88,7 +88,7 @@ func NewArtifactsCmd() *cobra.Command { executor, client := env.Cloud(ctx) proContext, err := client.GetProContext(ctx, &emptypb.Empty{}) var supported []*cloud.Capability - if err != nil { + if err != nil && !strings.Contains(err.Error(), "not supported") { fmt.Printf("Warning: couldn't get capabilities: %s\n", err.Error()) } if proContext != nil {