From a49ab71d0f09cce3e1b7ba0e373d5a5049ec3711 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 10 Sep 2024 14:33:19 -0400 Subject: [PATCH] feat: Option to delete the base cluster topology (#16) * Support for deleting the base cluster topology * Cleanup * Lint * Cleanup * Cleanup * Add CI test * Remove uneeded Sprintf --- .github/workflows/ci-e2e-tests.yml | 8 +++ kontrol-service/api/server.go | 97 ++++++++++++++++++++------- kontrol-service/database/flow.go | 11 +++ kontrol-service/database/plugin.go | 11 +++ kontrol-service/engine/flow/render.go | 11 ++- 5 files changed, 114 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci-e2e-tests.yml b/.github/workflows/ci-e2e-tests.yml index 7cd8c13..312c9f0 100644 --- a/.github/workflows/ci-e2e-tests.yml +++ b/.github/workflows/ci-e2e-tests.yml @@ -112,3 +112,11 @@ jobs: - name: Delete template run: | KARDINAL_CLI_DEV_MODE=TRUE kardinal template delete extra-item-shared + + - name: Delete base topology and dev flows + run: | + KARDINAL_CLI_DEV_MODE=TRUE kardinal flow delete prod + if KARDINAL_CLI_DEV_MODE=TRUE kardinal flow ls | grep prod; then echo "Topologies not deleted"; exit 1; fi + tenant_id=${{ steps.tenant.outputs.id }} + deployments=$(curl http://localhost:8080/tenant/${tenant_id}/cluster-resources | jq -r '.deployments[].metadata.name' | tr " " "\n" | sort -g | tr "\n" " " | xargs) + if [ "${deployments}" != "" ]; then echo "Deployments list not empty"; exit 1; fi diff --git a/kontrol-service/api/server.go b/kontrol-service/api/server.go index a403ec6..562bc04 100644 --- a/kontrol-service/api/server.go +++ b/kontrol-service/api/server.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + api "github.com/kurtosis-tech/kardinal/libs/cli-kontrol-api/api/golang/server" apitypes "github.com/kurtosis-tech/kardinal/libs/cli-kontrol-api/api/golang/types" managerapi "github.com/kurtosis-tech/kardinal/libs/manager-kontrol-api/api/golang/server" @@ -80,10 +81,10 @@ func (sv *Server) PostTenantUuidDeploy(_ context.Context, request api.PostTenant namespace := *request.Body.Namespace if namespace == "" { - namespace = "prod" + namespace = prodFlowId } - flowId := "prod" + flowId := prodFlowId err, urls := applyProdOnlyFlow(sv, request.Uuid, serviceConfigs, ingressConfigs, namespace, flowId) if err != nil { errMsg := fmt.Sprintf("An error occurred deploying flow '%v'", prodFlowId) @@ -102,13 +103,29 @@ func (sv *Server) DeleteTenantUuidFlowFlowId(_ context.Context, request api.Dele logrus.Infof("deleting dev flow for tenant '%s'", request.Uuid) sv.analyticsWrapper.TrackEvent(EVENT_FLOW_DELETE, request.Uuid) - _, allFlows, _, _, _, err := getTenantTopologies(sv, request.Uuid) + clusterTopology, allFlows, _, _, _, err := getTenantTopologies(sv, request.Uuid) if err != nil { resourceType := "tenant" missing := api.NotFoundJSONResponse{ResourceType: resourceType, Id: request.Uuid} return api.DeleteTenantUuidFlowFlowId404JSONResponse{NotFoundJSONResponse: missing}, nil } + if request.FlowId == clusterTopology.Namespace { + // We received a request to delete the base topology so we do that + the flows + err = deleteTenantTopologies(sv, request.Uuid) + if err != nil { + errMsg := "An error occurred deleting the topologies" + errResp := api.ErrorJSONResponse{ + Error: err.Error(), + Msg: &errMsg, + } + return api.DeleteTenantUuidFlowFlowId500JSONResponse{errResp}, nil + } + + logrus.Infof("Successfully deleted topologies.") + return api.DeleteTenantUuidFlowFlowId2xxResponse{StatusCode: 200}, nil + } + if flowTopology, found := allFlows[request.FlowId]; found { logrus.Infof("deleting flow %s", request.FlowId) pluginRunner := plugins.NewPluginRunner(plugins.NewGitPluginProviderImpl(), request.Uuid, sv.db) @@ -494,14 +511,6 @@ func getTenantTopologies(sv *Server, tenantUuidStr string) (*resolved.ClusterTop return nil, nil, nil, nil, nil, fmt.Errorf("Cannot find tenant %s", tenantUuidStr) } - var clusterTopology resolved.ClusterTopology - //TODO fix it throws an error if tenant.BaseClusterTopology is nil, which is the case when there is no tenant saved in the dB yet - err = json.Unmarshal(tenant.BaseClusterTopology, &clusterTopology) - if err != nil { - logrus.Errorf("An error occurred decoding the cluster topology for tenant '%v'", tenant.TenantId) - return nil, nil, nil, nil, nil, err - } - flows := map[string]resolved.ClusterTopology{} for _, flow := range tenant.Flows { var clusterTopology resolved.ClusterTopology @@ -525,28 +534,70 @@ func getTenantTopologies(sv *Server, tenantUuidStr string) (*resolved.ClusterTop } var baseClusterTopology resolved.ClusterTopology - err = json.Unmarshal(tenant.BaseClusterTopology, &baseClusterTopology) - if err != nil { - logrus.Errorf("An error occurred decoding the cluster topology for tenant '%v'", tenantUuidStr) - return nil, nil, nil, nil, nil, err + if tenant.BaseClusterTopology != nil { + err = json.Unmarshal(tenant.BaseClusterTopology, &baseClusterTopology) + if err != nil { + logrus.Errorf("An error occurred decoding the cluster topology for tenant '%v'", tenantUuidStr) + return nil, nil, nil, nil, nil, err + } + } else { + baseClusterTopology.FlowID = prodFlowId + baseClusterTopology.Namespace = prodFlowId } var serviceConfigs []apitypes.ServiceConfig - err = json.Unmarshal(tenant.ServiceConfigs, &serviceConfigs) - if err != nil { - logrus.Errorf("An error occurred decoding the service configs for tenant '%v'", tenantUuidStr) - return nil, nil, nil, nil, nil, err + if tenant.ServiceConfigs != nil { + err = json.Unmarshal(tenant.ServiceConfigs, &serviceConfigs) + if err != nil { + logrus.Errorf("An error occurred decoding the service configs for tenant '%v'", tenantUuidStr) + return nil, nil, nil, nil, nil, err + } } var ingressConfigs []apitypes.IngressConfig - err = json.Unmarshal(tenant.IngressConfigs, &ingressConfigs) - if err != nil { - logrus.Errorf("An error occurred decoding the ingress configs for tenant '%v'", tenantUuidStr) - return nil, nil, nil, nil, nil, err + if tenant.IngressConfigs != nil { + err = json.Unmarshal(tenant.IngressConfigs, &ingressConfigs) + if err != nil { + logrus.Errorf("An error occurred decoding the ingress configs for tenant '%v'", tenantUuidStr) + return nil, nil, nil, nil, nil, err + } } + return &baseClusterTopology, flows, tenantTemplates, serviceConfigs, ingressConfigs, nil } +func deleteTenantTopologies(sv *Server, tenantUuidStr string) error { + tenant, err := sv.db.GetTenant(tenantUuidStr) + if err != nil { + logrus.Errorf("an error occured while getting the tenant %s\n: '%v'", tenantUuidStr, err.Error()) + return err + } + + tenant.BaseClusterTopology = nil + tenant.ServiceConfigs = nil + tenant.IngressConfigs = nil + + err = sv.db.SaveTenant(tenant) + if err != nil { + logrus.Errorf("an error occured while saving tenant %s. erro was \n: '%v'", tenant.TenantId, err.Error()) + return err + } + + err = sv.db.DeleteTenantFlows(tenant.TenantId) + if err != nil { + logrus.Errorf("an error occured while deleting tenant flows %s. erro was \n: '%v'", tenant.TenantId, err.Error()) + return err + } + + err = sv.db.DeleteTenantPluginConfigs(tenant.TenantId) + if err != nil { + logrus.Errorf("an error occured while deleting tenant plugin configs %s. erro was \n: '%v'", tenant.TenantId, err.Error()) + return err + } + + return nil +} + func newClIAPITemplates(templates []templates.Template) []apitypes.Template { var apiTypeTemplates []apitypes.Template for _, template := range templates { diff --git a/kontrol-service/database/flow.go b/kontrol-service/database/flow.go index 9436ac3..c74ad78 100644 --- a/kontrol-service/database/flow.go +++ b/kontrol-service/database/flow.go @@ -43,3 +43,14 @@ func (db *Db) DeleteFlow( logrus.Infof("Success! Deleted flow %s in database", flowId) return nil } + +func (db *Db) DeleteTenantFlows( + tenantId string, +) error { + result := db.db.Where("tenant_id = ?", tenantId).Delete(&Flow{}) + if result.Error != nil { + return stacktrace.Propagate(result.Error, "An internal error has occurred deleting the tenants %s flows", tenantId) + } + logrus.Infof("Success! Deleted tenant %s flows in database", tenantId) + return nil +} diff --git a/kontrol-service/database/plugin.go b/kontrol-service/database/plugin.go index 11c1ce6..19288be 100644 --- a/kontrol-service/database/plugin.go +++ b/kontrol-service/database/plugin.go @@ -43,6 +43,17 @@ func (db *Db) DeletePluginConfig( return nil } +func (db *Db) DeleteTenantPluginConfigs( + tenantId string, +) error { + result := db.db.Where("tenant_id = ?", tenantId).Delete(&PluginConfig{}) + if result.Error != nil { + return stacktrace.Propagate(result.Error, "An internal error has occurred deleting the tenant %s plugin configs", tenantId) + } + + return nil +} + func (db *Db) GetPluginConfigByFlowID( tenantId string, flowId string, diff --git a/kontrol-service/engine/flow/render.go b/kontrol-service/engine/flow/render.go index b4a060a..c275a0d 100644 --- a/kontrol-service/engine/flow/render.go +++ b/kontrol-service/engine/flow/render.go @@ -348,7 +348,16 @@ func getGateway(ingresses []*resolved.Ingress, namespace string) *istioclient.Ga } extHosts = lo.Uniq(extHosts) - ingressId := ingresses[0].IngressID + // We need to return a gateway as part of the cluster resources so we return a dummy one + // if there are no ingresses defined. This can happen when the tenant does not have a base + // cluster topology: no initial deploy or the topologies have been deleted. This gateway also allows + // us to communicate the namespace to the kardinal manager helping the resources to be cleaned up. + ingressId := "dummy" + if len(ingresses) > 0 { + ingressId = ingresses[0].IngressID + } else { + extHosts = []string{"dummy.kardinal.dev"} + } return &istioclient.Gateway{ TypeMeta: metav1.TypeMeta{