From 0cee1ced439cdafe704a1b6b21b43c2773d7ba31 Mon Sep 17 00:00:00 2001 From: erikzaadi Date: Mon, 4 Nov 2024 08:15:07 +0200 Subject: [PATCH] Make tests immutable --- .github/workflows/go.yml | 13 +- pkg/crd/crd_test.go | 88 ++++- pkg/defaults/defaults_test.go | 35 +- pkg/event_handler/polling/polling_test.go | 23 +- pkg/handlers/controllers_test.go | 180 ++++++--- pkg/k8s/controller_test.go | 452 ++++++++++++++-------- pkg/port/blueprint/blueprint.go | 13 + pkg/port/cli/blueprint.go | 44 +++ pkg/port/models.go | 6 + test_utils/cleanup.go | 13 +- test_utils/immutable.go | 11 + 11 files changed, 640 insertions(+), 238 deletions(-) create mode 100644 test_utils/immutable.go diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4fa3f8a..792edac 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -26,8 +26,17 @@ jobs: run: go clean -testcache - name: Test - run: go test -v ./... + run: go run gotest.tools/gotestsum@latest -f github-actions --junitfile ./test-results/junit.xml --format-hide-empty-pkg --junitfile-hide-empty-pkg env: PORT_CLIENT_ID: ${{ secrets.PORT_CLIENT_ID }} PORT_CLIENT_SECRET: ${{ secrets.PORT_CLIENT_SECRET }} - PORT_BASE_URL: https://api.stg-01.getport.io \ No newline at end of file + PORT_BASE_URL: ${{ secrets.PORT_BASE_URL }} + + - name: Publish Test Report + uses: mikepenz/action-junit-report@v4 + if: ${{ always() }} + with: + report_paths: './test-results/junit.xml' + include_passed: true + require_tests: true + fail_on_failure: true diff --git a/pkg/crd/crd_test.go b/pkg/crd/crd_test.go index 7c63150..8cec016 100644 --- a/pkg/crd/crd_test.go +++ b/pkg/crd/crd_test.go @@ -1,9 +1,11 @@ package crd import ( + "fmt" "slices" "testing" + guuid "github.com/google/uuid" "github.com/port-labs/port-k8s-exporter/pkg/config" "github.com/port-labs/port-k8s-exporter/pkg/port" "github.com/port-labs/port-k8s-exporter/pkg/port/blueprint" @@ -20,13 +22,27 @@ type Fixture struct { apiextensionClient *fakeapiextensionsv1.FakeApiextensionsV1 portClient *cli.PortClient portConfig *port.IntegrationAppConfig + stateKey string } -func deleteDefaultResources(portClient *cli.PortClient) { - _ = blueprint.DeleteBlueprint(portClient, "testkind") +var ( + blueprintPrefix = "k8s-crd-test" +) + +func getBlueprintId(stateKey string) string { + return testUtils.GetBlueprintIdFromPrefixAndStateKey(blueprintPrefix, stateKey) +} + +func deleteDefaultResources(stateKey string, portClient *cli.PortClient) { + blueprintId := getBlueprintId(stateKey) + _ = blueprint.DeleteBlueprintEntities(portClient, blueprintId) + _ = blueprint.DeleteBlueprint(portClient, blueprintId) } func newFixture(t *testing.T, userAgent string, namespaced bool, crdsDiscoveryPattern string) *Fixture { + + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) apiExtensionsFakeClient := fakeapiextensionsv1.FakeApiextensionsV1{Fake: &clienttesting.Fake{}} apiExtensionsFakeClient.AddReactor("list", "customresourcedefinitions", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { @@ -37,7 +53,7 @@ func newFixture(t *testing.T, userAgent string, namespaced bool, crdsDiscoveryPa Group: "testgroup", Names: v1.CustomResourceDefinitionNames{ Kind: "TestKind", - Singular: "testkind", + Singular: blueprintId, Plural: "testkinds", }, Versions: []v1.CustomResourceDefinitionVersion{ @@ -100,12 +116,28 @@ func newFixture(t *testing.T, userAgent string, namespaced bool, crdsDiscoveryPa return true, fakeCrd, nil }) + newConfig := &config.ApplicationConfiguration{ + ConfigFilePath: config.ApplicationConfig.ConfigFilePath, + ResyncInterval: config.ApplicationConfig.ResyncInterval, + PortBaseURL: config.ApplicationConfig.PortBaseURL, + EventListenerType: config.ApplicationConfig.EventListenerType, + CreateDefaultResources: config.ApplicationConfig.CreateDefaultResources, + OverwriteConfigurationOnRestart: config.ApplicationConfig.OverwriteConfigurationOnRestart, + Resources: config.ApplicationConfig.Resources, + DeleteDependents: config.ApplicationConfig.DeleteDependents, + CreateMissingRelatedEntities: config.ApplicationConfig.CreateMissingRelatedEntities, + UpdateEntityOnlyOnDiff: config.ApplicationConfig.UpdateEntityOnlyOnDiff, + PortClientId: config.ApplicationConfig.PortClientId, + PortClientSecret: config.ApplicationConfig.PortClientSecret, + StateKey: stateKey, + } + if userAgent == "" { - userAgent = "port-k8s-exporter/0.1" + userAgent = fmt.Sprintf("%s/0.1", stateKey) } - portClient := cli.New(config.ApplicationConfig) - deleteDefaultResources(portClient) + portClient := cli.New(newConfig) + deleteDefaultResources(stateKey, portClient) return &Fixture{ t: t, @@ -114,12 +146,14 @@ func newFixture(t *testing.T, userAgent string, namespaced bool, crdsDiscoveryPa portConfig: &port.IntegrationAppConfig{ CRDSToDiscover: crdsDiscoveryPattern, }, + stateKey: stateKey, } } func checkBlueprintAndActionsProperties(t *testing.T, f *Fixture, namespaced bool) { - bp, err := blueprint.GetBlueprint(f.portClient, "testkind") + blueprintId := getBlueprintId(f.stateKey) + bp, err := blueprint.GetBlueprint(f.portClient, blueprintId) if err != nil { t.Errorf("Error getting blueprint: %s", err.Error()) } @@ -153,7 +187,7 @@ func checkBlueprintAndActionsProperties(t *testing.T, f *Fixture, namespaced boo } }) - createAction, err := cli.GetAction(f.portClient, "create_testkind") + createAction, err := cli.GetAction(f.portClient, fmt.Sprintf("create_%s", blueprintId)) if err != nil { t.Errorf("Error getting create action: %s", err.Error()) } @@ -199,7 +233,7 @@ func checkBlueprintAndActionsProperties(t *testing.T, f *Fixture, namespaced boo } }) - updateAction, err := cli.GetAction(f.portClient, "update_testkind") + updateAction, err := cli.GetAction(f.portClient, fmt.Sprintf("update_%s", blueprintId)) if err != nil { t.Errorf("Error getting update action: %s", err.Error()) } @@ -239,7 +273,7 @@ func checkBlueprintAndActionsProperties(t *testing.T, f *Fixture, namespaced boo } }) - deleteAction, err := cli.GetAction(f.portClient, "delete_testkind") + deleteAction, err := cli.GetAction(f.portClient, fmt.Sprintf("delete_%s", blueprintId)) if err != nil { t.Errorf("Error getting delete action: %s", err.Error()) } @@ -263,27 +297,55 @@ func checkBlueprintAndActionsProperties(t *testing.T, f *Fixture, namespaced boo func TestCRD_crd_autoDiscoverCRDsToActionsClusterScoped(t *testing.T) { f := newFixture(t, "", false, "true") + blueprintId := getBlueprintId(f.stateKey) + AutodiscoverCRDsToActions(f.portConfig, f.apiextensionClient, f.portClient) checkBlueprintAndActionsProperties(t, f, false) - testUtils.CheckResourcesExistence(true, f.portClient, t, []string{"testkind"}, []string{}, []string{"create_testkind", "update_testkind", "delete_testkind"}) + testUtils.CheckResourcesExistence( + true, true, f.portClient, t, + []string{blueprintId}, []string{}, + []string{ + fmt.Sprintf("create_%s", blueprintId), + fmt.Sprintf("update_%s", blueprintId), + fmt.Sprintf("delete_%s", blueprintId), + }, + ) } func TestCRD_crd_autoDiscoverCRDsToActionsNamespaced(t *testing.T) { f := newFixture(t, "", true, "true") + blueprintId := getBlueprintId(f.stateKey) AutodiscoverCRDsToActions(f.portConfig, f.apiextensionClient, f.portClient) checkBlueprintAndActionsProperties(t, f, true) - testUtils.CheckResourcesExistence(true, f.portClient, t, []string{"testkind"}, []string{}, []string{"create_testkind", "update_testkind", "delete_testkind"}) + testUtils.CheckResourcesExistence( + true, true, f.portClient, t, + []string{blueprintId}, []string{}, + []string{ + fmt.Sprintf("create_%s", blueprintId), + fmt.Sprintf("update_%s", blueprintId), + fmt.Sprintf("delete_%s", blueprintId), + }, + ) } func TestCRD_crd_autoDiscoverCRDsToActionsNoCRDs(t *testing.T) { f := newFixture(t, "", false, "false") + blueprintId := getBlueprintId(f.stateKey) AutodiscoverCRDsToActions(f.portConfig, f.apiextensionClient, f.portClient) - testUtils.CheckResourcesExistence(false, f.portClient, t, []string{"testkind"}, []string{}, []string{"create_testkind", "update_testkind", "delete_testkind"}) + testUtils.CheckResourcesExistence( + false, false, f.portClient, t, + []string{blueprintId}, []string{}, + []string{ + fmt.Sprintf("create_%s", blueprintId), + fmt.Sprintf("update_%s", blueprintId), + fmt.Sprintf("delete_%s", blueprintId), + }, + ) } diff --git a/pkg/defaults/defaults_test.go b/pkg/defaults/defaults_test.go index 94cfb92..6b0262c 100644 --- a/pkg/defaults/defaults_test.go +++ b/pkg/defaults/defaults_test.go @@ -20,6 +20,14 @@ type Fixture struct { stateKey string } +func tearDownFixture( + t *testing.T, + f *Fixture, +) { + t.Logf("Deleting default resources for %s", f.stateKey) + deleteDefaultResources(f.portClient, f.stateKey) +} + func NewFixture(t *testing.T) *Fixture { stateKey := guuid.NewString() portClient := cli.New(config.ApplicationConfig) @@ -48,8 +56,11 @@ func (f *Fixture) CleanIntegration() { func deleteDefaultResources(portClient *cli.PortClient, stateKey string) { _ = integration.DeleteIntegration(portClient, stateKey) + _ = blueprint.DeleteBlueprintEntities(portClient, "workload") _ = blueprint.DeleteBlueprint(portClient, "workload") + _ = blueprint.DeleteBlueprintEntities(portClient, "namespace") _ = blueprint.DeleteBlueprint(portClient, "namespace") + _ = blueprint.DeleteBlueprintEntities(portClient, "cluster") _ = blueprint.DeleteBlueprint(portClient, "cluster") _ = page.DeletePage(portClient, "workload_overview_dashboard") _ = page.DeletePage(portClient, "availability_scorecard_dashboard") @@ -57,6 +68,7 @@ func deleteDefaultResources(portClient *cli.PortClient, stateKey string) { func Test_InitIntegration_InitDefaults(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) e := InitIntegration(f.portClient, &port.Config{ StateKey: f.stateKey, EventListenerType: "POLLING", @@ -85,6 +97,7 @@ func Test_InitIntegration_InitDefaults(t *testing.T) { func Test_InitIntegration_InitDefaults_CreateDefaultResources_False(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) e := InitIntegration(f.portClient, &port.Config{ StateKey: f.stateKey, EventListenerType: "POLLING", @@ -95,11 +108,12 @@ func Test_InitIntegration_InitDefaults_CreateDefaultResources_False(t *testing.T _, err := integration.GetIntegration(f.portClient, f.stateKey) assert.Nil(t, err) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) } func Test_InitIntegration_BlueprintExists(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) if _, err := blueprint.NewBlueprint(f.portClient, port.Blueprint{ Identifier: "workload", Title: "Workload", @@ -123,11 +137,12 @@ func Test_InitIntegration_BlueprintExists(t *testing.T) { _, err = blueprint.GetBlueprint(f.portClient, "workload") assert.Nil(t, err) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) } func Test_InitIntegration_PageExists(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) if err := page.CreatePage(f.portClient, port.Page{ Identifier: "workload_overview_dashboard", Title: "Workload Overview Dashboard", @@ -148,11 +163,12 @@ func Test_InitIntegration_PageExists(t *testing.T) { _, err = page.GetPage(f.portClient, "workload_overview_dashboard") assert.Nil(t, err) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"availability_scorecard_dashboard"}, []string{}) } func Test_InitIntegration_ExistingIntegration(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) err := integration.CreateIntegration(f.portClient, f.stateKey, "", nil) if err != nil { t.Errorf("Error creating Port integration: %s", err.Error()) @@ -167,11 +183,12 @@ func Test_InitIntegration_ExistingIntegration(t *testing.T) { _, err = integration.GetIntegration(f.portClient, f.stateKey) assert.Nil(t, err) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) } func Test_InitIntegration_LocalResourcesConfiguration(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) err := integration.CreateIntegration(f.portClient, f.stateKey, "", nil) if err != nil { t.Errorf("Error creating Port integration: %s", err.Error()) @@ -208,11 +225,12 @@ func Test_InitIntegration_LocalResourcesConfiguration(t *testing.T) { assert.Equal(t, expectedResources, i.Config.Resources) assert.Nil(t, err) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) } func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_EmptyConfiguration(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) err := integration.CreateIntegration(f.portClient, f.stateKey, "POLLING", nil) if err != nil { t.Errorf("Error creating Port integration: %s", err.Error()) @@ -229,11 +247,12 @@ func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_EmptyC assert.Nil(t, err) assert.Equal(t, "KAFKA", i.EventListener.Type) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) } func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_WithConfiguration_WithOverwriteConfigurationOnRestartFlag(t *testing.T) { f := NewFixture(t) + expectedConfig := &port.IntegrationAppConfig{ Resources: []port.Resource{ { @@ -275,5 +294,7 @@ func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_WithCo assert.Nil(t, err) assert.Equal(t, expectedConfig.Resources, i.Config.Resources) - testUtils.CheckResourcesExistence(false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + testUtils.CheckResourcesExistence(false, false, f.portClient, f.t, []string{"workload", "namespace", "cluster"}, []string{"workload_overview_dashboard", "availability_scorecard_dashboard"}, []string{}) + defer tearDownFixture(t, f) + } diff --git a/pkg/event_handler/polling/polling_test.go b/pkg/event_handler/polling/polling_test.go index 7f0fdf6..f0d17f4 100644 --- a/pkg/event_handler/polling/polling_test.go +++ b/pkg/event_handler/polling/polling_test.go @@ -31,7 +31,24 @@ func (m *MockTicker) GetC() <-chan time.Time { func NewFixture(t *testing.T, c chan time.Time) *Fixture { stateKey := guuid.NewString() - portClient := cli.New(config.ApplicationConfig) + + newConfig := &config.ApplicationConfiguration{ + ConfigFilePath: config.ApplicationConfig.ConfigFilePath, + ResyncInterval: config.ApplicationConfig.ResyncInterval, + PortBaseURL: config.ApplicationConfig.PortBaseURL, + EventListenerType: config.ApplicationConfig.EventListenerType, + CreateDefaultResources: config.ApplicationConfig.CreateDefaultResources, + OverwriteConfigurationOnRestart: config.ApplicationConfig.OverwriteConfigurationOnRestart, + Resources: config.ApplicationConfig.Resources, + DeleteDependents: config.ApplicationConfig.DeleteDependents, + CreateMissingRelatedEntities: config.ApplicationConfig.CreateMissingRelatedEntities, + UpdateEntityOnlyOnDiff: config.ApplicationConfig.UpdateEntityOnlyOnDiff, + PortClientId: config.ApplicationConfig.PortClientId, + PortClientSecret: config.ApplicationConfig.PortClientSecret, + StateKey: stateKey, + } + + portClient := cli.New(newConfig) _ = integration.DeleteIntegration(portClient, stateKey) err := integration.CreateIntegration(portClient, stateKey, "", &port.IntegrationAppConfig{ @@ -63,7 +80,7 @@ func TestPolling_DifferentConfiguration(t *testing.T) { }) c <- time.Now() - time.Sleep(time.Millisecond * 500) + time.Sleep(time.Millisecond * 1500) assert.False(t, called) _ = integration.PatchIntegration(fixture.portClient, fixture.stateKey, &port.Integration{ @@ -73,7 +90,7 @@ func TestPolling_DifferentConfiguration(t *testing.T) { }) c <- time.Now() - time.Sleep(time.Millisecond * 500) + time.Sleep(time.Millisecond * 1500) assert.True(t, called) } diff --git a/pkg/handlers/controllers_test.go b/pkg/handlers/controllers_test.go index c8db339..75e2088 100644 --- a/pkg/handlers/controllers_test.go +++ b/pkg/handlers/controllers_test.go @@ -4,13 +4,21 @@ import ( "context" "errors" "fmt" + "strings" + "sync" + "testing" + "time" + guuid "github.com/google/uuid" "github.com/port-labs/port-k8s-exporter/pkg/config" "github.com/port-labs/port-k8s-exporter/pkg/defaults" "github.com/port-labs/port-k8s-exporter/pkg/k8s" "github.com/port-labs/port-k8s-exporter/pkg/port" + "github.com/port-labs/port-k8s-exporter/pkg/port/blueprint" "github.com/port-labs/port-k8s-exporter/pkg/port/cli" + "github.com/port-labs/port-k8s-exporter/pkg/port/integration" _ "github.com/port-labs/port-k8s-exporter/test_utils" + testUtils "github.com/port-labs/port-k8s-exporter/test_utils" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -27,23 +35,24 @@ import ( k8sfake "k8s.io/client-go/dynamic/fake" fakeclientset "k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/restmapper" - "strings" - "sync" - "testing" - "time" ) var ( - blueprint = "k8s-export-test-bp" - deploymentKind = "apps/v1/deployments" - daemonSetKind = "apps/v1/daemonsets" + blueprintPrefix = "k8s-export-test" + deploymentKind = "apps/v1/deployments" + daemonSetKind = "apps/v1/daemonsets" ) +func getBlueprintId(stateKey string) string { + return testUtils.GetBlueprintIdFromPrefixAndStateKey(blueprintPrefix, stateKey) +} + type fixture struct { t *testing.T controllersHandler *ControllersHandler k8sClient *k8s.Client portClient *cli.PortClient + stateKey string } type fixtureConfig struct { @@ -55,6 +64,26 @@ type fixtureConfig struct { existingObjects []runtime.Object } +func tearDownFixture( + t *testing.T, + f *fixture, +) { + blueprintId := getBlueprintId(f.stateKey) + t.Logf("deleting resources for %s", f.stateKey) + _ = integration.DeleteIntegration( + f.portClient, + f.stateKey, + ) + _ = blueprint.DeleteBlueprintEntities( + f.portClient, + blueprintId, + ) + _ = blueprint.DeleteBlueprint( + f.portClient, + blueprintId, + ) +} + type resourceMapEntry struct { list *metav1.APIResourceList err error @@ -201,6 +230,40 @@ func newFixture(t *testing.T, fixtureConfig *fixtureConfig) *fixture { discoveryMapper := restmapper.NewDeferredDiscoveryRESTMapper(cacheClient) k8sClient := &k8s.Client{DiscoveryClient: discoveryClient, DynamicClient: dynamicClient, DiscoveryMapper: discoveryMapper, ApiExtensionClient: apiExtensionsClient} portClient := cli.New(applicationConfig) + blueprintIdentifier := getBlueprintId(exporterConfig.StateKey) + + blueprintRaw := port.Blueprint{ + Identifier: blueprintIdentifier, + Title: blueprintIdentifier, + Schema: port.BlueprintSchema{ + Properties: map[string]port.Property{ + "bool": { + Type: "boolean", + }, + "text": { + Type: "string", + }, + "num": { + Type: "number", + }, + "obj": { + Type: "object", + }, + "arr": { + Type: "array", + }, + }, + }, + } + t.Logf("creating blueprint %s", blueprintIdentifier) + if _, err := blueprint.NewBlueprint(portClient, blueprintRaw); err != nil { + t.Logf("Error creating Port blueprint: %s, retrying once", err.Error()) + if _, secondErr := blueprint.NewBlueprint(portClient, blueprintRaw); secondErr != nil { + t.Errorf("Error when retrying to create Port blueprint: %s ", secondErr.Error()) + t.FailNow() + } + } + err := defaults.InitIntegration(portClient, exporterConfig) if err != nil { t.Errorf("error initializing integration: %v", err) @@ -213,6 +276,7 @@ func newFixture(t *testing.T, fixtureConfig *fixtureConfig) *fixture { controllersHandler: controllersHandler, k8sClient: k8sClient, portClient: portClient, + stateKey: exporterConfig.StateKey, } } @@ -230,9 +294,10 @@ func newResource(selectorQuery string, mappings []port.EntityMapping, kind strin } } -func newDeployment() *appsv1.Deployment { +func newDeployment(stateKey string) *appsv1.Deployment { + blueprintId := getBlueprintId(stateKey) labels := map[string]string{ - "app": "port-k8s-exporter", + "app": blueprintId, } return &appsv1.Deployment{ TypeMeta: v1.TypeMeta{ @@ -240,8 +305,8 @@ func newDeployment() *appsv1.Deployment { APIVersion: "apps/v1", }, ObjectMeta: v1.ObjectMeta{ - Name: "port-k8s-exporter", - Namespace: "port-k8s-exporter", + Name: blueprintId, + Namespace: blueprintId, }, Spec: appsv1.DeploymentSpec{ Selector: &v1.LabelSelector{ @@ -254,8 +319,8 @@ func newDeployment() *appsv1.Deployment { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: "port-k8s-exporter", - Image: "port-k8s-exporter:latest", + Name: blueprintId, + Image: fmt.Sprintf("%s:latest", blueprintId), }, }, }, @@ -264,9 +329,10 @@ func newDeployment() *appsv1.Deployment { } } -func newDaemonSet() *appsv1.DaemonSet { +func newDaemonSet(stateKey string) *appsv1.DaemonSet { + blueprintId := getBlueprintId(stateKey) labels := map[string]string{ - "app": "port-k8s-exporter", + "app": blueprintId, } return &appsv1.DaemonSet{ TypeMeta: v1.TypeMeta{ @@ -274,8 +340,8 @@ func newDaemonSet() *appsv1.DaemonSet { APIVersion: "apps/v1", }, ObjectMeta: v1.ObjectMeta{ - Name: "port-k8s-exporter-ds", - Namespace: "port-k8s-exporter", + Name: fmt.Sprintf("%s-ds", blueprintId), + Namespace: blueprintId, }, Spec: appsv1.DaemonSetSpec{ Selector: &v1.LabelSelector{ @@ -288,8 +354,8 @@ func newDaemonSet() *appsv1.DaemonSet { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: "port-k8s-exporter", - Image: "port-k8s-exporter:latest", + Name: blueprintId, + Image: fmt.Sprintf("%s:latest", blueprintId), }, }, }, @@ -318,13 +384,15 @@ func getGvr(kind string) schema.GroupVersionResource { return schema.GroupVersionResource{Group: s[0], Version: s[1], Resource: s[2]} } -func getBaseResource(kind string) port.Resource { +func getBaseResource(stateKey string, kind string) port.Resource { + blueprintId := getBlueprintId(stateKey) + return newResource("", []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), Icon: "\"Microservice\"", - Team: "\"Test\"", + // Team: "\"Test\"", Properties: map[string]string{ "text": "\"pod\"", "num": "1", @@ -332,9 +400,9 @@ func getBaseResource(kind string) port.Resource { "obj": ".spec.selector", "arr": ".spec.template.spec.containers", }, - Relations: map[string]interface{}{ + /* Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", - }, + }, */ }, }, kind) } @@ -397,7 +465,7 @@ func (f *fixture) assertObjectsHandled(objects []struct{ kind, name string }) { } return true - }, time.Second*10, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) assert.Eventually(f.t, func() bool { entities, err := f.portClient.SearchEntities(context.Background(), port.SearchBody{ @@ -405,7 +473,7 @@ func (f *fixture) assertObjectsHandled(objects []struct{ kind, name string }) { { Property: "$datasource", Operator: "contains", - Value: "port-k8s-exporter", + Value: f.controllersHandler.stateKey, }, { Property: "$datasource", @@ -430,7 +498,7 @@ func (f *fixture) assertObjectsHandled(objects []struct{ kind, name string }) { } return err == nil && len(entities) == len(objects) - }, time.Second*10, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) } func (f *fixture) runControllersHandle() { @@ -438,38 +506,39 @@ func (f *fixture) runControllersHandle() { } func TestSuccessfulControllersHandle(t *testing.T) { - de := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + de := newDeployment(stateKey) de.Name = guuid.NewString() - da := newDaemonSet() + da := newDaemonSet(stateKey) da.Name = guuid.NewString() - resources := []port.Resource{getBaseResource(deploymentKind), getBaseResource(daemonSetKind)} - f := newFixture(t, &fixtureConfig{resources: resources, existingObjects: []runtime.Object{newUnstructured(de), newUnstructured(da)}, stateKey: guuid.NewString()}) - defer f.portClient.DeleteIntegration(f.controllersHandler.stateKey) + resources := []port.Resource{getBaseResource(stateKey, deploymentKind), getBaseResource(stateKey, daemonSetKind)} + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resources: resources, existingObjects: []runtime.Object{newUnstructured(de), newUnstructured(da)}}) // To test later that the delete stale entities is working - f.portClient.CreateEntity(context.Background(), &port.EntityRequest{Blueprint: blueprint, Identifier: guuid.NewString()}, "", false) + f.portClient.CreateEntity(context.Background(), &port.EntityRequest{Blueprint: blueprintId, Identifier: guuid.NewString()}, "", false) f.runControllersHandle() f.assertObjectsHandled([]struct{ kind, name string }{{kind: deploymentKind, name: de.Name}, {kind: daemonSetKind, name: da.Name}}) - nde := newDeployment() + nde := newDeployment(stateKey) nde.Name = guuid.NewString() f.createObjects([]*unstructured.Unstructured{newUnstructured(nde)}, deploymentKind) - nda := newDaemonSet() + nda := newDaemonSet(stateKey) nda.Name = guuid.NewString() f.createObjects([]*unstructured.Unstructured{newUnstructured(nda)}, daemonSetKind) assert.Eventually(t, func() bool { for _, eid := range []string{nde.Name, nda.Name} { - _, err := f.portClient.ReadEntity(context.Background(), eid, blueprint) + _, err := f.portClient.ReadEntity(context.Background(), eid, blueprintId) if err != nil { return false } } return true - }, time.Second*10, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) nde.Spec.Selector.MatchLabels["app"] = "new-label" f.updateObjects([]*unstructured.Unstructured{newUnstructured(nde)}, deploymentKind) @@ -477,13 +546,13 @@ func TestSuccessfulControllersHandle(t *testing.T) { f.updateObjects([]*unstructured.Unstructured{newUnstructured(da)}, daemonSetKind) assert.Eventually(t, func() bool { - entity, err := f.portClient.ReadEntity(context.Background(), nde.Name, blueprint) + entity, err := f.portClient.ReadEntity(context.Background(), nde.Name, blueprintId) if err != nil || entity.Properties["obj"].(map[string]interface{})["matchLabels"].(map[string]interface{})["app"] != nde.Spec.Selector.MatchLabels["app"] { return false } - entity, err = f.portClient.ReadEntity(context.Background(), da.Name, blueprint) + entity, err = f.portClient.ReadEntity(context.Background(), da.Name, blueprintId) return err == nil && entity.Properties["obj"].(map[string]interface{})["matchLabels"].(map[string]interface{})["app"] == nde.Spec.Selector.MatchLabels["app"] - }, time.Second*10, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) f.deleteObjects([]struct{ kind, namespace, name string }{ {kind: deploymentKind, namespace: de.Namespace, name: de.Name}, {kind: daemonSetKind, namespace: da.Namespace, name: da.Name}, @@ -491,46 +560,55 @@ func TestSuccessfulControllersHandle(t *testing.T) { assert.Eventually(t, func() bool { for _, eid := range []string{de.Name, da.Name, nde.Name, nda.Name} { - _, err := f.portClient.ReadEntity(context.Background(), eid, blueprint) + _, err := f.portClient.ReadEntity(context.Background(), eid, blueprintId) if err == nil || !strings.Contains(err.Error(), "was not found") { return false } } return true - }, time.Second*10, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) + defer tearDownFixture(t, f) } func TestControllersHandleTolerateFailure(t *testing.T) { - resources := []port.Resource{getBaseResource(deploymentKind)} - f := newFixture(t, &fixtureConfig{resources: resources, existingObjects: []runtime.Object{}}) + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + resources := []port.Resource{getBaseResource(stateKey, deploymentKind)} + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resources: resources, existingObjects: []runtime.Object{}}) f.runControllersHandle() invalidId := fmt.Sprintf("%s!@#", guuid.NewString()) - d := newDeployment() + d := newDeployment(stateKey) d.Name = invalidId f.createObjects([]*unstructured.Unstructured{newUnstructured(d)}, deploymentKind) id := guuid.NewString() d.Name = id + t.Logf("before creating %s", id) f.createObjects([]*unstructured.Unstructured{newUnstructured(d)}, deploymentKind) + t.Logf("after creating %s", id) assert.Eventually(t, func() bool { - _, err := f.portClient.ReadEntity(context.Background(), id, blueprint) + _, err := f.portClient.ReadEntity(context.Background(), id, blueprintId) return err == nil - }, time.Second*5, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) f.deleteObjects([]struct{ kind, namespace, name string }{{kind: deploymentKind, namespace: d.Namespace, name: d.Name}}) assert.Eventually(t, func() bool { - _, err := f.portClient.ReadEntity(context.Background(), id, blueprint) + _, err := f.portClient.ReadEntity(context.Background(), id, blueprintId) return err != nil && strings.Contains(err.Error(), "was not found") - }, time.Second*5, time.Millisecond*500) + }, time.Second*15, time.Millisecond*500) + defer tearDownFixture(t, f) } func TestControllersHandler_Stop(t *testing.T) { - resources := []port.Resource{getBaseResource(deploymentKind)} - f := newFixture(t, &fixtureConfig{resources: resources, existingObjects: []runtime.Object{}}) + stateKey := guuid.NewString() + + resources := []port.Resource{getBaseResource(stateKey, deploymentKind)} + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resources: resources, existingObjects: []runtime.Object{}}) + defer tearDownFixture(t, f) f.controllersHandler.Stop() assert.True(t, f.controllersHandler.isStopped) diff --git a/pkg/k8s/controller_test.go b/pkg/k8s/controller_test.go index 7f3475f..fdf38db 100644 --- a/pkg/k8s/controller_test.go +++ b/pkg/k8s/controller_test.go @@ -3,16 +3,20 @@ package k8s import ( "context" "fmt" - guuid "github.com/google/uuid" - "github.com/port-labs/port-k8s-exporter/pkg/signal" "reflect" "strings" "testing" "time" + guuid "github.com/google/uuid" + "github.com/port-labs/port-k8s-exporter/pkg/port/blueprint" + "github.com/port-labs/port-k8s-exporter/pkg/port/cli" + "github.com/port-labs/port-k8s-exporter/pkg/port/integration" + "github.com/port-labs/port-k8s-exporter/pkg/signal" + "github.com/port-labs/port-k8s-exporter/pkg/config" "github.com/port-labs/port-k8s-exporter/pkg/port" - _ "github.com/port-labs/port-k8s-exporter/test_utils" + testUtils "github.com/port-labs/port-k8s-exporter/test_utils" "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -28,13 +32,18 @@ import ( var ( noResyncPeriodFunc = func() time.Duration { return 0 } - blueprint = "k8s-export-test-bp" + blueprintPrefix = "k8s-export-test" ) +func getBlueprintId(stateKey string) string { + return testUtils.GetBlueprintIdFromPrefixAndStateKey(blueprintPrefix, stateKey) +} + type fixture struct { t *testing.T controller *Controller kubeClient *k8sfake.FakeDynamicClient + stateKey string } type fixtureConfig struct { @@ -46,6 +55,26 @@ type fixtureConfig struct { existingObjects []runtime.Object } +func tearDownFixture( + t *testing.T, + f *fixture, +) { + blueprintId := getBlueprintId(f.stateKey) + t.Logf("deleting resources for %s", f.stateKey) + _ = integration.DeleteIntegration( + f.controller.portClient, + f.stateKey, + ) + _ = blueprint.DeleteBlueprintEntities( + f.controller.portClient, + blueprintId, + ) + _ = blueprint.DeleteBlueprint( + f.controller.portClient, + blueprintId, + ) +} + func newFixture(t *testing.T, fixtureConfig *fixtureConfig) *fixture { defaultTrue := true sendRawDataExamples := &defaultTrue @@ -86,14 +115,54 @@ func newFixture(t *testing.T, fixtureConfig *fixtureConfig) *fixture { newConfig.StateKey = fixtureConfig.stateKey } + if newConfig.StateKey == "" { + newConfig.StateKey = guuid.NewString() + } + + portClient := cli.New(newConfig) + blueprintIdentifier := getBlueprintId(newConfig.StateKey) + kubeClient := k8sfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), newGvrToListKind(), fixtureConfig.existingObjects...) controller := newController(t, fixtureConfig.resource, kubeClient, interationConfig, newConfig) controller.portClient.Authenticate(context.Background(), newConfig.PortClientId, newConfig.PortClientSecret) + blueprintRaw := port.Blueprint{ + Identifier: blueprintIdentifier, + Title: blueprintIdentifier, + Schema: port.BlueprintSchema{ + Properties: map[string]port.Property{ + "bool": { + Type: "boolean", + }, + "text": { + Type: "string", + }, + "num": { + Type: "number", + }, + "obj": { + Type: "object", + }, + "arr": { + Type: "array", + }, + }, + }, + } + + t.Logf("creating blueprint %s", blueprintIdentifier) + if _, err := blueprint.NewBlueprint(portClient, blueprintRaw); err != nil { + t.Logf("Error creating Port blueprint: %s, retrying", err.Error()) + if _, secondErr := blueprint.NewBlueprint(portClient, blueprintRaw); secondErr != nil { + t.Logf("Error when retrying to create Port blueprint: %s ", secondErr.Error()) + } + } + return &fixture{ t: t, controller: controller, kubeClient: kubeClient, + stateKey: newConfig.StateKey, } } @@ -111,9 +180,10 @@ func newResource(selectorQuery string, mappings []port.EntityMapping) port.Resou } } -func newDeployment() *appsv1.Deployment { +func newDeployment(stateKey string) *appsv1.Deployment { + blueprintIdentifier := getBlueprintId(stateKey) labels := map[string]string{ - "app": "port-k8s-exporter", + "app": blueprintIdentifier, } return &appsv1.Deployment{ TypeMeta: v1.TypeMeta{ @@ -121,8 +191,8 @@ func newDeployment() *appsv1.Deployment { APIVersion: "apps/v1", }, ObjectMeta: v1.ObjectMeta{ - Name: "port-k8s-exporter", - Namespace: "port-k8s-exporter", + Name: blueprintIdentifier, + Namespace: blueprintIdentifier, }, Spec: appsv1.DeploymentSpec{ Selector: &v1.LabelSelector{ @@ -135,8 +205,8 @@ func newDeployment() *appsv1.Deployment { Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: "port-k8s-exporter", - Image: "port-k8s-exporter:latest", + Name: blueprintIdentifier, + Image: fmt.Sprintf("%s:latest", blueprintIdentifier), }, }, }, @@ -145,19 +215,22 @@ func newDeployment() *appsv1.Deployment { } } -func newDeploymentWithCustomLabels(generation int64, +func newDeploymentWithCustomLabels( + stateKey string, + generation int64, generateName string, creationTimestamp v1.Time, labels map[string]string, ) *appsv1.Deployment { + blueprintIdentifier := getBlueprintId(stateKey) return &appsv1.Deployment{ TypeMeta: v1.TypeMeta{ Kind: "Deployment", APIVersion: "apps/v1", }, ObjectMeta: v1.ObjectMeta{ - Name: "port-k8s-exporter", - Namespace: "port-k8s-exporter", + Name: blueprintIdentifier, + Namespace: blueprintIdentifier, GenerateName: generateName, Generation: generation, CreationTimestamp: creationTimestamp, @@ -173,8 +246,8 @@ func newDeploymentWithCustomLabels(generation int64, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: "port-k8s-exporter", - Image: "port-k8s-exporter:latest", + Name: blueprintIdentifier, + Image: fmt.Sprintf("%s:latest", blueprintIdentifier), }, }, }, @@ -227,13 +300,14 @@ func getKey(deployment *appsv1.Deployment, t *testing.T) string { return key } -func getBaseDeploymentResource() port.Resource { +func getBaseDeploymentResource(stateKey string) port.Resource { return newResource("", []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", getBlueprintId(stateKey)), Icon: "\"Microservice\"", - Team: "\"Test\"", + // TODO: Create this explicitly and suffix with stateKey + // Team: "\"Test\"", Properties: map[string]string{ "text": "\"pod\"", "num": "1", @@ -241,9 +315,10 @@ func getBaseDeploymentResource() port.Resource { "obj": ".spec.selector", "arr": ".spec.template.spec.containers", }, - Relations: map[string]interface{}{ + /* Relations: map[string]interface{}{ + // TODO: Explicitly build this :( perhaps with stateKey as well "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", - }, + }, */ }, }) } @@ -322,88 +397,106 @@ func (f *fixture) runControllerEventsSync() { } func TestSuccessfulRunInitialSync(t *testing.T) { - ud1 := newUnstructured(newDeployment()) + stateKey := guuid.NewString() + blueprintIdentifier := getBlueprintId(stateKey) + ud1 := newUnstructured(newDeployment(stateKey)) ud1.SetName("deployment1") - ud2 := newUnstructured(newDeployment()) + ud2 := newUnstructured(newDeployment(stateKey)) ud2.SetName("deployment2") - f := newFixture(t, &fixtureConfig{resource: getBaseDeploymentResource(), existingObjects: []runtime.Object{ud1, ud2}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: getBaseDeploymentResource(stateKey), existingObjects: []runtime.Object{ud1, ud2}}) + defer tearDownFixture(t, f) f.runControllerInitialSync(&SyncResult{ - EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, ud1.GetName()): nil, fmt.Sprintf("%s;%s", blueprint, ud2.GetName()): nil}, + EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintIdentifier, ud1.GetName()): nil, fmt.Sprintf("%s;%s", blueprintIdentifier, ud2.GetName()): nil}, RawDataExamples: []interface{}{ud1.Object, ud2.Object}, ShouldDeleteStaleEntities: true, }) } func TestRunInitialSyncWithSelectorQuery(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + d := newDeployment(stateKey) ud := newUnstructured(d) - notSelectedResource := getBaseDeploymentResource() - notSelectedResource.Selector.Query = ".metadata.name != \"port-k8s-exporter\"" - f := newFixture(t, &fixtureConfig{resource: notSelectedResource, existingObjects: []runtime.Object{ud}}) + notSelectedResource := getBaseDeploymentResource(stateKey) + notSelectedResource.Selector.Query = fmt.Sprintf(".metadata.name != \"%s\"", getBlueprintId(stateKey)) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: notSelectedResource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{}, ShouldDeleteStaleEntities: true}) } func TestRunInitialSyncWithBadPropMapping(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + d := newDeployment(stateKey) ud := newUnstructured(d) - badPropMappingResource := getBaseDeploymentResource() + badPropMappingResource := getBaseDeploymentResource(stateKey) badPropMappingResource.Port.Entity.Mappings[0].Properties["text"] = "bad-jq" - f := newFixture(t, &fixtureConfig{resource: badPropMappingResource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: badPropMappingResource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{}, ShouldDeleteStaleEntities: false}) } func TestRunInitialSyncWithBadEntity(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + d := newDeployment(stateKey) ud := newUnstructured(d) - badEntityResource := getBaseDeploymentResource() + badEntityResource := getBaseDeploymentResource(stateKey) badEntityResource.Port.Entity.Mappings[0].Identifier = "\"!@#\"" - f := newFixture(t, &fixtureConfig{resource: badEntityResource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: badEntityResource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: false}) } func TestRunInitialSyncWithBadSelector(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + d := newDeployment(stateKey) ud := newUnstructured(d) - badSelectorResource := getBaseDeploymentResource() + badSelectorResource := getBaseDeploymentResource(stateKey) badSelectorResource.Selector.Query = "bad-jq" - f := newFixture(t, &fixtureConfig{resource: badSelectorResource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: badSelectorResource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{}, ShouldDeleteStaleEntities: false}) } func TestRunEventsSyncWithCreateEvent(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + d := newDeployment(stateKey) ud := newUnstructured(d) id := guuid.NewString() + blueprintId := getBlueprintId(stateKey) resource := newResource("", []port.EntityMapping{ { Identifier: fmt.Sprintf("\"%s\"", id), - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), }, }) - f := newFixture(t, &fixtureConfig{stateKey: config.ApplicationConfig.StateKey, resource: resource, existingObjects: []runtime.Object{}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{}}) + + defer tearDownFixture(t, f) f.createObjects([]*unstructured.Unstructured{ud}) - defer f.controller.portClient.DeleteEntity(context.Background(), id, blueprint, true) + defer f.controller.portClient.DeleteEntity(context.Background(), id, blueprintId, true) f.runControllerEventsSync() assert.Eventually(t, func() bool { - _, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprint) + _, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprintId) return err == nil }, time.Second*5, time.Millisecond*500) } func TestRunEventsSyncWithUpdateEvent(t *testing.T) { id := guuid.NewString() - resource := getBaseDeploymentResource() + stateKey := guuid.NewString() + blueprintIdentifier := getBlueprintId(stateKey) + resource := getBaseDeploymentResource(stateKey) resource.Port.Entity.Mappings[0].Identifier = fmt.Sprintf("\"%s\"", id) resource.Port.Entity.Mappings[0].Properties["bool"] = ".spec.selector.matchLabels.app == \"new-label\"" - d := newDeployment() + d := newDeployment(stateKey) ud := newUnstructured(d) - f := newFixture(t, &fixtureConfig{stateKey: config.ApplicationConfig.StateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) - defer f.controller.portClient.DeleteEntity(context.Background(), id, blueprint, true) - f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, id): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}) + defer f.controller.portClient.DeleteEntity(context.Background(), id, getBlueprintId(stateKey), true) + defer tearDownFixture(t, f) + f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintIdentifier, id): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}) assert.Eventually(t, func() bool { - entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprint) + entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprintIdentifier) return err == nil && entity.Properties["bool"] == false }, time.Second*5, time.Millisecond*500) @@ -412,46 +505,31 @@ func TestRunEventsSyncWithUpdateEvent(t *testing.T) { f.runControllerEventsSync() assert.Eventually(t, func() bool { - entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprint) + entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprintIdentifier) return err == nil && entity.Properties["bool"] == true }, time.Second*5, time.Millisecond*500) } -func TestRunEventsSyncWithDeleteEvent(t *testing.T) { - d := newDeployment() - ud := newUnstructured(d) - resource := newResource("", []port.EntityMapping{ - { - Identifier: "\"entityToBeDeleted\"", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), - }, - }) - f := newFixture(t, &fixtureConfig{stateKey: config.ApplicationConfig.StateKey, resource: resource, existingObjects: []runtime.Object{ud}}) - - f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, "entityToBeDeleted"): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}) - f.deleteObjects([]struct{ namespace, name string }{{namespace: d.Namespace, name: d.Name}}) - - assert.Eventually(t, func() bool { - _, err := f.controller.portClient.ReadEntity(context.Background(), "entityToBeDeleted", blueprint) - return err != nil && strings.Contains(err.Error(), "was not found") - }, time.Second*5, time.Millisecond*500) - -} - func TestCreateDeployment(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) - resource := getBaseDeploymentResource() + resource := getBaseDeploymentResource(stateKey) item := EventItem{Key: getKey(d, t), ActionType: CreateAction} - f := newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) - f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, d.Name): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) + f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintId, d.Name): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) } +/* // TODO: Verify relations here func TestCreateDeploymentWithSearchRelation(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) item := EventItem{Key: getKey(d, t), ActionType: CreateAction} - resource := getBaseDeploymentResource() + resource := getBaseDeploymentResource(stateKey) resource.Port.Entity.Mappings[0].Relations = map[string]interface{}{ "k8s-relation": map[string]interface{}{ "combinator": "\"or\"", @@ -469,107 +547,174 @@ func TestCreateDeploymentWithSearchRelation(t *testing.T) { }, }, } - f := newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) - f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, d.Name): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) -} + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) + f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintId, d.Name): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) +} */ func TestUpdateDeployment(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) - resource := getBaseDeploymentResource() + resource := getBaseDeploymentResource(stateKey) item := EventItem{Key: getKey(d, t), ActionType: UpdateAction} - f := newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) - f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, d.Name): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) + f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintId, d.Name): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) } func TestDeleteDeploymentSameOwner(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) resource := newResource("", []port.EntityMapping{ { Identifier: "\"entityWithSameOwner\"", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), }, }) createItem := EventItem{Key: getKey(d, t), ActionType: CreateAction} item := EventItem{Key: getKey(d, t), ActionType: DeleteAction} - f := newFixture(t, &fixtureConfig{stateKey: config.ApplicationConfig.StateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) - f.runControllerSyncHandler(createItem, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;entityWithSameOwner", blueprint): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + f.runControllerSyncHandler(createItem, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;entityWithSameOwner", blueprintId): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) - _, err := f.controller.portClient.ReadEntity(context.Background(), "entityWithSameOwner", blueprint) + _, err := f.controller.portClient.ReadEntity(context.Background(), "entityWithSameOwner", blueprintId) if err != nil && !strings.Contains(err.Error(), "was not found") { t.Errorf("expected entity to be deleted") } } +/* func TestDeleteDeploymentDifferentOwner(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) resource := newResource("", []port.EntityMapping{ { Identifier: "\"entityWithDifferentOwner\"", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), }, }) createItem := EventItem{Key: getKey(d, t), ActionType: CreateAction} item := EventItem{Key: getKey(d, t), ActionType: DeleteAction} f := newFixture(t, &fixtureConfig{stateKey: "non_exist_statekey", resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) - f.runControllerSyncHandler(createItem, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;entityWithDifferentOwner", blueprint): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + f.runControllerSyncHandler(createItem, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;entityWithDifferentOwner", blueprintId): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) - _, err := f.controller.portClient.ReadEntity(context.Background(), "entityWithDifferentOwner", blueprint) + _, err := f.controller.portClient.ReadEntity(context.Background(), "entityWithDifferentOwner", blueprintId) if err != nil && strings.Contains(err.Error(), "was not found") { t.Errorf("expected entity to exist") } -} +} */ func TestSelectorQueryFilterDeployment(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) - resource := newResource(".metadata.name != \"port-k8s-exporter\"", []port.EntityMapping{ + resource := newResource(fmt.Sprintf(".metadata.name != \"%s\"", blueprintId), []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"wrong-%s\"", blueprint), + Blueprint: fmt.Sprintf("\"wrong-%s\"", blueprintId), }, }) item := EventItem{Key: getKey(d, t), ActionType: DeleteAction} - f := newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{}, ShouldDeleteStaleEntities: true}, false) } func TestFailPortAuth(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) resource := newResource("", []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), }, }) item := EventItem{Key: getKey(d, t), ActionType: CreateAction} - f := newFixture(t, &fixtureConfig{portClientId: "wrongclientid", portClientSecret: "wrongclientsecret", resource: resource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, portClientId: "wrongclientid", portClientSecret: "wrongclientsecret", resource: resource, existingObjects: []runtime.Object{ud}}) + // defer tearDownFixture(t, f) f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: nil, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: false}, true) } func TestFailDeletePortEntity(t *testing.T) { - d := newDeployment() + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) ud := newUnstructured(d) resource := newResource("", []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"wrong-%s\"", blueprint), + Blueprint: fmt.Sprintf("\"wrong-%s\"", blueprintId), }, }) item := EventItem{Key: getKey(d, t), ActionType: DeleteAction} - f := newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + defer tearDownFixture(t, f) f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) } -func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { +func TestCreateDeploymentWithSearchIdentifier(t *testing.T) { + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + id := guuid.NewString() + randTxt := guuid.NewString() + d := newDeployment(stateKey) + ud := newUnstructured(d) + resource := getBaseDeploymentResource(stateKey) + resource.Port.Entity.Mappings[0].Identifier = fmt.Sprintf("\"%s\"", id) + resource.Port.Entity.Mappings[0].Properties["text"] = fmt.Sprintf("\"%s\"", randTxt) + resource.Port.Entity.Mappings[0].Properties["bool"] = "true" + item := EventItem{Key: getKey(d, t), ActionType: CreateAction} + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + + f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintId, id): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + + entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprintId) + if err != nil { + t.Errorf("error reading entity: %v", err) + } + assert.True(t, entity.Properties["bool"] == true, fmt.Sprintf("expected bool to be true, got: %v", entity.Properties["bool"])) + + item = EventItem{Key: getKey(d, t), ActionType: UpdateAction} + resource.Port.Entity.Mappings[0].Identifier = map[string]interface{}{ + "combinator": "\"and\"", + "rules": []interface{}{ + map[string]interface{}{ + "property": "\"text\"", + "operator": "\"=\"", + "value": fmt.Sprintf("\"%s\"", randTxt), + }, + }} + resource.Port.Entity.Mappings[0].Properties["bool"] = "false" + f = newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) + + defer tearDownFixture(t, f) + f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintId, id): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + entity, err = f.controller.portClient.ReadEntity(context.Background(), id, blueprintId) + if err != nil { + t.Errorf("error reading entity: %v", err) + } + assert.True(t, entity.Properties["bool"] == false, fmt.Sprintf("expected bool to be false, got: %v", entity.Properties["bool"])) + + deleteItem := EventItem{Key: getKey(d, t), ActionType: DeleteAction} + f.runControllerSyncHandler(deleteItem, &SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) +} + +func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { type Property struct { Value interface{} ShouldSendEvent bool @@ -579,33 +724,33 @@ func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { newResource("", []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: "\"to-be-replaced\"", Icon: "\"Microservice\"", - Team: "\"Test\"", + // Team: "\"Test\"", Properties: map[string]string{ "labels": ".spec.selector", "generation": ".metadata.generation", "generateName": ".metadata.generateName", "creationTimestamp": ".metadata.creationTimestamp", }, - Relations: map[string]interface{}{ + /* Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", - }, + }, */ }, }), newResource("", []port.EntityMapping{ { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: "\"to-be-replaced\"", Icon: "\"Microservice\"", - Team: "\"Test\"", + // Team: "\"Test\"", Properties: map[string]string{}, Relations: map[string]interface{}{}, }, { Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), + Blueprint: "\"to-be-replaced\"", Icon: "\"Microservice\"", Team: "\"Test\"", Properties: map[string]string{ @@ -614,29 +759,31 @@ func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { "generateName": ".metadata.generateName", "creationTimestamp": ".metadata.creationTimestamp", }, - Relations: map[string]interface{}{ + /* Relations: map[string]interface{}{ "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", - }, + }, */ }, }), } for _, mapping := range fullMapping { + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) - controllerWithFullMapping := newFixture(t, &fixtureConfig{resource: mapping, existingObjects: []runtime.Object{}}).controller - + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: mapping, existingObjects: []runtime.Object{}}) + controllerWithFullMapping := f.controller // Test changes in each individual property properties := map[string]Property{ - "metadata.name": {Value: "port-k8s-exporter", ShouldSendEvent: false}, - "something_without_mapping": {Value: "port-k8s-exporter", ShouldSendEvent: false}, + "metadata.name": {Value: blueprintId, ShouldSendEvent: false}, + "something_without_mapping": {Value: blueprintId, ShouldSendEvent: false}, "metadata.generation": {Value: int64(3), ShouldSendEvent: true}, "metadata.generateName": {Value: "new-port-k8s-exporter2", ShouldSendEvent: true}, "metadata.creationTimestamp": {Value: v1.Now().Add(1 * time.Hour).Format(time.RFC3339), ShouldSendEvent: true}, } for property, value := range properties { - newDep := newUnstructured(newDeploymentWithCustomLabels(2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "port-k8s-exporter"})) - oldDep := newUnstructured(newDeploymentWithCustomLabels(2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "port-k8s-exporter"})) + newDep := newUnstructured(newDeploymentWithCustomLabels(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": blueprintId})) + oldDep := newUnstructured(newDeploymentWithCustomLabels(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": blueprintId})) // Update the property in the new deployment unstructured.SetNestedField(newDep.Object, value.Value, strings.Split(property, ".")...) @@ -652,57 +799,40 @@ func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { } // Add a case for json update because you can't edit the json directly - newDep := newUnstructured(newDeploymentWithCustomLabels(2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "port-k8s-exporter"})) - oldDep := newUnstructured(newDeploymentWithCustomLabels(2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "new-port-k8s-exporter"})) + newDep := newUnstructured(newDeploymentWithCustomLabels(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": stateKey})) + oldDep := newUnstructured(newDeploymentWithCustomLabels(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "new-port-k8s-exporter"})) result := controllerWithFullMapping.shouldSendUpdateEvent(oldDep, newDep, true) assert.True(t, result, fmt.Sprintf("Expected true when labels changes and feature flag is on")) result = controllerWithFullMapping.shouldSendUpdateEvent(oldDep, newDep, false) assert.True(t, result, fmt.Sprintf("Expected true when labels changes and feature flag is off")) + defer tearDownFixture(t, f) } } -func TestCreateDeploymentWithSearchIdentifier(t *testing.T) { - id := guuid.NewString() - randTxt := guuid.NewString() - d := newDeployment() - ud := newUnstructured(d) - resource := getBaseDeploymentResource() - resource.Port.Entity.Mappings[0].Identifier = fmt.Sprintf("\"%s\"", id) - resource.Port.Entity.Mappings[0].Properties["text"] = fmt.Sprintf("\"%s\"", randTxt) - resource.Port.Entity.Mappings[0].Properties["bool"] = "true" - item := EventItem{Key: getKey(d, t), ActionType: CreateAction} - f := newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) - - f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, id): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) +/* - entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprint) - if err != nil { - t.Errorf("error reading entity: %v", err) - } - assert.True(t, entity.Properties["bool"] == true, fmt.Sprintf("expected bool to be true, got: %v", entity.Properties["bool"])) - - item = EventItem{Key: getKey(d, t), ActionType: UpdateAction} - resource.Port.Entity.Mappings[0].Identifier = map[string]interface{}{ - "combinator": "\"and\"", - "rules": []interface{}{ - map[string]interface{}{ - "property": "\"text\"", - "operator": "\"=\"", - "value": fmt.Sprintf("\"%s\"", randTxt), - }, - }} - resource.Port.Entity.Mappings[0].Properties["bool"] = "false" - f = newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) - - f.runControllerSyncHandler(item, &SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, id): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) +func TestRunEventsSyncWithDeleteEvent(t *testing.T) { + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) + d := newDeployment(stateKey) + ud := newUnstructured(d) + resource := newResource("", []port.EntityMapping{ + { + Identifier: "\"entityToBeDeleted\"", + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), + }, + }) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: resource, existingObjects: []runtime.Object{ud}}) - entity, err = f.controller.portClient.ReadEntity(context.Background(), id, blueprint) - if err != nil { - t.Errorf("error reading entity: %v", err) - } - assert.True(t, entity.Properties["bool"] == false, fmt.Sprintf("expected bool to be false, got: %v", entity.Properties["bool"])) + f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprintId, "entityToBeDeleted"): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}) + f.deleteObjects([]struct{ namespace, name string }{{namespace: d.Namespace, name: d.Name}}) - deleteItem := EventItem{Key: getKey(d, t), ActionType: DeleteAction} - f.runControllerSyncHandler(deleteItem, &SyncResult{EntitiesSet: map[string]interface{}{}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}, false) + assert.Eventually(t, func() bool { + _, err := f.controller.portClient.ReadEntity(context.Background(), "entityToBeDeleted", blueprintId) + return err != nil && strings.Contains(err.Error(), "was not found") + }, time.Second*15, time.Millisecond*500) + defer tearDownFixture(t, f) } + +*/ diff --git a/pkg/port/blueprint/blueprint.go b/pkg/port/blueprint/blueprint.go index 2de0bbf..b7defb6 100644 --- a/pkg/port/blueprint/blueprint.go +++ b/pkg/port/blueprint/blueprint.go @@ -48,6 +48,19 @@ func DeleteBlueprint(portClient *cli.PortClient, blueprintIdentifier string) err return nil } +func DeleteBlueprintEntities(portClient *cli.PortClient, blueprintIdentifier string) error { + _, err := portClient.Authenticate(context.Background(), portClient.ClientID, portClient.ClientSecret) + if err != nil { + return fmt.Errorf("error authenticating with Port: %v", err) + } + + err = cli.DeleteBlueprintEntities(portClient, blueprintIdentifier) + if err != nil { + return fmt.Errorf("error deleting Port blueprint entities: %v", err) + } + return nil +} + func GetBlueprint(portClient *cli.PortClient, blueprintIdentifier string) (*port.Blueprint, error) { _, err := portClient.Authenticate(context.Background(), portClient.ClientID, portClient.ClientSecret) if err != nil { diff --git a/pkg/port/cli/blueprint.go b/pkg/port/cli/blueprint.go index 4ca5fa8..1f7e969 100644 --- a/pkg/port/cli/blueprint.go +++ b/pkg/port/cli/blueprint.go @@ -2,6 +2,8 @@ package cli import ( "fmt" + "slices" + "time" "github.com/port-labs/port-k8s-exporter/pkg/port" ) @@ -50,6 +52,48 @@ func DeleteBlueprint(portClient *PortClient, blueprintIdentifier string) error { return nil } +func DeleteBlueprintEntities(portClient *PortClient, blueprintIdentifier string) error { + pb := &port.ResponseBody{} + resp, err := portClient.Client.R(). + SetResult(&pb). + Delete(fmt.Sprintf("v1/blueprints/%s/all-entities?delete_blueprint=false", blueprintIdentifier)) + if err != nil { + return err + } + + if !pb.OK { + return fmt.Errorf("failed to delete blueprint, got: %s", resp.Body()) + } + + migrationId := pb.MigrationId + + inProgressStatuses := []string{ + "RUNNING", + "INITIALIZING", + "PENDING", + } + + isCompleted := false + for !isCompleted { + migrResp, migrErr := portClient.Client.R(). + SetResult(&pb). + Get(fmt.Sprintf("v1/migrations/%s", migrationId)) + + if migrErr != nil { + return fmt.Errorf("failed to fetch entities delete migration for '%s', got: %s", migrationId, migrResp.Body()) + } + + if slices.Contains(inProgressStatuses, pb.Migration.Status) { + time.Sleep(2 * time.Second) + } else { + isCompleted = true + } + + } + + return nil +} + func GetBlueprint(portClient *PortClient, blueprintIdentifier string) (*port.Blueprint, error) { pb := &port.ResponseBody{} resp, err := portClient.Client.R(). diff --git a/pkg/port/models.go b/pkg/port/models.go index 8e2edff..d568b1d 100644 --- a/pkg/port/models.go +++ b/pkg/port/models.go @@ -227,6 +227,12 @@ type ResponseBody struct { OrgDetails OrgDetails `json:"organization"` Scorecard Scorecard `json:"scorecard"` Pages Page `json:"pages"` + MigrationId string `json:"migrationId"` + Migration Migration `json:"migration"` +} + +type Migration struct { + Status string `json:"status"` } type IntegrationKindsResponse struct { diff --git a/test_utils/cleanup.go b/test_utils/cleanup.go index 2f12c25..39a5c7f 100644 --- a/test_utils/cleanup.go +++ b/test_utils/cleanup.go @@ -9,7 +9,15 @@ import ( "github.com/stretchr/testify/assert" ) -func CheckResourcesExistence(shouldExist bool, portClient *cli.PortClient, t *testing.T, blueprints []string, pages []string, actions []string) { +func CheckResourcesExistence( + shouldExist bool, + shouldDeleteEntities bool, + portClient *cli.PortClient, + t *testing.T, + blueprints []string, + pages []string, + actions []string, +) { for _, a := range actions { _, err := cli.GetAction(portClient, a) if err == nil { @@ -25,6 +33,9 @@ func CheckResourcesExistence(shouldExist bool, portClient *cli.PortClient, t *te for _, bp := range blueprints { _, err := blueprint.GetBlueprint(portClient, bp) if err == nil { + if shouldDeleteEntities { + _ = blueprint.DeleteBlueprintEntities(portClient, bp) + } _ = blueprint.DeleteBlueprint(portClient, bp) } if shouldExist { diff --git a/test_utils/immutable.go b/test_utils/immutable.go new file mode 100644 index 0000000..11686ca --- /dev/null +++ b/test_utils/immutable.go @@ -0,0 +1,11 @@ +package testing_init + +import ( + "fmt" + "strings" +) + +func GetBlueprintIdFromPrefixAndStateKey(blueprintPrefix string, stateKey string) string { + stateKeySplit := strings.Split(stateKey, "-") + return fmt.Sprintf("%s-%s", blueprintPrefix, stateKeySplit[len(stateKeySplit)-1]) +}