Repository: klothoplatform/klotho
Pull request #842 (2024-01-03 21:48:31)

support imports in iac
changes to the iac plugin to render imported resources using a new templated function

### 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/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/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/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/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,
 		},
 	}