diff --git a/pkg/streamline/applier/applier.go b/pkg/streamline/applier/applier.go index b7698f169..ae6543e80 100644 --- a/pkg/streamline/applier/applier.go +++ b/pkg/streamline/applier/applier.go @@ -48,6 +48,11 @@ func (in *Applier) Apply(ctx context.Context, serviceErrorList := make([]client.ServiceErrorAttributes, 0) toSkip := make([]unstructured.Unstructured, 0) + err := in.store.SyncServiceComponents(service.ID, resources) + if err != nil { + return componentList, serviceErrorList, err + } + deleteFilterFunc, err := in.getDeleteFilterFunc(service.ID) if err != nil { return componentList, serviceErrorList, err @@ -189,7 +194,57 @@ func (in *Applier) Apply(ctx context.Context, componentList[idx].Children = lo.ToSlicePtr(children) } - return componentList, serviceErrorList, nil + // Append unsynced resources to the component list so all of them will be visible in the UI. + componentList, err = in.appendUnsyncedResources(service.ID, componentList, resources) + return componentList, serviceErrorList, err +} + +func (in *Applier) appendUnsyncedResources(serviceId string, components []client.ComponentAttributes, resources []unstructured.Unstructured) ([]client.ComponentAttributes, error) { + result := make([]client.ComponentAttributes, 0) + keys := containers.NewSet[smcommon.Key]() + + for _, component := range components { + result = append(result, component) + keys.Add(smcommon.NewStoreKeyFromComponentAttributes(component).Key()) + } + + hooks, err := in.store.GetHookComponents(serviceId) + if err != nil { + return nil, err + } + + // Create a map of hooks for an easy lookup. + keyToHookComponent := make(map[smcommon.Key]smcommon.HookComponent) + for _, hook := range hooks { + keyToHookComponent[hook.StoreKey().Key()] = hook + } + + for _, resource := range resources { + key := smcommon.NewStoreKeyFromUnstructured(resource).Key() + + if !keys.Has(key) { + // If a resource has any delete policy, check if it was already deleted. + // If it was deleted, then do not include it in the result. + deletePolicies := smcommon.ParseHookDeletePolicy(resource) + hook, ok := keyToHookComponent[key] + if len(deletePolicies) > 0 && ok && hook.HasDesiredState(deletePolicies) { + continue + } + + gvk := resource.GroupVersionKind() + result = append(result, client.ComponentAttributes{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + Name: resource.GetName(), + Namespace: resource.GetNamespace(), + Synced: false, + State: lo.ToPtr(client.ComponentStatePending), + }) + } + } + + return result, nil } func (in *Applier) Destroy(ctx context.Context, serviceID string) ([]client.ComponentAttributes, error) { @@ -275,7 +330,7 @@ var ( ) func (in *Applier) getDeleteFilterFunc(serviceID string) (func(resources []unstructured.Unstructured) (toDelete []unstructured.Unstructured, toApply []unstructured.Unstructured), error) { - components, err := in.store.GetServiceComponents(serviceID) + components, err := in.store.GetServiceComponents(serviceID, true) if err != nil { return nil, err } @@ -358,7 +413,7 @@ func (in *Applier) getDeleteFilterFunc(serviceID string) (func(resources []unstr } func (in *Applier) getServiceComponents(serviceID string) ([]client.ComponentAttributes, error) { - components, err := in.store.GetServiceComponents(serviceID) + components, err := in.store.GetServiceComponents(serviceID, true) if err != nil { return nil, err } diff --git a/pkg/streamline/common/hook_component.go b/pkg/streamline/common/hook_component.go index 77f3fee27..5e0e1438c 100644 --- a/pkg/streamline/common/hook_component.go +++ b/pkg/streamline/common/hook_component.go @@ -10,15 +10,16 @@ import ( // HookComponent represents hook resources that have deletion policy set. type HookComponent struct { - UID string - Group string - Version string - Kind string - Name string - Namespace string - Status string - ManifestSHA string - ServiceID string + UID string + Group string + Version string + Kind string + Name string + Namespace string + Status string + ManifestSHA string + ServiceID string + DeletePolicies []string } func (in *HookComponent) GroupVersionKind() schema.GroupVersionKind { @@ -33,6 +34,9 @@ func (in *HookComponent) Failed() bool { return in.Status == string(client.ComponentStateFailed) } +// HasDesiredState checks if the hook has the desired state based on its delete policies. +// Delete policies from the live resource can be passed as an argument to make check better. +// To use the delete policies from the store, use HadDesiredState. func (in *HookComponent) HasDesiredState(policies []string) bool { for _, policy := range policies { if policy == HookDeletePolicySucceeded && in.Succeeded() { @@ -45,6 +49,13 @@ func (in *HookComponent) HasDesiredState(policies []string) bool { return false } +// HadDesiredState checks if the hook had the desired state based on its stored delete policies. +// It uses delete policies from the store that may have changed. +// If possible, use HasDesiredState with live resource delete policies. +func (in *HookComponent) HadDesiredState() bool { + return in.HasDesiredState(in.DeletePolicies) +} + func (in *HookComponent) HasManifestChanged(u unstructured.Unstructured) bool { sha, _ := utils.HashResource(u) return in.ManifestSHA != sha diff --git a/pkg/streamline/common/key.go b/pkg/streamline/common/key.go index 9379251a7..baab6eaab 100644 --- a/pkg/streamline/common/key.go +++ b/pkg/streamline/common/key.go @@ -3,6 +3,7 @@ package common import ( "fmt" + "github.com/pluralsh/console/go/client" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" ) @@ -54,3 +55,11 @@ func (in StoreKey) ReplaceGroup(group string) StoreKey { func NewStoreKeyFromUnstructured(u unstructured.Unstructured) StoreKey { return StoreKey{GVK: u.GroupVersionKind(), Namespace: u.GetNamespace(), Name: u.GetName()} } + +func NewStoreKeyFromComponentAttributes(a client.ComponentAttributes) StoreKey { + return StoreKey{ + GVK: schema.GroupVersionKind{Group: a.Group, Version: a.Version, Kind: a.Kind}, + Namespace: a.Namespace, + Name: a.Name, + } +} diff --git a/pkg/streamline/common/sync_delete_policy.go b/pkg/streamline/common/sync_delete_policy.go index 3f299108f..373464c32 100644 --- a/pkg/streamline/common/sync_delete_policy.go +++ b/pkg/streamline/common/sync_delete_policy.go @@ -61,5 +61,9 @@ func helmHookDeletePolicy(annotations map[string]string) string { } func ParseHookDeletePolicy(resource unstructured.Unstructured) []string { - return strings.Split(strings.ReplaceAll(GetPhaseHookDeletePolicy(resource), " ", ""), ",") + return SplitHookDeletePolicy(GetPhaseHookDeletePolicy(resource)) +} + +func SplitHookDeletePolicy(policy string) []string { + return strings.Split(strings.ReplaceAll(policy, " ", ""), ",") } diff --git a/pkg/streamline/store/db_queries.go b/pkg/streamline/store/db_queries.go index 92d162a03..8ea784d55 100644 --- a/pkg/streamline/store/db_queries.go +++ b/pkg/streamline/store/db_queries.go @@ -21,7 +21,8 @@ const ( transient_manifest_sha TEXT, apply_sha TEXT, server_sha TEXT, - manifest BOOLEAN DEFAULT 0 + manifest BOOLEAN DEFAULT 0, -- Indicates if the component was created from an original manifest set of a service + applied BOOLEAN DEFAULT 0 -- Indicates if the component was already applied to the cluster ); CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_component ON component("group", version, kind, namespace, name); CREATE INDEX IF NOT EXISTS idx_parent ON component(parent_uid); @@ -57,7 +58,8 @@ const ( name TEXT, status INT, manifest_sha TEXT, - service_id TEXT + service_id TEXT, + delete_policies TEXT ); -- Add indexes to the hook component table @@ -77,52 +79,12 @@ const ( WHERE uid = ? ` - getComponentsByServiceID = ` - SELECT uid, parent_uid, "group", version, kind, name, namespace, health, delete_phase, manifest - FROM component - WHERE service_id = ? AND (manifest = 1 OR (parent_uid is NULL or parent_uid = '')) - ` - getComponentsByGVK = ` SELECT uid, "group", version, kind, namespace, name, server_sha, delete_phase, manifest FROM component WHERE "group" = ? AND version = ? AND kind = ? ` - setComponent = ` - INSERT INTO component ( - uid, - parent_uid, - "group", - version, - kind, - namespace, - name, - health, - node, - created_at, - service_id - ) VALUES ( - ?, - ?, - ?, - ?, - ?, - ?, - ?, - ?, - ?, - ?, - ? - ) ON CONFLICT("group", version, kind, namespace, name) DO UPDATE SET - uid = excluded.uid, - parent_uid = excluded.parent_uid, - health = excluded.health, - node = excluded.node, - created_at = excluded.created_at, - service_id = excluded.service_id - ` - setComponentWithSHA = ` INSERT INTO component ( uid, @@ -137,7 +99,8 @@ const ( created_at, service_id, delete_phase, - server_sha + server_sha, + applied ) VALUES ( ?, ?, @@ -151,6 +114,7 @@ const ( ?, ?, ?, + ?, ? ) ON CONFLICT("group", version, kind, namespace, name) DO UPDATE SET uid = excluded.uid, @@ -160,7 +124,8 @@ const ( created_at = excluded.created_at, service_id = excluded.service_id, delete_phase = excluded.delete_phase, - server_sha = excluded.server_sha + server_sha = excluded.server_sha, + applied = excluded.applied ` expireSHA = ` @@ -296,12 +261,13 @@ const ( ` setHookComponent = ` - INSERT INTO hook_component ("group", version, kind, namespace, name, uid, status, service_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO hook_component ("group", version, kind, namespace, name, uid, status, service_id, delete_policies) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT("group", version, kind, namespace, name) DO UPDATE SET uid = excluded.uid, status = excluded.status, - service_id = excluded.service_id + service_id = excluded.service_id, + delete_policies = excluded.delete_policies ` setHookComponentWithManifestSHA = ` diff --git a/pkg/streamline/store/db_store.go b/pkg/streamline/store/db_store.go index a7e6b69bd..6593f426c 100644 --- a/pkg/streamline/store/db_store.go +++ b/pkg/streamline/store/db_store.go @@ -11,6 +11,7 @@ import ( "github.com/pluralsh/console/go/client" "github.com/pluralsh/deployment-operator/internal/utils" + "github.com/pluralsh/polly/containers" "github.com/samber/lo" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -259,11 +260,12 @@ func (in *DatabaseStore) SaveComponent(obj unstructured.Unstructured) error { serviceID, smcommon.GetDeletePhase(obj), serverSHA, + true, }, }) } -func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured) error { +func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured, applied *bool) error { if len(objects) == 0 { return nil } @@ -282,7 +284,26 @@ func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured) err }() var sb strings.Builder - sb.WriteString(` + if applied != nil { + sb.WriteString(` + INSERT INTO component ( + uid, + parent_uid, + "group", + version, + kind, + namespace, + name, + health, + node, + created_at, + service_id, + delete_phase, + server_sha, + applied + ) VALUES `) + } else { + sb.WriteString(` INSERT INTO component ( uid, parent_uid, @@ -298,6 +319,7 @@ func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured) err delete_phase, server_sha ) VALUES `) + } valueStrings := make([]string, 0, len(objects)) @@ -338,21 +360,40 @@ func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured) err continue } - valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s','%s','%s','%s','%s','%s',%d,'%s',%d,'%s','%s','%s')", - obj.GetUID(), - lo.FromPtr(ownerRef), - gvk.Group, - gvk.Version, - gvk.Kind, - obj.GetNamespace(), - obj.GetName(), - int(state), - nodeName, - obj.GetCreationTimestamp().Unix(), - serviceID, - smcommon.GetDeletePhase(obj), - serverSHA, - )) + if applied != nil { + valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s','%s','%s','%s','%s','%s',%d,'%s',%d,'%s','%s','%s', '%d')", + obj.GetUID(), + lo.FromPtr(ownerRef), + gvk.Group, + gvk.Version, + gvk.Kind, + obj.GetNamespace(), + obj.GetName(), + int(state), + nodeName, + obj.GetCreationTimestamp().Unix(), + serviceID, + smcommon.GetDeletePhase(obj), + serverSHA, + applied, + )) + } else { + valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s','%s','%s','%s','%s','%s',%d,'%s',%d,'%s','%s','%s')", + obj.GetUID(), + lo.FromPtr(ownerRef), + gvk.Group, + gvk.Version, + gvk.Kind, + obj.GetNamespace(), + obj.GetName(), + int(state), + nodeName, + obj.GetCreationTimestamp().Unix(), + serviceID, + smcommon.GetDeletePhase(obj), + serverSHA, + )) + } } if len(valueStrings) == 0 { @@ -368,7 +409,8 @@ func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured) err created_at = excluded.created_at, service_id = excluded.service_id, delete_phase = excluded.delete_phase, - server_sha = excluded.server_sha + server_sha = excluded.server_sha, + applied = excluded.applied `) in.maybeSaveHookComponents(conn, objects) @@ -376,6 +418,34 @@ func (in *DatabaseStore) SaveComponents(objects []unstructured.Unstructured) err return sqlitex.Execute(conn, sb.String(), nil) } +func (in *DatabaseStore) SyncServiceComponents(serviceID string, resources []unstructured.Unstructured) error { + // Get the list of all components for the service from the store. + components, err := in.GetServiceComponents(serviceID, false) + if err != nil { + return err + } + + // Create a set of keys for an easy lookup. + resourceKeys := containers.NewSet[smcommon.Key]() + for _, resource := range resources { + resourceKeys.Add(smcommon.NewKeyFromUnstructured(resource)) + } + + // Check if all components that were not applied yet are still present in the resources. + // Those that are not present anymore should be deleted from the store. + // TODO: Use single delete query to minimize number of database calls. + for _, component := range components { + if !resourceKeys.Has(component.Key()) { + if err = in.DeleteComponent(component.StoreKey()); err != nil { + return err + } + } + } + + // Save resources to the store. + return in.SaveComponents(resources, nil) +} + func (in *DatabaseStore) SetServiceChildren(serviceID, parentUID string, keys []smcommon.StoreKey) (int, error) { if len(keys) == 0 { return 0, nil @@ -446,31 +516,6 @@ func (in *DatabaseStore) SetServiceChildren(serviceID, parentUID string, keys [] return updatedCount, nil } -func (in *DatabaseStore) SaveComponentAttributes(obj client.ComponentChildAttributes, args ...any) error { - conn, err := in.pool.Take(context.Background()) - if err != nil { - return err - } - defer in.pool.Put(conn) - - if len(args) != 3 { - args = []any{nil, nil, nil} - } - - return sqlitex.ExecuteTransient(conn, setComponent, &sqlitex.ExecOptions{ - Args: append([]interface{}{ - obj.UID, - lo.FromPtr(obj.ParentUID), - lo.FromPtr(obj.Group), - obj.Version, - obj.Kind, - lo.FromPtr(obj.Namespace), - obj.Name, - NewComponentState(obj.State), - }, args...), - }) -} - func (in *DatabaseStore) GetComponent(obj unstructured.Unstructured) (result *smcommon.Component, err error) { conn, err := in.pool.Take(context.Background()) if err != nil { @@ -589,15 +634,23 @@ func (in *DatabaseStore) DeleteComponents(group, version, kind string) error { &sqlitex.ExecOptions{Args: []any{group, version, kind}}) } -func (in *DatabaseStore) GetServiceComponents(serviceID string) (smcommon.Components, error) { +func (in *DatabaseStore) GetServiceComponents(serviceID string, onlyApplied bool) (smcommon.Components, error) { conn, err := in.pool.Take(context.Background()) if err != nil { return nil, err } defer in.pool.Put(conn) + var sb strings.Builder + sb.WriteString(`SELECT uid, parent_uid, "group", version, kind, name, namespace, health, delete_phase, manifest + FROM component WHERE service_id = ?`) + if onlyApplied { + sb.WriteString(" AND applied = 1") + } + sb.WriteString(` AND (manifest = 1 OR (parent_uid IS NULL OR parent_uid = ''))`) + result := make([]smcommon.Component, 0) - err = sqlitex.ExecuteTransient(conn, getComponentsByServiceID, &sqlitex.ExecOptions{ + err = sqlitex.ExecuteTransient(conn, sb.String(), &sqlitex.ExecOptions{ Args: []interface{}{serviceID}, ResultFunc: func(stmt *sqlite.Stmt) error { result = append(result, smcommon.Component{ @@ -818,7 +871,8 @@ func (in *DatabaseStore) SyncAppliedResource(obj unstructured.Unstructured) erro ELSE transient_manifest_sha END, transient_manifest_sha = NULL, - manifest = 1 + manifest = 1, + applied = 1 WHERE "group" = ? AND version = ? AND kind = ? @@ -908,6 +962,7 @@ func (in *DatabaseStore) maybeSaveHookComponent(conn *sqlite.Conn, resource unst resource.GetUID(), NewComponentState(&state), serviceID, + smcommon.GetPhaseHookDeletePolicy(resource), }}); err != nil { klog.V(log.LogLevelMinimal).ErrorS(err, "failed to save hook", "resource", resource) } @@ -920,7 +975,7 @@ func (in *DatabaseStore) maybeSaveHookComponents(conn *sqlite.Conn, resources [] } var sb strings.Builder - sb.WriteString(`INSERT INTO hook_component ("group", version, kind, namespace, name, uid, status, service_id) VALUES `) + sb.WriteString(`INSERT INTO hook_component ("group", version, kind, namespace, name, uid, status, service_id, delete_policies) VALUES `) valueStrings := make([]string, 0, len(resources)) for _, resource := range resources { @@ -948,8 +1003,9 @@ func (in *DatabaseStore) maybeSaveHookComponents(conn *sqlite.Conn, resources [] gvk := resource.GroupVersionKind() valueStrings = append(valueStrings, - fmt.Sprintf("('%s','%s','%s','%s','%s','%s','%s','%s')", gvk.Group, gvk.Version, gvk.Kind, - resource.GetNamespace(), resource.GetName(), resource.GetUID(), NewComponentState(&state), serviceID)) + fmt.Sprintf("('%s','%s','%s','%s','%s','%s','%s','%s','%s')", gvk.Group, gvk.Version, gvk.Kind, + resource.GetNamespace(), resource.GetName(), resource.GetUID(), NewComponentState(&state), serviceID, + smcommon.GetPhaseHookDeletePolicy(resource))) } if len(valueStrings) == 0 { @@ -977,20 +1033,21 @@ func (in *DatabaseStore) GetHookComponents(serviceID string) ([]smcommon.HookCom result := make([]smcommon.HookComponent, 0) err = sqlitex.ExecuteTransient( conn, - `SELECT "group", version, kind, namespace, name, uid, status, manifest_sha FROM hook_component WHERE service_id = ?`, + `SELECT "group", version, kind, namespace, name, uid, status, manifest_sha, delete_policies FROM hook_component WHERE service_id = ?`, &sqlitex.ExecOptions{ Args: []interface{}{serviceID}, ResultFunc: func(stmt *sqlite.Stmt) error { result = append(result, smcommon.HookComponent{ - Group: stmt.ColumnText(0), - Version: stmt.ColumnText(1), - Kind: stmt.ColumnText(2), - Namespace: stmt.ColumnText(3), - Name: stmt.ColumnText(4), - UID: stmt.ColumnText(5), - Status: ComponentState(stmt.ColumnInt32(6)).String(), - ManifestSHA: stmt.ColumnText(7), - ServiceID: serviceID, + Group: stmt.ColumnText(0), + Version: stmt.ColumnText(1), + Kind: stmt.ColumnText(2), + Namespace: stmt.ColumnText(3), + Name: stmt.ColumnText(4), + UID: stmt.ColumnText(5), + Status: ComponentState(stmt.ColumnInt32(6)).String(), + ManifestSHA: stmt.ColumnText(7), + ServiceID: serviceID, + DeletePolicies: smcommon.SplitHookDeletePolicy(stmt.ColumnText(8)), }) return nil }, diff --git a/pkg/streamline/store/db_store_test.go b/pkg/streamline/store/db_store_test.go index 65700e0c1..d62074117 100644 --- a/pkg/streamline/store/db_store_test.go +++ b/pkg/streamline/store/db_store_test.go @@ -11,6 +11,7 @@ import ( "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -36,41 +37,126 @@ var ( hourAgoTimestamp = time.Now().Add(-time.Hour).Unix() ) -type CreateComponentOption func(component *client.ComponentChildAttributes) +func createComponent(uid string, option ...CreateComponentOption) unstructured.Unstructured { + u := unstructured.Unstructured{} + u.SetGroupVersionKind(schema.GroupVersionKind{Group: testGroup, Version: testVersion, Kind: testKind}) + u.SetNamespace(testNamespace) + u.SetName(testName) + u.SetUID(types.UID(uid)) -func WithGroup(group string) CreateComponentOption { - return func(component *client.ComponentChildAttributes) { - component.Group = &group + for _, opt := range option { + opt(&u) } + + return u } -func WithVersion(version string) CreateComponentOption { - return func(component *client.ComponentChildAttributes) { - component.Version = version +func createPod(uid, name, namespace string, status client.ComponentState, timestamp int64) unstructured.Unstructured { + u := createComponent(uid, WithGVK("", "v1", "Pod"), WithName(name), WithNamespace(namespace)) + u.SetCreationTimestamp(v1.Time{Time: time.Unix(timestamp, 0)}) + u.Object["spec"] = map[string]any{"nodeName": testNode} + + switch status { + case client.ComponentStateFailed: + u.Object["status"] = map[string]any{ + "phase": "Failed", + "message": "Pod failed", + "containerStatuses": []any{ + map[string]any{ + "name": "container", + "ready": false, + "state": map[string]any{ + "terminated": map[string]any{ + "exitCode": 1, + "reason": "Error", + "message": "Container failed", + }, + }, + }, + }, + } + case client.ComponentStateRunning: + u.Object["status"] = map[string]any{ + "phase": "Running", + "message": "", + "containerStatuses": []any{ + map[string]any{ + "name": "container", + "ready": true, + "state": map[string]any{ + "running": map[string]any{ + "startedAt": v1.Time{Time: time.Unix(timestamp, 0)}, + }, + }, + }, + }, + "conditions": []any{ + map[string]any{ + "type": "Ready", + "status": "True", + }, + }, + } + case client.ComponentStatePending: + u.Object["status"] = map[string]any{ + "phase": "Pending", + "message": "Pod is pending", + "containerStatuses": []any{ + map[string]any{ + "name": "container", + "ready": false, + "state": map[string]any{ + "waiting": map[string]any{ + "reason": "ContainerCreating", + "message": "Container is being created", + }, + }, + }, + }, + } } + + return u } -func WithKind(kind string) CreateComponentOption { - return func(component *client.ComponentChildAttributes) { - component.Kind = kind +type CreateComponentOption func(u *unstructured.Unstructured) + +func WithParent(uid string) CreateComponentOption { + return func(u *unstructured.Unstructured) { + u.SetOwnerReferences([]v1.OwnerReference{{ + APIVersion: testGroup + "/" + testVersion, + Kind: testKind, + Name: testName, + UID: types.UID(uid), + }}) } } -func WithNamespace(namespace string) CreateComponentOption { - return func(component *client.ComponentChildAttributes) { - component.Namespace = &namespace +// WithService should be called as a last option to ensure that TrackingIdentifierKey will be valid. +func WithService(id string) CreateComponentOption { + return func(u *unstructured.Unstructured) { + u.SetAnnotations(map[string]string{ + common.OwningInventoryKey: id, + common.TrackingIdentifierKey: common.NewKeyFromUnstructured(lo.FromPtr(u)).String(), + }) } } -func WithName(name string) CreateComponentOption { - return func(component *client.ComponentChildAttributes) { - component.Name = name +func WithGVK(group, version, kind string) CreateComponentOption { + return func(u *unstructured.Unstructured) { + u.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: version, Kind: kind}) } } -func WithState(state client.ComponentState) CreateComponentOption { - return func(component *client.ComponentChildAttributes) { - component.State = &state +func WithNamespace(namespace string) CreateComponentOption { + return func(u *unstructured.Unstructured) { + u.SetNamespace(namespace) + } +} + +func WithName(name string) CreateComponentOption { + return func(u *unstructured.Unstructured) { + u.SetName(name) } } @@ -122,25 +208,6 @@ func createStoreKey(option ...CreateStoreKeyOption) common.StoreKey { return result.StoreKey() } -func createComponent(uid string, parentUID *string, option ...CreateComponentOption) client.ComponentChildAttributes { - result := client.ComponentChildAttributes{ - UID: uid, - ParentUID: parentUID, - Group: lo.ToPtr(testGroup), - Version: testVersion, - Kind: testKind, - Namespace: lo.ToPtr(testNamespace), - Name: testName, - State: lo.ToPtr(client.ComponentStateRunning), - } - - for _, opt := range option { - opt(&result) - } - - return result -} - func TestComponentCache_Init(t *testing.T) { t.Run("cache should initialize", func(t *testing.T) { storeInstance, err := store.NewDatabaseStore(store.WithStorage(api.StorageFile)) @@ -167,12 +234,12 @@ func TestComponentCache_SetComponent(t *testing.T) { uid := testUID - component := createComponent(uid, nil, WithName("parent-component")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(uid, WithName("parent-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) - childComponent := createComponent(testChildUID, &uid, WithName("child-component")) - err = storeInstance.SaveComponentAttributes(childComponent) + childComponent := createComponent(testChildUID, WithParent(uid), WithName("child-component")) + err = storeInstance.SaveComponent(childComponent) require.NoError(t, err) children, err := storeInstance.GetComponentChildren(uid) @@ -193,38 +260,38 @@ func TestComponentCache_ComponentChildren(t *testing.T) { // Root rootUID := "root-uid" - component := createComponent(rootUID, nil, WithName("root-component")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(rootUID, WithName("root-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 1 uid1 := "uid-1" - component = createComponent(uid1, &rootUID, WithName("level-1-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid1, WithParent(rootUID), WithName("level-1-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 2 uid2 := "uid-2" - component = createComponent(uid2, &uid1, WithName("level-2-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid2, WithParent(uid1), WithName("level-2-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 3 uid3 := "uid-3" - component = createComponent(uid3, &uid2, WithName("level-3-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid3, WithParent(uid2), WithName("level-3-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 4 uid4 := "uid-4" - component = createComponent(uid4, &uid3, WithName("level-4-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid4, WithParent(uid3), WithName("level-4-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 5 uid5 := "uid-5" - component = createComponent(uid5, &uid4, WithName("level-5-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid5, WithParent(uid4), WithName("level-5-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) children, err := storeInstance.GetComponentChildren(rootUID) @@ -241,43 +308,43 @@ func TestComponentCache_ComponentChildren(t *testing.T) { // Root rootUID := testUID - component := createComponent(rootUID, nil, WithName("multi-root-component")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(rootUID, WithName("multi-root-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 1 uid1 := "uid-1" - component = createComponent(uid1, &rootUID, WithName("multi-level-1-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid1, WithParent(rootUID), WithName("multi-level-1-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 2 uid2 := "uid-2" - component = createComponent(uid2, &uid1, WithName("multi-level-2-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid2, WithParent(uid1), WithName("multi-level-2-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 3 uid3 := "uid-3" - component = createComponent(uid3, &uid2, WithName("multi-level-3-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid3, WithParent(uid2), WithName("multi-level-3-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 4 uid4 := "uid-4" - component = createComponent(uid4, &uid3, WithName("multi-level-4-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid4, WithParent(uid3), WithName("multi-level-4-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) uid44 := "uid-44" - component = createComponent(uid44, &uid3, WithName("multi-level-4b-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid44, WithParent(uid3), WithName("multi-level-4b-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Level 5 uid5 := "uid-5" - component = createComponent(uid5, &uid4, WithName("multi-level-5-component")) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent(uid5, WithParent(uid4), WithName("multi-level-5-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) children, err := storeInstance.GetComponentChildren(rootUID) @@ -295,17 +362,17 @@ func TestComponentCache_DeleteComponent(t *testing.T) { }(storeInstance) uid := testUID - component := createComponent(uid, nil, WithName("delete-parent-component")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(uid, WithName("delete-parent-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) childUid := "child-uid" - childComponent := createComponent(childUid, &uid, WithName("delete-child-component")) - err = storeInstance.SaveComponentAttributes(childComponent) + childComponent := createComponent(childUid, WithParent(uid), WithName("delete-child-component")) + err = storeInstance.SaveComponent(childComponent) require.NoError(t, err) - grandchildComponent := createComponent("grandchild-uid", &childUid, WithName("delete-grandchild-component")) - err = storeInstance.SaveComponentAttributes(grandchildComponent) + grandchildComponent := createComponent("grandchild-uid", WithParent(childUid), WithName("delete-grandchild-component")) + err = storeInstance.SaveComponent(grandchildComponent) require.NoError(t, err) children, err := storeInstance.GetComponentChildren(uid) @@ -328,22 +395,22 @@ func TestComponentCache_DeleteComponent(t *testing.T) { }(storeInstance) uid := testUID - component := createComponent(uid, nil, WithName("multi-delete-parent")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(uid, WithName("multi-delete-parent")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) childUid := "child-uid" - childComponent := createComponent(childUid, &uid, WithName("multi-delete-child")) - err = storeInstance.SaveComponentAttributes(childComponent) + childComponent := createComponent(childUid, WithParent(uid), WithName("multi-delete-child")) + err = storeInstance.SaveComponent(childComponent) require.NoError(t, err) - grandchildComponent := createComponent("grandchild-uid", &childUid, WithName("multi-delete-grandchild")) - err = storeInstance.SaveComponentAttributes(grandchildComponent) + grandchildComponent := createComponent("grandchild-uid", WithParent(childUid), WithName("multi-delete-grandchild")) + err = storeInstance.SaveComponent(grandchildComponent) require.NoError(t, err) child2Uid := "child2-uid" - child2Component := createComponent(child2Uid, &uid, WithName("multi-delete-child2")) - err = storeInstance.SaveComponentAttributes(child2Component) + child2Component := createComponent(child2Uid, WithParent(uid), WithName("multi-delete-child2")) + err = storeInstance.SaveComponent(child2Component) require.NoError(t, err) children, err := storeInstance.GetComponentChildren(uid) @@ -370,12 +437,12 @@ func TestComponentCache_GroupHandling(t *testing.T) { group := testGroup uid := testUID - component := createComponent(uid, nil, WithName("group-test-parent")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(uid, WithName("group-test-parent")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) - child := createComponent("child-uid", &uid, WithGroup(group), WithName("group-test-child")) - err = storeInstance.SaveComponentAttributes(child) + child := createComponent("child-uid", WithParent(uid), WithGVK(group, testVersion, testKind), WithName("group-test-child")) + err = storeInstance.SaveComponent(child) require.NoError(t, err) children, err := storeInstance.GetComponentChildren(uid) @@ -384,9 +451,8 @@ func TestComponentCache_GroupHandling(t *testing.T) { require.Equal(t, group, *children[0].Group) // Test empty group - child.UID = "child2-uid" - child.Group = lo.ToPtr("") - err = storeInstance.SaveComponentAttributes(child) + child = createComponent("child2-uid", WithParent(uid), WithGVK("", testVersion, testKind), WithName("group-test-child")) + err = storeInstance.SaveComponent(child) require.NoError(t, err) tested, err := storeInstance.GetComponentByUID("child2-uid") @@ -394,9 +460,9 @@ func TestComponentCache_GroupHandling(t *testing.T) { require.Nil(t, tested.Group) // Test nil group - child.UID = "child3-uid" - child.Group = lo.ToPtr("") - err = storeInstance.SaveComponentAttributes(child) + // Test nil group + child = createComponent("child3-uid", WithParent(uid), WithGVK("", testVersion, testKind), WithName("group-test-child")) + err = storeInstance.SaveComponent(child) require.NoError(t, err) tested, err = storeInstance.GetComponentByUID("child3-uid") @@ -414,32 +480,32 @@ func TestComponentCache_HealthScore(t *testing.T) { }(storeInstance) uid := testUID - component := createComponent(uid, nil, WithState(client.ComponentStateRunning), WithKind("Pod"), WithName("test-pod-1")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(uid, WithGVK("", testVersion, "Pod"), WithName("test-pod-1")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) - child1 := createComponent("child1", &uid, WithState(client.ComponentStateRunning), WithKind("Pod"), WithName("child-pod-1")) - err = storeInstance.SaveComponentAttributes(child1) + child1 := createComponent("child1", WithParent(uid), WithGVK("", testVersion, "Pod"), WithName("child-pod-1")) + err = storeInstance.SaveComponent(child1) require.NoError(t, err) - child2 := createComponent("child2", &uid, WithState(client.ComponentStateRunning), WithKind("Pod"), WithName("child-pod-2")) - err = storeInstance.SaveComponentAttributes(child2) + child2 := createComponent("child2", WithParent(uid), WithGVK("", testVersion, "Pod"), WithName("child-pod-2")) + err = storeInstance.SaveComponent(child2) require.NoError(t, err) score, err := storeInstance.GetHealthScore() require.NoError(t, err) assert.Equal(t, int64(100), score) - child3 := createComponent("child3", &uid, WithState(client.ComponentStateFailed), WithKind("Pod"), WithName("child-pod-3")) - err = storeInstance.SaveComponentAttributes(child3) + child3 := createComponent("child3", WithParent(uid), WithGVK("", testVersion, "Pod"), WithName("child-pod-3")) + err = storeInstance.SaveComponent(child3) require.NoError(t, err) score, err = storeInstance.GetHealthScore() require.NoError(t, err) assert.Equal(t, int64(75), score) - child4 := createComponent("child4", &uid, WithState(client.ComponentStateFailed), WithKind("Deployment"), WithName("child-deployment-1")) - err = storeInstance.SaveComponentAttributes(child4) + child4 := createComponent("child4", WithParent(uid), WithGVK("", testVersion, "Deployment"), WithName("child-deployment-1")) + err = storeInstance.SaveComponent(child4) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -447,8 +513,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(60), score) // Invalid certificate should deduct an additional 10 points. - child5 := createComponent("child5", &uid, WithState(client.ComponentStateFailed), WithKind("Certificate"), WithName("child-cert-1")) - err = storeInstance.SaveComponentAttributes(child5) + child5 := createComponent("child5", WithParent(uid), WithGVK("", testVersion, "Certificate"), WithName("child-cert-1")) + err = storeInstance.SaveComponent(child5) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -456,8 +522,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(40), score) // Failing resources in kube-system namespace should deduct an additional 20 points. - child6 := createComponent("child6", &uid, WithState(client.ComponentStateFailed), WithKind("Pod"), WithNamespace("kube-system"), WithName("child-pod-kube-system")) - err = storeInstance.SaveComponentAttributes(child6) + child6 := createComponent("child6", WithParent(uid), WithGVK("", testVersion, "Pod"), WithNamespace("kube-system"), WithName("child-pod-kube-system")) + err = storeInstance.SaveComponent(child6) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -466,8 +532,8 @@ func TestComponentCache_HealthScore(t *testing.T) { // Failing persistent volume should deduct an additional 10 points. // The score should not go below 0. - child7 := createComponent("child7", &uid, WithState(client.ComponentStateFailed), WithKind("PersistentVolume"), WithName("child-pv-1")) - err = storeInstance.SaveComponentAttributes(child7) + child7 := createComponent("child7", WithParent(uid), WithGVK("", testVersion, "PersistentVolume"), WithName("child-pv-1")) + err = storeInstance.SaveComponent(child7) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -483,8 +549,8 @@ func TestComponentCache_HealthScore(t *testing.T) { }(storeInstance) uid := testUID - component := createComponent(uid, nil, WithState(client.ComponentStateRunning), WithName("standalone-component")) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent(uid, WithName("standalone-component")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) score, err := storeInstance.GetHealthScore() @@ -499,25 +565,25 @@ func TestComponentCache_HealthScore(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - baseComponent := createComponent(testUID, nil, WithState(client.ComponentStateRunning), WithName("base-test-component")) - err = storeInstance.SaveComponentAttributes(baseComponent) + baseComponent := createComponent(testUID, WithName("base-test-component")) + err = storeInstance.SaveComponent(baseComponent) require.NoError(t, err) - runningPod := createComponent("running-pod", nil, WithState(client.ComponentStateRunning), WithKind("Pod"), WithName("running-pod-unique")) - err = storeInstance.SaveComponentAttributes(runningPod) + runningPod := createComponent("running-pod", WithGVK("", testVersion, "Pod"), WithName("running-pod-unique")) + err = storeInstance.SaveComponent(runningPod) require.NoError(t, err) - runningDeployment := createComponent("running-deployment", nil, WithState(client.ComponentStateRunning), WithKind("Deployment"), WithName("running-deployment-unique")) - err = storeInstance.SaveComponentAttributes(runningDeployment) + runningDeployment := createComponent("running-deployment", WithGVK("", testVersion, "Deployment"), WithName("running-deployment-unique")) + err = storeInstance.SaveComponent(runningDeployment) require.NoError(t, err) - runningService := createComponent("running-service", nil, WithState(client.ComponentStateRunning), WithKind("Service"), WithName("running-service-unique")) - err = storeInstance.SaveComponentAttributes(runningService) + runningService := createComponent("running-service", WithGVK("", testVersion, "Service"), WithName("running-service-unique")) + err = storeInstance.SaveComponent(runningService) require.NoError(t, err) // Test CoreDNS failure (50 point deduction) - coredns := createComponent("coredns", nil, WithState(client.ComponentStateFailed), WithKind("Deployment"), WithName("coredns-test")) - err = storeInstance.SaveComponentAttributes(coredns) + coredns := createComponent("coredns", WithGVK("", testVersion, "Deployment"), WithName("coredns-test")) + err = storeInstance.SaveComponent(coredns) require.NoError(t, err) score, err := storeInstance.GetHealthScore() @@ -525,8 +591,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(30), score) // Test AWS CNI failure (additional 50 point deduction) - awscni := createComponent("aws-cni", nil, WithState(client.ComponentStateFailed), WithKind("DaemonSet"), WithName("aws-cni-test")) - err = storeInstance.SaveComponentAttributes(awscni) + awscni := createComponent("aws-cni", WithGVK("", testVersion, "DaemonSet"), WithName("aws-cni-test")) + err = storeInstance.SaveComponent(awscni) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -534,8 +600,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(0), score) // Test ingress-nginx service failure (would deduct 50 but already at 0) - ingress := createComponent("ingress", nil, WithState(client.ComponentStateFailed), WithKind("Service"), WithName("ingress-nginx-controller-test"), WithNamespace("ingress-nginx")) - err = storeInstance.SaveComponentAttributes(ingress) + ingress := createComponent("ingress", WithGVK("", testVersion, "Service"), WithNamespace("ingress-nginx"), WithName("ingress-nginx-controller-test")) + err = storeInstance.SaveComponent(ingress) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -550,13 +616,13 @@ func TestComponentCache_HealthScore(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - baseComponent := createComponent(testUID, nil, WithState(client.ComponentStateRunning), WithName("base-combined-test")) - err = storeInstance.SaveComponentAttributes(baseComponent) + baseComponent := createComponent(testUID, WithName("base-combined-test")) + err = storeInstance.SaveComponent(baseComponent) require.NoError(t, err) // Failed Certificate (10 point deduction) - cert := createComponent("cert", nil, WithState(client.ComponentStateFailed), WithKind("Certificate"), WithName("test-cert-combined")) - err = storeInstance.SaveComponentAttributes(cert) + cert := createComponent("cert", WithGVK("", testVersion, "Certificate"), WithName("test-cert-combined")) + err = storeInstance.SaveComponent(cert) require.NoError(t, err) score, err := storeInstance.GetHealthScore() @@ -564,8 +630,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(40), score) // Failed kube-system resource (20 point deduction) - kubeSystem := createComponent("kube-system-res", nil, WithState(client.ComponentStateFailed), WithKind("Pod"), WithNamespace("kube-system"), WithName("kube-system-pod-test")) - err = storeInstance.SaveComponentAttributes(kubeSystem) + kubeSystem := createComponent("kube-system-res", WithGVK("", testVersion, "Pod"), WithNamespace("kube-system"), WithName("kube-system-pod-test")) + err = storeInstance.SaveComponent(kubeSystem) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -573,8 +639,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(3), score) // Failed PersistentVolume (10 point deduction) - pv := createComponent("pv", nil, WithState(client.ComponentStateFailed), WithKind("PersistentVolume"), WithName("test-pv-combined")) - err = storeInstance.SaveComponentAttributes(pv) + pv := createComponent("pv", WithGVK("", testVersion, "PersistentVolume"), WithName("test-pv-combined")) + err = storeInstance.SaveComponent(pv) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -582,8 +648,8 @@ func TestComponentCache_HealthScore(t *testing.T) { assert.Equal(t, int64(0), score) // Failed istio-system resource (50 point deduction) - istio := createComponent("istio-res", nil, WithState(client.ComponentStateFailed), WithKind("Service"), WithNamespace("istio-system"), WithName("istio-service-test")) - err = storeInstance.SaveComponentAttributes(istio) + istio := createComponent("istio-res", WithGVK("", testVersion, "Service"), WithNamespace("istio-system"), WithName("istio-service-test")) + err = storeInstance.SaveComponent(istio) require.NoError(t, err) score, err = storeInstance.GetHealthScore() @@ -600,63 +666,51 @@ func TestComponentCache_UniqueConstraint(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - component1 := createComponent("uid-1", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component1 := createComponent("uid-1", + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), WithName("my-app")) - err = storeInstance.SaveComponentAttributes(component1) + err = storeInstance.SaveComponent(component1) require.NoError(t, err) // Component with different name - should succeed - component2 := createComponent("uid-2", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component2 := createComponent("uid-2", + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), WithName("my-other-app")) - err = storeInstance.SaveComponentAttributes(component2) + err = storeInstance.SaveComponent(component2) require.NoError(t, err) // Component with different namespace - should succeed - component3 := createComponent("uid-3", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component3 := createComponent("uid-3", + WithGVK("apps", "v1", "Deployment"), WithNamespace("production"), WithName("my-app")) - err = storeInstance.SaveComponentAttributes(component3) + err = storeInstance.SaveComponent(component3) require.NoError(t, err) // Component with different kind - should succeed - component4 := createComponent("uid-4", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("StatefulSet"), + component4 := createComponent("uid-4", + WithGVK("apps", "v1", "StatefulSet"), WithNamespace("default"), WithName("my-app")) - err = storeInstance.SaveComponentAttributes(component4) + err = storeInstance.SaveComponent(component4) require.NoError(t, err) // Component with different version - should succeed - component5 := createComponent("uid-5", nil, - WithGroup("apps"), - WithVersion("v2"), - WithKind("Deployment"), + component5 := createComponent("uid-5", + WithGVK("apps", "v2", "Deployment"), WithNamespace("default"), WithName("my-app")) - err = storeInstance.SaveComponentAttributes(component5) + err = storeInstance.SaveComponent(component5) require.NoError(t, err) // Component with different group - should succeed - component6 := createComponent("uid-6", nil, - WithGroup("extensions"), - WithVersion("v1"), - WithKind("Deployment"), + component6 := createComponent("uid-6", + WithGVK("extensions", "v1", "Deployment"), WithNamespace("default"), WithName("my-app")) - err = storeInstance.SaveComponentAttributes(component6) + err = storeInstance.SaveComponent(component6) require.NoError(t, err) }) @@ -667,17 +721,18 @@ func TestComponentCache_UniqueConstraint(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - component := createComponent("uid-1", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component := createComponent("uid-1", + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), WithName("my-app")) - err = storeInstance.SaveComponentAttributes(component) + err = storeInstance.SaveComponent(component) require.NoError(t, err) - component.State = lo.ToPtr(client.ComponentStatePaused) - err = storeInstance.SaveComponentAttributes(component) + component = createComponent("uid-1", + WithGVK("apps", "v1", "Deployment"), + WithNamespace("default"), + WithName("my-app")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) }) @@ -688,22 +743,18 @@ func TestComponentCache_UniqueConstraint(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - component1 := createComponent("uid-1", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component1 := createComponent("uid-1", + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), WithName("duplicate-app")) - err = storeInstance.SaveComponentAttributes(component1) + err = storeInstance.SaveComponent(component1) require.NoError(t, err) - component2 := createComponent("uid-2", nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component2 := createComponent("uid-2", + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), WithName("duplicate-app")) - err = storeInstance.SaveComponentAttributes(component2) + err = storeInstance.SaveComponent(component2) require.NoError(t, err) }) @@ -717,25 +768,19 @@ func TestComponentCache_UniqueConstraint(t *testing.T) { uid := "update-test-uid" // Create initial component - component := createComponent(uid, nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + component := createComponent(uid, + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), - WithName("updatable-app"), - WithState(client.ComponentStateRunning)) - err = storeInstance.SaveComponentAttributes(component) + WithName("updatable-app")) + err = storeInstance.SaveComponent(component) require.NoError(t, err) // Update the same component with different state - should succeed - updatedComponent := createComponent(uid, nil, - WithGroup("apps"), - WithVersion("v1"), - WithKind("Deployment"), + updatedComponent := createComponent(uid, + WithGVK("apps", "v1", "Deployment"), WithNamespace("default"), - WithName("updatable-app"), - WithState(client.ComponentStateFailed)) - err = storeInstance.SaveComponentAttributes(updatedComponent) + WithName("updatable-app")) + err = storeInstance.SaveComponent(updatedComponent) require.NoError(t, err) }) @@ -761,63 +806,18 @@ func TestComponentCache_UniqueConstraint(t *testing.T) { u.SetName(name) u.SetNamespace(namespace) - component := createComponent("uid-1", nil, WithGroup(group), WithVersion(version), WithKind(kind), WithName(name), WithNamespace(namespace)) - err = storeInstance.SaveComponentAttributes(component) + component := createComponent("uid-1", WithGVK(group, version, kind), WithName(name), WithNamespace(namespace)) + err = storeInstance.SaveComponent(component) require.NoError(t, err) - sameComponentWithDifferentUID := createComponent("uid-2", nil, WithGroup(group), WithVersion(version), WithKind(kind), WithName(name), WithNamespace(namespace)) - err = storeInstance.SaveComponentAttributes(sameComponentWithDifferentUID) + sameComponentWithDifferentUID := createComponent("uid-2", WithGVK(group, version, kind), WithName(name), WithNamespace(namespace)) + err = storeInstance.SaveComponent(sameComponentWithDifferentUID) require.NoError(t, err) dbc, err := storeInstance.GetComponent(u) require.NoError(t, err) assert.Equal(t, "uid-2", dbc.UID) }) - - t.Run("should treat nil values in the same way as empty strings", func(t *testing.T) { - storeInstance, err := store.NewDatabaseStore(store.WithStorage(api.StorageFile)) - assert.NoError(t, err) - defer func(storeInstance store.Store) { - require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") - }(storeInstance) - - group := "apps" - version := "v1" - kind := "Deployment" - name := "test" - namespace := "" - - var u unstructured.Unstructured - u.SetGroupVersionKind(schema.GroupVersionKind{ - Group: group, - Version: version, - Kind: kind, - }) - u.SetName(name) - u.SetNamespace(namespace) - - componentWithEmptyNamespace := createComponent("uid", nil, WithGroup(group), WithVersion(version), WithKind(kind), WithName(name), WithNamespace(namespace)) - err = storeInstance.SaveComponentAttributes(componentWithEmptyNamespace) - require.NoError(t, err) - - componentWithNilNamespace := createComponent("uid-2", nil, WithGroup(group), WithVersion(version), WithKind(kind), WithName(name)) - componentWithNilNamespace.Namespace = nil - err = storeInstance.SaveComponentAttributes(componentWithNilNamespace) - require.NoError(t, err) - - databaseComponent, err := storeInstance.GetComponent(u) - require.NoError(t, err) - assert.Equal(t, "uid-2", databaseComponent.UID, "component in database should have updated UID") - - entry, err := storeInstance.GetComponentByUID("uid") - require.NoError(t, err) - require.Nil(t, entry, "component with old UID should not be found") - }) -} - -func createPod(s store.Store, name, uid string, timestamp int64) error { - return s.SaveComponentAttributes( - createComponent(uid, nil, WithKind("Pod"), WithName(name), WithNamespace(testNamespace), WithState(client.ComponentStateFailed)), testNode, timestamp, nil) } func TestPendingPodsCache(t *testing.T) { @@ -828,8 +828,8 @@ func TestPendingPodsCache(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - require.NoError(t, createPod(storeInstance, "pending-pod-1", "pod-1-uid", hourAgoTimestamp)) - require.NoError(t, createPod(storeInstance, "pending-pod-2", "pod-2-uid", hourAgoTimestamp)) + require.NoError(t, storeInstance.SaveComponent(createPod("pod-1-uid", "pending-pod-1", "default", client.ComponentStatePending, hourAgoTimestamp))) + require.NoError(t, storeInstance.SaveComponent(createPod("pod-2-uid", "pending-pod-2", "default", client.ComponentStatePending, hourAgoTimestamp))) stats, err := storeInstance.GetNodeStatistics() require.NoError(t, err) @@ -845,9 +845,9 @@ func TestPendingPodsCache(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - require.NoError(t, createPod(storeInstance, "fresh-pending-pod", "pod-uid", nowTimestamp)) - require.NoError(t, createPod(storeInstance, "pending-pod-1", "pod-1-uid", hourAgoTimestamp)) - require.NoError(t, createPod(storeInstance, "pending-pod-2", "pod-2-uid", hourAgoTimestamp)) + require.NoError(t, storeInstance.SaveComponent(createPod("pod-uid", "fresh-pending-pod", "default", client.ComponentStatePending, nowTimestamp))) + require.NoError(t, storeInstance.SaveComponent(createPod("pod-1-uid", "pending-pod-1", "default", client.ComponentStatePending, hourAgoTimestamp))) + require.NoError(t, storeInstance.SaveComponent(createPod("pod-2-uid", "pending-pod-2", "default", client.ComponentStatePending, hourAgoTimestamp))) stats, err := storeInstance.GetNodeStatistics() require.NoError(t, err) @@ -863,14 +863,15 @@ func TestPendingPodsCache(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - require.NoError(t, createPod(storeInstance, "pending-pod-1", "pod-1-uid", hourAgoTimestamp)) + pod := createPod("pod-1-uid", "pending-pod-1", "default", client.ComponentStatePending, hourAgoTimestamp) + require.NoError(t, storeInstance.SaveComponent(pod)) stats, err := storeInstance.GetNodeStatistics() require.NoError(t, err) require.Len(t, stats, 1) assert.Equal(t, int64(1), *stats[0].PendingPods) - err = storeInstance.DeleteComponent(createStoreKey(WithStoreKeyName("pending-pod-1"), WithStoreKeyKind("Pod"))) + err = storeInstance.DeleteComponent(common.NewStoreKeyFromUnstructured(pod)) require.NoError(t, err) stats, err = storeInstance.GetNodeStatistics() @@ -889,67 +890,101 @@ func TestComponentCache_ComponentInsights(t *testing.T) { } }() - // Define test components with various states - testComponents := []client.ComponentChildAttributes{ - // Running components - createComponent("app-frontend-1", nil, WithKind("Deployment"), WithState(client.ComponentStateRunning), WithName("app-frontend-1")), - createComponent("app-backend-1", nil, WithKind("Deployment"), WithState(client.ComponentStateRunning), WithName("app-backend-1")), - createComponent("app-database-1", nil, WithKind("Deployment"), WithState(client.ComponentStateRunning), WithName("app-database-1")), - - // Running components chain (ignored because of depth level > 4) - createComponent("app-1", nil, WithKind("Deployment"), WithState(client.ComponentStateRunning), WithName("app-1")), - createComponent("app-child-1", lo.ToPtr("app-1"), WithKind("Pod"), WithState(client.ComponentStateRunning), WithName("app-child-1")), - createComponent("app-child-2", lo.ToPtr("app-child-1"), WithKind("Pod"), WithState(client.ComponentStateRunning), WithName("app-child-2")), - createComponent("app-child-3", lo.ToPtr("app-child-2"), WithKind("Pod"), WithState(client.ComponentStateRunning), WithName("app-child-3")), - createComponent("app-child-4", lo.ToPtr("app-child-3"), WithKind("Pod"), WithState(client.ComponentStateFailed), WithName("app-child-4")), - - // 1-level Failed components - createComponent("app-redis-1", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("app-redis-1")), - createComponent("app-cronjob-1", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("app-cronjob-1")), - - // Pending component - createComponent("app-migration-1", nil, WithKind("Deployment"), WithState(client.ComponentStatePending), WithName("app-migration-1")), - - // Ingress (failed) -> Certificate (failed) - createComponent("app-ingress-1", nil, WithKind("Ingress"), WithState(client.ComponentStateFailed), WithName("app-ingress-1")), - createComponent("app-certificate-1", lo.ToPtr("app-ingress-1"), WithKind("Certificate"), WithState(client.ComponentStateFailed), WithName("app-certificate-1")), - - // Ingress (pending) -> Certificate (failed) - createComponent("app-ingress-2", nil, WithKind("Ingress"), WithState(client.ComponentStatePending), WithName("app-ingress-2")), - createComponent("app-certificate-2", lo.ToPtr("app-ingress-2"), WithKind("Certificate"), WithState(client.ComponentStateFailed), WithName("app-certificate-2")), - - // StatefulSet (failed) - createComponent("app-statefulset-1", nil, WithKind("StatefulSet"), WithState(client.ComponentStateFailed), WithName("app-statefulset-1")), - - // DaemonSet (failed) - createComponent("app-daemonset-1", nil, WithKind("DaemonSet"), WithState(client.ComponentStateFailed), WithName("app-daemonset-1")), - - // Deployment (pending) -> Pod (failed) - createComponent("app-deployment-1", nil, WithKind("Deployment"), WithState(client.ComponentStatePending), WithName("app-deployment-1")), - createComponent("app-pod-1", lo.ToPtr("app-deployment-1"), WithKind("Pod"), WithState(client.ComponentStateFailed), WithName("app-pod-1")), - - // CRD (pending) -> Deployment (pending) -> Pod (failed) - createComponent("app-crd-1", nil, WithKind("CustomResourceDefinition"), WithState(client.ComponentStatePending), WithName("app-crd-1")), - createComponent("app-deployment-2", lo.ToPtr("app-crd-1"), WithKind("Deployment"), WithState(client.ComponentStatePending), WithName("app-deployment-2")), - createComponent("app-pod-2", lo.ToPtr("app-deployment-2"), WithKind("Pod"), WithState(client.ComponentStateFailed), WithName("app-pod-2")), - - // CRD (failed) -> Deployment (failed) -> Pod (failed) - createComponent("app-crd-2", nil, WithKind("CustomResourceDefinition"), WithState(client.ComponentStateFailed), WithName("app-crd-2")), - createComponent("app-deployment-3", lo.ToPtr("app-crd-2"), WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("app-deployment-3")), - createComponent("app-pod-3", lo.ToPtr("app-deployment-3"), WithKind("Pod"), WithState(client.ComponentStateFailed), WithName("app-pod-3")), - - // CRD (failed) -> Deployment (failed) -> ReplicaSet (failed) -> Pod (failed) - createComponent("app-crd-3", nil, WithKind("CustomResourceDefinition"), WithState(client.ComponentStateFailed), WithName("app-crd-3")), - createComponent("app-deployment-4", lo.ToPtr("app-crd-3"), WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("app-deployment-4")), - createComponent("app-replicaset-1", lo.ToPtr("app-deployment-4"), WithKind("ReplicaSet"), WithState(client.ComponentStateFailed), WithName("app-replicaset-1")), - createComponent("app-pod-4", lo.ToPtr("app-replicaset-1"), WithKind("Pod"), WithState(client.ComponentStateFailed), WithName("app-pod-4")), - - // Deployment (pending) -> (ReplicaSet (pending) -> Pod (failed)) | Secret (running) - createComponent("app-deployment-5", nil, WithKind("Deployment"), WithState(client.ComponentStatePending), WithName("app-deployment-5")), - createComponent("app-replicaset-2", lo.ToPtr("app-deployment-5"), WithKind("ReplicaSet"), WithState(client.ComponentStatePending), WithName("app-replicaset-2")), - createComponent("app-pod-5", lo.ToPtr("app-replicaset-2"), WithKind("Pod"), WithState(client.ComponentStateFailed), WithName("app-pod-5")), - createComponent("app-secret-1", lo.ToPtr("app-deployment-5"), WithKind("Secret"), WithState(client.ComponentStateRunning), WithName("app-secret-1")), - } + // Running components + err = storeInstance.SaveComponent(createComponent("app-frontend-1", WithGVK("", testVersion, "Deployment"), WithName("app-frontend-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-backend-1", WithGVK("", testVersion, "Deployment"), WithName("app-backend-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-database-1", WithGVK("", testVersion, "Deployment"), WithName("app-database-1"))) + require.NoError(t, err) + + // Running components chain + uid1 := "app-1" + err = storeInstance.SaveComponent(createComponent(uid1, WithGVK("", testVersion, "Deployment"), WithName("app-1"))) + require.NoError(t, err) + uid2 := "app-child-1" + err = storeInstance.SaveComponent(createComponent(uid2, WithParent(uid1), WithGVK("", testVersion, "Pod"), WithName("app-child-1"))) + require.NoError(t, err) + uid3 := "app-child-2" + err = storeInstance.SaveComponent(createComponent(uid3, WithParent(uid2), WithGVK("", testVersion, "Pod"), WithName("app-child-2"))) + require.NoError(t, err) + uid4 := "app-child-3" + err = storeInstance.SaveComponent(createComponent(uid4, WithParent(uid3), WithGVK("", testVersion, "Pod"), WithName("app-child-3"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-child-4", WithParent(uid4), WithGVK("", testVersion, "Pod"), WithName("app-child-4"))) + require.NoError(t, err) + + // Failed components + err = storeInstance.SaveComponent(createComponent("app-redis-1", WithGVK("", testVersion, "Deployment"), WithName("app-redis-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-cronjob-1", WithGVK("", testVersion, "Deployment"), WithName("app-cronjob-1"))) + require.NoError(t, err) + + // Pending component + err = storeInstance.SaveComponent(createComponent("app-migration-1", WithGVK("", testVersion, "Deployment"), WithName("app-migration-1"))) + require.NoError(t, err) + + // Ingress and Certificate chain + err = storeInstance.SaveComponent(createComponent("app-ingress-1", WithGVK("", testVersion, "Ingress"), WithName("app-ingress-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-certificate-1", WithParent("app-ingress-1"), WithGVK("", testVersion, "Certificate"), WithName("app-certificate-1"))) + require.NoError(t, err) + + // Ingress pending with failed certificate + err = storeInstance.SaveComponent(createComponent("app-ingress-2", WithGVK("", testVersion, "Ingress"), WithName("app-ingress-2"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-certificate-2", WithParent("app-ingress-2"), WithGVK("", testVersion, "Certificate"), WithName("app-certificate-2"))) + require.NoError(t, err) + + // StatefulSet + err = storeInstance.SaveComponent(createComponent("app-statefulset-1", WithGVK("", testVersion, "StatefulSet"), WithName("app-statefulset-1"))) + require.NoError(t, err) + + // DaemonSet + err = storeInstance.SaveComponent(createComponent("app-daemonset-1", WithGVK("", testVersion, "DaemonSet"), WithName("app-daemonset-1"))) + require.NoError(t, err) + + // Deployment with pod + err = storeInstance.SaveComponent(createComponent("app-deployment-1", WithGVK("", testVersion, "Deployment"), WithName("app-deployment-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-pod-1", WithParent("app-deployment-1"), WithGVK("", testVersion, "Pod"), WithName("app-pod-1"))) + require.NoError(t, err) + + // CRD with deployment and pod + err = storeInstance.SaveComponent(createComponent("app-crd-1", WithGVK("", testVersion, "CustomResourceDefinition"), WithName("app-crd-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-deployment-2", WithParent("app-crd-1"), WithGVK("", testVersion, "Deployment"), WithName("app-deployment-2"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-pod-2", WithParent("app-deployment-2"), WithGVK("", testVersion, "Pod"), WithName("app-pod-2"))) + require.NoError(t, err) + + // Failed CRD with failed deployment and failed pod + err = storeInstance.SaveComponent(createComponent("app-crd-2", WithGVK("", testVersion, "CustomResourceDefinition"), WithName("app-crd-2"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-deployment-3", WithParent("app-crd-2"), WithGVK("", testVersion, "Deployment"), WithName("app-deployment-3"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-pod-3", WithParent("app-deployment-3"), WithGVK("", testVersion, "Pod"), WithName("app-pod-3"))) + require.NoError(t, err) + + // CRD with deployment, replicaset, and pod + err = storeInstance.SaveComponent(createComponent("app-crd-3", WithGVK("", testVersion, "CustomResourceDefinition"), WithName("app-crd-3"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-deployment-4", WithParent("app-crd-3"), WithGVK("", testVersion, "Deployment"), WithName("app-deployment-4"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-replicaset-1", WithParent("app-deployment-4"), WithGVK("", testVersion, "ReplicaSet"), WithName("app-replicaset-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-pod-4", WithParent("app-replicaset-1"), WithGVK("", testVersion, "Pod"), WithName("app-pod-4"))) + require.NoError(t, err) + + // Deployment with replicaset and secret + err = storeInstance.SaveComponent(createComponent("app-deployment-5", WithGVK("", testVersion, "Deployment"), WithName("app-deployment-5"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-replicaset-2", WithParent("app-deployment-5"), WithGVK("", testVersion, "ReplicaSet"), WithName("app-replicaset-2"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-pod-5", WithParent("app-replicaset-2"), WithGVK("", testVersion, "Pod"), WithName("app-pod-5"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-secret-1", WithParent("app-deployment-5"), WithGVK("", testVersion, "Secret"), WithName("app-secret-1"))) + require.NoError(t, err) expectedComponents := []string{ "app-redis-1", @@ -967,13 +1002,6 @@ func TestComponentCache_ComponentInsights(t *testing.T) { "app-deployment-5", } - // Insert all test components into cache - for _, tc := range testComponents { - err := storeInstance.SaveComponentAttributes(tc) - require.NoError(t, err, "Failed to add component %s to cache", tc.UID) - } - - // Get component insights insights, err := storeInstance.GetComponentInsights() require.NoError(t, err, "Failed to get component insights") @@ -982,8 +1010,6 @@ func TestComponentCache_ComponentInsights(t *testing.T) { func(i client.ClusterInsightComponentAttributes) string { return i.Name }, ) - // Verify expected components in insights - // Sort both arrays to ensure order-independent comparison sort.Strings(actualNames) sort.Strings(expectedComponents) @@ -1003,24 +1029,29 @@ func TestComponentCache_ComponentInsights(t *testing.T) { } }() - // Define test components with various kinds to test priority assignment - testComponents := []client.ComponentChildAttributes{ - // Critical priority resources - createComponent("ingress-1", nil, WithKind("Ingress"), WithState(client.ComponentStateFailed), WithName("ingress-1")), - createComponent("certificate-1", nil, WithKind("Certificate"), WithState(client.ComponentStateFailed), WithName("certificate-1")), - createComponent("cert-manager-1", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("cert-manager-webhook"), WithNamespace("cert-manager")), - createComponent("coredns-1", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("coredns")), + // Critical priority resources + err = storeInstance.SaveComponent(createComponent("ingress-1", WithGVK("", testVersion, "Ingress"), WithName("ingress-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("certificate-1", WithGVK("", testVersion, "Certificate"), WithName("certificate-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("cert-manager-1", WithGVK("", testVersion, "Deployment"), WithName("cert-manager-webhook"), WithNamespace("cert-manager"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("coredns-1", WithGVK("", testVersion, "Deployment"), WithName("coredns"))) + require.NoError(t, err) - // High priority resources - createComponent("statefulset-1", nil, WithKind("StatefulSet"), WithState(client.ComponentStateFailed), WithName("statefulset-1")), - createComponent("node-exporter", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("node-exporter")), + // High priority resources + err = storeInstance.SaveComponent(createComponent("statefulset-1", WithGVK("", testVersion, "StatefulSet"), WithName("statefulset-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("node-exporter", WithGVK("", testVersion, "Deployment"), WithName("node-exporter"))) + require.NoError(t, err) - // Medium priority resources - createComponent("daemonset-1", nil, WithKind("DaemonSet"), WithState(client.ComponentStateFailed), WithName("daemonset-1")), + // Medium priority resources + err = storeInstance.SaveComponent(createComponent("daemonset-1", WithGVK("", testVersion, "DaemonSet"), WithName("daemonset-1"))) + require.NoError(t, err) - // Low priority resources (default) - createComponent("deployment-1", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("deployment-1")), - } + // Low priority resources (default) + err = storeInstance.SaveComponent(createComponent("deployment-1", WithGVK("", testVersion, "Deployment"), WithName("deployment-1"))) + require.NoError(t, err) expectedComponentPriorityMap := map[string]client.InsightComponentPriority{ "ingress-1": client.InsightComponentPriorityCritical, @@ -1033,17 +1064,9 @@ func TestComponentCache_ComponentInsights(t *testing.T) { "deployment-1": client.InsightComponentPriorityLow, } - // Insert all test components into cache - for _, tc := range testComponents { - err := storeInstance.SaveComponentAttributes(tc) - require.NoError(t, err, "Failed to add component %s to cache", tc.UID) - } - - // Get component insights insights, err := storeInstance.GetComponentInsights() require.NoError(t, err, "Failed to get component insights") - // Build a map of component name to priority for easier testing priorityMap := make(map[string]client.InsightComponentPriority) for _, insight := range insights { priorityMap[insight.Name] = *insight.Priority @@ -1063,27 +1086,35 @@ func TestComponentCache_ComponentInsights(t *testing.T) { } }() - // Define test components with variations of similar names to test fuzzy matching - testComponents := []client.ComponentChildAttributes{ - // Components with names very similar to critical priority resources - createComponent("cert-manager-1", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("cert-manager"), WithNamespace("kube-system")), - createComponent("coredns-similar", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("core-dns"), WithNamespace("kube-system")), - createComponent("istio-ingressgateway", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("istio-ingressgateway"), WithNamespace("istio-system")), - createComponent("linkerd-proxy", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("linkerd-proxy"), WithNamespace("linkerd")), - createComponent("ebs-csi-node", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("ebs-csi-node"), WithNamespace("kube-system")), - createComponent("gce-pd-csi-controller", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("gce-pd-csi-controller-sa"), WithNamespace("kube-system")), - - // Components with namespaces containing priority keywords - createComponent("app-in-cert-manager", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("app-in-sensitive-ns-1"), WithNamespace("cert-manager")), - createComponent("app-in-kube-proxy", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("app-in-sensitive-ns-2"), WithNamespace("kube-proxy")), - - // Components with partial name matches to high priority resources - createComponent("node-exporter-similar", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("node-metrics-exporter"), WithNamespace("monitoring")), - - // Components with no special priority that could be slightly similar to other resources - createComponent("app-similar-to-istio", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("iso-ist-app"), WithNamespace("default")), - createComponent("app-similar-to-cert-manager", nil, WithKind("Deployment"), WithState(client.ComponentStateFailed), WithName("custom-cert-provisioner"), WithNamespace("default")), - } + // Components with names similar to critical priority resources + err = storeInstance.SaveComponent(createComponent("cert-manager-1", WithGVK("", testVersion, "Deployment"), WithName("cert-manager"), WithNamespace("kube-system"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("coredns-similar", WithGVK("", testVersion, "Deployment"), WithName("core-dns"), WithNamespace("kube-system"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("istio-ingressgateway", WithGVK("", testVersion, "Deployment"), WithName("istio-ingressgateway"), WithNamespace("istio-system"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("linkerd-proxy", WithGVK("", testVersion, "Deployment"), WithName("linkerd-proxy"), WithNamespace("linkerd"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("ebs-csi-node", WithGVK("", testVersion, "Deployment"), WithName("ebs-csi-node"), WithNamespace("kube-system"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("gce-pd-csi-controller", WithGVK("", testVersion, "Deployment"), WithName("gce-pd-csi-controller-sa"), WithNamespace("kube-system"))) + require.NoError(t, err) + + // Components in sensitive namespaces + err = storeInstance.SaveComponent(createComponent("app-in-cert-manager", WithGVK("", testVersion, "Deployment"), WithName("app-in-sensitive-ns-1"), WithNamespace("cert-manager"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-in-kube-proxy", WithGVK("", testVersion, "Deployment"), WithName("app-in-sensitive-ns-2"), WithNamespace("kube-proxy"))) + require.NoError(t, err) + + // Components with partial name matches + err = storeInstance.SaveComponent(createComponent("node-exporter-similar", WithGVK("", testVersion, "Deployment"), WithName("node-metrics-exporter"), WithNamespace("monitoring"))) + require.NoError(t, err) + + // Components with low priority similarity + err = storeInstance.SaveComponent(createComponent("app-similar-to-istio", WithGVK("", testVersion, "Deployment"), WithName("iso-ist-app"), WithNamespace("default"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("app-similar-to-cert-manager", WithGVK("", testVersion, "Deployment"), WithName("custom-cert-provisioner"), WithNamespace("default"))) + require.NoError(t, err) expectedComponentPriorityMap := map[string]client.InsightComponentPriority{ "cert-manager": client.InsightComponentPriorityCritical, @@ -1099,17 +1130,9 @@ func TestComponentCache_ComponentInsights(t *testing.T) { "custom-cert-provisioner": client.InsightComponentPriorityLow, } - // Insert all test components into cache - for _, tc := range testComponents { - err := storeInstance.SaveComponentAttributes(tc) - require.NoError(t, err, "Failed to add component %s to cache", tc.UID) - } - - // Get component insights insights, err := storeInstance.GetComponentInsights() require.NoError(t, err, "Failed to get component insights") - // Build a map of component name to priority for easier testing priorityMap := make(map[string]client.InsightComponentPriority) for _, insight := range insights { priorityMap[insight.Name] = *insight.Priority @@ -1146,21 +1169,21 @@ func TestComponentCountsCache(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - testComponents := []client.ComponentChildAttributes{ - // Components with names very similar to critical priority resources - createComponent("a", nil, WithKind("Namespace"), WithState(client.ComponentStateRunning), WithName("a")), - createComponent("b", nil, WithKind("Namespace"), WithState(client.ComponentStateRunning), WithName("b")), - createComponent("c", nil, WithKind("Namespace"), WithState(client.ComponentStateRunning), WithName("c")), - createComponent("node-1", nil, WithKind("Node"), WithState(client.ComponentStateRunning), WithName("node-1")), - createComponent("node-2", nil, WithKind("Node"), WithState(client.ComponentStateRunning), WithName("node-2")), - createComponent("node-3", nil, WithKind("Node"), WithState(client.ComponentStateRunning), WithName("node-3")), - } + // Create 3 namespaces + err = storeInstance.SaveComponent(createComponent("a", WithGVK("", testVersion, "Namespace"), WithName("a"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("b", WithGVK("", testVersion, "Namespace"), WithName("b"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("c", WithGVK("", testVersion, "Namespace"), WithName("c"))) + require.NoError(t, err) - // Insert all test components into cache - for _, tc := range testComponents { - err := storeInstance.SaveComponentAttributes(tc) - require.NoError(t, err, "Failed to add component %s to cache", tc.UID) - } + // Create 3 nodes + err = storeInstance.SaveComponent(createComponent("node-1", WithGVK("", testVersion, "Node"), WithName("node-1"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("node-2", WithGVK("", testVersion, "Node"), WithName("node-2"))) + require.NoError(t, err) + err = storeInstance.SaveComponent(createComponent("node-3", WithGVK("", testVersion, "Node"), WithName("node-3"))) + require.NoError(t, err) nodes, namespaces, err := storeInstance.GetComponentCounts() require.NoError(t, err, "Failed to get component counts") @@ -1177,30 +1200,24 @@ func TestComponentCountsCache(t *testing.T) { }(storeInstance) // Create 2 nodes - node1 := createComponent("node-1", nil, WithKind("Node"), WithName("worker-1")) - err = storeInstance.SaveComponentAttributes(node1) + err = storeInstance.SaveComponent(createComponent("node-1", WithGVK("", testVersion, "Node"), WithName("worker-1"))) require.NoError(t, err) - node2 := createComponent("node-2", nil, WithKind("Node"), WithName("worker-2")) - err = storeInstance.SaveComponentAttributes(node2) + err = storeInstance.SaveComponent(createComponent("node-2", WithGVK("", testVersion, "Node"), WithName("worker-2"))) require.NoError(t, err) // Create 3 namespaces - ns1 := createComponent("ns-1", nil, WithKind("Namespace"), WithName("default")) - err = storeInstance.SaveComponentAttributes(ns1) + err = storeInstance.SaveComponent(createComponent("ns-1", WithGVK("", testVersion, "Namespace"), WithName("default"))) require.NoError(t, err) - ns2 := createComponent("ns-2", nil, WithKind("Namespace"), WithName("kube-system")) - err = storeInstance.SaveComponentAttributes(ns2) + err = storeInstance.SaveComponent(createComponent("ns-2", WithGVK("", testVersion, "Namespace"), WithName("kube-system"))) require.NoError(t, err) - ns3 := createComponent("ns-3", nil, WithKind("Namespace"), WithName("production")) - err = storeInstance.SaveComponentAttributes(ns3) + err = storeInstance.SaveComponent(createComponent("ns-3", WithGVK("", testVersion, "Namespace"), WithName("production"))) require.NoError(t, err) // Create some other resources that should not be counted - pod := createComponent("pod-1", nil, WithKind("Pod"), WithName("test-pod")) - err = storeInstance.SaveComponentAttributes(pod) + err = storeInstance.SaveComponent(createComponent("pod-1", WithGVK("", testVersion, "Pod"), WithName("test-pod"))) require.NoError(t, err) nodeCount, namespaceCount, err := storeInstance.GetComponentCounts() @@ -1221,17 +1238,18 @@ func TestComponentCache_ComponentChildrenLimit(t *testing.T) { // Create parent component parentUID := "parent-with-many-children" - parent := createComponent(parentUID, nil, WithName("parent-with-many-children")) - err = storeInstance.SaveComponentAttributes(parent) + err = storeInstance.SaveComponent(createComponent(parentUID, WithName("parent-with-many-children"))) + require.NoError(t, err) require.NoError(t, err) // Create 150 child components to test the 100 limit totalChildren := 150 for i := 0; i < totalChildren; i++ { - childUID := fmt.Sprintf("child-%d", i) - childName := fmt.Sprintf("child-component-%d", i) - child := createComponent(childUID, &parentUID, WithName(childName)) - err := storeInstance.SaveComponentAttributes(child) + err := storeInstance.SaveComponent(createComponent( + fmt.Sprintf("child-%d", i), + WithName(fmt.Sprintf("child-component-%d", i)), + WithParent(parentUID))) + require.NoError(t, err) require.NoError(t, err) } @@ -1257,17 +1275,16 @@ func TestComponentCache_ComponentChildrenLimit(t *testing.T) { // Create parent component parentUID := "parent-with-few-children" - parent := createComponent(parentUID, nil, WithName("parent-with-few-children")) - err = storeInstance.SaveComponentAttributes(parent) + err = storeInstance.SaveComponent(createComponent(parentUID, WithName("parent-with-few-children"))) require.NoError(t, err) // Create 50 child components (under the limit) totalChildren := 50 for i := 0; i < totalChildren; i++ { - childUID := fmt.Sprintf("few-child-%d", i) - childName := fmt.Sprintf("few-child-component-%d", i) - child := createComponent(childUID, &parentUID, WithName(childName)) - err := storeInstance.SaveComponentAttributes(child) + err := storeInstance.SaveComponent(createComponent( + fmt.Sprintf("few-child-%d", i), + WithParent(parentUID), + WithName(fmt.Sprintf("few-child-component-%d", i)))) require.NoError(t, err) } @@ -1293,8 +1310,7 @@ func TestComponentCache_ComponentChildrenLimit(t *testing.T) { // Create root component rootUID := "root-with-deep-hierarchy" - root := createComponent(rootUID, nil, WithName("root-with-deep-hierarchy")) - err = storeInstance.SaveComponentAttributes(root) + err = storeInstance.SaveComponent(createComponent(rootUID, WithName("root-with-deep-hierarchy"))) require.NoError(t, err) // Create a multi-level hierarchy that exceeds 100 total descendants @@ -1303,8 +1319,7 @@ func TestComponentCache_ComponentChildrenLimit(t *testing.T) { for i := 0; i < 30; i++ { uid := fmt.Sprintf("level1-%d", i) level1UIDs[i] = uid - component := createComponent(uid, &rootUID, WithName(fmt.Sprintf("level1-component-%d", i))) - err := storeInstance.SaveComponentAttributes(component) + err := storeInstance.SaveComponent(createComponent(uid, WithParent(rootUID), WithName(fmt.Sprintf("level1-component-%d", i)))) require.NoError(t, err) } @@ -1314,8 +1329,7 @@ func TestComponentCache_ComponentChildrenLimit(t *testing.T) { parentUID := level1UIDs[i%len(level1UIDs)] for j := 0; j < 2 && level2Count < 40; j++ { uid := fmt.Sprintf("level2-%d-%d", i, j) - component := createComponent(uid, &parentUID, WithName(fmt.Sprintf("level2-component-%d-%d", i, j))) - err := storeInstance.SaveComponentAttributes(component) + err := storeInstance.SaveComponent(createComponent(uid, WithParent(parentUID), WithName(fmt.Sprintf("level2-component-%d-%d", i, j)))) require.NoError(t, err) level2Count++ } @@ -1328,8 +1342,7 @@ func TestComponentCache_ComponentChildrenLimit(t *testing.T) { level2UID := fmt.Sprintf("level2-%d-0", i%20) for j := 0; j < 2 && level3Count < 50; j++ { uid := fmt.Sprintf("level3-%d-%d", i, j) - component := createComponent(uid, &level2UID, WithName(fmt.Sprintf("level3-component-%d-%d", i, j))) - err := storeInstance.SaveComponentAttributes(component) + err := storeInstance.SaveComponent(createComponent(uid, WithParent(level2UID), WithName(fmt.Sprintf("level3-component-%d-%d", i, j)))) require.NoError(t, err) level3Count++ } @@ -1499,21 +1512,16 @@ func TestGetComponentsByGVK(t *testing.T) { }(storeInstance) // Insert components with matching GVK and different names/namespaces to avoid unique conflict - c1 := createComponent("gvk-uid-1", nil, WithGroup(gvk.Group), WithVersion(gvk.Version), WithKind(gvk.Kind), WithNamespace("ns-1"), WithName("alpha")) - require.NoError(t, storeInstance.SaveComponentAttributes(c1)) - - c2 := createComponent("gvk-uid-2", nil, WithGroup(gvk.Group), WithVersion(gvk.Version), WithKind(gvk.Kind), WithNamespace("ns-2"), WithName("beta")) - require.NoError(t, storeInstance.SaveComponentAttributes(c2)) - - c3 := createComponent("gvk-uid-3", nil, WithGroup(gvk.Group), WithVersion(gvk.Version), WithKind(gvk.Kind), WithNamespace("ns-3"), WithName("gamma")) - require.NoError(t, storeInstance.SaveComponentAttributes(c3)) + require.NoError(t, storeInstance.SaveComponent(createComponent("gvk-uid-1", WithGVK(gvk.Group, gvk.Version, gvk.Kind), WithNamespace("ns-1"), WithName("alpha")))) + require.NoError(t, storeInstance.SaveComponent(createComponent("gvk-uid-2", WithGVK(gvk.Group, gvk.Version, gvk.Kind), WithNamespace("ns-2"), WithName("beta")))) + require.NoError(t, storeInstance.SaveComponent(createComponent("gvk-uid-3", WithGVK(gvk.Group, gvk.Version, gvk.Kind), WithNamespace("ns-3"), WithName("gamma")))) // Insert components with different GVK to ensure they are filtered out - diff1 := createComponent("other-uid-1", nil, WithGroup("apps"), WithVersion("v1"), WithKind("StatefulSet"), WithNamespace("ns-1"), WithName("alpha")) - require.NoError(t, storeInstance.SaveComponentAttributes(diff1)) + diff1 := createComponent("other-uid-1", WithGVK("apps", "v1", "StatefulSet"), WithNamespace("ns-1"), WithName("alpha")) + require.NoError(t, storeInstance.SaveComponent(diff1)) - diff2 := createComponent("other-uid-2", nil, WithGroup("extensions"), WithVersion("v1"), WithKind("Deployment"), WithNamespace("ns-1"), WithName("delta")) - require.NoError(t, storeInstance.SaveComponentAttributes(diff2)) + diff2 := createComponent("other-uid-2", WithGVK("extensions", "v1", "Deployment"), WithNamespace("ns-1"), WithName("delta")) + require.NoError(t, storeInstance.SaveComponent(diff2)) entries, err := storeInstance.GetComponentsByGVK(gvk) require.NoError(t, err) @@ -1528,7 +1536,6 @@ func TestGetComponentsByGVK(t *testing.T) { assert.Equal(t, gvk.Kind, e.Kind, "all entries should have correct kind") names = append(names, e.Name) nss = append(nss, e.Namespace) - assert.Equal(t, "", e.ServerSHA, "server SHA is not set when saving component attributes, it should be empty") } assert.ElementsMatch(t, []string{"alpha", "beta", "gamma"}, names, "expected names to match, order not guaranteed") assert.ElementsMatch(t, []string{"ns-1", "ns-2", "ns-3"}, nss, "expected namespaces to match, order not guaranteed") @@ -1547,27 +1554,27 @@ func TestComponentCache_DeleteComponents(t *testing.T) { }(storeInstance) // Create components with same GVK but different names/namespaces - component1 := createComponent("uid-1", nil, WithGroup(deploymentsGVK.Group), WithVersion(deploymentsGVK.Version), WithKind(deploymentsGVK.Kind), WithName("deployment-1"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(component1) + component1 := createComponent("uid-1", WithGVK(deploymentsGVK.Group, deploymentsGVK.Version, deploymentsGVK.Kind), WithName("deployment-1"), WithNamespace("default")) + err = storeInstance.SaveComponent(component1) require.NoError(t, err) - component2 := createComponent("uid-2", nil, - WithGroup(deploymentsGVK.Group), WithVersion(deploymentsGVK.Version), WithKind(deploymentsGVK.Kind), + component2 := createComponent("uid-2", + WithGVK(deploymentsGVK.Group, deploymentsGVK.Version, deploymentsGVK.Kind), WithName("deployment-2"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(component2) + err = storeInstance.SaveComponent(component2) require.NoError(t, err) - component3 := createComponent("uid-3", nil, - WithGroup(deploymentsGVK.Group), WithVersion(deploymentsGVK.Version), WithKind(deploymentsGVK.Kind), + component3 := createComponent("uid-3", + WithGVK(deploymentsGVK.Group, deploymentsGVK.Version, deploymentsGVK.Kind), WithName("deployment-3"), WithNamespace("kube-system")) - err = storeInstance.SaveComponentAttributes(component3) + err = storeInstance.SaveComponent(component3) require.NoError(t, err) // Create component with different GVK - component4 := createComponent("uid-4", nil, - WithGroup(servicesGVK.Group), WithVersion(servicesGVK.Version), WithKind(servicesGVK.Kind), + component4 := createComponent("uid-4", + WithGVK(servicesGVK.Group, servicesGVK.Version, servicesGVK.Kind), WithName("service-1"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(component4) + err = storeInstance.SaveComponent(component4) require.NoError(t, err) components, err := storeInstance.GetComponentsByGVK(deploymentsGVK) @@ -1598,31 +1605,29 @@ func TestComponentCache_DeleteComponents(t *testing.T) { }(storeInstance) // Create components with empty group (core resources) - pod1 := createComponent("pod-uid-1", nil, WithGroup(""), WithVersion("v1"), WithKind("Pod"), WithName("pod-1"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(pod1) + err = storeInstance.SaveComponent(createComponent("job-uid-1", WithGVK("", "v1", "Job"), WithName("job-1"), WithNamespace("default"))) require.NoError(t, err) - pod2 := createComponent("pod-uid-2", nil, WithGroup(""), WithVersion("v1"), WithKind("Pod"), WithName("pod-2"), WithNamespace("kube-system")) - err = storeInstance.SaveComponentAttributes(pod2) + err = storeInstance.SaveComponent(createComponent("job-uid-2", WithGVK("", "v1", "Job"), WithName("job-2"), WithNamespace("kube-system"))) require.NoError(t, err) - service := createComponent("service-uid", nil, WithGroup(""), WithVersion("v1"), WithKind("Service"), WithName("service-1"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(service) + service := createComponent("service-uid", WithGVK("", "v1", "Service"), WithName("service-1"), WithNamespace("default")) + err = storeInstance.SaveComponent(service) require.NoError(t, err) - // Verify pods exist - pods, err := storeInstance.GetComponentsByGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}) + // Verify jobs exist + jobs, err := storeInstance.GetComponentsByGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Job"}) require.NoError(t, err) - assert.Len(t, pods, 2) + assert.Len(t, jobs, 2) - // Delete all pods (empty group) - err = storeInstance.DeleteComponents("", "v1", "Pod") + // Delete all jobs (empty group) + err = storeInstance.DeleteComponents("", "v1", "Job") require.NoError(t, err) - // Verify pods are deleted - pods, err = storeInstance.GetComponentsByGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Pod"}) + // Verify jobs are deleted + jobs, err = storeInstance.GetComponentsByGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Job"}) require.NoError(t, err) - assert.Len(t, pods, 0) + assert.Len(t, jobs, 0) // Verify service still exists services, err := storeInstance.GetComponentsByGVK(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Service"}) @@ -1637,8 +1642,7 @@ func TestComponentCache_DeleteComponents(t *testing.T) { require.NoError(t, storeInstance.Shutdown(), "failed to shutdown store") }(storeInstance) - component := createComponent("existing-uid", nil, WithGroup("apps"), WithVersion("v1"), WithKind("Deployment"), WithName("existing-deployment"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(component) + err = storeInstance.SaveComponent(createComponent("existing-uid", WithGVK("apps", "v1", "Deployment"), WithName("existing-deployment"), WithNamespace("default"))) require.NoError(t, err) err = storeInstance.DeleteComponents("nonexistent", "v1", "NonExistentKind") @@ -1661,25 +1665,21 @@ func TestComponentCache_GetServiceComponents(t *testing.T) { serviceID := "test-service-123" // Create components with the target service ID - component1 := createComponent("service-comp-1", nil, WithGroup("apps"), WithVersion("v1"), WithKind("Deployment"), WithName("app-deployment"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(component1, "test-node", time.Now().Unix(), serviceID) + err = storeInstance.SaveComponent(createComponent("service-comp-1", WithGVK("apps", "v1", "Deployment"), WithName("app-deployment"), WithNamespace("default"), WithService(serviceID))) require.NoError(t, err) - component2 := createComponent("service-comp-2", nil, WithGroup(""), WithVersion("v1"), WithKind("Pod"), WithName("app-pod"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(component2, "test-node", time.Now().Unix(), serviceID) + err = storeInstance.SaveComponent(createComponent("service-comp-2", WithGVK("", "v1", "Job"), WithName("app-job"), WithNamespace("default"), WithService(serviceID))) require.NoError(t, err) // Create component with different service ID - otherComponent := createComponent("other-comp", nil, WithGroup("apps"), WithVersion("v1"), WithKind("Deployment"), WithName("other-deployment"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(otherComponent, "test-node", time.Now().Unix(), "other-service") + err = storeInstance.SaveComponent(createComponent("other-comp", WithService("other-service"), WithGVK("apps", "v1", "Deployment"), WithName("other-deployment"), WithNamespace("default"))) require.NoError(t, err) // Create component with no service ID - noServiceComponent := createComponent("no-service-comp", nil, WithGroup(""), WithVersion("v1"), WithKind("Service"), WithName("no-service"), WithNamespace("default")) - err = storeInstance.SaveComponentAttributes(noServiceComponent, "test-node", time.Now().Unix(), nil) + err = storeInstance.SaveComponent(createComponent("no-service-comp", WithGVK("", "v1", "Service"), WithName("no-service"), WithNamespace("default"))) require.NoError(t, err) - components, err := storeInstance.GetServiceComponents(serviceID) + components, err := storeInstance.GetServiceComponents(serviceID, true) require.NoError(t, err, "failed to get components for service") assert.Len(t, components, 2, "expected 2 components with matching service ID") @@ -1703,12 +1703,11 @@ func TestComponentCache_GetServiceComponents(t *testing.T) { }(storeInstance) // Create some components with different service IDs - component := createComponent("test-comp", nil, WithName("test-component")) - err = storeInstance.SaveComponentAttributes(component, "test-node", time.Now().Unix(), "existing-service") + err = storeInstance.SaveComponent(createComponent("test-comp", WithName("test-component"))) require.NoError(t, err) // Try to get components for non-existent service - components, err := storeInstance.GetServiceComponents("non-existent-service") + components, err := storeInstance.GetServiceComponents("non-existent-service", true) require.NoError(t, err) assert.Len(t, components, 0) }) @@ -1816,7 +1815,7 @@ func TestComponentCache_SaveComponents(t *testing.T) { obj := newUnstructured(uid, name, "default", "apps", "v1", "Deployment") objs = append(objs, obj) } - require.NoError(t, storeInstance.SaveComponents(objs)) + require.NoError(t, storeInstance.SaveComponents(objs, lo.ToPtr(true))) for _, obj := range objs { entry, err := storeInstance.GetComponent(obj) @@ -1890,7 +1889,7 @@ func TestComponentCache_ProcessedHookComponents(t *testing.T) { createHookJob("default", "check", serviceID), } - require.NoError(t, storeInstance.SaveComponents(r)) + require.NoError(t, storeInstance.SaveComponents(r, lo.ToPtr(true))) result, err := storeInstance.GetHookComponents(serviceID) require.NoError(t, err) @@ -1945,7 +1944,7 @@ func TestComponentCache_ProcessedHookComponents(t *testing.T) { createHookJob("default", "app2-migrator", serviceID2), } - require.NoError(t, storeInstance.SaveComponents(hooks)) + require.NoError(t, storeInstance.SaveComponents(hooks, lo.ToPtr(true))) // Check service 1 hooks result1, err := storeInstance.GetHookComponents(serviceID1) @@ -2000,7 +1999,7 @@ func TestComponentCache_ProcessedHookComponents(t *testing.T) { hooks := []unstructured.Unstructured{hookJob, hookPod, hookConfigMap} - require.NoError(t, storeInstance.SaveComponents(hooks)) + require.NoError(t, storeInstance.SaveComponents(hooks, lo.ToPtr(true))) result, err := storeInstance.GetHookComponents(serviceID) require.NoError(t, err) @@ -2028,7 +2027,7 @@ func TestComponentCache_ProcessedHookComponents(t *testing.T) { hook := createHookJob("default", "test-hook", serviceID) expectedUID := hook.GetUID() - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{hook})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{hook}, lo.ToPtr(true))) result, err := storeInstance.GetHookComponents(serviceID) require.NoError(t, err) @@ -2055,7 +2054,7 @@ func TestComponentCache_ProcessedHookComponents(t *testing.T) { hooks[i] = createHookJob("default", fmt.Sprintf("hook-%d", i), serviceID) } - require.NoError(t, storeInstance.SaveComponents(hooks)) + require.NoError(t, storeInstance.SaveComponents(hooks, lo.ToPtr(true))) result, err := storeInstance.GetHookComponents(serviceID) require.NoError(t, err) @@ -2083,7 +2082,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { obj.Object["spec"] = map[string]interface{}{"replicas": "3"} // Save the component first - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) // Get the component before sync componentBefore, err := storeInstance.GetComponent(obj) @@ -2114,7 +2113,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { obj := createUnstructuredResource("apps", "v1", "StatefulSet", "default", "test-statefulset") obj.Object["spec"] = map[string]interface{}{"replicas": "2"} - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) // Get the component before sync to check manifest_sha componentBefore, err := storeInstance.GetComponent(obj) @@ -2152,7 +2151,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { }, } - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) // First, set a transient_manifest_sha by calling UpdateComponentSHA require.NoError(t, storeInstance.UpdateComponentSHA(obj, store.TransientManifestSHA)) @@ -2192,7 +2191,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { "key": "value", } - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) require.NoError(t, storeInstance.UpdateComponentSHA(obj, store.TransientManifestSHA)) // Verify transient_manifest_sha is set before sync @@ -2221,7 +2220,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { "password": "secret", } - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) require.NoError(t, storeInstance.UpdateComponentSHA(obj, store.TransientManifestSHA)) // Get component before sync @@ -2281,7 +2280,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { "nodeName": "test-node", } - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) require.NoError(t, storeInstance.SyncAppliedResource(obj)) component, err := storeInstance.GetComponent(obj) @@ -2308,7 +2307,7 @@ func TestComponentCache_SyncAppliedResource(t *testing.T) { }, } - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) require.NoError(t, storeInstance.SyncAppliedResource(obj)) component, err := storeInstance.GetComponent(obj) @@ -2350,7 +2349,7 @@ func TestComponentCache_SetServiceChildren(t *testing.T) { // Create a test resource obj := createUnstructuredResource("apps", "v1", "Deployment", "default", "existing-deployment") - require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj})) + require.NoError(t, storeInstance.SaveComponents([]unstructured.Unstructured{obj}, lo.ToPtr(true))) updated, err := storeInstance.SetServiceChildren("abc", "123", []common.StoreKey{ { diff --git a/pkg/streamline/store/store.go b/pkg/streamline/store/store.go index d848a16ea..dcc039dab 100644 --- a/pkg/streamline/store/store.go +++ b/pkg/streamline/store/store.go @@ -14,9 +14,12 @@ import ( type Store interface { SaveComponent(obj unstructured.Unstructured) error - SaveComponents(obj []unstructured.Unstructured) error + SaveComponents(obj []unstructured.Unstructured, applied *bool) error - SaveComponentAttributes(obj client.ComponentChildAttributes, args ...any) error + // SyncServiceComponents stores all service components in the store before applying them. + // It also ensures that components that are no longer part of the service + // and were not applied are removed from the store. + SyncServiceComponents(serviceID string, resources []unstructured.Unstructured) error GetComponent(obj unstructured.Unstructured) (*smcommon.Component, error) @@ -35,7 +38,7 @@ type Store interface { // GetServiceComponents retrieves all parent components associated with a given service ID. // All components with parents are filtered out. // It returns a slice of Component structs containing information about each component and any error encountered. - GetServiceComponents(serviceID string) (smcommon.Components, error) + GetServiceComponents(serviceID string, onlyApplied bool) (smcommon.Components, error) // GetComponentChildren retrieves all child components and their descendants up to 4 levels deep for a given component UID. // It returns a slice of ComponentChildAttributes containing information about each child component and any error encountered. diff --git a/pkg/streamline/supervisor.go b/pkg/streamline/supervisor.go index fd2c81890..017514690 100644 --- a/pkg/streamline/supervisor.go +++ b/pkg/streamline/supervisor.go @@ -7,6 +7,7 @@ import ( "time" cmap "github.com/orcaman/concurrent-map/v2" + "github.com/pluralsh/console/go/client" smcommon "github.com/pluralsh/deployment-operator/pkg/streamline/common" "github.com/samber/lo" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -365,14 +366,36 @@ func (in *Supervisor) processComponentUpdate() bool { func (in *Supervisor) flushComponentUpdates(serviceId string) { // Get service components. - components, err := in.store.GetServiceComponents(serviceId) + components, err := in.store.GetServiceComponents(serviceId, false) if err != nil { klog.ErrorS(err, "failed to get service components", "service", serviceId) return } + // Get hooks. + hooks, err := in.store.GetHookComponents(serviceId) + if err != nil { + return + } + + // Create a map of hooks for an easy lookup. + keyToHookComponent := make(map[smcommon.Key]smcommon.HookComponent) + for _, hook := range hooks { + keyToHookComponent[hook.StoreKey().Key()] = hook + } + + // Exclude non-existing components with a deletion policy that have reached their desired state. + attributes := make([]client.ComponentAttributes, 0, len(components)) + for _, component := range components { + if hook, ok := keyToHookComponent[component.Key()]; ok && hook.HadDesiredState() { + continue + } + + attributes = append(attributes, component.ComponentAttributes()) + } + // Update components. - if err = in.statusSynchronizer.UpdateServiceComponents(serviceId, lo.ToSlicePtr(components.ComponentAttributes())); err != nil { + if err = in.statusSynchronizer.UpdateServiceComponents(serviceId, lo.ToSlicePtr(attributes)); err != nil { klog.ErrorS(err, "failed to update service components", "service", serviceId) } } diff --git a/pkg/streamline/synchronizer.go b/pkg/streamline/synchronizer.go index 415eb9f12..199316e38 100644 --- a/pkg/streamline/synchronizer.go +++ b/pkg/streamline/synchronizer.go @@ -148,7 +148,7 @@ func (in *synchronizer) handleList(list unstructured.UnstructuredList) { in.notifyEventSubscribers(watch.Event{Type: watch.Added, Object: resource.DeepCopyObject()}) } - if err := in.store.SaveComponents(list.Items); err != nil { + if err := in.store.SaveComponents(list.Items, lo.ToPtr(true)); err != nil { klog.ErrorS(err, "failed to save resource", "gvr", in.gvr) } }