From a1632d5aecd857f8de5b77db342c39daff91bcb2 Mon Sep 17 00:00:00 2001 From: Edgar Gomes Date: Thu, 3 Oct 2024 14:20:41 -0300 Subject: [PATCH] feat!: add support to StatefulSet (#263) --- .golangci.yml | 9 ++ ci/obd-demo.yaml | 2 +- kardinal-cli/cmd/root.go | 96 ++++++++++--------- .../cluster_manager/cluster_manager.go | 74 +++++++++++++- .../api/golang/server/server.gen.go | 61 ++++++------ .../api/golang/types/types.gen.go | 25 +++-- .../api/typescript/client/types.d.ts | 7 ++ libs/cli-kontrol-api/specs/api.yaml | 26 ++++- .../api/golang/server/server.gen.go | 28 +++--- .../api/golang/types/types.gen.go | 2 + .../api/typescript/client/types.d.ts | 1 + libs/manager-kontrol-api/specs/api.yaml | 7 ++ website/app/docs/concepts/plugins/page.mdx | 16 ++-- 13 files changed, 244 insertions(+), 110 deletions(-) create mode 100644 .golangci.yml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..4ce7517 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,9 @@ +linters: + enable: + - exhaustruct + - exportloopref + - gomnd + - staticcheck + - exhaustive + max-issues-per-linter: 0 + sort-results: true diff --git a/ci/obd-demo.yaml b/ci/obd-demo.yaml index e1b766d..36bfbbf 100644 --- a/ci/obd-demo.yaml +++ b/ci/obd-demo.yaml @@ -162,7 +162,7 @@ spec: --- apiVersion: apps/v1 -kind: Deployment +kind: StatefulSet metadata: name: postgres-v1 labels: diff --git a/kardinal-cli/cmd/root.go b/kardinal-cli/cmd/root.go index f94772f..793e8b9 100644 --- a/kardinal-cli/cmd/root.go +++ b/kardinal-cli/cmd/root.go @@ -23,6 +23,7 @@ import ( "kardinal.cli/multi_os_cmd_executor" "github.com/kurtosis-tech/stacktrace" + "github.com/samber/lo" "github.com/segmentio/analytics-go/v3" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -119,7 +120,7 @@ var deployCmd = &cobra.Command{ Short: "Deploy services", Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { - serviceConfigs, ingressConfigs, gatewayConfigs, routeConfigs, namespace, err := parseKubernetesManifestFile(kubernetesManifestFile) + services, deployments, statefulSets, ingresses, gateways, routes, namespace, err := parseKubernetesManifestFile(kubernetesManifestFile) if err != nil { log.Fatalf("Error loading k8s manifest file: %v", err) } @@ -128,7 +129,7 @@ var deployCmd = &cobra.Command{ log.Fatal("Error getting or creating user tenant UUID", err) } - deploy(tenantUuid.String(), serviceConfigs, ingressConfigs, gatewayConfigs, routeConfigs, namespace) + deploy(tenantUuid.String(), services, deployments, statefulSets, ingresses, gateways, routes, namespace) }, } @@ -144,7 +145,7 @@ var templateCreateCmd = &cobra.Command{ // A valid template only modifies services // A valid template has metadata.name // A valid template modifies at least one service - serviceConfigs, _, _, _, _, err := parseKubernetesManifestFile(templateYamlFile) + serviceConfigs, _, _, _, _, _, _, err := parseKubernetesManifestFile(templateYamlFile) if err != nil { log.Fatalf("Error loading template file: %v", err) } @@ -678,19 +679,28 @@ func parsePairs(pairs []string) map[string]string { return pairsMap } -func parseKubernetesManifestFile(kubernetesManifestFile string) ([]api_types.ServiceConfig, []api_types.IngressConfig, []api_types.GatewayConfig, []api_types.RouteConfig, string, error) { +func parseKubernetesManifestFile(kubernetesManifestFile string) ( + []api_types.ServiceConfig, + []api_types.DeploymentConfig, + []api_types.StatefulSetConfig, + []api_types.IngressConfig, + []api_types.GatewayConfig, + []api_types.RouteConfig, string, error, +) { fileBytes, err := loadKubernetesManifestFile(kubernetesManifestFile) if err != nil { log.Fatalf("Error loading kubernetest manifest file: %v", err) - return nil, nil, nil, nil, "", err + return nil, nil, nil, nil, nil, nil, "", err } manifest := string(fileBytes) var namespace string - serviceConfigs := map[string]*api_types.ServiceConfig{} - ingressConfigs := map[string]*api_types.IngressConfig{} - gatewayConfigs := map[string]*api_types.GatewayConfig{} - routeConfigs := map[string]*api_types.RouteConfig{} + serviceConfigs := map[string]api_types.ServiceConfig{} + deploymentConfigs := map[string]api_types.DeploymentConfig{} + statefulSetConfigs := map[string]api_types.StatefulSetConfig{} + ingressConfigs := map[string]api_types.IngressConfig{} + gatewayConfigs := map[string]api_types.GatewayConfig{} + routeConfigs := map[string]api_types.RouteConfig{} // Register the gateway scheme to parse the Gateway CRD gatewayscheme.AddToScheme(scheme.Scheme) @@ -701,7 +711,7 @@ func parseKubernetesManifestFile(kubernetesManifestFile string) ([]api_types.Ser } obj, _, err := decode([]byte(spec), nil, nil) if err != nil { - return nil, nil, nil, nil, "", stacktrace.Propagate(err, "An error occurred parsing the spec: %s", spec) + return nil, nil, nil, nil, nil, nil, "", stacktrace.Propagate(err, "An error occurred parsing the spec: %s", spec) } switch obj := obj.(type) { case *corev1.Service: @@ -709,27 +719,38 @@ func parseKubernetesManifestFile(kubernetesManifestFile string) ([]api_types.Ser serviceName := getObjectName(service.GetObjectMeta().(*metav1.ObjectMeta)) _, ok := serviceConfigs[serviceName] if !ok { - serviceConfigs[serviceName] = &api_types.ServiceConfig{ + serviceConfigs[serviceName] = api_types.ServiceConfig{ Service: *service, } } else { - serviceConfigs[serviceName].Service = *service + logrus.Warnf("Service %s already exists, skipping it", serviceName) } case *appv1.Deployment: deployment := obj deploymentName := getObjectName(deployment.GetObjectMeta().(*metav1.ObjectMeta)) - _, ok := serviceConfigs[deploymentName] + _, ok := deploymentConfigs[deploymentName] if !ok { - serviceConfigs[deploymentName] = &api_types.ServiceConfig{ + deploymentConfigs[deploymentName] = api_types.DeploymentConfig{ Deployment: *deployment, } } else { - serviceConfigs[deploymentName].Deployment = *deployment + logrus.Warnf("Deployment %s already exists, skipping it", deploymentName) + } + case *appv1.StatefulSet: + statefulset := obj + statefulsetName := getObjectName(statefulset.GetObjectMeta().(*metav1.ObjectMeta)) + _, ok := statefulSetConfigs[statefulsetName] + if !ok { + statefulSetConfigs[statefulsetName] = api_types.StatefulSetConfig{ + StatefulSet: *statefulset, + } + } else { + logrus.Warnf("StatefulSet %s already exists, skipping it", statefulsetName) } case *k8snet.Ingress: ingress := obj ingressName := getObjectName(ingress.GetObjectMeta().(*metav1.ObjectMeta)) - ingressConfigs[ingressName] = &api_types.IngressConfig{Ingress: *ingress} + ingressConfigs[ingressName] = api_types.IngressConfig{Ingress: *ingress} case *corev1.Namespace: namespaceObj := obj namespaceName := getObjectName(namespaceObj.GetObjectMeta().(*metav1.ObjectMeta)) @@ -737,37 +758,17 @@ func parseKubernetesManifestFile(kubernetesManifestFile string) ([]api_types.Ser case *gateway.Gateway: gatewayObj := obj gatewayName := getObjectName(gatewayObj.GetObjectMeta().(*metav1.ObjectMeta)) - gatewayConfigs[gatewayName] = &api_types.GatewayConfig{Gateway: *gatewayObj} + gatewayConfigs[gatewayName] = api_types.GatewayConfig{Gateway: *gatewayObj} case *gateway.HTTPRoute: routeObj := obj routeName := getObjectName(routeObj.GetObjectMeta().(*metav1.ObjectMeta)) - routeConfigs[routeName] = &api_types.RouteConfig{HttpRoute: *routeObj} + routeConfigs[routeName] = api_types.RouteConfig{HttpRoute: *routeObj} default: - return nil, nil, nil, nil, "", stacktrace.NewError("An error occurred parsing the manifest because of an unsupported kubernetes type") + return nil, nil, nil, nil, nil, nil, "", stacktrace.NewError("An error occurred parsing the manifest because of an unsupported kubernetes type") } } - finalServiceConfigs := []api_types.ServiceConfig{} - for _, serviceConfig := range serviceConfigs { - finalServiceConfigs = append(finalServiceConfigs, *serviceConfig) - } - - finalIngressConfigs := []api_types.IngressConfig{} - for _, ingressConfig := range ingressConfigs { - finalIngressConfigs = append(finalIngressConfigs, *ingressConfig) - } - - finalGatewayConfigs := []api_types.GatewayConfig{} - for _, gatewayConfig := range gatewayConfigs { - finalGatewayConfigs = append(finalGatewayConfigs, *gatewayConfig) - } - - finalRouteConfigs := []api_types.RouteConfig{} - for _, routeConfig := range routeConfigs { - finalRouteConfigs = append(finalRouteConfigs, *routeConfig) - } - - return finalServiceConfigs, finalIngressConfigs, finalGatewayConfigs, finalRouteConfigs, namespace, nil + return lo.Values(serviceConfigs), lo.Values(deploymentConfigs), lo.Values(statefulSetConfigs), lo.Values(ingressConfigs), lo.Values(gatewayConfigs), lo.Values(routeConfigs), namespace, nil } func parseTemplateArgs(filepathOrJson string) (map[string]interface{}, error) { @@ -817,7 +818,6 @@ func listDevFlow(tenantUuid api_types.Uuid) { } printFlowTable(flows) - return } func getTenantUuidFlows(tenantUuid api_types.Uuid) ([]api_types.Flow, error) { @@ -890,6 +890,8 @@ func createDevFlow(tenantUuid api_types.Uuid, pairsMap map[string]string, templa func deploy( tenantUuid api_types.Uuid, serviceConfigs []api_types.ServiceConfig, + deploymentConfigs []api_types.DeploymentConfig, + statefulSetConfigs []api_types.StatefulSetConfig, ingressConfigs []api_types.IngressConfig, gatewayConfigs []api_types.GatewayConfig, routeConfigs []api_types.RouteConfig, @@ -898,11 +900,13 @@ func deploy( ctx := context.Background() body := api_types.PostTenantUuidDeployJSONRequestBody{ - ServiceConfigs: &serviceConfigs, - IngressConfigs: &ingressConfigs, - GatewayConfigs: &gatewayConfigs, - RouteConfigs: &routeConfigs, - Namespace: &namespace, + ServiceConfigs: &serviceConfigs, + DeploymentConfigs: &deploymentConfigs, + StatefulSetConfigs: &statefulSetConfigs, + IngressConfigs: &ingressConfigs, + GatewayConfigs: &gatewayConfigs, + RouteConfigs: &routeConfigs, + Namespace: &namespace, } client := getKontrolServiceClient() diff --git a/kardinal-manager/kardinal-manager/cluster_manager/cluster_manager.go b/kardinal-manager/kardinal-manager/cluster_manager/cluster_manager.go index 6106318..f16c3af 100644 --- a/kardinal-manager/kardinal-manager/cluster_manager/cluster_manager.go +++ b/kardinal-manager/kardinal-manager/cluster_manager/cluster_manager.go @@ -3,9 +3,10 @@ package cluster_manager import ( "context" "encoding/json" - "k8s.io/apimachinery/pkg/labels" "strings" + "k8s.io/apimachinery/pkg/labels" + "github.com/kurtosis-tech/kardinal/libs/manager-kontrol-api/api/golang/types" "github.com/kurtosis-tech/stacktrace" "github.com/samber/lo" @@ -206,6 +207,7 @@ func (manager *ClusterManager) ApplyClusterResources(ctx context.Context, cluste allNSs := [][]string{ lo.Uniq(lo.Map(*clusterResources.Services, func(item corev1.Service, _ int) string { return item.Namespace })), lo.Uniq(lo.Map(*clusterResources.Deployments, func(item appsv1.Deployment, _ int) string { return item.Namespace })), + lo.Uniq(lo.Map(*clusterResources.StatefulSets, func(item appsv1.StatefulSet, _ int) string { return item.Namespace })), lo.Uniq(lo.Map(*clusterResources.VirtualServices, func(item v1alpha3.VirtualService, _ int) string { return item.Namespace })), lo.Uniq(lo.Map(*clusterResources.DestinationRules, func(item v1alpha3.DestinationRule, _ int) string { return item.Namespace })), lo.Uniq(lo.Map(*clusterResources.Gateways, func(item gateway.Gateway, _ int) string { return item.Namespace })), @@ -242,6 +244,12 @@ func (manager *ClusterManager) ApplyClusterResources(ctx context.Context, cluste } } + for _, statefulSet := range *clusterResources.StatefulSets { + if err := manager.createOrUpdateStatefulSet(ctx, &statefulSet); err != nil { + return stacktrace.Propagate(err, "An error occurred while creating or updating statefulSet '%s'", statefulSet.GetName()) + } + } + for _, virtualService := range *clusterResources.VirtualServices { if err := manager.createOrUpdateVirtualService(ctx, &virtualService); err != nil { return stacktrace.Propagate(err, "An error occurred while creating or updating virtual service '%s'", virtualService.GetName()) @@ -394,6 +402,14 @@ func (manager *ClusterManager) CleanUpClusterResources(ctx context.Context, clus } } + // Clean up deployments + statefulSetsByNS := lo.GroupBy(*clusterResources.StatefulSets, func(item appsv1.StatefulSet) string { return item.Namespace }) + for namespace, statefulSets := range statefulSetsByNS { + if err := manager.cleanUpStatefulSetsInNamespace(ctx, namespace, statefulSets); err != nil { + return stacktrace.Propagate(err, "An error occurred cleaning up statefulSets '%+v' in namespace '%s'", statefulSets, namespace) + } + } + // Cleanup authorization policies if clusterResources.AuthorizationPolicies != nil { authorizationPoliciesByNS := lo.GroupBy(*clusterResources.AuthorizationPolicies, func(item securityv1beta1.AuthorizationPolicy) string { @@ -479,7 +495,6 @@ func (manager *ClusterManager) removeNamespace(ctx context.Context, namespace *c } func (manager *ClusterManager) ensureNamespace(ctx context.Context, name string) error { - if name == istioSystemNamespace { // Some resources might be under the istio system namespace but we don't want to alter // this namespace because it is managed by Istio @@ -561,6 +576,27 @@ func (manager *ClusterManager) createOrUpdateDeployment(ctx context.Context, dep return nil } +func (manager *ClusterManager) createOrUpdateStatefulSet(ctx context.Context, statefulSet *appsv1.StatefulSet) error { + statefulSetClient := manager.kubernetesClient.clientSet.AppsV1().StatefulSets(statefulSet.Namespace) + existingStatefulSet, err := statefulSetClient.Get(ctx, statefulSet.Name, metav1.GetOptions{}) + if err != nil { + _, err = statefulSetClient.Create(ctx, statefulSet, globalCreateOptions) + if err != nil { + return stacktrace.Propagate(err, "Failed to create statefulSet: %s", statefulSet.GetName()) + } + } else { + if !deepCheckEqual(existingStatefulSet.Spec, statefulSet.Spec) { + updateStatefulSetWithRelevantValuesFromCurrentDeployment(statefulSet, existingStatefulSet) + _, err = statefulSetClient.Update(ctx, statefulSet, globalUpdateOptions) + if err != nil { + return stacktrace.Propagate(err, "Failed to update statefulSet: %s", statefulSet.GetName()) + } + } + } + + return nil +} + func updateDeploymentWithRelevantValuesFromCurrentDeployment(newDeployment *appsv1.Deployment, currentDeployment *appsv1.Deployment) { newDeployment.ResourceVersion = currentDeployment.ResourceVersion // merge annotations @@ -577,6 +613,22 @@ func updateDeploymentWithRelevantValuesFromCurrentDeployment(newDeployment *apps newDeployment.Spec.Template.Annotations = newAnnotations } +func updateStatefulSetWithRelevantValuesFromCurrentDeployment(newStatefulSet *appsv1.StatefulSet, currentStatefulSet *appsv1.StatefulSet) { + newStatefulSet.ResourceVersion = currentStatefulSet.ResourceVersion + // merge annotations + newAnnotations := newStatefulSet.Spec.Template.GetAnnotations() + currentAnnotations := currentStatefulSet.Spec.Template.GetAnnotations() + + for annotationKey, annotationValue := range currentAnnotations { + if annotationKey == telepresenceRestartedAtAnnotation { + // This key is necessary for Kardinal/Telepresence (https://www.telepresence.io/) integration + // keeping this annotation because otherwise the telepresence traffic-agent container will be removed from the pod + newAnnotations[annotationKey] = annotationValue + } + } + newStatefulSet.Spec.Template.Annotations = newAnnotations +} + func (manager *ClusterManager) createOrUpdateVirtualService(ctx context.Context, virtualService *v1alpha3.VirtualService) error { virtServiceClient := manager.istioClient.clientSet.NetworkingV1alpha3().VirtualServices(virtualService.GetNamespace()) @@ -770,6 +822,24 @@ func (manager *ClusterManager) cleanUpDeploymentsInNamespace(ctx context.Context return nil } +func (manager *ClusterManager) cleanUpStatefulSetsInNamespace(ctx context.Context, namespace string, statefulSetsToKeep []appsv1.StatefulSet) error { + statefulSetClient := manager.kubernetesClient.clientSet.AppsV1().StatefulSets(namespace) + allstatefulSets, err := statefulSetClient.List(ctx, globalListOptions) + if err != nil { + return stacktrace.Propagate(err, "Failed to list statefulSets in namespace %s", namespace) + } + for _, statefulSet := range allstatefulSets.Items { + _, exists := lo.Find(statefulSetsToKeep, func(item appsv1.StatefulSet) bool { return item.Name == statefulSet.Name }) + if !exists { + err = statefulSetClient.Delete(ctx, statefulSet.Name, globalDeleteOptions) + if err != nil { + return stacktrace.Propagate(err, "Failed to delete statefulSet %s", statefulSet.GetName()) + } + } + } + return nil +} + func (manager *ClusterManager) cleanUpVirtualServicesInNamespace(ctx context.Context, namespace string, virtualServicesToKeep []v1alpha3.VirtualService) error { virtServiceClient := manager.istioClient.clientSet.NetworkingV1alpha3().VirtualServices(namespace) allVirtServices, err := virtServiceClient.List(ctx, globalListOptions) diff --git a/libs/cli-kontrol-api/api/golang/server/server.gen.go b/libs/cli-kontrol-api/api/golang/server/server.gen.go index 08384c4..99fbcd8 100644 --- a/libs/cli-kontrol-api/api/golang/server/server.gen.go +++ b/libs/cli-kontrol-api/api/golang/server/server.gen.go @@ -1058,36 +1058,37 @@ func (sh *strictHandler) GetTenantUuidTopology(ctx echo.Context, uuid Uuid) erro // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "ySy4KIyU3zZSm81fAQAA//8RkEwHCigAAA==", + "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", + "1Hb7dwAAAP//1z52lM4pAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/libs/cli-kontrol-api/api/golang/types/types.gen.go b/libs/cli-kontrol-api/api/golang/types/types.gen.go index 2144e5e..4806aff 100644 --- a/libs/cli-kontrol-api/api/golang/types/types.gen.go +++ b/libs/cli-kontrol-api/api/golang/types/types.gen.go @@ -23,6 +23,11 @@ type ClusterTopology struct { Nodes []Node `json:"nodes"` } +// DeploymentConfig defines model for DeploymentConfig. +type DeploymentConfig struct { + Deployment appv1.Deployment `json:"deployment"` +} + // Edge defines model for Edge. type Edge struct { // Label Label for the edge. @@ -70,11 +75,13 @@ type IngressConfig struct { // MainClusterConfig defines model for MainClusterConfig. type MainClusterConfig struct { - GatewayConfigs *[]GatewayConfig `json:"gateway-configs,omitempty"` - IngressConfigs *[]IngressConfig `json:"ingress-configs,omitempty"` - Namespace *string `json:"namespace,omitempty"` - RouteConfigs *[]RouteConfig `json:"route-configs,omitempty"` - ServiceConfigs *[]ServiceConfig `json:"service-configs,omitempty"` + DeploymentConfigs *[]DeploymentConfig `json:"deployment-configs,omitempty"` + GatewayConfigs *[]GatewayConfig `json:"gateway-configs,omitempty"` + IngressConfigs *[]IngressConfig `json:"ingress-configs,omitempty"` + Namespace *string `json:"namespace,omitempty"` + RouteConfigs *[]RouteConfig `json:"route-configs,omitempty"` + ServiceConfigs *[]ServiceConfig `json:"service-configs,omitempty"` + StatefulSetConfigs *[]StatefulSetConfig `json:"stateful-set-configs,omitempty"` } // Node defines model for Node. @@ -109,8 +116,12 @@ type RouteConfig struct { // ServiceConfig defines model for ServiceConfig. type ServiceConfig struct { - Deployment appv1.Deployment `json:"deployment"` - Service corev1.Service `json:"service"` + Service corev1.Service `json:"service"` +} + +// StatefulSetConfig defines model for StatefulSetConfig. +type StatefulSetConfig struct { + StatefulSet appv1.StatefulSet `json:"stateful-set"` } // Template defines model for Template. diff --git a/libs/cli-kontrol-api/api/typescript/client/types.d.ts b/libs/cli-kontrol-api/api/typescript/client/types.d.ts index af21ac8..9fd0c18 100644 --- a/libs/cli-kontrol-api/api/typescript/client/types.d.ts +++ b/libs/cli-kontrol-api/api/typescript/client/types.d.ts @@ -231,6 +231,8 @@ export interface components { schemas: { MainClusterConfig: { "service-configs"?: components["schemas"]["ServiceConfig"][]; + "deployment-configs"?: components["schemas"]["DeploymentConfig"][]; + "stateful-set-configs"?: components["schemas"]["StatefulSetConfig"][]; "ingress-configs"?: components["schemas"]["IngressConfig"][]; "gateway-configs"?: components["schemas"]["GatewayConfig"][]; "route-configs"?: components["schemas"]["RouteConfig"][]; @@ -294,8 +296,13 @@ export interface components { }; ServiceConfig: { service: unknown; + }; + DeploymentConfig: { deployment: unknown; }; + StatefulSetConfig: { + "stateful-set": unknown; + }; TemplateConfig: { service: unknown[]; /** @description The name to give the template */ diff --git a/libs/cli-kontrol-api/specs/api.yaml b/libs/cli-kontrol-api/specs/api.yaml index 09ebaa9..422e127 100644 --- a/libs/cli-kontrol-api/specs/api.yaml +++ b/libs/cli-kontrol-api/specs/api.yaml @@ -298,6 +298,14 @@ components: type: array items: $ref: "#/components/schemas/ServiceConfig" + deployment-configs: + type: array + items: + $ref: "#/components/schemas/DeploymentConfig" + stateful-set-configs: + type: array + items: + $ref: "#/components/schemas/StatefulSetConfig" ingress-configs: type: array items: @@ -453,15 +461,31 @@ components: x-go-type-import: path: k8s.io/api/core/v1 name: corev1 + required: + - service + + DeploymentConfig: + type: object + properties: deployment: x-go-type: appv1.Deployment x-go-type-import: path: k8s.io/api/apps/v1 name: appv1 required: - - service - deployment + StatefulSetConfig: + type: object + properties: + stateful-set: + x-go-type: appv1.StatefulSet + x-go-type-import: + path: k8s.io/api/apps/v1 + name: appv1 + required: + - stateful-set + TemplateConfig: type: object properties: diff --git a/libs/manager-kontrol-api/api/golang/server/server.gen.go b/libs/manager-kontrol-api/api/golang/server/server.gen.go index f1a585c..c32ffcf 100644 --- a/libs/manager-kontrol-api/api/golang/server/server.gen.go +++ b/libs/manager-kontrol-api/api/golang/server/server.gen.go @@ -160,20 +160,20 @@ func (sh *strictHandler) GetTenantUuidClusterResources(ctx echo.Context, uuid Uu // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/libs/manager-kontrol-api/api/golang/types/types.gen.go b/libs/manager-kontrol-api/api/golang/types/types.gen.go index 7b45ec4..0b4d148 100644 --- a/libs/manager-kontrol-api/api/golang/types/types.gen.go +++ b/libs/manager-kontrol-api/api/golang/types/types.gen.go @@ -7,6 +7,7 @@ import ( v1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3" v1beta1 "istio.io/client-go/pkg/apis/security/v1beta1" appsv1 "k8s.io/api/apps/v1" + appv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" net "k8s.io/api/networking/v1" gateway "sigs.k8s.io/gateway-api/apis/v1" @@ -29,6 +30,7 @@ type ClusterResources struct { HttpRoutes *[]gateway.HTTPRoute `json:"http_routes,omitempty"` Ingresses *[]net.Ingress `json:"ingresses,omitempty"` Services *[]corev1.Service `json:"services,omitempty"` + StatefulSets *[]appv1.StatefulSet `json:"stateful-sets,omitempty"` VirtualServices *[]v1alpha3.VirtualService `json:"virtual_services,omitempty"` } diff --git a/libs/manager-kontrol-api/api/typescript/client/types.d.ts b/libs/manager-kontrol-api/api/typescript/client/types.d.ts index 9b2cd38..aec107c 100644 --- a/libs/manager-kontrol-api/api/typescript/client/types.d.ts +++ b/libs/manager-kontrol-api/api/typescript/client/types.d.ts @@ -41,6 +41,7 @@ export interface components { ClusterResources: { services?: unknown[]; deployments?: unknown[]; + "stateful-sets"?: unknown[]; virtual_services?: unknown[]; destination_rules?: unknown[]; gateways?: unknown[]; diff --git a/libs/manager-kontrol-api/specs/api.yaml b/libs/manager-kontrol-api/specs/api.yaml index 488b037..10fff4f 100644 --- a/libs/manager-kontrol-api/specs/api.yaml +++ b/libs/manager-kontrol-api/specs/api.yaml @@ -78,6 +78,13 @@ components: x-go-type-import: path: k8s.io/api/apps/v1 name: appsv1 + stateful-sets: + type: array + items: + x-go-type: appv1.StatefulSet + x-go-type-import: + path: k8s.io/api/apps/v1 + name: appv1 virtual_services: type: array items: diff --git a/website/app/docs/concepts/plugins/page.mdx b/website/app/docs/concepts/plugins/page.mdx index ebae4a7..fed0601 100644 --- a/website/app/docs/concepts/plugins/page.mdx +++ b/website/app/docs/concepts/plugins/page.mdx @@ -41,15 +41,15 @@ Plugins are Python scripts hosted on GitHub. Each plugin should have two main fu ```python # main.py -def create_flow(service_spec, deployment_spec, flow_uuid, optional_argument): +def create_flow(service_spec, pod_spec, flow_uuid, optional_argument): # Modify the deployment spec # Generate a config map if needed # service_spec - the Kubernetes service spec json - # deployment_spec - Deployment spec of the service json + # pod_spec - pod spec of the service json # flow_uuid - the uuid of the flow string # optional_argument - you can have any number of these, passed via annotations return { - "deployment_spec": modified_deployment_spec, + "pod_spec": modified_pod_spec, "config_map": config_map } @@ -61,7 +61,7 @@ def delete_flow(config_map, flow_uuid): ### Plugin Guidelines - You need a `main.py` in the root of your repository with the above structure -- Modify the `deployment_spec` as needed in the `create_flow` function. +- Modify the `pod_spec` as needed in the `create_flow` function. - Use the `config_map` to store information that might be needed during flow deletion. - If your plugin has external dependencies, include a `requirements.txt` file in the root of your repository. @@ -72,17 +72,15 @@ Here's an example of a simple plugin that replaces text in various parts of the ```python REPLACED = "the-text-has-been-replaced" -def create_flow(service_spec, deployment_spec, flow_uuid, text_to_replace): - deployment_spec['template']['metadata']['labels']['app'] = deployment_spec['template']['metadata']['labels']['app'].replace(text_to_replace, REPLACED) - deployment_spec['selector']['matchLabels']['app'] = deployment_spec['selector']['matchLabels']['app'].replace(text_to_replace, REPLACED) - deployment_spec['template']['spec']['containers'][0]['name'] = deployment_spec['template']['spec']['containers'][0]['name'].replace(text_to_replace, REPLACED) +def create_flow(service_spec, pod_spec, flow_uuid, text_to_replace): + pod_spec['containers'][0]['name'] = pod_spec['containers'][0]['name'].replace(text_to_replace, REPLACED) config_map = { "original_text": text_to_replace } return { - "deployment_spec": deployment_spec, + "pod_spec": pod_spec, "config_map": config_map }