Repository: klothoplatform/klotho
Pull request #844 (2024-01-04 20:28:49)

add eks add on for cloudwatch
switches out the custom fluent bit implementation for the eks add on

- also fixes the validation for lists so that we validate sub properties (this was causing eks tests to fail due to not properly sanitizing the env variables names) 
- added unit tests for the validation of list properties to cover added/untested functionality

eks node s3 test
```
============================= test session starts ==============================
platform darwin -- Python 3.10.11, pytest-7.4.3, pluggy-1.3.0
rootdir: /Users/jordansinger/workspace/engine-e2e-tests/output/aws-3qrnjn
plugins: repeat-0.9.3, html-4.1.1, xdist-3.4.0, asyncio-0.21.1, metadata-3.0.0, anyio-3.7.1, check-2.2.2
asyncio: mode=strict
created: 1/1 worker
1 worker [1 item]

.                                                                        [100%]
- generated xml file: /var/folders/n_/bbppnfk179d6l4vz4qmz0chh0000gn/T/tmpq9ofvje3 -
============================== 1 passed in 1.20s ===============================
```

### Standard checks

- **Unit tests**: Any special considerations?
- **Docs**: Do we need to update any docs, internal or public?
- **Backwards compatibility**: Will this break existing apps? If so, what would be the extra work required to keep them working?



Patch:
diff --git a/pkg/construct2/graph_io.go b/pkg/construct2/graph_io.go
index 2391e1b7..53056345 100644
--- a/pkg/construct2/graph_io.go
+++ b/pkg/construct2/graph_io.go
@@ -46,7 +46,9 @@ func (g YamlGraph) MarshalYAML() (interface{}, error) {
 			errs = errors.Join(errs, err)
 			continue
 		}
-
+		if r.Imported {
+			r.Properties["imported"] = r.Imported
+		}
 		props, err := yaml_util.MarshalMap(r.Properties, func(a, b string) bool { return a < b })
 		if err != nil {
 			errs = errors.Join(errs, err)
@@ -106,8 +108,8 @@ func (g YamlGraph) MarshalYAML() (interface{}, error) {
 
 func (g *YamlGraph) UnmarshalYAML(n *yaml.Node) error {
 	type graph struct {
-		Resouces map[ResourceId]Properties `yaml:"resources"`
-		Edges    map[SimpleEdge]struct{}   `yaml:"edges"`
+		Resources map[ResourceId]Properties `yaml:"resources"`
+		Edges     map[SimpleEdge]struct{}   `yaml:"edges"`
 	}
 	var y graph
 	if err := n.Decode(&y); err != nil {
@@ -119,10 +121,21 @@ func (g *YamlGraph) UnmarshalYAML(n *yaml.Node) error {
 	}
 
 	var errs error
-	for rid, props := range y.Resouces {
+	for rid, props := range y.Resources {
+		var imported bool
+		if imp, ok := props["imported"]; ok {
+			val, ok := imp.(bool)
+			if !ok {
+				errs = errors.Join(errs, fmt.Errorf("unable to parse imported value as boolean for resource %s", rid))
+				// Don't continue here so that the vertex is still added, otherwise it could erroneously cause failures in the edge copying
+			}
+			imported = val
+			delete(props, "imported")
+		}
 		err := g.Graph.AddVertex(&Resource{
 			ID:         rid,
 			Properties: props,
+			Imported:   imported,
 		})
 		errs = errors.Join(errs, err)
 	}
diff --git a/pkg/construct2/resource.go b/pkg/construct2/resource.go
index 890ebd42..041a6af6 100644
--- a/pkg/construct2/resource.go
+++ b/pkg/construct2/resource.go
@@ -3,11 +3,5 @@ package construct2
 type Resource struct {
 	ID         ResourceId
 	Properties Properties
-}
-
-// Id is a temporary bridge to the old Resource interface.
-//
-// Deprecated: remove in favour of direct ID field access.
-func (r *Resource) Id() ResourceId {
-	return r.ID
+	Imported   bool
 }
diff --git a/pkg/engine2/constraints.go b/pkg/engine2/constraints.go
index 30dd3042..d77c8e68 100644
--- a/pkg/engine2/constraints.go
+++ b/pkg/engine2/constraints.go
@@ -57,6 +57,10 @@ func applyApplicationConstraint(ctx solution_context.SolutionContext, constraint
 	case constraints.AddConstraintOperator:
 		return ctx.OperationalView().AddVertex(res)
 
+	case constraints.ImportConstraintOperator:
+		res.Imported = true
+		return ctx.OperationalView().AddVertex(res)
+
 	case constraints.RemoveConstraintOperator:
 		return reconciler.RemoveResource(ctx, res.ID, true)
 
diff --git a/pkg/engine2/constraints/application_constraint.go b/pkg/engine2/constraints/application_constraint.go
index 4c25df9a..492e5067 100644
--- a/pkg/engine2/constraints/application_constraint.go
+++ b/pkg/engine2/constraints/application_constraint.go
@@ -87,6 +87,9 @@ func (constraint *ApplicationConstraint) Validate() error {
 	if constraint.Operator == RemoveConstraintOperator && (constraint.Node == construct.ResourceId{}) {
 		return errors.New("remove constraint must have a node defined")
 	}
+	if constraint.Operator == ImportConstraintOperator && (constraint.Node == construct.ResourceId{}) {
+		return errors.New("import constraint must have a node defined")
+	}
 	return nil
 }
 
diff --git a/pkg/engine2/constraints/constraint.go b/pkg/engine2/constraints/constraint.go
index 77d6c6dc..9439aa06 100644
--- a/pkg/engine2/constraints/constraint.go
+++ b/pkg/engine2/constraints/constraint.go
@@ -69,6 +69,7 @@ const (
 	MustExistConstraintOperator    ConstraintOperator = "must_exist"
 	MustNotExistConstraintOperator ConstraintOperator = "must_not_exist"
 	AddConstraintOperator          ConstraintOperator = "add"
+	ImportConstraintOperator       ConstraintOperator = "import"
 	RemoveConstraintOperator       ConstraintOperator = "remove"
 	ReplaceConstraintOperator      ConstraintOperator = "replace"
 	EqualsConstraintOperator       ConstraintOperator = "equals"
diff --git a/pkg/engine2/operational_eval/vertex_path_expand.go b/pkg/engine2/operational_eval/vertex_path_expand.go
index a81a877a..f0e054ec 100644
--- a/pkg/engine2/operational_eval/vertex_path_expand.go
+++ b/pkg/engine2/operational_eval/vertex_path_expand.go
@@ -60,7 +60,7 @@ func expansionResultString(result construct.Graph, dep construct.ResourceEdge) (
 
 	path, err := graph.ShortestPathStable(result, dep.Source.ID, dep.Target.ID, construct.ResourceIdLess)
 	if err != nil {
-		return "", fmt.Errorf("expansion result does not contain path from %s to %s: %w", dep.Source, dep.Target, err)
+		return "", fmt.Errorf("expansion result does not contain path from %s to %s: %w", dep.Source.ID, dep.Target.ID, err)
 	}
 	for i, res := range path {
 		if i == 0 {
diff --git a/pkg/engine2/operational_eval/vertex_property.go b/pkg/engine2/operational_eval/vertex_property.go
index cbba7c2d..ac5a8b80 100644
--- a/pkg/engine2/operational_eval/vertex_property.go
+++ b/pkg/engine2/operational_eval/vertex_property.go
@@ -119,12 +119,20 @@ func (v *propertyVertex) Evaluate(eval *Evaluator) error {
 		return err
 	}
 
+	// we know we cannot change properties of imported resources only users through constraints
+	// we still want to be able to update ids in case they are setting the property of a namespaced resource
+	// so we just conditionally run the edge operational rules
+	//
+	// we still need to run the resource operational rules though,
+	// to make sure dependencies exist where properties have operational rules set
 	if err := v.evaluateResourceOperational(sol, res); err != nil {
 		return err
 	}
 
-	if err := v.evaluateEdgeOperational(sol, res); err != nil {
-		return err
+	if !res.Imported {
+		if err := v.evaluateEdgeOperational(sol, res); err != nil {
+			return err
+		}
 	}
 
 	if err := eval.UpdateId(v.Ref.Resource, res.ID); err != nil {
@@ -174,19 +182,20 @@ func (v *propertyVertex) evaluateConstraints(sol solution_context.SolutionContex
 
 	ctx := solution_context.DynamicCtx(sol)
 	var defaultVal any
-	if currentValue == nil {
+	if currentValue == nil && !res.Imported {
 		defaultVal, err = v.Template.GetDefaultValue(ctx, dynData)
 		if err != nil {
 			return fmt.Errorf("could not get default value for %s: %w", v.Ref, err)
 		}
 	}
-	if currentValue == nil && setConstraint.Operator == "" && v.Template != nil && defaultVal != nil {
+	if currentValue == nil && setConstraint.Operator == "" && v.Template != nil && defaultVal != nil && !res.Imported {
 		err = solution_context.ConfigureResource(
 			sol,
 			res,
 			knowledgebase.Configuration{Field: v.Ref.Property, Value: defaultVal},
 			dynData,
 			"set",
+			false,
 		)
 		if err != nil {
 			return fmt.Errorf("could not set default value for %s: %w", v.Ref, err)
@@ -208,6 +217,7 @@ func (v *propertyVertex) evaluateConstraints(sol solution_context.SolutionContex
 			knowledgebase.Configuration{Field: v.Ref.Property, Value: setConstraint.Value},
 			dynData,
 			"set",
+			true,
 		)
 		if err != nil {
 			return fmt.Errorf("could not apply initial constraint for %s: %w", v.Ref, err)
@@ -231,6 +241,7 @@ func (v *propertyVertex) evaluateConstraints(sol solution_context.SolutionContex
 			knowledgebase.Configuration{Field: v.Ref.Property, Value: c.Value},
 			dynData,
 			action,
+			true,
 		))
 		dynData.Resource = res.ID
 	}
diff --git a/pkg/engine2/operational_rule/operational_configuration.go b/pkg/engine2/operational_rule/operational_configuration.go
index a4a88085..588366f7 100644
--- a/pkg/engine2/operational_rule/operational_configuration.go
+++ b/pkg/engine2/operational_rule/operational_configuration.go
@@ -25,7 +25,7 @@ func (ctx OperationalRuleContext) HandleConfigurationRule(config knowledgebase.C
 	}
 	config.Config.Field = resolvedField
 
-	err = solution_context.ConfigureResource(ctx.Solution, resource, config.Config, ctx.Data, "add")
+	err = solution_context.ConfigureResource(ctx.Solution, resource, config.Config, ctx.Data, "add", false)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/engine2/operational_rule/operational_step.go b/pkg/engine2/operational_rule/operational_step.go
index 72948a26..101c8ed3 100644
--- a/pkg/engine2/operational_rule/operational_step.go
+++ b/pkg/engine2/operational_rule/operational_step.go
@@ -50,7 +50,7 @@ func (ctx OperationalRuleContext) HandleOperationalStep(step knowledgebase.Opera
 		}
 	}
 
-	if len(ids) >= step.NumNeeded && step.NumNeeded > 0 {
+	if len(ids) >= step.NumNeeded && step.NumNeeded > 0 || resource.Imported {
 		return nil
 	}
 
@@ -243,18 +243,32 @@ func (ctx OperationalRuleContext) removeDependencyForDirection(direction knowled
 }
 
 func (ctx OperationalRuleContext) SetField(resource, fieldResource *construct.Resource, step knowledgebase.OperationalStep) error {
+
 	if ctx.Property == nil {
 		return nil
 	}
-	// snapshot the ID from before any field changes
-	oldId := resource.ID
 
+	path := ctx.Property.Details().Path
+	propVal, err := resource.GetProperty(path)
+	if err != nil {
+		zap.S().Debugf("property %s not found on resource %s", path, resource.ID)
+	}
 	var propertyValue any
 	propertyValue = fieldResource.ID
 	if step.UsePropertyRef != "" {
 		propertyValue = construct.PropertyRef{Resource: fieldResource.ID, Property: step.UsePropertyRef}
 	}
-	path := ctx.Property.Details().Path
+
+	if resource.Imported {
+		if ctx.Property.Contains(propVal, propertyValue) {
+			ctx.namespace(resource, fieldResource, resource.ID)
+			return nil
+		}
+		return fmt.Errorf("cannot set field on imported resource %s", resource.ID)
+	}
+
+	// snapshot the ID from before any field changes
+	oldId := resource.ID
 
 	removeResource := func(currResId construct.ResourceId) error {
 		err := ctx.removeDependencyForDirection(step.Direction, resource.ID, currResId)
@@ -270,10 +284,6 @@ func (ctx OperationalRuleContext) SetField(resource, fieldResource *construct.Re
 		return nil
 	}
 
-	propVal, err := resource.GetProperty(path)
-	if err != nil {
-		zap.S().Debugf("property %s not found on resource %s", path, resource.ID)
-	}
 	switch val := propVal.(type) {
 	case construct.ResourceId:
 		if val != fieldResource.ID {
@@ -306,6 +316,11 @@ func (ctx OperationalRuleContext) SetField(resource, fieldResource *construct.Re
 		return fmt.Errorf("error appending field %s#%s with %s: %w", resource.ID, path, fieldResource.ID, err)
 	}
 	zap.S().Infof("appended field %s#%s with %s", resource.ID, path, fieldResource.ID)
+	ctx.namespace(resource, fieldResource, oldId)
+	return nil
+}
+
+func (ctx *OperationalRuleContext) namespace(resource, fieldResource *construct.Resource, oldId construct.ResourceId) {
 	if ctx.Property.Details().Namespace {
 		resource.ID.Namespace = fieldResource.ID.Name
 	}
@@ -323,5 +338,4 @@ func (ctx OperationalRuleContext) SetField(resource, fieldResource *construct.Re
 			ctx.Data.Edge.Target = resource.ID
 		}
 	}
-	return nil
 }
diff --git a/pkg/engine2/path_selection/candidate_validity.go b/pkg/engine2/path_selection/candidate_validity.go
index 66975ea5..f6af69d7 100644
--- a/pkg/engine2/path_selection/candidate_validity.go
+++ b/pkg/engine2/path_selection/candidate_validity.go
@@ -246,7 +246,7 @@ func (d downstreamChecker) makeValid(resource, operationResource *construct.Reso
 		var errs error
 		rt, err := d.ctx.KnowledgeBase().GetResourceTemplate(r)
 		if err != nil || rt == nil {
-			return false, fmt.Errorf("error getting resource template for resource %s: %w", resource, err)
+			return false, fmt.Errorf("error getting resource template for resource %s: %w", resource.ID, err)
 		}
 		p := rt.Properties[property]
 		for _, downstream := range downstreams {
@@ -263,7 +263,7 @@ func (d downstreamChecker) makeValid(resource, operationResource *construct.Reso
 			} else {
 				currRes, err = d.ctx.RawView().Vertex(r)
 				if err != nil {
-					errs = errors.Join(errs, fmt.Errorf("error getting resource %s: %w", resource, err))
+					errs = errors.Join(errs, fmt.Errorf("error getting resource %s: %w", resource.ID, err))
 					continue
 				}
 			}
diff --git a/pkg/engine2/path_selection/edge_validity.go b/pkg/engine2/path_selection/edge_validity.go
index ac31b9ea..249dada2 100644
--- a/pkg/engine2/path_selection/edge_validity.go
+++ b/pkg/engine2/path_selection/edge_validity.go
@@ -87,7 +87,7 @@ func checkProperties(ctx solution_context.SolutionContext, resource, toCheck *co
 			match, err := selector.CanUse(solution_context.DynamicCtx(ctx), knowledgebase.DynamicValueData{Resource: resource.ID},
 				toCheck)
 			if err != nil {
-				return fmt.Errorf("error checking if resource %s matches selector %s: %w", toCheck, selector.Selector, err)
+				return fmt.Errorf("error checking if resource %s matches selector %s: %w", toCheck.ID, selector.Selector, err)
 			}
 			// if its a match for the selectors, lets ensure that it has a dependency and exists in the properties of the rul
 			if !match {
@@ -95,7 +95,7 @@ func checkProperties(ctx solution_context.SolutionContext, resource, toCheck *co
 			}
 			property, err := resource.GetProperty(details.Path)
 			if err != nil {
-				return fmt.Errorf("error getting property %s for resource %s: %w", details.Path, toCheck, err)
+				return fmt.Errorf("error getting property %s for resource %s: %w", details.Path, toCheck.ID, err)
 			}
 			if property != nil {
 				if checkIfPropertyContainsResource(property, toCheck.ID) {
@@ -261,7 +261,7 @@ func checkIfCreatedAsUniqueValidity(ctx solution_context.SolutionContext, resour
 				match, err := selector.CanUse(solution_context.DynamicCtx(ctx), knowledgebase.DynamicValueData{Resource: currRes.ID},
 					resource)
 				if err != nil {
-					return fmt.Errorf("error checking if resource %s matches selector %s: %w", other, selector.Selector, err)
+					return fmt.Errorf("error checking if resource %s matches selector %s: %w", other.ID, selector.Selector, err)
 				}
 				// if its a match for the selectors, lets ensure that it has a dependency and exists in the properties of the rul
 				if !match {
diff --git a/pkg/engine2/path_selection/path_expansion.go b/pkg/engine2/path_selection/path_expansion.go
index 7e4ada17..3f45d07c 100644
--- a/pkg/engine2/path_selection/path_expansion.go
+++ b/pkg/engine2/path_selection/path_expansion.go
@@ -320,7 +320,7 @@ func expandPath(
 		}
 		valid, err := checkNamespaceValidity(ctx, resource, input.Dep.Target.ID)
 		if err != nil {
-			return errors.Join(nerr, fmt.Errorf("error checking namespace validity of %s: %w", resource, err))
+			return errors.Join(nerr, fmt.Errorf("error checking namespace validity of %s: %w", resource.ID, err))
 		}
 		if !valid {
 			return nerr
diff --git a/pkg/engine2/reconciler/reconciler.go b/pkg/engine2/reconciler/reconciler.go
index d181fd5e..079d0d40 100644
--- a/pkg/engine2/reconciler/reconciler.go
+++ b/pkg/engine2/reconciler/reconciler.go
@@ -239,7 +239,13 @@ func canDeleteResource(
 	upstreamNodes set.Set[construct.ResourceId],
 	downstreamNodes set.Set[construct.ResourceId],
 ) (bool, error) {
-	if template.GetFunctionality() != knowledgebase.Unknown && !explicit {
+
+	res, err := ctx.RawView().Vertex(resource)
+	if err != nil {
+		return false, err
+	}
+	// dont allow deletion of imported or functional resources unless it is explicitly stated
+	if (template.GetFunctionality() != knowledgebase.Unknown || res.Imported) && !explicit {
 		return false, nil
 	}
 
diff --git a/pkg/engine2/solution_context/resource_configuration.go b/pkg/engine2/solution_context/resource_configuration.go
index 83b4fb4f..8dd0013a 100644
--- a/pkg/engine2/solution_context/resource_configuration.go
+++ b/pkg/engine2/solution_context/resource_configuration.go
@@ -18,10 +18,14 @@ func ConfigureResource(
 	configuration knowledgebase.Configuration,
 	data knowledgebase.DynamicValueData,
 	action string,
+	userInitiated bool,
 ) error {
 	if resource == nil {
 		return fmt.Errorf("resource does not exist")
 	}
+	if resource.Imported && !userInitiated {
+		return fmt.Errorf("cannot configure imported resource %s", resource.ID)
+	}
 	if data.Resource != resource.ID {
 		return fmt.Errorf("data resource (%s) does not match configuring resource (%s)", data.Resource, resource.ID)
 	}
diff --git a/pkg/infra/iac3/find_import_func.scm b/pkg/infra/iac3/find_import_func.scm
new file mode 100644
index 00000000..10d50f5a
--- /dev/null
+++ b/pkg/infra/iac3/find_import_func.scm
@@ -0,0 +1,17 @@
+(program
+  (function_declaration
+    name: (identifier) @function_name
+    parameters: (formal_parameters
+      .
+      (required_parameter
+        pattern: (identifier)
+        type: (type_annotation (_) @input_type)
+      )
+      .
+    )
+    return_type: (type_annotation (_) @return_type)
+    body: (statement_block) @body
+  )
+  (#eq? @function_name "importResource")
+  (#eq? @input_type "Args")
+)
diff --git a/pkg/infra/iac3/resource.go b/pkg/infra/iac3/resource.go
index 0c3822c4..fe643442 100644
--- a/pkg/infra/iac3/resource.go
+++ b/pkg/infra/iac3/resource.go
@@ -41,9 +41,16 @@ func (tc *TemplatesCompiler) RenderResource(out io.Writer, rid construct.Resourc
 			return err
 		}
 	}
-	err = resTmpl.Template.Execute(out, inputs)
-	if err != nil {
-		return fmt.Errorf("could not render resource %s: %w", rid, err)
+	if r.Imported {
+		err = resTmpl.ImportResource.Execute(out, inputs)
+		if err != nil {
+			return fmt.Errorf("could not render resource %s: %w", rid, err)
+		}
+	} else {
+		err = resTmpl.Template.Execute(out, inputs)
+		if err != nil {
+			return fmt.Errorf("could not render resource %s: %w", rid, err)
+		}
 	}
 
 	exportData := PropertyTemplateData{
diff --git a/pkg/infra/iac3/resource_template.go b/pkg/infra/iac3/resource_template.go
index b071e009..35144d27 100644
--- a/pkg/infra/iac3/resource_template.go
+++ b/pkg/infra/iac3/resource_template.go
@@ -28,6 +28,7 @@ type (
 		Args              map[string]Arg
 		Path              string
 		Exports           map[string]*template.Template
+		ImportResource    *template.Template
 	}
 
 	PropertyTemplateData struct {
@@ -74,6 +75,9 @@ var (
 	//go:embed find_export_func.scm
 	findExportFunc string
 
+	//go:embed find_import_func.scm
+	findImportFunc string
+
 	curlyEscapes     = regexp.MustCompile(`~~{{`)
 	templateComments = regexp.MustCompile(`//*TMPL ?`)
 )
@@ -106,6 +110,14 @@ func (tc *TemplatesCompiler) ParseTemplate(name string, r io.Reader) (*ResourceT
 	if err != nil {
 		return nil, err
 	}
+	var outputType string
+	rt.ImportResource, outputType, err = importFuncNodeToTemplate(node, name)
+	if err != nil {
+		return nil, err
+	}
+	if outputType != "" && outputType != rt.OutputType {
+		return nil, fmt.Errorf("output type mismatch: %s != %s", outputType, rt.OutputType)
+	}
 
 	return rt, nil
 }
@@ -242,6 +254,30 @@ func exportsNodeToTemplate(tc *TemplatesCompiler, tmpl *ResourceTemplate, node *
 	return exportsTemplates, errs
 }
 
+func importFuncNodeToTemplate(node *sitter.Node, name string) (*template.Template, string, error) {
+	importFunc := doQuery(node, findImportFunc)
+	imp, found := importFunc()
+	if !found {
+		return nil, "", nil
+	}
+	outputType := imp["return_type"].Content()
+	var expressionBody string
+	body, found := doQuery(imp["body"], findReturn)()
+	if !found {
+		return nil, "", fmt.Errorf("no 'return' found in %s body:```\n%s\n```", name, imp["body"].Content())
+	}
+	expressionBody = body["return_body"].Content()
+
+	expressionBody = parameterizeArgs(expressionBody, "")
+	expressionBody = templateComments.ReplaceAllString(expressionBody, "")
+
+	// transform escaped double curly brace literals e.g. ~~{{ .ID }} -> {{ `{{` }} .ID }}
+	expressionBody = curlyEscapes.ReplaceAllString(expressionBody, "{{ `{{` }}")
+	tmpl, err := template.New(name).Parse(expressionBody)
+
+	return tmpl, outputType, err
+}
+
 var templateTSLang = types.SourceLanguage{
 	ID:     types.LanguageId("ts"),
 	Sitter: typescript.GetLanguage(),
diff --git a/pkg/infra/iac3/templates/aws/vpc/factory.ts b/pkg/infra/iac3/templates/aws/vpc/factory.ts
index d354a427..01c1f080 100644
--- a/pkg/infra/iac3/templates/aws/vpc/factory.ts
+++ b/pkg/infra/iac3/templates/aws/vpc/factory.ts
@@ -5,6 +5,7 @@ interface Args {
     CidrBlock: string
     EnableDnsHostnames: boolean
     EnableDnsSupport: boolean
+    Arn?: string
 }
 
 // noinspection JSUnusedLocalSymbols
@@ -22,5 +23,10 @@ function create(args: Args): aws.ec2.Vpc {
 function properties(object: aws.ec2.Vpc, args: Args) {
     return {
         Id: object.id,
+        Arn: object.arn,
     }
 }
+
+function importResource(args: Args): aws.ec2.Vpc {
+    return aws.ec2.Vpc.get(args.Name, args.Arn)
+}
diff --git a/pkg/knowledge_base2/properties/list_property.go b/pkg/knowledge_base2/properties/list_property.go
index d56e9ac6..bf06d9d5 100644
--- a/pkg/knowledge_base2/properties/list_property.go
+++ b/pkg/knowledge_base2/properties/list_property.go
@@ -189,12 +189,12 @@ func (l *ListProperty) Validate(resource *construct.Resource, value any, ctx kno
 			return fmt.Errorf("list value %v is too long. max length is %d", value, *l.MaxLength)
 		}
 	}
-	// Only validate values if its a primitive list, otherwise let the sub properties handle their own validation
-	if l.ItemProperty != nil {
-		var errs error
-		hasSanitized := false
-		validList := make([]any, len(listVal))
-		for i, v := range listVal {
+
+	validList := make([]any, len(listVal))
+	var errs error
+	hasSanitized := false
+	for i, v := range listVal {
+		if l.ItemProperty != nil {
 			err := l.ItemProperty.Validate(resource, v, ctx)
 			if err != nil {
 				var sanitizeErr *knowledgebase.SanitizeError
@@ -207,17 +207,43 @@ func (l *ListProperty) Validate(resource *construct.Resource, value any, ctx kno
 			} else {
 				validList[i] = v
 			}
-		}
-		if errs != nil {
-			return errs
-		}
-		if hasSanitized {
-			return &knowledgebase.SanitizeError{
-				Input:     listVal,
-				Sanitized: validList,
+		} else {
+			vmap, ok := v.(map[string]any)
+			if !ok {
+				return fmt.Errorf("invalid value for list index %d in sub properties validation: expected map[string]any got %T", i, v)
 			}
+			validIndex := make(map[string]any)
+			for _, prop := range l.SubProperties() {
+				val, ok := vmap[prop.Details().Name]
+				if !ok {
+					continue
+				}
+				err := prop.Validate(resource, val, ctx)
+				if err != nil {
+					var sanitizeErr *knowledgebase.SanitizeError
+					if errors.As(err, &sanitizeErr) {
+						validIndex[prop.Details().Name] = sanitizeErr.Sanitized
+						hasSanitized = true
+					} else {
+						errs = errors.Join(errs, err)
+					}
+				} else {
+					validIndex[prop.Details().Name] = val
+				}
+			}
+			validList[i] = validIndex
 		}
 	}
+	if errs != nil {
+		return errs
+	}
+	if hasSanitized {
+		return &knowledgebase.SanitizeError{
+			Input:     listVal,
+			Sanitized: validList,
+		}
+	}
+
 	return nil
 }
 
diff --git a/pkg/knowledge_base2/properties/resource_property.go b/pkg/knowledge_base2/properties/resource_property.go
index 75435857..0443f430 100644
--- a/pkg/knowledge_base2/properties/resource_property.go
+++ b/pkg/knowledge_base2/properties/resource_property.go
@@ -108,6 +108,11 @@ func (r *ResourceProperty) ZeroValue() any {
 }
 
 func (r *ResourceProperty) Contains(value any, contains any) bool {
+	if val, ok := value.(construct.ResourceId); ok {
+		if cont, ok := contains.(construct.ResourceId); ok {
+			return val.Matches(cont)
+		}
+	}
 	return false
 }
 
diff --git a/pkg/templates/aws/edges/eks_node_group-eks_cluster.yaml b/pkg/templates/aws/edges/eks_node_group-eks_cluster.yaml
index fcd581b4..38d52881 100644
--- a/pkg/templates/aws/edges/eks_node_group-eks_cluster.yaml
+++ b/pkg/templates/aws/edges/eks_node_group-eks_cluster.yaml
@@ -23,85 +23,42 @@ operational_rules:
               Chart: metrics-server
               Internal: true
 
-  - if: | # check if there is an aws-cloudwatch namespace so we can install fluent bit in there
+  - if: | # check if there is an eks add on for cloudwatch so we can install observability
       {{ $needsCreation := true}}
-      {{ $namespaces := allUpstream "kubernetes:namespace" .Target }}
-      {{ range $index, $ns := $namespaces }}
-        {{- if eq (fieldValue "Object.metadata.name" $ns) "amazon-cloudwatch" }}
+      {{ $addOns := allUpstream "aws:eks_add_on" .Target }}
+      {{ range $index, $addOn := $addOns }}
+        {{- if eq (fieldValue "AddOnName" $addOn) "amazon-cloudwatch-observability" }}
           {{ $needsCreation = false}}
           {{break}}
         {{- end }}
       {{ end }}
       {{ $needsCreation }}
-    steps:
-      - resource: '{{ .Target }}'
-        direction: upstream
-        resources:
-          - selector: kubernetes:namespace:amazon-cloudwatch
-            properties:
-              Cluster: '{{ .Target }}'
-              Object.metadata.name: amazon-cloudwatch
-              Object.metadata.labels.name: amazon-cloudwatch
-
-  - if: | # check if there is an fluent bit config map
-      {{ $needsCreation := true}}
-      {{ $cms := allUpstream "kubernetes:config_map" .Target }}
-      {{ range $index, $cm := $cms }}
-        {{- if eq  (fieldValue "Object.metadata.name" $cm) "fluent-bit-cluster-info" }}
-          {{ $needsCreation = false }}
-          {{break}}
-        {{- end }}
-      {{ end }}
-      {{ $needsCreation }}
-    steps:
-      - resource: '{{ .Target }}'
-        direction: upstream
-        resources:
-          - selector: kubernetes:config_map:fluent-bit-cluster-info
-            properties:
-              Cluster: '{{.Target}}'
-              Object.metadata.name: fluent-bit-cluster-info
-              Object.metadata.namespace: kubernetes:namespace:{{.Target.Name}}:amazon-cloudwatch
-              'Object.metadata.labels.k8s-app': fluent-bit
-              Object.data.cluster.name: '{{ .Target }}#Name'
-              Object.data.logs.region: '{{ downstream "aws:region" .Target }}#Name'
-              Object.data.http.server: 'On'
-              Object.data.http.port: '2020'
-              Object.data.read.head: 'Off'
-              Object.data.read.tail: 'On'
-
-      - resource: kubernetes:config_map:fluent-bit-cluster-info
-        direction: downstream
-        resources:
-          - selector: kubernetes:namespace
-            properties:
-              Object.metadata.name: amazon-cloudwatch
-
-  - if: | # check if there is an aws-cloudwatch namespace so we can install fluent bit in there
-      {{ $needsCreation := true}}
-      {{ $manifests := allUpstream "kubernetes:manifest" .Target }}
-      {{ range $index, $manifest := $manifests }}
-        {{- if eq (fieldValue "FilePath" $manifest) "https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/fluent-bit/fluent-bit.yaml" }}
-          {{ $needsCreation = false }}
-          {{break}}
-        {{- end }}
-      {{ end }}
-      {{ $needsCreation }}
-    steps:
+    steps:             
       - resource: '{{ .Target }}'
         direction: upstream
         resources:
-          - selector: kubernetes:manifest:fluent-bit
+          - selector: aws:eks_add_on:amazon-cloudwatch-observability
             properties:
+              AddOnName: amazon-cloudwatch-observability
               Cluster: '{{ .Target }}'
-              FilePath: https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/fluent-bit/fluent-bit.yaml
 
-      - resource: kubernetes:manifest:fluent-bit
+      - resource: |
+          {{ $addOns := allUpstream "aws:eks_add_on" .Target }}
+          {{ range $index, $addOn := $addOns }}
+            {{- if eq (fieldValue "AddOnName" $addOn) "amazon-cloudwatch-observability" }}
+              {{ $addOn }}
+              {{break}}
+            {{- end }}
+          {{ end }}
         direction: downstream
         resources:
-          - selector: kubernetes:config_map
+          - selector: aws:iam_role
             properties:
-              Object.metadata.name: fluent-bit-cluster-info
+              ManagedPolicies:
+                - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
+                - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
+        unique: true    
+          
 
   - if: | #Check if the ami type is gpu and if so install nvidia plugin
       {{ $needsCreation := true}}
diff --git a/pkg/templates/aws/resources/subnet.yaml b/pkg/templates/aws/resources/subnet.yaml
index d8f0a434..3e91158d 100644
--- a/pkg/templates/aws/resources/subnet.yaml
+++ b/pkg/templates/aws/resources/subnet.yaml
@@ -54,6 +54,11 @@ properties:
   MapPublicIpOnLaunch:
     type: bool
     default_value: false
+  Id:
+    type: string
+    configuration_disabled: true
+    deploy_time: true
+    required: true
 
 path_satisfaction:
   as_source:
diff --git a/pkg/templates/aws/resources/vpc.yaml b/pkg/templates/aws/resources/vpc.yaml
index 60afe0a6..3bff020b 100644
--- a/pkg/templates/aws/resources/vpc.yaml
+++ b/pkg/templates/aws/resources/vpc.yaml
@@ -20,6 +20,11 @@ properties:
     type: string
     configuration_disabled: true
     deploy_time: true
+  Arn:
+    type: string
+    configuration_disabled: true
+    deploy_time: true
+    required: true
 
 classification:
   is:


Test patch:
diff --git a/pkg/engine2/constraints_test.go b/pkg/engine2/constraints_test.go
index 9585dc05..305de4ce 100644
--- a/pkg/engine2/constraints_test.go
+++ b/pkg/engine2/constraints_test.go
@@ -14,11 +14,12 @@ import (
 
 func TestApplyConstraints(t *testing.T) {
 	tests := []struct {
-		name        string
-		init        []any
-		constraints constraints.Constraints
-		want        enginetesting.ExpectedGraphs
-		wantErr     bool
+		name           string
+		init           []any
+		constraints    constraints.Constraints
+		want           enginetesting.ExpectedGraphs
+		resourceChecks func(t *testing.T, ctx *enginetesting.TestSolution)
+		wantErr        bool
 	}{
 		{
 			name: "add resource",
@@ -32,6 +33,23 @@ func TestApplyConstraints(t *testing.T) {
 				Deployment: []any{"p:t:test"},
 			},
 		},
+		{
+			name: "import resource",
+			constraints: constraints.Constraints{
+				Application: []constraints.ApplicationConstraint{
+					{Operator: constraints.ImportConstraintOperator, Node: graphtest.ParseId(t, "p:t:test")},
+				},
+			},
+			want: enginetesting.ExpectedGraphs{
+				Dataflow:   []any{"p:t:test"},
+				Deployment: []any{"p:t:test"},
+			},
+			resourceChecks: func(t *testing.T, ctx *enginetesting.TestSolution) {
+				res, err := ctx.RawView().Vertex(graphtest.ParseId(t, "p:t:test"))
+				require.NoError(t, err)
+				require.True(t, res.Imported)
+			},
+		},
 		{
 			name: "add edge",
 			init: []any{"p:t:A", "p:t:B"},
diff --git a/pkg/engine2/operational_rule/operational_step_test.go b/pkg/engine2/operational_rule/operational_step_test.go
index 8c41694c..d4ef1105 100644
--- a/pkg/engine2/operational_rule/operational_step_test.go
+++ b/pkg/engine2/operational_rule/operational_step_test.go
@@ -404,6 +404,7 @@ func Test_setField(t *testing.T) {
 		step          knowledgebase.OperationalStep
 		wantResource  *construct.Resource
 		wantGraph     enginetesting.ExpectedGraphs
+		wantErr       bool
 	}{
 		{
 			name: "field is being replaced",
@@ -547,6 +548,58 @@ func Test_setField(t *testing.T) {
 				Deployment: []any{"mock:resource2:test", "mock:resource1:test1", "mock:resource2:test -> mock:resource1:test1"},
 			},
 		},
+		{
+			name: "imported resource gets ignored",
+			resource: &construct.Resource{
+				ID: construct.ResourceId{Provider: "mock", Type: "resource1", Name: "test1"},
+				Properties: map[string]interface{}{
+					"Res4": res4.ID,
+				},
+				Imported: true,
+			},
+			resourceToSet: res4,
+			property: &properties.ResourceProperty{
+				PropertyDetails: knowledgebase.PropertyDetails{
+					Name:      "Res4",
+					Path:      "Res4",
+					Namespace: true,
+				},
+			},
+			step:         knowledgebase.OperationalStep{Direction: knowledgebase.DirectionDownstream},
+			initialState: []any{"mock:resource4:test4", "mock:resource1:test1"},
+			wantResource: &construct.Resource{
+				ID: construct.ResourceId{Provider: "mock", Type: "resource1", Namespace: res4.ID.Name, Name: "test1"},
+				Properties: map[string]interface{}{
+					"Res4": res4.ID,
+				},
+				Imported: true,
+			},
+			wantGraph: enginetesting.ExpectedGraphs{
+				Dataflow:   []any{"mock:resource1:test1", "mock:resource4:test4"},
+				Deployment: []any{"mock:resource1:test1", "mock:resource4:test4"},
+			},
+		},
+		{
+			name: "imported resource throws error if field is changing",
+			resource: &construct.Resource{
+				ID: construct.ResourceId{Provider: "mock", Type: "resource1", Name: "test1"},
+				Properties: map[string]interface{}{
+					"Res4": res4ToReplace.ID,
+				},
+				Imported: true,
+			},
+			resourceToSet: res4,
+			property: &properties.ResourceProperty{
+				PropertyDetails: knowledgebase.PropertyDetails{
+					Name:      "Res4",
+					Path:      "Res4",
+					Namespace: true,
+				},
+			},
+			step:         knowledgebase.OperationalStep{Direction: knowledgebase.DirectionDownstream},
+			initialState: []any{"mock:resource4:test4", "mock:resource1:test1", "mock:resource1:test1 -> mock:resource4:thisWillBeReplaced"},
+			wantErr:      true,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -560,6 +613,10 @@ func Test_setField(t *testing.T) {
 			}
 
 			err := ctx.SetField(tt.resource, tt.resourceToSet, tt.step)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
 			if !assert.NoError(err) {
 				return
 			}
diff --git a/pkg/engine2/reconciler/reconciler_test.go b/pkg/engine2/reconciler/reconciler_test.go
index e6c3bd24..dddb1ddf 100644
--- a/pkg/engine2/reconciler/reconciler_test.go
+++ b/pkg/engine2/reconciler/reconciler_test.go
@@ -389,7 +389,7 @@ func Test_addAllDeploymentDependencies(t *testing.T) {
 
 func Test_canDeleteResource(t *testing.T) {
 	type args struct {
-		resource    construct.ResourceId
+		resource    *construct.Resource
 		explicit    bool
 		template    *knowledgebase.ResourceTemplate
 		upstreams   []construct.ResourceId
@@ -406,7 +406,7 @@ func Test_canDeleteResource(t *testing.T) {
 		{
 			name: "can delete resource when no upstreams or downstreams",
 			args: args{
-				resource: construct.ResourceId{},
+				resource: &construct.Resource{ID: construct.ResourceId{Name: "test"}},
 				explicit: false,
 				template: &knowledgebase.ResourceTemplate{},
 			},
@@ -415,7 +415,7 @@ func Test_canDeleteResource(t *testing.T) {
 		{
 			name: "can delete if explicit",
 			args: args{
-				resource: construct.ResourceId{},
+				resource: &construct.Resource{ID: construct.ResourceId{Name: "test"}},
 				explicit: true,
 				template: &knowledgebase.ResourceTemplate{},
 				upstreams: []construct.ResourceId{
@@ -442,7 +442,18 @@ func Test_canDeleteResource(t *testing.T) {
 		{
 			name: "cannot delete if functional and not explicit",
 			args: args{
-				resource: construct.ResourceId{},
+				resource: &construct.Resource{ID: construct.ResourceId{Name: "test"}},
+				explicit: false,
+				template: &knowledgebase.ResourceTemplate{
+					Classification: knowledgebase.Classification{Is: []string{string(knowledgebase.Compute)}},
+				},
+			},
+			want: false,
+		},
+		{
+			name: "cannot delete if imported and not explicit",
+			args: args{
+				resource: &construct.Resource{ID: construct.ResourceId{Name: "test"}, Imported: true},
 				explicit: false,
 				template: &knowledgebase.ResourceTemplate{
 					Classification: knowledgebase.Classification{Is: []string{string(knowledgebase.Compute)}},
@@ -458,13 +469,12 @@ func Test_canDeleteResource(t *testing.T) {
 			upstreamSet.Add(tt.args.upstreams...)
 			downstreamSet := set.Set[construct.ResourceId]{}
 			downstreamSet.Add(tt.args.downstreams...)
-			sol := &enginetesting.TestSolution{
-				KB: enginetesting.MockKB{},
-			}
+			sol := enginetesting.NewTestSolution()
+			sol.DataflowGraph().AddVertex(tt.args.resource)
 			for _, call := range tt.mockKBCalls {
 				sol.KB.On(call.Method, call.Arguments...).Return(call.ReturnArguments...)
 			}
-			got, err := canDeleteResource(sol, tt.args.resource, tt.args.explicit, tt.args.template, upstreamSet, downstreamSet)
+			got, err := canDeleteResource(sol, tt.args.resource.ID, tt.args.explicit, tt.args.template, upstreamSet, downstreamSet)
 			if tt.wantErr {
 				assert.Error(err)
 				return
diff --git a/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml b/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml
index 1b68ae8b..50a2f40a 100755
--- a/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml
+++ b/pkg/engine2/testdata/k8s_api.dataflow-viz.yaml
@@ -13,11 +13,13 @@ resources:
         - aws:api_method:rest_api_4:rest_api_4_integration_0_method
         - aws:api_resource:rest_api_4:api_resource-0
         - aws:api_stage:rest_api_4:api_stage-0
+        - aws:eks_add_on:amazon-cloudwatch-observability
         - aws:eks_node_group:eks_node_group-0
         - aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip
         - aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
         - aws:iam_oidc_provider:eks_cluster-0
         - aws:iam_policy:iam_policy-0
+        - aws:iam_role:amazon-cloudwatch-observability-iam_role
         - aws:iam_role:aws-load-balancer-controller
         - aws:iam_role:eks_node_group-0-iam_role
         - aws:iam_role:pod2
@@ -46,11 +48,13 @@ resources:
         - aws:api_method:rest_api_4:rest_api_4_integration_0_method
         - aws:api_resource:rest_api_4:api_resource-0
         - aws:api_stage:rest_api_4:api_stage-0
+        - aws:eks_add_on:amazon-cloudwatch-observability
         - aws:eks_node_group:eks_node_group-0
         - aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip
         - aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
         - aws:iam_oidc_provider:eks_cluster-0
         - aws:iam_policy:iam_policy-0
+        - aws:iam_role:amazon-cloudwatch-observability-iam_role
         - aws:iam_role:aws-load-balancer-controller
         - aws:iam_role:eks_node_group-0-iam_role
         - aws:iam_role:pod2
@@ -78,11 +82,13 @@ resources:
         - aws:api_method:rest_api_4:rest_api_4_integration_0_method
         - aws:api_resource:rest_api_4:api_resource-0
         - aws:api_stage:rest_api_4:api_stage-0
+        - aws:eks_add_on:amazon-cloudwatch-observability
         - aws:eks_node_group:eks_node_group-0
         - aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip
         - aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
         - aws:iam_oidc_provider:eks_cluster-0
         - aws:iam_policy:iam_policy-0
+        - aws:iam_role:amazon-cloudwatch-observability-iam_role
         - aws:iam_role:aws-load-balancer-controller
         - aws:iam_role:eks_node_group-0-iam_role
         - aws:iam_role:pod2
@@ -110,11 +116,13 @@ resources:
         - aws:api_method:rest_api_4:rest_api_4_integration_0_method
         - aws:api_resource:rest_api_4:api_resource-0
         - aws:api_stage:rest_api_4:api_stage-0
+        - aws:eks_add_on:amazon-cloudwatch-observability
         - aws:eks_node_group:eks_node_group-0
         - aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip
         - aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
         - aws:iam_oidc_provider:eks_cluster-0
         - aws:iam_policy:iam_policy-0
+        - aws:iam_role:amazon-cloudwatch-observability-iam_role
         - aws:iam_role:aws-load-balancer-controller
         - aws:iam_role:eks_node_group-0-iam_role
         - aws:iam_role:pod2
@@ -142,19 +150,18 @@ resources:
 
   eks_cluster/eks_cluster-0:
     children:
+        - aws:eks_add_on:amazon-cloudwatch-observability
         - aws:eks_add_on:vpc-cni
         - aws:eks_node_group:eks_node_group-0
         - aws:iam_oidc_provider:eks_cluster-0
         - aws:iam_policy:iam_policy-0
         - aws:iam_role:ClusterRole-eks_cluster-0
+        - aws:iam_role:amazon-cloudwatch-observability-iam_role
         - aws:iam_role:aws-load-balancer-controller
         - aws:iam_role:eks_node_group-0-iam_role
         - aws:iam_role:pod2
         - aws:security_group:vpc-0:eks_cluster-0-security_group
         - aws:target_group:rest-api-4-integbcc77100
-        - kubernetes:config_map:fluent-bit-cluster-info
-        - kubernetes:manifest:fluent-bit
-        - kubernetes:namespace:amazon-cloudwatch
         - kubernetes:service:eks_cluster-0:restapi4integration0-pod2
         - kubernetes:service_account:eks_cluster-0:aws-load-balancer-controller
         - kubernetes:service_account:eks_cluster-0:pod2
@@ -167,11 +174,13 @@ resources:
         - aws:api_method:rest_api_4:rest_api_4_integration_0_method
         - aws:api_resource:rest_api_4:api_resource-0
         - aws:api_stage:rest_api_4:api_stage-0
+        - aws:eks_add_on:amazon-cloudwatch-observability
         - aws:eks_node_group:eks_node_group-0
         - aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip
         - aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
         - aws:iam_oidc_provider:eks_cluster-0
         - aws:iam_policy:iam_policy-0
+        - aws:iam_role:amazon-cloudwatch-observability-iam_role
         - aws:iam_role:aws-load-balancer-controller
         - aws:iam_role:eks_node_group-0-iam_role
         - aws:iam_role:pod2
diff --git a/pkg/engine2/testdata/k8s_api.expect.yaml b/pkg/engine2/testdata/k8s_api.expect.yaml
index 118e19b9..d4ae3b2a 100755
--- a/pkg/engine2/testdata/k8s_api.expect.yaml
+++ b/pkg/engine2/testdata/k8s_api.expect.yaml
@@ -3,6 +3,10 @@ resources:
         Deployment: aws:api_deployment:rest_api_4:api_deployment-0
         RestApi: aws:rest_api:rest_api_4
         StageName: stage
+    aws:eks_add_on:amazon-cloudwatch-observability:
+        AddOnName: amazon-cloudwatch-observability
+        Cluster: aws:eks_cluster:eks_cluster-0
+        Role: aws:iam_role:amazon-cloudwatch-observability-iam_role
     aws:eks_add_on:vpc-cni:
         AddOnName: vpc-cni
         Cluster: aws:eks_cluster:eks_cluster-0
@@ -47,48 +51,28 @@ resources:
                         - --region
                         - aws:region:region-0#Name
                     command: aws
-    kubernetes:manifest:fluent-bit:
-        Cluster: aws:eks_cluster:eks_cluster-0
-        FilePath: https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/fluent-bit/fluent-bit.yaml
     aws:api_deployment:rest_api_4:api_deployment-0:
         RestApi: aws:rest_api:rest_api_4
         Triggers:
             rest_api_4_integration_0: rest_api_4_integration_0
             rest_api_4_integration_0_method: rest_api_4_integration_0_method
-    kubernetes:config_map:fluent-bit-cluster-info:
-        Cluster: aws:eks_cluster:eks_cluster-0
-        Object:
-            apiVersion: v1
-            data:
-                cluster:
-                    name: aws:eks_cluster:eks_cluster-0#Name
-                http:
-                    port: "2020"
-                    server: "On"
-                logs:
-                    region: aws:region:region-0#Name
-                read:
-                    head: "Off"
-                    tail: "On"
-            kind: ConfigMap
-            metadata:
-                labels:
-                    k8s-app: fluent-bit
-                name: fluent-bit-cluster-info
-                namespace: kubernetes:namespace:amazon-cloudwatch
+    aws:iam_role:amazon-cloudwatch-observability-iam_role:
+        AssumeRolePolicyDoc:
+            Statement:
+                - Action:
+                    - sts:AssumeRoleWithWebIdentity
+                  Effect: Allow
+                  Principal:
+                    Federated:
+                        - aws:iam_oidc_provider:eks_cluster-0#Arn
+            Version: "2012-10-17"
+        ManagedPolicies:
+            - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
+            - arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
     aws:rest_api:rest_api_4:
         BinaryMediaTypes:
             - application/octet-stream
             - image/*
-    kubernetes:namespace:amazon-cloudwatch:
-        Cluster: aws:eks_cluster:eks_cluster-0
-        Object:
-            apiVersion: v1
-            kind: Namespace
-            metadata:
-                labels:
-                    name: amazon-cloudwatch
-                name: amazon-cloudwatch
     aws:api_resource:rest_api_4:api_resource-0:
         FullPath: /{proxy+}
         PathPart: '{proxy+}'
@@ -283,12 +267,12 @@ resources:
                         - ec2.amazonaws.com
             Version: "2012-10-17"
         ManagedPolicies:
+            - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
+            - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
             - arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy
             - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
             - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
             - arn:aws:iam::aws:policy/AWSCloudMapFullAccess
-            - arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
-            - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
     aws:iam_role:pod2:
         AssumeRolePolicyDoc:
             Statement:
@@ -587,22 +571,20 @@ resources:
 edges:
     aws:api_stage:rest_api_4:api_stage-0 -> aws:api_deployment:rest_api_4:api_deployment-0:
     aws:api_stage:rest_api_4:api_stage-0 -> aws:rest_api:rest_api_4:
+    aws:eks_add_on:amazon-cloudwatch-observability -> aws:eks_cluster:eks_cluster-0:
+    aws:eks_add_on:amazon-cloudwatch-observability -> aws:iam_role:amazon-cloudwatch-observability-iam_role:
     aws:eks_add_on:vpc-cni -> aws:eks_cluster:eks_cluster-0:
     aws:security_group_rule:security_group_rule-0 -> aws:vpc:vpc-0:
     kubernetes:helm_chart:eks_cluster-0:metricsserver -> aws:eks_cluster:eks_cluster-0:
     kubernetes:helm_chart:eks_cluster-0:metricsserver -> aws:eks_node_group:eks_node_group-0:
     kubernetes:kube_config:eks_cluster-0-kube_config -> aws:eks_cluster:eks_cluster-0:
-    kubernetes:manifest:fluent-bit -> aws:eks_cluster:eks_cluster-0:
-    kubernetes:manifest:fluent-bit -> kubernetes:config_map:fluent-bit-cluster-info:
     aws:api_deployment:rest_api_4:api_deployment-0 -> aws:api_integration:rest_api_4:rest_api_4_integration_0:
     aws:api_deployment:rest_api_4:api_deployment-0 -> aws:api_method:rest_api_4:rest_api_4_integration_0_method:
     aws:api_deployment:rest_api_4:api_deployment-0 -> aws:rest_api:rest_api_4:
-    kubernetes:config_map:fluent-bit-cluster-info -> aws:eks_cluster:eks_cluster-0:
-    kubernetes:config_map:fluent-bit-cluster-info -> kubernetes:namespace:amazon-cloudwatch:
+    aws:iam_role:amazon-cloudwatch-observability-iam_role -> aws:iam_oidc_provider:eks_cluster-0:
     aws:rest_api:rest_api_4 -> aws:api_integration:rest_api_4:rest_api_4_integration_0:
     aws:rest_api:rest_api_4 -> aws:api_method:rest_api_4:rest_api_4_integration_0_method:
     aws:rest_api:rest_api_4 -> aws:api_resource:rest_api_4:api_resource-0:
-    kubernetes:namespace:amazon-cloudwatch -> aws:eks_cluster:eks_cluster-0:
     aws:api_resource:rest_api_4:api_resource-0 -> aws:api_integration:rest_api_4:rest_api_4_integration_0:
     aws:api_resource:rest_api_4:api_resource-0 -> aws:api_method:rest_api_4:rest_api_4_integration_0_method:
     aws:api_method:rest_api_4:rest_api_4_integration_0_method -> aws:api_integration:rest_api_4:rest_api_4_integration_0:
diff --git a/pkg/engine2/testdata/k8s_api.iac-viz.yaml b/pkg/engine2/testdata/k8s_api.iac-viz.yaml
index 2798df59..bf7669a0 100755
--- a/pkg/engine2/testdata/k8s_api.iac-viz.yaml
+++ b/pkg/engine2/testdata/k8s_api.iac-viz.yaml
@@ -97,9 +97,6 @@ resources:
   kubernetes:pod:eks_cluster-0/pod2 -> eks_cluster/eks_cluster-0:
   kubernetes:pod:eks_cluster-0/pod2 -> eks_node_group/eks_node_group-0:
   kubernetes:pod:eks_cluster-0/pod2 -> kubernetes:service_account:eks_cluster-0/pod2:
-  kubernetes:namespace/amazon-cloudwatch:
-
-  kubernetes:namespace/amazon-cloudwatch -> eks_cluster/eks_cluster-0:
   aws:internet_gateway:vpc-0/internet_gateway-0:
 
   aws:internet_gateway:vpc-0/internet_gateway-0 -> vpc/vpc-0:
@@ -130,10 +127,6 @@ resources:
 
   kubernetes:service:eks_cluster-0/restapi4integration0-pod2 -> eks_cluster/eks_cluster-0:
   kubernetes:service:eks_cluster-0/restapi4integration0-pod2 -> kubernetes:pod:eks_cluster-0/pod2:
-  kubernetes:config_map/fluent-bit-cluster-info:
-
-  kubernetes:config_map/fluent-bit-cluster-info -> eks_cluster/eks_cluster-0:
-  kubernetes:config_map/fluent-bit-cluster-info -> kubernetes:namespace/amazon-cloudwatch:
   aws:route_table:vpc-0/subnet-3-route_table:
 
   aws:route_table:vpc-0/subnet-3-route_table -> aws:internet_gateway:vpc-0/internet_gateway-0:
@@ -152,6 +145,9 @@ resources:
   aws:route_table:vpc-0/subnet-0-route_table -> vpc/vpc-0:
   iam_policy/iam_policy-0:
 
+  iam_role/amazon-cloudwatch-observability-iam_role:
+
+  iam_role/amazon-cloudwatch-observability-iam_role -> iam_oidc_provider/eks_cluster-0:
   aws:api_deployment:rest_api_4/api_deployment-0:
 
   aws:api_deployment:rest_api_4/api_deployment-0 -> aws:api_integration:rest_api_4/rest_api_4_integration_0:
@@ -163,10 +159,6 @@ resources:
   kubernetes:target_group_binding:eks_cluster-0/restapi4integration0-pod2 -> target_group/rest-api-4-integbcc77100:
   kubernetes:target_group_binding:eks_cluster-0/restapi4integration0-pod2 -> kubernetes:helm_chart:eks_cluster-0/aws-load-balancer-controller:
   kubernetes:target_group_binding:eks_cluster-0/restapi4integration0-pod2 -> kubernetes:service:eks_cluster-0/restapi4integration0-pod2:
-  kubernetes:manifest/fluent-bit:
-
-  kubernetes:manifest/fluent-bit -> eks_cluster/eks_cluster-0:
-  kubernetes:manifest/fluent-bit -> kubernetes:config_map/fluent-bit-cluster-info:
   kubernetes:kube_config/eks_cluster-0-kube_config:
 
   kubernetes:kube_config/eks_cluster-0-kube_config -> eks_cluster/eks_cluster-0:
@@ -205,6 +197,10 @@ resources:
   eks_add_on/vpc-cni:
 
   eks_add_on/vpc-cni -> eks_cluster/eks_cluster-0:
+  eks_add_on/amazon-cloudwatch-observability:
+
+  eks_add_on/amazon-cloudwatch-observability -> eks_cluster/eks_cluster-0:
+  eks_add_on/amazon-cloudwatch-observability -> iam_role/amazon-cloudwatch-observability-iam_role:
   aws:api_stage:rest_api_4/api_stage-0:
 
   aws:api_stage:rest_api_4/api_stage-0 -> aws:api_deployment:rest_api_4/api_deployment-0:
diff --git a/pkg/engine2/testdata/vpc_import.dataflow-viz.yaml b/pkg/engine2/testdata/vpc_import.dataflow-viz.yaml
new file mode 100644
index 00000000..d9450d94
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import.dataflow-viz.yaml
@@ -0,0 +1,5 @@
+provider: aws
+resources:
+  vpc/vpc:
+    tag: parent
+
diff --git a/pkg/engine2/testdata/vpc_import.expect.yaml b/pkg/engine2/testdata/vpc_import.expect.yaml
new file mode 100644
index 00000000..a9679a5c
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import.expect.yaml
@@ -0,0 +1,24 @@
+resources:
+    aws:subnet:vpc:subnet1:
+        Type: private
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:vpc:subnet2:
+        Type: private
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:vpc:subnet3:
+        Type: public
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:vpc:subnet4:
+        Type: public
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:vpc:vpc:
+        imported: true
+edges:
+    aws:subnet:vpc:subnet1 -> aws:vpc:vpc:
+    aws:subnet:vpc:subnet2 -> aws:vpc:vpc:
+    aws:subnet:vpc:subnet3 -> aws:vpc:vpc:
+    aws:subnet:vpc:subnet4 -> aws:vpc:vpc:
diff --git a/pkg/engine2/testdata/vpc_import.iac-viz.yaml b/pkg/engine2/testdata/vpc_import.iac-viz.yaml
new file mode 100644
index 00000000..4a8dd088
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import.iac-viz.yaml
@@ -0,0 +1,16 @@
+provider: aws
+resources:
+  vpc/vpc:
+
+  aws:subnet:vpc/subnet4:
+
+  aws:subnet:vpc/subnet4 -> vpc/vpc:
+  aws:subnet:vpc/subnet3:
+
+  aws:subnet:vpc/subnet3 -> vpc/vpc:
+  aws:subnet:vpc/subnet2:
+
+  aws:subnet:vpc/subnet2 -> vpc/vpc:
+  aws:subnet:vpc/subnet1:
+
+  aws:subnet:vpc/subnet1 -> vpc/vpc:
diff --git a/pkg/engine2/testdata/vpc_import.input.yaml b/pkg/engine2/testdata/vpc_import.input.yaml
new file mode 100644
index 00000000..804be0b5
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import.input.yaml
@@ -0,0 +1,66 @@
+constraints:
+- node: aws:vpc:vpc
+  operator: import
+  scope: application
+- node: aws:subnet:subnet1
+  operator: import
+  scope: application
+- node: aws:subnet:subnet2
+  operator: import
+  scope: application
+- node: aws:subnet:subnet3
+  operator: import
+  scope: application
+- node: aws:subnet:subnet4
+  operator: import
+  scope: application
+- operator: equals
+  property: Type
+  scope: resource
+  target: aws:subnet:subnet1
+  value: private
+- operator: equals
+  property: Vpc
+  scope: resource
+  target: aws:subnet:subnet1
+  value: aws:vpc:vpc
+- operator: equals
+  property: Type
+  scope: resource
+  target: aws:subnet:subnet2
+  value: private
+- operator: equals
+  property: Vpc
+  scope: resource
+  target: aws:subnet:subnet2
+  value: aws:vpc:vpc
+- operator: equals
+  property: Type
+  scope: resource
+  target: aws:subnet:subnet3
+  value: public
+- operator: equals
+  property: Vpc
+  scope: resource
+  target: aws:subnet:subnet3
+  value: aws:vpc:vpc
+- operator: equals
+  property: Type
+  scope: resource
+  target: aws:subnet:subnet4
+  value: public
+- operator: equals
+  property: Vpc
+  scope: resource
+  target: aws:subnet:subnet4
+  value: aws:vpc:vpc
+- operator: equals
+  property: Type
+  scope: resource
+  target: aws:subnet:subnet1
+  value: private
+- operator: equals
+  property: Vpc
+  scope: resource
+  target: aws:subnet:subnet1
+  value: aws:vpc:vpc 
\ No newline at end of file
diff --git a/pkg/engine2/testdata/vpc_import_to_lambda.dataflow-viz.yaml b/pkg/engine2/testdata/vpc_import_to_lambda.dataflow-viz.yaml
new file mode 100644
index 00000000..fe032689
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_to_lambda.dataflow-viz.yaml
@@ -0,0 +1,19 @@
+provider: aws
+resources:
+  vpc/vpc:
+    children:
+        - aws:iam_role:lambda_function-ExecutionRole
+        - aws:log_group:lambda_function-log-group
+        - aws:security_group:vpc:lambda_function-security_group
+    tag: parent
+
+  lambda_function/lambda_function:
+    children:
+        - aws:ecr_image:lambda_function-image
+        - aws:ecr_repo:ecr_repo-0
+        - aws:iam_role:lambda_function-ExecutionRole
+        - aws:log_group:lambda_function-log-group
+        - aws:security_group:vpc:lambda_function-security_group
+    parent: vpc/vpc
+    tag: big
+
diff --git a/pkg/engine2/testdata/vpc_import_to_lambda.expect.yaml b/pkg/engine2/testdata/vpc_import_to_lambda.expect.yaml
new file mode 100644
index 00000000..9181f1ad
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_to_lambda.expect.yaml
@@ -0,0 +1,85 @@
+resources:
+    aws:security_group:vpc:lambda_function-security_group:
+        EgressRules:
+            - CidrBlocks:
+                - 0.0.0.0/0
+              Description: Allows all outbound IPv4 traffic
+              FromPort: 0
+              Protocol: "-1"
+              ToPort: 0
+        IngressRules:
+            - Description: Allow ingress traffic from within the same security group
+              FromPort: 0
+              Protocol: "-1"
+              Self: true
+              ToPort: 0
+        Vpc: aws:vpc:vpc
+    aws:subnet:subnet3:
+        Type: public
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:subnet4:
+        Type: public
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:lambda_function:lambda_function:
+        ExecutionRole: aws:iam_role:lambda_function-ExecutionRole
+        Image: aws:ecr_image:lambda_function-image
+        LogGroup: aws:log_group:lambda_function-log-group
+        MemorySize: 512
+        SecurityGroups:
+            - aws:security_group:vpc:lambda_function-security_group
+        Subnets:
+            - aws:subnet:vpc:subnet1
+            - aws:subnet:subnet2
+        Timeout: 180
+    aws:ecr_image:lambda_function-image:
+        Context: .
+        Dockerfile: lambda_function-image.Dockerfile
+        Repo: aws:ecr_repo:ecr_repo-0
+    aws:iam_role:lambda_function-ExecutionRole:
+        AssumeRolePolicyDoc:
+            Statement:
+                - Action:
+                    - sts:AssumeRole
+                  Effect: Allow
+                  Principal:
+                    Service:
+                        - lambda.amazonaws.com
+            Version: "2012-10-17"
+        ManagedPolicies:
+            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+            - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
+    aws:subnet:subnet2:
+        Type: private
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:vpc:subnet1:
+        Type: private
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:ecr_repo:ecr_repo-0:
+        ForceDelete: true
+    aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:vpc:vpc:
+        imported: true
+    aws:log_group:lambda_function-log-group:
+        LogGroupName: /aws/lambda/lambda_function
+        RetentionInDays: 5
+edges:
+    aws:security_group:vpc:lambda_function-security_group -> aws:lambda_function:lambda_function:
+    aws:security_group:vpc:lambda_function-security_group -> aws:vpc:vpc:
+    aws:subnet:subnet3 -> aws:vpc:vpc:
+    aws:subnet:subnet4 -> aws:vpc:vpc:
+    aws:lambda_function:lambda_function -> aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:lambda_function:lambda_function -> aws:ecr_image:lambda_function-image:
+    aws:lambda_function:lambda_function -> aws:iam_role:lambda_function-ExecutionRole:
+    aws:lambda_function:lambda_function -> aws:subnet:subnet2:
+    aws:lambda_function:lambda_function -> aws:subnet:vpc:subnet1:
+    aws:ecr_image:lambda_function-image -> aws:ecr_repo:ecr_repo-0:
+    aws:iam_role:lambda_function-ExecutionRole -> aws:log_group:lambda_function-log-group:
+    aws:subnet:subnet2 -> aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:subnet:subnet2 -> aws:vpc:vpc:
+    aws:subnet:vpc:subnet1 -> aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:subnet:vpc:subnet1 -> aws:vpc:vpc:
+    aws:SERVICE_API:lambda_function-lambda_function-log-group -> aws:log_group:lambda_function-log-group:
diff --git a/pkg/engine2/testdata/vpc_import_to_lambda.iac-viz.yaml b/pkg/engine2/testdata/vpc_import_to_lambda.iac-viz.yaml
new file mode 100644
index 00000000..243a945c
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_to_lambda.iac-viz.yaml
@@ -0,0 +1,36 @@
+provider: aws
+resources:
+  ecr_repo/ecr_repo-0:
+
+  log_group/lambda_function-log-group:
+
+  vpc/vpc:
+
+  ecr_image/lambda_function-image:
+
+  ecr_image/lambda_function-image -> ecr_repo/ecr_repo-0:
+  iam_role/lambda_function-executionrole:
+
+  iam_role/lambda_function-executionrole -> log_group/lambda_function-log-group:
+  aws:security_group:vpc/lambda_function-security_group:
+
+  aws:security_group:vpc/lambda_function-security_group -> vpc/vpc:
+  subnet/subnet2:
+
+  subnet/subnet2 -> vpc/vpc:
+  aws:subnet:vpc/subnet1:
+
+  aws:subnet:vpc/subnet1 -> vpc/vpc:
+  subnet/subnet4:
+
+  subnet/subnet4 -> vpc/vpc:
+  subnet/subnet3:
+
+  subnet/subnet3 -> vpc/vpc:
+  lambda_function/lambda_function:
+
+  lambda_function/lambda_function -> ecr_image/lambda_function-image:
+  lambda_function/lambda_function -> iam_role/lambda_function-executionrole:
+  lambda_function/lambda_function -> aws:security_group:vpc/lambda_function-security_group:
+  lambda_function/lambda_function -> subnet/subnet2:
+  lambda_function/lambda_function -> aws:subnet:vpc/subnet1:
diff --git a/pkg/engine2/testdata/vpc_import_to_lambda.input.yaml b/pkg/engine2/testdata/vpc_import_to_lambda.input.yaml
new file mode 100644
index 00000000..92c2fb8c
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_to_lambda.input.yaml
@@ -0,0 +1,33 @@
+constraints:
+- operator: add
+  scope: application
+  node: aws:lambda_function:lambda_function
+- operator: must_exist
+  scope: edge
+  target:
+    source: aws:lambda_function:lambda_function
+    target: aws:vpc:vpc
+resources:
+    aws:subnet:subnet1:
+        Type: private
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:subnet2:
+        Type: private
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:subnet3:
+        Type: public
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:subnet:subnet4:
+        Type: public
+        Vpc: aws:vpc:vpc
+        imported: true
+    aws:vpc:vpc:
+        imported: true
+edges:
+    aws:subnet:subnet1 -> aws:vpc:vpc:
+    aws:subnet:subnet2 -> aws:vpc:vpc:
+    aws:subnet:subnet3 -> aws:vpc:vpc:
+    aws:subnet:subnet4 -> aws:vpc:vpc:
diff --git a/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.dataflow-viz.yaml b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.dataflow-viz.yaml
new file mode 100644
index 00000000..a44a958a
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.dataflow-viz.yaml
@@ -0,0 +1,28 @@
+provider: aws
+resources:
+  vpc/vpc:
+    children:
+        - aws:elastic_ip:lambda_function-vpc-route_table-nat_gateway-elastic_ip
+        - aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
+        - aws:iam_role:lambda_function-ExecutionRole
+        - aws:internet_gateway:vpc:internet_gateway-0
+        - aws:log_group:lambda_function-log-group
+        - aws:nat_gateway:subnet-2:lambda_function-vpc-route_table-nat_gateway
+        - aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway
+        - aws:route_table:vpc:lambda_function-vpc-route_table
+        - aws:route_table:vpc:subnet-1-route_table
+        - aws:route_table:vpc:subnet-2-route_table
+        - aws:route_table:vpc:subnet-3-route_table
+        - aws:security_group:vpc:lambda_function-security_group
+    tag: parent
+
+  lambda_function/lambda_function:
+    children:
+        - aws:ecr_image:lambda_function-image
+        - aws:ecr_repo:ecr_repo-0
+        - aws:iam_role:lambda_function-ExecutionRole
+        - aws:log_group:lambda_function-log-group
+        - aws:security_group:vpc:lambda_function-security_group
+    parent: vpc/vpc
+    tag: big
+
diff --git a/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.expect.yaml b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.expect.yaml
new file mode 100644
index 00000000..a906cf73
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.expect.yaml
@@ -0,0 +1,175 @@
+resources:
+    aws:security_group:vpc:lambda_function-security_group:
+        EgressRules:
+            - CidrBlocks:
+                - 0.0.0.0/0
+              Description: Allows all outbound IPv4 traffic
+              FromPort: 0
+              Protocol: "-1"
+              ToPort: 0
+        IngressRules:
+            - Description: Allow ingress traffic from within the same security group
+              FromPort: 0
+              Protocol: "-1"
+              Self: true
+              ToPort: 0
+        Vpc: aws:vpc:vpc
+    aws:lambda_function:lambda_function:
+        ExecutionRole: aws:iam_role:lambda_function-ExecutionRole
+        Image: aws:ecr_image:lambda_function-image
+        LogGroup: aws:log_group:lambda_function-log-group
+        MemorySize: 512
+        SecurityGroups:
+            - aws:security_group:vpc:lambda_function-security_group
+        Subnets:
+            - aws:subnet:vpc:lambda_function-vpc
+            - aws:subnet:vpc:subnet-1
+        Timeout: 180
+    aws:ecr_image:lambda_function-image:
+        Context: .
+        Dockerfile: lambda_function-image.Dockerfile
+        Repo: aws:ecr_repo:ecr_repo-0
+    aws:iam_role:lambda_function-ExecutionRole:
+        AssumeRolePolicyDoc:
+            Statement:
+                - Action:
+                    - sts:AssumeRole
+                  Effect: Allow
+                  Principal:
+                    Service:
+                        - lambda.amazonaws.com
+            Version: "2012-10-17"
+        ManagedPolicies:
+            - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
+            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+    aws:subnet:vpc:lambda_function-vpc:
+        AvailabilityZone: aws:availability_zone:region-0:availability_zone-0
+        CidrBlock: 10.0.128.0/18
+        MapPublicIpOnLaunch: false
+        RouteTable: aws:route_table:vpc:lambda_function-vpc-route_table
+        Type: private
+        Vpc: aws:vpc:vpc
+    aws:subnet:vpc:subnet-1:
+        AvailabilityZone: aws:availability_zone:region-0:availability_zone-1
+        CidrBlock: 10.0.192.0/18
+        MapPublicIpOnLaunch: false
+        RouteTable: aws:route_table:vpc:subnet-1-route_table
+        Type: private
+        Vpc: aws:vpc:vpc
+    aws:ecr_repo:ecr_repo-0:
+        ForceDelete: true
+    aws:route_table_association:lambda_function-vpc-lambda_function-vpc-route_table:
+        RouteTable: aws:route_table:vpc:lambda_function-vpc-route_table
+        Subnet: aws:subnet:vpc:lambda_function-vpc
+    aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:route_table_association:subnet-1-subnet-1-route_table:
+        RouteTable: aws:route_table:vpc:subnet-1-route_table
+        Subnet: aws:subnet:vpc:subnet-1
+    aws:route_table:vpc:lambda_function-vpc-route_table:
+        Routes:
+            - CidrBlock: 0.0.0.0/0
+              NatGateway: aws:nat_gateway:subnet-2:lambda_function-vpc-route_table-nat_gateway
+        Vpc: aws:vpc:vpc
+    aws:log_group:lambda_function-log-group:
+        LogGroupName: /aws/lambda/lambda_function
+        RetentionInDays: 5
+    aws:route_table:vpc:subnet-1-route_table:
+        Routes:
+            - CidrBlock: 0.0.0.0/0
+              NatGateway: aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway
+        Vpc: aws:vpc:vpc
+    aws:nat_gateway:subnet-2:lambda_function-vpc-route_table-nat_gateway:
+        ElasticIp: aws:elastic_ip:lambda_function-vpc-route_table-nat_gateway-elastic_ip
+        Subnet: aws:subnet:vpc:subnet-2
+    aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway:
+        ElasticIp: aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip
+        Subnet: aws:subnet:vpc:subnet-3
+    aws:elastic_ip:lambda_function-vpc-route_table-nat_gateway-elastic_ip:
+    aws:subnet:vpc:subnet-2:
+        AvailabilityZone: aws:availability_zone:region-0:availability_zone-0
+        CidrBlock: 10.0.0.0/18
+        MapPublicIpOnLaunch: false
+        RouteTable: aws:route_table:vpc:subnet-2-route_table
+        Type: public
+        Vpc: aws:vpc:vpc
+    aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip:
+    aws:subnet:vpc:subnet-3:
+        AvailabilityZone: aws:availability_zone:region-0:availability_zone-1
+        CidrBlock: 10.0.64.0/18
+        MapPublicIpOnLaunch: false
+        RouteTable: aws:route_table:vpc:subnet-3-route_table
+        Type: public
+        Vpc: aws:vpc:vpc
+    aws:availability_zone:region-0:availability_zone-0:
+        Index: 0
+        Region: aws:region:region-0
+    aws:route_table_association:subnet-2-subnet-2-route_table:
+        RouteTable: aws:route_table:vpc:subnet-2-route_table
+        Subnet: aws:subnet:vpc:subnet-2
+    aws:availability_zone:region-0:availability_zone-1:
+        Index: 1
+        Region: aws:region:region-0
+    aws:route_table_association:subnet-3-subnet-3-route_table:
+        RouteTable: aws:route_table:vpc:subnet-3-route_table
+        Subnet: aws:subnet:vpc:subnet-3
+    aws:route_table:vpc:subnet-2-route_table:
+        Routes:
+            - CidrBlock: 0.0.0.0/0
+              Gateway: aws:internet_gateway:vpc:internet_gateway-0
+        Vpc: aws:vpc:vpc
+    aws:region:region-0:
+    aws:route_table:vpc:subnet-3-route_table:
+        Routes:
+            - CidrBlock: 0.0.0.0/0
+              Gateway: aws:internet_gateway:vpc:internet_gateway-0
+        Vpc: aws:vpc:vpc
+    aws:internet_gateway:vpc:internet_gateway-0:
+        Vpc: aws:vpc:vpc
+    aws:vpc:vpc:
+        imported: true
+edges:
+    aws:security_group:vpc:lambda_function-security_group -> aws:lambda_function:lambda_function:
+    aws:security_group:vpc:lambda_function-security_group -> aws:vpc:vpc:
+    aws:lambda_function:lambda_function -> aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:lambda_function:lambda_function -> aws:ecr_image:lambda_function-image:
+    aws:lambda_function:lambda_function -> aws:iam_role:lambda_function-ExecutionRole:
+    aws:lambda_function:lambda_function -> aws:subnet:vpc:lambda_function-vpc:
+    aws:lambda_function:lambda_function -> aws:subnet:vpc:subnet-1:
+    aws:ecr_image:lambda_function-image -> aws:ecr_repo:ecr_repo-0:
+    aws:iam_role:lambda_function-ExecutionRole -> aws:log_group:lambda_function-log-group:
+    aws:subnet:vpc:lambda_function-vpc -> aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:subnet:vpc:lambda_function-vpc -> aws:availability_zone:region-0:availability_zone-0:
+    aws:subnet:vpc:lambda_function-vpc -> aws:route_table_association:lambda_function-vpc-lambda_function-vpc-route_table:
+    aws:subnet:vpc:lambda_function-vpc -> aws:vpc:vpc:
+    aws:subnet:vpc:subnet-1 -> aws:SERVICE_API:lambda_function-lambda_function-log-group:
+    aws:subnet:vpc:subnet-1 -> aws:availability_zone:region-0:availability_zone-1:
+    aws:subnet:vpc:subnet-1 -> aws:route_table_association:subnet-1-subnet-1-route_table:
+    aws:subnet:vpc:subnet-1 -> aws:vpc:vpc:
+    ? aws:route_table_association:lambda_function-vpc-lambda_function-vpc-route_table -> aws:route_table:vpc:lambda_function-vpc-route_table
+    :
+    aws:SERVICE_API:lambda_function-lambda_function-log-group -> aws:log_group:lambda_function-log-group:
+    aws:route_table_association:subnet-1-subnet-1-route_table -> aws:route_table:vpc:subnet-1-route_table:
+    aws:route_table:vpc:lambda_function-vpc-route_table -> aws:nat_gateway:subnet-2:lambda_function-vpc-route_table-nat_gateway:
+    aws:route_table:vpc:lambda_function-vpc-route_table -> aws:vpc:vpc:
+    aws:route_table:vpc:subnet-1-route_table -> aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway:
+    aws:route_table:vpc:subnet-1-route_table -> aws:vpc:vpc:
+    ? aws:nat_gateway:subnet-2:lambda_function-vpc-route_table-nat_gateway -> aws:elastic_ip:lambda_function-vpc-route_table-nat_gateway-elastic_ip
+    :
+    aws:nat_gateway:subnet-2:lambda_function-vpc-route_table-nat_gateway -> aws:subnet:vpc:subnet-2:
+    aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip:
+    aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:subnet:vpc:subnet-3:
+    aws:subnet:vpc:subnet-2 -> aws:availability_zone:region-0:availability_zone-0:
+    aws:subnet:vpc:subnet-2 -> aws:route_table_association:subnet-2-subnet-2-route_table:
+    aws:subnet:vpc:subnet-2 -> aws:vpc:vpc:
+    aws:subnet:vpc:subnet-3 -> aws:availability_zone:region-0:availability_zone-1:
+    aws:subnet:vpc:subnet-3 -> aws:route_table_association:subnet-3-subnet-3-route_table:
+    aws:subnet:vpc:subnet-3 -> aws:vpc:vpc:
+    aws:availability_zone:region-0:availability_zone-0 -> aws:region:region-0:
+    aws:route_table_association:subnet-2-subnet-2-route_table -> aws:route_table:vpc:subnet-2-route_table:
+    aws:availability_zone:region-0:availability_zone-1 -> aws:region:region-0:
+    aws:route_table_association:subnet-3-subnet-3-route_table -> aws:route_table:vpc:subnet-3-route_table:
+    aws:route_table:vpc:subnet-2-route_table -> aws:internet_gateway:vpc:internet_gateway-0:
+    aws:route_table:vpc:subnet-2-route_table -> aws:vpc:vpc:
+    aws:route_table:vpc:subnet-3-route_table -> aws:internet_gateway:vpc:internet_gateway-0:
+    aws:route_table:vpc:subnet-3-route_table -> aws:vpc:vpc:
+    aws:internet_gateway:vpc:internet_gateway-0 -> aws:vpc:vpc:
diff --git a/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.iac-viz.yaml b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.iac-viz.yaml
new file mode 100644
index 00000000..40d83508
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.iac-viz.yaml
@@ -0,0 +1,95 @@
+provider: aws
+resources:
+  region/region-0:
+
+  aws:availability_zone:region-0/availability_zone-1:
+
+  aws:availability_zone:region-0/availability_zone-1 -> region/region-0:
+  vpc/vpc:
+
+  aws:availability_zone:region-0/availability_zone-0:
+
+  aws:availability_zone:region-0/availability_zone-0 -> region/region-0:
+  elastic_ip/subnet-1-route_table-nat_gateway-elastic_ip:
+
+  aws:subnet:vpc/subnet-3:
+
+  aws:subnet:vpc/subnet-3 -> aws:availability_zone:region-0/availability_zone-1:
+  aws:subnet:vpc/subnet-3 -> vpc/vpc:
+  elastic_ip/lambda_function-vpc-route_table-nat_gateway-elastic_ip:
+
+  aws:subnet:vpc/subnet-2:
+
+  aws:subnet:vpc/subnet-2 -> aws:availability_zone:region-0/availability_zone-0:
+  aws:subnet:vpc/subnet-2 -> vpc/vpc:
+  aws:internet_gateway:vpc/internet_gateway-0:
+
+  aws:internet_gateway:vpc/internet_gateway-0 -> vpc/vpc:
+  aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway:
+
+  aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway -> elastic_ip/subnet-1-route_table-nat_gateway-elastic_ip:
+  aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway -> aws:subnet:vpc/subnet-3:
+  aws:nat_gateway:subnet-2/lambda_function-vpc-route_table-nat_gateway:
+
+  aws:nat_gateway:subnet-2/lambda_function-vpc-route_table-nat_gateway -> elastic_ip/lambda_function-vpc-route_table-nat_gateway-elastic_ip:
+  aws:nat_gateway:subnet-2/lambda_function-vpc-route_table-nat_gateway -> aws:subnet:vpc/subnet-2:
+  ecr_repo/ecr_repo-0:
+
+  log_group/lambda_function-log-group:
+
+  aws:route_table:vpc/subnet-3-route_table:
+
+  aws:route_table:vpc/subnet-3-route_table -> aws:internet_gateway:vpc/internet_gateway-0:
+  aws:route_table:vpc/subnet-3-route_table -> vpc/vpc:
+  aws:route_table:vpc/subnet-2-route_table:
+
+  aws:route_table:vpc/subnet-2-route_table -> aws:internet_gateway:vpc/internet_gateway-0:
+  aws:route_table:vpc/subnet-2-route_table -> vpc/vpc:
+  aws:route_table:vpc/subnet-1-route_table:
+
+  aws:route_table:vpc/subnet-1-route_table -> aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway:
+  aws:route_table:vpc/subnet-1-route_table -> vpc/vpc:
+  aws:subnet:vpc/subnet-1:
+
+  aws:subnet:vpc/subnet-1 -> aws:availability_zone:region-0/availability_zone-1:
+  aws:subnet:vpc/subnet-1 -> vpc/vpc:
+  aws:route_table:vpc/lambda_function-vpc-route_table:
+
+  aws:route_table:vpc/lambda_function-vpc-route_table -> aws:nat_gateway:subnet-2/lambda_function-vpc-route_table-nat_gateway:
+  aws:route_table:vpc/lambda_function-vpc-route_table -> vpc/vpc:
+  aws:subnet:vpc/lambda_function-vpc:
+
+  aws:subnet:vpc/lambda_function-vpc -> aws:availability_zone:region-0/availability_zone-0:
+  aws:subnet:vpc/lambda_function-vpc -> vpc/vpc:
+  ecr_image/lambda_function-image:
+
+  ecr_image/lambda_function-image -> ecr_repo/ecr_repo-0:
+  iam_role/lambda_function-executionrole:
+
+  iam_role/lambda_function-executionrole -> log_group/lambda_function-log-group:
+  aws:security_group:vpc/lambda_function-security_group:
+
+  aws:security_group:vpc/lambda_function-security_group -> vpc/vpc:
+  route_table_association/subnet-3-subnet-3-route_table:
+
+  route_table_association/subnet-3-subnet-3-route_table -> aws:route_table:vpc/subnet-3-route_table:
+  route_table_association/subnet-3-subnet-3-route_table -> aws:subnet:vpc/subnet-3:
+  route_table_association/subnet-2-subnet-2-route_table:
+
+  route_table_association/subnet-2-subnet-2-route_table -> aws:route_table:vpc/subnet-2-route_table:
+  route_table_association/subnet-2-subnet-2-route_table -> aws:subnet:vpc/subnet-2:
+  route_table_association/subnet-1-subnet-1-route_table:
+
+  route_table_association/subnet-1-subnet-1-route_table -> aws:route_table:vpc/subnet-1-route_table:
+  route_table_association/subnet-1-subnet-1-route_table -> aws:subnet:vpc/subnet-1:
+  route_table_association/lambda_function-vpc-lambda_function-vpc-route_table:
+
+  route_table_association/lambda_function-vpc-lambda_function-vpc-route_table -> aws:route_table:vpc/lambda_function-vpc-route_table:
+  route_table_association/lambda_function-vpc-lambda_function-vpc-route_table -> aws:subnet:vpc/lambda_function-vpc:
+  lambda_function/lambda_function:
+
+  lambda_function/lambda_function -> ecr_image/lambda_function-image:
+  lambda_function/lambda_function -> iam_role/lambda_function-executionrole:
+  lambda_function/lambda_function -> aws:security_group:vpc/lambda_function-security_group:
+  lambda_function/lambda_function -> aws:subnet:vpc/lambda_function-vpc:
+  lambda_function/lambda_function -> aws:subnet:vpc/subnet-1:
diff --git a/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.input.yaml b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.input.yaml
new file mode 100644
index 00000000..d42bc7c4
--- /dev/null
+++ b/pkg/engine2/testdata/vpc_import_wo_subnets_to_lambda.input.yaml
@@ -0,0 +1,13 @@
+constraints:
+- operator: add
+  scope: application
+  node: aws:lambda_function:lambda_function
+- operator: must_exist
+  scope: edge
+  target:
+    source: aws:lambda_function:lambda_function
+    target: aws:vpc:vpc
+resources:
+    aws:vpc:vpc:
+        imported: true
+edges:
diff --git a/pkg/knowledge_base2/properties/float_property_test.go b/pkg/knowledge_base2/properties/float_property_test.go
index 82a0a451..49be1c0b 100644
--- a/pkg/knowledge_base2/properties/float_property_test.go
+++ b/pkg/knowledge_base2/properties/float_property_test.go
@@ -6,7 +6,6 @@ import (
 	construct "github.com/klothoplatform/klotho/pkg/construct2"
 	"github.com/klothoplatform/klotho/pkg/engine2/enginetesting"
 	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2"
-	knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledge_base2"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
@@ -22,7 +21,7 @@ func Test_SetFloatProperty(t *testing.T) {
 			name:     "float property",
 			resource: &construct.Resource{Properties: make(map[string]any)},
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -52,7 +51,7 @@ func Test_AppendFloatProperty(t *testing.T) {
 			name:     "float property",
 			resource: &construct.Resource{Properties: make(map[string]any)},
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -66,7 +65,7 @@ func Test_AppendFloatProperty(t *testing.T) {
 				},
 			},
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -99,7 +98,7 @@ func Test_RemoveFloatProperty(t *testing.T) {
 				},
 			},
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -112,7 +111,7 @@ func Test_RemoveFloatProperty(t *testing.T) {
 				},
 			},
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test2",
 				},
 			},
@@ -141,7 +140,7 @@ func Test_ParseFloatValue(t *testing.T) {
 		{
 			name: "float value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -151,7 +150,7 @@ func Test_ParseFloatValue(t *testing.T) {
 		{
 			name: "template value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -161,7 +160,7 @@ func Test_ParseFloatValue(t *testing.T) {
 		{
 			name: "int value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -171,7 +170,7 @@ func Test_ParseFloatValue(t *testing.T) {
 		{
 			name: "invalid value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -182,8 +181,8 @@ func Test_ParseFloatValue(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			assert := assert.New(t)
-			ctx := knowledgebase2.DynamicValueContext{}
-			data := knowledgebase2.DynamicValueData{}
+			ctx := knowledgebase.DynamicValueContext{}
+			data := knowledgebase.DynamicValueData{}
 			value, err := test.property.Parse(test.value, ctx, data)
 			if test.wantErr {
 				assert.Error(err)
@@ -209,7 +208,7 @@ func Test_FloatProperty_Validate(t *testing.T) {
 		{
 			name: "float value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -218,7 +217,7 @@ func Test_FloatProperty_Validate(t *testing.T) {
 		{
 			name: "int value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -270,7 +269,7 @@ func Test_FloatProperty_GetDefaultValue(t *testing.T) {
 		{
 			name: "no default value",
 			property: &FloatProperty{
-				PropertyDetails: knowledgebase2.PropertyDetails{
+				PropertyDetails: knowledgebase.PropertyDetails{
 					Path: "test",
 				},
 			},
@@ -280,8 +279,8 @@ func Test_FloatProperty_GetDefaultValue(t *testing.T) {
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			assert := assert.New(t)
-			ctx := knowledgebase2.DynamicValueContext{}
-			data := knowledgebase2.DynamicValueData{}
+			ctx := knowledgebase.DynamicValueContext{}
+			data := knowledgebase.DynamicValueData{}
 			value, err := test.property.GetDefaultValue(ctx, data)
 			if !assert.NoError(err) {
 				return
@@ -294,7 +293,7 @@ func Test_FloatProperty_GetDefaultValue(t *testing.T) {
 func Test_FloatPRoperty_Clone(t *testing.T) {
 	assert := assert.New(t)
 	property := &FloatProperty{
-		PropertyDetails: knowledgebase2.PropertyDetails{
+		PropertyDetails: knowledgebase.PropertyDetails{
 			Path: "test",
 		},
 	}
@@ -306,7 +305,7 @@ func Test_FloatPRoperty_Clone(t *testing.T) {
 func Test_FloatProperty_Details(t *testing.T) {
 	assert := assert.New(t)
 	property := &FloatProperty{
-		PropertyDetails: knowledgebase2.PropertyDetails{
+		PropertyDetails: knowledgebase.PropertyDetails{
 			Path: "test",
 		},
 	}
@@ -317,7 +316,7 @@ func Test_FloatProperty_Details(t *testing.T) {
 func Test_FloatProperty_ZeroValue(t *testing.T) {
 	assert := assert.New(t)
 	property := &FloatProperty{
-		PropertyDetails: knowledgebase2.PropertyDetails{
+		PropertyDetails: knowledgebase.PropertyDetails{
 			Path: "test",
 		},
 	}
@@ -327,7 +326,7 @@ func Test_FloatProperty_ZeroValue(t *testing.T) {
 func Test_FloatProperty_Contains(t *testing.T) {
 	assert := assert.New(t)
 	property := &FloatProperty{
-		PropertyDetails: knowledgebase2.PropertyDetails{
+		PropertyDetails: knowledgebase.PropertyDetails{
 			Path: "test",
 		},
 	}
@@ -337,7 +336,7 @@ func Test_FloatProperty_Contains(t *testing.T) {
 func Test_FloatProperty_Type(t *testing.T) {
 	assert := assert.New(t)
 	property := &FloatProperty{
-		PropertyDetails: knowledgebase2.PropertyDetails{
+		PropertyDetails: knowledgebase.PropertyDetails{
 			Path: "test",
 		},
 	}
@@ -347,7 +346,7 @@ func Test_FloatProperty_Type(t *testing.T) {
 func Test_FloatProperty_SubProperties(t *testing.T) {
 	assert := assert.New(t)
 	property := &FloatProperty{
-		PropertyDetails: knowledgebase2.PropertyDetails{
+		PropertyDetails: knowledgebase.PropertyDetails{
 			Path: "test",
 		},
 	}
diff --git a/pkg/knowledge_base2/properties/int_property_test.go b/pkg/knowledge_base2/properties/int_property_test.go
index 5b1f5f7d..1f95eb94 100644
--- a/pkg/knowledge_base2/properties/int_property_test.go
+++ b/pkg/knowledge_base2/properties/int_property_test.go
@@ -5,7 +5,6 @@ import (
 
 	construct "github.com/klothoplatform/klotho/pkg/construct2"
 	"github.com/klothoplatform/klotho/pkg/engine2/enginetesting"
-	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2"
 	knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledge_base2"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -265,7 +264,7 @@ func Test_IntProperty_Validate(t *testing.T) {
 			for _, call := range test.mockKBCalls {
 				mockKB.On(call.Method, call.Arguments...).Return(call.ReturnArguments...)
 			}
-			ctx := knowledgebase.DynamicValueContext{
+			ctx := knowledgebase2.DynamicValueContext{
 				Graph:         graph,
 				KnowledgeBase: mockKB,
 			}
diff --git a/pkg/knowledge_base2/properties/list_property_test.go b/pkg/knowledge_base2/properties/list_property_test.go
index 4833f529..43d02a39 100644
--- a/pkg/knowledge_base2/properties/list_property_test.go
+++ b/pkg/knowledge_base2/properties/list_property_test.go
@@ -1,6 +1,7 @@
 package properties
 
 import (
+	"log"
 	"testing"
 
 	construct "github.com/klothoplatform/klotho/pkg/construct2"
@@ -286,6 +287,12 @@ func Test_ListProperty_Parse(t *testing.T) {
 func Test_ListProperty_Validate(t *testing.T) {
 	minLength := 1
 	maxLength := 2
+	sanitizeTmpl, err := knowledgebase.NewSanitizationTmpl("test", "{{ . | replace `[^[:alnum:]_]+` \"_\" | replace `^[^a-zA-Z]+` \"\" | upper }}")
+	if err != nil {
+		// handle error here
+		log.Fatalf("Failed to create sanitize template: %v", err)
+	}
+
 	tests := []struct {
 		name          string
 		property      *ListProperty
@@ -293,6 +300,7 @@ func Test_ListProperty_Validate(t *testing.T) {
 		mockKBCalls   []mock.Call
 		value         any
 		wantErr       bool
+		err           error
 	}{
 		{
 			name: "list property",
@@ -345,6 +353,89 @@ func Test_ListProperty_Validate(t *testing.T) {
 			value:   []any{"test", "test", "test"},
 			wantErr: true,
 		},
+		{
+			name: "list property checks item property validation, returns sanitization error",
+			property: &ListProperty{
+				PropertyDetails: knowledgebase.PropertyDetails{
+					Path: "test",
+				},
+				ItemProperty: &StringProperty{SanitizeTmpl: sanitizeTmpl},
+			},
+			value:   []any{"test", "t-t", "t#$@est"},
+			wantErr: true,
+			err: &knowledgebase.SanitizeError{
+				Input:     []any{"test", "t-t", "t#$@est"},
+				Sanitized: []any{"TEST", "T_T", "T_EST"},
+			},
+		},
+		{
+			name: "list property checks sub property validation",
+			property: &ListProperty{
+				PropertyDetails: knowledgebase.PropertyDetails{
+					Path: "test",
+				},
+				Properties: knowledgebase.Properties{
+					"test": &StringProperty{
+						AllowedValues:   []string{"val"},
+						PropertyDetails: knowledgebase.PropertyDetails{Name: "test"},
+					},
+				},
+			},
+			value: []any{map[string]any{
+				"test": "test",
+			}},
+			wantErr: true,
+		},
+		{
+			name: "list property checks item property validation, returns sanitization error",
+			property: &ListProperty{
+				PropertyDetails: knowledgebase.PropertyDetails{
+					Path: "test",
+				},
+				Properties: knowledgebase.Properties{
+					"test": &StringProperty{
+						SanitizeTmpl:    sanitizeTmpl,
+						PropertyDetails: knowledgebase.PropertyDetails{Name: "test"},
+					},
+				},
+			},
+			value: []any{
+				map[string]any{
+					"test": "test",
+				},
+				map[string]any{
+					"test": "t-t",
+				},
+				map[string]any{
+					"test": "t#$@est",
+				},
+			},
+			wantErr: true,
+			err: &knowledgebase.SanitizeError{
+				Input: []any{
+					map[string]any{
+						"test": "test",
+					},
+					map[string]any{
+						"test": "t-t",
+					},
+					map[string]any{
+						"test": "t#$@est",
+					},
+				},
+				Sanitized: []any{
+					map[string]any{
+						"test": "TEST",
+					},
+					map[string]any{
+						"test": "T_T",
+					},
+					map[string]any{
+						"test": "T_EST",
+					},
+				},
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -365,6 +456,9 @@ func Test_ListProperty_Validate(t *testing.T) {
 			err := tt.property.Validate(resource, tt.value, ctx)
 			if tt.wantErr {
 				assert.Error(err)
+				if tt.err != nil {
+					assert.Equal(tt.err, err)
+				}
 				return
 			}
 			assert.NoError(err)
diff --git a/pkg/knowledge_base2/properties/map_property_test.go b/pkg/knowledge_base2/properties/map_property_test.go
index 744636d2..31f0a5ea 100644
--- a/pkg/knowledge_base2/properties/map_property_test.go
+++ b/pkg/knowledge_base2/properties/map_property_test.go
@@ -5,7 +5,6 @@ import (
 
 	construct "github.com/klothoplatform/klotho/pkg/construct2"
 	"github.com/klothoplatform/klotho/pkg/engine2/enginetesting"
-	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2"
 	knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledge_base2"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -286,7 +285,7 @@ func Test_MapProperty_Validate(t *testing.T) {
 			for _, call := range test.mockKBCalls {
 				mockKB.On(call.Method, call.Arguments...).Return(call.ReturnArguments...)
 			}
-			ctx := knowledgebase.DynamicValueContext{
+			ctx := knowledgebase2.DynamicValueContext{
 				Graph:         graph,
 				KnowledgeBase: mockKB,
 			}
diff --git a/pkg/knowledge_base2/properties/resource_property_test.go b/pkg/knowledge_base2/properties/resource_property_test.go
index 456fca98..0fd1adf4 100644
--- a/pkg/knowledge_base2/properties/resource_property_test.go
+++ b/pkg/knowledge_base2/properties/resource_property_test.go
@@ -288,6 +288,28 @@ func Test_ResourcePropertyContains(t *testing.T) {
 			},
 			value:    construct.ResourceId{Provider: "test"},
 			contains: construct.ResourceId{Provider: "test"},
+			want:     true,
+		},
+		{
+			name: "resource is different",
+			property: &ResourceProperty{
+				PropertyDetails: knowledgebase2.PropertyDetails{
+					Path: "test",
+				},
+			},
+			value:    construct.ResourceId{Provider: "test"},
+			contains: construct.ResourceId{Provider: "test2"},
+			want:     false,
+		},
+		{
+			name: "resource property different namespace",
+			property: &ResourceProperty{
+				PropertyDetails: knowledgebase2.PropertyDetails{
+					Path: "test",
+				},
+			},
+			value:    construct.ResourceId{Provider: "test"},
+			contains: construct.ResourceId{Provider: "test2", Namespace: "test2"},
 			want:     false,
 		},
 	}