From adcd2f2673497b0c8c79142a5791b47eb3bdeeac Mon Sep 17 00:00:00 2001 From: erikzaadi Date: Mon, 4 Nov 2024 08:15:07 +0200 Subject: [PATCH] Initial effort on making tests immutable --- .github/workflows/go.yml | 13 +- pkg/defaults/defaults_test.go | 20 ++ pkg/k8s/controller_test.go | 476 ++++++++++++++++++++------------ pkg/port/blueprint/blueprint.go | 13 + pkg/port/cli/blueprint.go | 14 + 5 files changed, 355 insertions(+), 181 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4fa3f8a..2e56cb4 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 . 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: https://api.stg-01.getport.io + + - 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/defaults/defaults_test.go b/pkg/defaults/defaults_test.go index 94cfb92..3934fd1 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) @@ -57,6 +65,8 @@ func deleteDefaultResources(portClient *cli.PortClient, stateKey string) { func Test_InitIntegration_InitDefaults(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) + defer tearDownFixture(t, f) e := InitIntegration(f.portClient, &port.Config{ StateKey: f.stateKey, EventListenerType: "POLLING", @@ -85,6 +95,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", @@ -100,6 +111,7 @@ func Test_InitIntegration_InitDefaults_CreateDefaultResources_False(t *testing.T 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", @@ -128,6 +140,7 @@ func Test_InitIntegration_BlueprintExists(t *testing.T) { 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", @@ -153,6 +166,7 @@ func Test_InitIntegration_PageExists(t *testing.T) { 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()) @@ -172,6 +186,7 @@ func Test_InitIntegration_ExistingIntegration(t *testing.T) { 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()) @@ -213,6 +228,7 @@ func Test_InitIntegration_LocalResourcesConfiguration(t *testing.T) { 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()) @@ -234,6 +250,8 @@ func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_EmptyC func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_WithConfiguration_WithOverwriteConfigurationOnRestartFlag(t *testing.T) { f := NewFixture(t) + defer tearDownFixture(t, f) + expectedConfig := &port.IntegrationAppConfig{ Resources: []port.Resource{ { @@ -276,4 +294,6 @@ func Test_InitIntegration_LocalResourcesConfiguration_ExistingIntegration_WithCo 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{}) + defer tearDownFixture(t, f) + } diff --git a/pkg/k8s/controller_test.go b/pkg/k8s/controller_test.go index 7f3475f..b7aa068 100644 --- a/pkg/k8s/controller_test.go +++ b/pkg/k8s/controller_test.go @@ -3,13 +3,17 @@ 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" @@ -28,13 +32,19 @@ import ( var ( noResyncPeriodFunc = func() time.Duration { return 0 } - blueprint = "k8s-export-test-bp" + blueprintPrefix = "k8s-export-test" ) +func getBlueprintId(stateKey string) string { + stateKeySplit := strings.Split(stateKey, "-") + return fmt.Sprintf("%s-%s", blueprintPrefix, stateKeySplit[len(stateKeySplit)-1]) +} + type fixture struct { t *testing.T controller *Controller kubeClient *k8sfake.FakeDynamicClient + stateKey string } type fixtureConfig struct { @@ -46,6 +56,21 @@ type fixtureConfig struct { existingObjects []runtime.Object } +func tearDownFixture( + t *testing.T, + f *fixture, +) { + t.Logf("deleting resources for %s", f.stateKey) + _ = integration.DeleteIntegration( + f.controller.portClient, + f.stateKey, + ) + _ = blueprint.DeleteBlueprintWithEntities( + f.controller.portClient, + getBlueprintId(f.stateKey), + ) +} + func newFixture(t *testing.T, fixtureConfig *fixtureConfig) *fixture { defaultTrue := true sendRawDataExamples := &defaultTrue @@ -86,14 +111,49 @@ 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) + t.Logf("creating blueprint %s", blueprintIdentifier) + if _, err := blueprint.NewBlueprint(portClient, 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", + }, + }, + }, + }); err != nil { + t.Logf("Error creating Port blueprint: %s", err.Error()) + } + return &fixture{ t: t, controller: controller, kubeClient: kubeClient, + stateKey: newConfig.StateKey, } } @@ -111,9 +171,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 +182,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 +196,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 +206,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 +237,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 +291,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 +306,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 +388,105 @@ 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}}) 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 +495,54 @@ 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() +/* 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\"", blueprint), + Blueprint: fmt.Sprintf("\"%s\"", blueprintId), }, }) - 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}}) - f.runControllerInitialSync(&SyncResult{EntitiesSet: map[string]interface{}{fmt.Sprintf("%s;%s", blueprint, "entityToBeDeleted"): nil}, RawDataExamples: []interface{}{ud.Object}, ShouldDeleteStaleEntities: true}) + defer tearDownFixture(t, f) + 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}}) assert.Eventually(t, func() bool { - _, err := f.controller.portClient.ReadEntity(context.Background(), "entityToBeDeleted", blueprint) + _, err := f.controller.portClient.ReadEntity(context.Background(), "entityToBeDeleted", blueprintId) 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,214 +560,240 @@ 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) { - - type Property struct { - Value interface{} - ShouldSendEvent bool - } +/* + func TestUpdateHandlerWithIndividualPropertyChanges(t *testing.T) { + type Property struct { + Value interface{} + ShouldSendEvent bool + } - fullMapping := []port.Resource{ - newResource("", []port.EntityMapping{ - { - Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), - Icon: "\"Microservice\"", - Team: "\"Test\"", - Properties: map[string]string{ - "labels": ".spec.selector", - "generation": ".metadata.generation", - "generateName": ".metadata.generateName", - "creationTimestamp": ".metadata.creationTimestamp", - }, - Relations: map[string]interface{}{ - "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", + fullMapping := []port.Resource{ + newResource("", []port.EntityMapping{ + { + Identifier: ".metadata.name", + Blueprint: "\"to-be-replaced\"", + Icon: "\"Microservice\"", + Team: "\"Test\"", + Properties: map[string]string{ + "labels": ".spec.selector", + "generation": ".metadata.generation", + "generateName": ".metadata.generateName", + "creationTimestamp": ".metadata.creationTimestamp", + }, + Relations: map[string]interface{}{ + // TODO: DADUCK + "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", + }, }, - }, - }), - newResource("", []port.EntityMapping{ - - { - Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), - Icon: "\"Microservice\"", - Team: "\"Test\"", - Properties: map[string]string{}, - Relations: map[string]interface{}{}, - }, - { - Identifier: ".metadata.name", - Blueprint: fmt.Sprintf("\"%s\"", blueprint), - Icon: "\"Microservice\"", - Team: "\"Test\"", - Properties: map[string]string{ - "labels": ".spec.selector", - "generation": ".metadata.generation", - "generateName": ".metadata.generateName", - "creationTimestamp": ".metadata.creationTimestamp", + }), + newResource("", []port.EntityMapping{ + + { + Identifier: ".metadata.name", + Blueprint: "\"to-be-replaced\"", + Icon: "\"Microservice\"", + Team: "\"Test\"", + Properties: map[string]string{}, + Relations: map[string]interface{}{}, }, - Relations: map[string]interface{}{ - "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", + { + Identifier: ".metadata.name", + Blueprint: "\"to-be-replaced\"", + Icon: "\"Microservice\"", + Team: "\"Test\"", + Properties: map[string]string{ + "labels": ".spec.selector", + "generation": ".metadata.generation", + "generateName": ".metadata.generateName", + "creationTimestamp": ".metadata.creationTimestamp", + }, + Relations: map[string]interface{}{ + "k8s-relation": "\"e_AgPMYvq1tAs8TuqM\"", + }, }, - }, - }), - } - - for _, mapping := range fullMapping { - - controllerWithFullMapping := newFixture(t, &fixtureConfig{resource: mapping, existingObjects: []runtime.Object{}}).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.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"})) + for _, mapping := range fullMapping { + stateKey := guuid.NewString() + // blueprintId := getBlueprintId(stateKey) + // blueprintIdEscaped := fmt.Sprintf("\"%s\"", blueprintId) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, resource: mapping, existingObjects: []runtime.Object{}}) + defer tearDownFixture(t, f) + 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.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(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "port-k8s-exporter"})) + oldDep := newUnstructured(newDeploymentWithCustomLabels(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "port-k8s-exporter"})) + + // Update the property in the new deployment + unstructured.SetNestedField(newDep.Object, value.Value, strings.Split(property, ".")...) + + result := controllerWithFullMapping.shouldSendUpdateEvent(oldDep, newDep, true) + if value.ShouldSendEvent { + assert.True(t, result, fmt.Sprintf("Expected true when %s changes and feature flag is on", property)) + } else { + assert.False(t, result, fmt.Sprintf("Expected false when %s changes and feature flag is on", property)) + } + result = controllerWithFullMapping.shouldSendUpdateEvent(oldDep, newDep, false) + assert.True(t, result, fmt.Sprintf("Expected true when %s changes and feature flag is off", property)) + } - // Update the property in the new deployment - unstructured.SetNestedField(newDep.Object, value.Value, strings.Split(property, ".")...) + // Add a case for json update because you can't edit the json directly + newDep := newUnstructured(newDeploymentWithCustomLabels(stateKey, 2, "new-port-k8s-exporter", v1.Now(), map[string]string{"app": "port-k8s-exporter"})) + 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) - if value.ShouldSendEvent { - assert.True(t, result, fmt.Sprintf("Expected true when %s changes and feature flag is on", property)) - } else { - assert.False(t, result, fmt.Sprintf("Expected false when %s changes and feature flag is on", property)) - } + 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 %s changes and feature flag is off", property)) + assert.True(t, result, fmt.Sprintf("Expected true when labels changes and feature flag is off")) } - - // 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"})) - - 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")) } -} - +*/ func TestCreateDeploymentWithSearchIdentifier(t *testing.T) { + stateKey := guuid.NewString() + blueprintId := getBlueprintId(stateKey) id := guuid.NewString() randTxt := guuid.NewString() - d := newDeployment() + d := newDeployment(stateKey) ud := newUnstructured(d) - resource := getBaseDeploymentResource() + 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{resource: resource, existingObjects: []runtime.Object{ud}}) + f := newFixture(t, &fixtureConfig{stateKey: stateKey, 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) + 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, blueprint) + entity, err := f.controller.portClient.ReadEntity(context.Background(), id, blueprintId) if err != nil { t.Errorf("error reading entity: %v", err) } @@ -693,11 +810,12 @@ func TestCreateDeploymentWithSearchIdentifier(t *testing.T) { }, }} resource.Port.Entity.Mappings[0].Properties["bool"] = "false" - f = newFixture(t, &fixtureConfig{resource: resource, existingObjects: []runtime.Object{ud}}) + f = newFixture(t, &fixtureConfig{stateKey: stateKey, 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) + 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, blueprint) + entity, err = f.controller.portClient.ReadEntity(context.Background(), id, blueprintId) if err != nil { t.Errorf("error reading entity: %v", err) } diff --git a/pkg/port/blueprint/blueprint.go b/pkg/port/blueprint/blueprint.go index 2de0bbf..e6863d5 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 DeleteBlueprintWithEntities(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.DeleteBlueprintWithEntities(portClient, blueprintIdentifier) + if err != nil { + return fmt.Errorf("error deleting Port blueprint: %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..27f381d 100644 --- a/pkg/port/cli/blueprint.go +++ b/pkg/port/cli/blueprint.go @@ -50,6 +50,20 @@ func DeleteBlueprint(portClient *PortClient, blueprintIdentifier string) error { return nil } +func DeleteBlueprintWithEntities(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=true", blueprintIdentifier)) + if err != nil { + return err + } + if !pb.OK { + return fmt.Errorf("failed to delete blueprint, got: %s", resp.Body()) + } + return nil +} + func GetBlueprint(portClient *PortClient, blueprintIdentifier string) (*port.Blueprint, error) { pb := &port.ResponseBody{} resp, err := portClient.Client.R().