Repository: klothoplatform/klotho
Pull request #586 (2023-05-12 21:38:42)

validate visualizer types during GH test
This depends on https://github.com/klothoplatform/visualizer/pull/23 (which I already deployed to the dev stage).

It sets the `KLOTHO_VIZ_URL_BASE` env var, so that we run the visualizer's `all_types_test.go` tests. It also replaces the one-call-per-type pattern with a single call that fetches all of the validations, followed by a loop to test each one. This results in slightly more complicated test code, but is much faster

### Standard checks

- **Unit tests**: n/a
- **Docs**: not needed
- **Backwards compatibility**: no issues



Patch:
diff --git a/go.mod b/go.mod
index 6bff0fc0..f11675ba 100644
--- a/go.mod
+++ b/go.mod
@@ -42,6 +42,7 @@ require (
 	github.com/kr/pretty v0.3.0 // indirect
 	github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
+	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/rivo/uniseg v0.4.4 // indirect
 	golang.org/x/mod v0.9.0 // indirect
 	golang.org/x/oauth2 v0.4.0 // indirect
diff --git a/go.sum b/go.sum
index da7582a5..f8434add 100644
--- a/go.sum
+++ b/go.sum
@@ -521,6 +521,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
 github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
 github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
diff --git a/pkg/core/annotation.go b/pkg/core/annotation.go
index 181cb122..df4db772 100644
--- a/pkg/core/annotation.go
+++ b/pkg/core/annotation.go
@@ -49,6 +49,17 @@ func (a *Annotation) MarshalJSON() ([]byte, error) {
 	return json.Marshal(m)
 }
 
+func DedupeAnnotationKeys(a []AnnotationKey) []AnnotationKey {
+	result := make(map[AnnotationKey]struct{})
+	for _, key := range a {
+		result[key] = struct{}{}
+	}
+	keys := make([]AnnotationKey, 0, len(result))
+	for key := range result {
+		keys = append(keys, key)
+	}
+	return keys
+}
 func (a Annotation) Format(s fmt.State, verb rune) {
 	fmt.Fprintf(s, "@klotho::%s", a.Capability.Name)
 	if len(a.Capability.Directives) > 0 {
diff --git a/pkg/core/construct_graph.go b/pkg/core/construct_graph.go
index d3219713..b993bbd9 100644
--- a/pkg/core/construct_graph.go
+++ b/pkg/core/construct_graph.go
@@ -36,7 +36,7 @@ func (cg *ConstructGraph) AddConstruct(construct Construct) {
 }
 
 func (cg *ConstructGraph) AddDependency(source string, dest string) {
-	cg.underlying.AddEdge(source, dest)
+	cg.underlying.AddEdge(source, dest, nil)
 }
 
 func (cg *ConstructGraph) GetConstruct(key string) Construct {
diff --git a/pkg/core/resource_graph.go b/pkg/core/resource_graph.go
index d3e3260b..8e93be1b 100644
--- a/pkg/core/resource_graph.go
+++ b/pkg/core/resource_graph.go
@@ -1,9 +1,12 @@
 package core
 
 import (
+	"fmt"
 	"reflect"
 
 	"github.com/klothoplatform/klotho/pkg/graph"
+	"github.com/klothoplatform/klotho/pkg/multierr"
+	"github.com/pkg/errors"
 	"go.uber.org/zap"
 )
 
@@ -47,13 +50,30 @@ func (rg *ResourceGraph) AddResource(resource Resource) {
 //
 //	rg.AddDependency(lambda, lambda.Role)
 func (rg *ResourceGraph) AddDependency(deployedSecond Resource, deployedFirst Resource) {
+
+	rg.AddResource(deployedSecond)
+	rg.AddResource(deployedFirst)
+
+	if cycle, _ := rg.underlying.CreatesCycle(deployedSecond.Id().String(), deployedFirst.Id().String()); cycle {
+		zap.S().Errorf("Not Adding Dependency, Cycle would be created from edge %s -> %s", deployedSecond.Id(), deployedFirst.Id())
+	} else {
+		rg.underlying.AddEdge(deployedSecond.Id().String(), deployedFirst.Id().String(), nil)
+		zap.S().Debugf("adding %s -> %s", deployedSecond.Id(), deployedFirst.Id())
+	}
+}
+
+// AddDependencyWithData Adds a dependency such that `deployedSecond` has to be deployed after `deployedFirst`. This makes the left-to-right
+// association consistent with our visualizer, and with the Go struct graph.
+// This method also allows any edge data to be attached to the dependency in the ResourceGraph
+func (rg *ResourceGraph) AddDependencyWithData(deployedSecond Resource, deployedFirst Resource, data any) {
+
 	for _, res := range []Resource{deployedSecond, deployedFirst} {
 		rg.AddResource(res)
 	}
 	if cycle, _ := rg.underlying.CreatesCycle(deployedSecond.Id().String(), deployedFirst.Id().String()); cycle {
 		zap.S().Errorf("Not Adding Dependency, Cycle would be created from edge %s -> %s", deployedSecond.Id(), deployedFirst.Id())
 	} else {
-		rg.underlying.AddEdge(deployedSecond.Id().String(), deployedFirst.Id().String())
+		rg.underlying.AddEdge(deployedSecond.Id().String(), deployedFirst.Id().String(), data)
 		zap.S().Debugf("adding %s -> %s", deployedSecond.Id(), deployedFirst.Id())
 	}
 }
@@ -74,6 +94,10 @@ func (rg *ResourceGraph) GetDependencyByVertexIds(source string, target string)
 	return rg.underlying.GetEdge(source, target)
 }
 
+func (rg *ResourceGraph) RemoveDependency(source string, target string) error {
+	return rg.underlying.RemoveEdge(source, target)
+}
+
 func (rg *ResourceGraph) ListResources() []Resource {
 	return rg.underlying.GetAllVertices()
 }
@@ -235,3 +259,245 @@ func (rg *ResourceGraph) getAllDownstreamResourcesSet(source Resource, upstreams
 	}
 	return upstreams
 }
+
+// CreateDependencies takes in a resource and set of metadata and looks at any fields which point to specific dependencies which match the Resource Interface
+// If a specific dependency is found, the .Create method will be called to create the parent resource.
+// Each specific dependency value will be updated on the resources field itself
+// This method will recurse down each one of the resources fields and do a DFS until a Resource is found
+//
+// Before adding a dependency and setting the value on the resource's field, the method will ensure there is no existing node in the graph for the named node created
+// If there is an existing node, the method will ensure the resource's field is set to point to the already existent node
+//
+// IaCValues today are not labeled as a specific resource, so the value must already be present. This method will still add the dependency and ensure the resource is created based on the params passed in, but
+// does not have the knowledge of which resource to create
+//
+//	T params are a map of the reflection field names, to their respective fields
+//
+// Example:
+// for a struct:
+//
+//	 Test{
+//	   Field1 Resource
+//	   Field2 Resource
+//	}
+//
+// The corresponding params would be:
+//
+//	params := map[string]any{
+//	   "Field1": ResourceParams
+//	   "Field2": ResourceParams
+//	}
+//
+// Params for direct resources or IaCValues correlate to the field they are for
+// For params on fields which correlate to the following types, the formats are:
+//   - Map: map[string]ParamType    (the string key corresponds to the key in the map)
+//   - Struct: map[string]ParamType (the string key corresponds to the field in the struct)
+//   - Array or slice: []ParamType
+func (rg *ResourceGraph) CreateDependencies(res Resource, params map[string]any) error {
+	var merr multierr.Error
+	source := reflect.ValueOf(res)
+
+	for source.Kind() == reflect.Pointer {
+		source = source.Elem()
+	}
+	for i := 0; i < source.NumField(); i++ {
+		targetValue := source.Field(i)
+		fieldsParams := params[source.Type().Field(i).Name]
+		if fieldsParams != nil {
+			merr.Append(rg.actOnValue(targetValue, res, fieldsParams, nil, reflect.Value{}))
+		}
+	}
+	rg.AddDependenciesReflect(res)
+	return merr.ErrOrNil()
+}
+
+// CreateResource is a wrapper around a Resources .Create method
+//
+// CreateResource provides safety in assuring that the up to date resource (which is inline with what exists in the ResourceGraph) is returned
+func CreateResource[T Resource](rg *ResourceGraph, params any) (resource T, err error) {
+
+	res := reflect.New(reflect.TypeOf(resource).Elem()).Interface()
+	err = rg.callCreate(reflect.ValueOf(res), params)
+	if err != nil {
+		return
+	}
+	castedRes, ok := res.(Resource)
+	if !ok {
+		err = fmt.Errorf("unable to cast to type Resource")
+		return
+	}
+
+	currValue := rg.GetResourceByVertexId(castedRes.Id().String())
+	if currValue != nil {
+		return currValue.(T), nil
+	}
+	return castedRes.(T), nil
+}
+
+func (rg *ResourceGraph) actOnValue(targetValue reflect.Value, res Resource, metadata any, parent *reflect.Value, index reflect.Value) error {
+	switch value := targetValue.Interface().(type) {
+	case Resource:
+		if targetValue.IsNil() {
+			value = reflect.New(targetValue.Type().Elem()).Interface().(Resource)
+		}
+		err := rg.callCreate(reflect.ValueOf(value), metadata)
+		currValue := rg.GetResourceByVertexId(value.Id().String())
+		if currValue != nil {
+			value = currValue
+		}
+		if err == nil && value != nil {
+			if parent != nil {
+				parent.SetMapIndex(index, reflect.ValueOf(value))
+			} else {
+				targetValue.Set(reflect.ValueOf(value))
+			}
+		} else {
+			return err
+		}
+	case *IaCValue:
+		if value != nil && value.Resource != nil {
+			err := rg.callCreate(reflect.ValueOf(value.Resource), metadata)
+			currValue := rg.GetResourceByVertexId(value.Resource.Id().String())
+			if currValue != nil {
+				value.Resource = currValue
+			}
+			if err == nil && value.Resource != nil {
+				if parent != nil {
+					parent.SetMapIndex(index, reflect.ValueOf(value))
+				} else {
+					targetValue.Set(reflect.ValueOf(value))
+				}
+			} else {
+				return err
+			}
+		}
+	case IaCValue:
+		if value.Resource != nil {
+			err := rg.callCreate(reflect.ValueOf(value.Resource), metadata)
+			currValue := rg.GetResourceByVertexId(value.Resource.Id().String())
+			if currValue != nil {
+				value.Resource = currValue
+			}
+			if err == nil && value.Resource != nil {
+				if parent != nil {
+					parent.SetMapIndex(index, reflect.ValueOf(value))
+				} else {
+					targetValue.Set(reflect.ValueOf(value))
+				}
+			} else {
+				return err
+			}
+		}
+	default:
+		correspondingValue := targetValue
+		for correspondingValue.Kind() == reflect.Pointer {
+			correspondingValue = targetValue.Elem()
+		}
+
+		err := rg.checkChild(correspondingValue, res, metadata)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (rg *ResourceGraph) callCreate(targetValue reflect.Value, metadata any) error {
+	method := targetValue.MethodByName("Create")
+	if method.IsValid() {
+		var callArgs []reflect.Value
+		callArgs = append(callArgs, reflect.ValueOf(rg))
+		params := reflect.New(method.Type().In(1)).Interface()
+		decoder := GetMapDecoder(params)
+		err := decoder.Decode(metadata)
+		if err != nil {
+			return errors.Wrap(err, fmt.Sprintf("error decoding the following type %s", reflect.New(method.Type().In(1)).Type().String()))
+		}
+		callArgs = append(callArgs, reflect.ValueOf(params).Elem())
+		eval := method.Call(callArgs)
+		if eval[0].IsNil() {
+			return nil
+		} else {
+			err, ok := eval[0].Interface().(error)
+			if !ok {
+				return fmt.Errorf("return type should be an error")
+			}
+			return err
+		}
+	}
+	return nil
+}
+
+// CallConfigure uses the resource graph to ensure the node passed in exists, then uses reflection to call the resources Configure method
+func (rg *ResourceGraph) CallConfigure(resource Resource, metadata any) error {
+
+	if rg.GetResource(resource.Id()) == nil {
+		return fmt.Errorf("resource with id %s cannot be configured since it does not exist in the ResourceGraph", resource.Id())
+	}
+
+	method := reflect.ValueOf(resource).MethodByName("Configure")
+	if method.IsValid() {
+		var callArgs []reflect.Value
+		params := reflect.New(method.Type().In(0)).Interface()
+		decoder := GetMapDecoder(params)
+		err := decoder.Decode(metadata)
+		if err != nil {
+			return errors.Wrapf(err, fmt.Sprintf("error decoding the following type %s", reflect.New(method.Type().In(0)).Type()))
+		}
+		callArgs = append(callArgs, reflect.ValueOf(params).Elem())
+		eval := method.Call(callArgs)
+		if eval[0].IsNil() {
+			return nil
+		} else {
+			err, ok := eval[0].Interface().(error)
+			if !ok {
+				return fmt.Errorf("return type should be an error")
+			}
+			return err
+		}
+	}
+	return nil
+}
+
+func (rg *ResourceGraph) checkChild(child reflect.Value, res Resource, metadata any) error {
+	var merr multierr.Error
+	switch child.Kind() {
+	case reflect.Struct:
+		params := reflect.ValueOf(metadata)
+		if params.Kind() != reflect.Map {
+			return fmt.Errorf("field %s params does not conform to type for structs", child.Type().String())
+		}
+		for i := 0; i < child.NumField(); i++ {
+			childVal := child.Field(i)
+			fieldName := child.Type().Field(i).Name
+
+			// Loop over the keys of the params map and see if anything correlates to the field in the struct. If so then we will act on that field of the struct
+			for _, key := range params.MapKeys() {
+				if key.String() == fieldName {
+					merr.Append(rg.actOnValue(childVal, res, params.MapIndex(reflect.ValueOf(fieldName)).Interface(), nil, reflect.Value{}))
+				}
+			}
+		}
+	case reflect.Slice, reflect.Array:
+		params := reflect.ValueOf(metadata)
+		if params.Kind() != reflect.Slice && params.Kind() != reflect.Array {
+			return fmt.Errorf("field %s does not match parent type %s", params.Type().String(), child.Type().String())
+		} else if params.Len() != child.Len() {
+			return fmt.Errorf("field %s does not have the same number of elements as parent", child.Type().String())
+		}
+		for elemIdx := 0; elemIdx < child.Len(); elemIdx++ {
+			elemValue := child.Index(elemIdx)
+			merr.Append(rg.actOnValue(elemValue, res, params.Index(elemIdx).Interface(), nil, reflect.Value{}))
+		}
+	case reflect.Map:
+		params := reflect.ValueOf(metadata)
+		if params.Kind() != reflect.Map {
+			return fmt.Errorf("field %s params does not conform to type for maps", child.Type().String())
+		}
+		for _, key := range child.MapKeys() {
+			elemValue := child.MapIndex(key)
+			merr.Append(rg.actOnValue(elemValue, res, params.MapIndex(key).Interface(), &child, key))
+		}
+	}
+	return merr.ErrOrNil()
+}
diff --git a/pkg/core/resources.go b/pkg/core/resources.go
index 514f2334..3bc56f94 100644
--- a/pkg/core/resources.go
+++ b/pkg/core/resources.go
@@ -4,6 +4,7 @@ import (
 	"strings"
 
 	"github.com/klothoplatform/klotho/pkg/graph"
+	"github.com/mitchellh/mapstructure"
 	"github.com/pkg/errors"
 )
 
@@ -107,3 +108,11 @@ func (id ResourceId) MarshalTOML() ([]byte, error) {
 func (id *ResourceId) UnmarshalTOML(data []byte) error {
 	return id.UnmarshalText(data)
 }
+
+func GetMapDecoder(result interface{}) *mapstructure.Decoder {
+	decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ErrorUnset: true, Result: result})
+	if err != nil {
+		panic(err)
+	}
+	return decoder
+}
diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go
index 7dd0016e..291e30b5 100644
--- a/pkg/graph/graph.go
+++ b/pkg/graph/graph.go
@@ -21,6 +21,7 @@ type (
 	Edge[V any] struct {
 		Source      V
 		Destination V
+		Properties  graph.EdgeProperties
 	}
 )
 
@@ -103,8 +104,8 @@ func (d *Directed[V]) AddVerticesAndEdge(source V, dest V) {
 	}
 }
 
-func (d *Directed[V]) AddEdge(source string, dest string) {
-	err := d.underlying.AddEdge(source, dest)
+func (d *Directed[V]) AddEdge(source string, dest string, data any) {
+	err := d.underlying.AddEdge(source, dest, graph.EdgeData(data))
 	if err != nil && !errors.Is(err, graph.ErrEdgeAlreadyExists) {
 		zap.S().With("error", zap.Error(err)).Errorf(
 			`Unexpected error while adding edge between "%v" and "%v"`, source, dest)
@@ -148,7 +149,7 @@ func (d *Directed[V]) GetEdge(source string, target string) *Edge[V] {
 	v, err := d.underlying.Edge(source, target)
 	switch {
 	case err == nil:
-		return &Edge[V]{Source: v.Source, Destination: v.Target}
+		return &Edge[V]{Source: v.Source, Destination: v.Target, Properties: v.Properties}
 
 	case errors.Is(err, graph.ErrEdgeNotFound):
 		return nil
@@ -160,6 +161,10 @@ func (d *Directed[V]) GetEdge(source string, target string) *Edge[V] {
 	}
 }
 
+func (d *Directed[V]) RemoveEdge(source string, target string) error {
+	return d.underlying.RemoveEdge(source, target)
+}
+
 func (d *Directed[V]) IdForNode(v V) string {
 	return d.hasher(v)
 }
@@ -185,7 +190,7 @@ func (d *Directed[V]) GetAllEdges() []Edge[V] {
 				zap.S().With(zap.Error(err)).Errorf(
 					`Ignoring edge %v because I couldn't resolve the destination vertex. %s`, edge, ourFault)
 			}
-			results = append(results, Edge[V]{Source: sourceV, Destination: destV})
+			results = append(results, Edge[V]{Source: sourceV, Destination: destV, Properties: edge.Properties})
 		}
 	}
 	return results
diff --git a/pkg/infra/kubernetes/exec_unit.go b/pkg/infra/kubernetes/exec_unit.go
index 27a088ea..35992f1e 100644
--- a/pkg/infra/kubernetes/exec_unit.go
+++ b/pkg/infra/kubernetes/exec_unit.go
@@ -11,7 +11,7 @@ import (
 
 	"go.uber.org/zap"
 	apps "k8s.io/api/apps/v1"
-	autoscaling "k8s.io/api/autoscaling/v2beta2"
+	autoscaling "k8s.io/api/autoscaling/v2"
 	corev1 "k8s.io/api/core/v1"
 	elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
 	"sigs.k8s.io/yaml"
@@ -146,6 +146,12 @@ var podTransformer = manifestTransformer[*corev1.Pod]{
 			pod.Labels = make(map[string]string)
 		}
 		pod.Labels["execUnit"] = unit.Name
+
+		// If this pod is bound to a target group, this label informs the ALB Controller to inject a pod readiness gate.
+		if unit.TargetGroupBinding != nil {
+			pod.Labels["elbv2.k8s.aws/pod-readiness-gate-inject"] = "enabled"
+		}
+
 		pod.Spec.ServiceAccountName = unit.getServiceAccountName()
 
 		output, err := yaml.Marshal(pod)
@@ -204,6 +210,10 @@ var deploymentTransformer = manifestTransformer[*apps.Deployment]{
 		}
 		deployment.Spec.Template.Labels["execUnit"] = unit.Name
 		deployment.Spec.Template.Spec.ServiceAccountName = unit.getServiceAccountName()
+		// If this pod is bound to a target group, this label informs the ALB Controller to inject a pod readiness gate.
+		if unit.TargetGroupBinding != nil {
+			deployment.Spec.Template.Labels["elbv2.k8s.aws/pod-readiness-gate-inject"] = "enabled"
+		}
 		extraLabels.addTo(deployment.Spec.Template.Labels)
 
 		if deployment.Spec.Selector.MatchLabels == nil {
diff --git a/pkg/infra/kubernetes/helm_chart.go b/pkg/infra/kubernetes/helm_chart.go
index 1502ed67..6f409c8c 100644
--- a/pkg/infra/kubernetes/helm_chart.go
+++ b/pkg/infra/kubernetes/helm_chart.go
@@ -7,7 +7,7 @@ import (
 
 	"go.uber.org/zap"
 	apps "k8s.io/api/apps/v1"
-	autoscaling "k8s.io/api/autoscaling/v2beta2"
+	autoscaling "k8s.io/api/autoscaling/v2"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
@@ -117,6 +117,12 @@ func (t *HelmChart) AssignFilesToUnits() error {
 func (chart *HelmChart) handleExecutionUnit(unit *HelmExecUnit, eu *core.ExecutionUnit, cfg config.ExecutionUnit, constructGraph *core.ConstructGraph) ([]HelmChartValue, error) {
 	values := []HelmChartValue{}
 
+	upstreamValues, err := chart.handleUpstreamUnitDependencies(unit, constructGraph, cfg)
+	if err != nil {
+		return nil, err
+	}
+	values = append(values, upstreamValues...)
+
 	if shouldTransformImage(eu) {
 		if unit.Deployment != nil {
 			deploymentValues, err := deploymentTransformer.apply(unit, cfg)
@@ -168,11 +174,6 @@ func (chart *HelmChart) handleExecutionUnit(unit *HelmExecUnit, eu *core.Executi
 			values = append(values, serviceAccountValues...)
 		}
 	}
-	upstreamValues, err := chart.handleUpstreamUnitDependencies(unit, constructGraph, cfg)
-	if err != nil {
-		return nil, err
-	}
-	values = append(values, upstreamValues...)
 
 	unitEnvValues, err := unit.AddUnitsEnvironmentVariables(eu)
 	if err != nil {
diff --git a/pkg/infra/kubernetes/manifests/horizontal_pod_autoscaler.yaml.tmpl b/pkg/infra/kubernetes/manifests/horizontal_pod_autoscaler.yaml.tmpl
index f73c2d84..6f361749 100644
--- a/pkg/infra/kubernetes/manifests/horizontal_pod_autoscaler.yaml.tmpl
+++ b/pkg/infra/kubernetes/manifests/horizontal_pod_autoscaler.yaml.tmpl
@@ -1,4 +1,4 @@
-apiVersion: autoscaling/v2beta2
+apiVersion: autoscaling/v2
 kind: HorizontalPodAutoscaler
 metadata:
   name: {{ .Name }}
diff --git a/pkg/knowledge_base/edge_kb.go b/pkg/knowledge_base/edge_kb.go
new file mode 100644
index 00000000..b735492c
--- /dev/null
+++ b/pkg/knowledge_base/edge_kb.go
@@ -0,0 +1,304 @@
+package knowledgebase
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/multierr"
+	"go.uber.org/zap"
+)
+
+type (
+	// Edge defines an entry in a Knowledge base. An Edge represents a valid linking between two types of resources
+	Edge struct {
+		// Source represents the source resource in the edge
+		Source reflect.Type
+		// Destination represents the target resource in the edge
+		Destination reflect.Type
+	}
+
+	// EdgeDetails defines the set of characteristics and edge in the knowledge base contains. The details are used to ensure graph correctness for ResourceGraphs
+	EdgeDetails struct {
+		// ExpansionFunc is a function used to create the To and From resource and necessary intermediate resources, if any do not exist, to ensure the nodes are in place for correct functionality.
+		ExpansionFunc ExpandEdge
+		// Configure is a function used to configure the To and From resources and necessary dependent resources, to ensure the nodes will guarantee correct functionality.
+		Configure ConfigureEdge
+		// ValidDestinations is a list of end destinations the edge supports. This field is used within determining the path (edge expansion) between two resources.
+		ValidDestinations []reflect.Type
+	}
+
+	// EdgeKB is a map (knowledge base) of edges and their respective details used to configure ResourceGraphs
+	EdgeKB map[Edge]EdgeDetails
+
+	// EdgeExpander is a function used to create the To and From resource and necessary intermediate resources, if any do not exist, to ensure the nodes are in place for correct functionality.
+	ExpandEdge func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error
+	// EdgeConfigurer is a function used to configure the To and From resources and necessary dependent resources, to ensure the nodes will guarantee correct functionality.
+	ConfigureEdge func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error
+
+	// EdgeConstraint is an object defined on EdgeData which can influence the path picked when expansion occurs.
+	EdgeConstraint struct {
+		// NodeMustExist specifies a list of resources which must exist in the path when edge expansion occurs. The resources type will be correlated to the types in the generated paths
+		NodeMustExist []core.Resource
+		// NodeMustNotExist specifies a list of resources which must not exist when edge expansion occurs. The resources type will be correlated to the types in the generated paths
+		NodeMustNotExist []core.Resource
+	}
+
+	// EdgeData is an object attached to edges in the ResourceGraph to help the knowledge base understand context when performing expansion and configuration tasks
+	EdgeData struct {
+		// AppName refers to the application name of the global ResourceGraph
+		AppName string
+		// EnvironmentVaribles specify and environment variables which will need to be configured during the edge expansion process
+		EnvironmentVariables []core.EnvironmentVariable
+		// Constraint refers to the EdgeConstraints defined during the edge expansion
+		Constraint EdgeConstraint
+		// Source refers to the initial source resource node when edge expansion is called
+		Source core.Resource
+		// Destination refers to the initial target resource node when edge expansion is called
+		Destination core.Resource
+	}
+
+	Path []Edge
+)
+
+// GetEdgeDetails takes in a source and target to retrieve the edge details for the given key. Will return nil if no edge exists for the given source and target
+func (kb EdgeKB) GetEdgeDetails(source reflect.Type, target reflect.Type) EdgeDetails {
+	return kb[Edge{Source: source, Destination: target}]
+}
+
+// GetEdgesWithSource will return all edges where the source type parameter is the From of the edge
+func (kb EdgeKB) GetEdgesWithSource(source reflect.Type) []Edge {
+	result := []Edge{}
+	for edge := range kb {
+		if edge.Source == source {
+			result = append(result, edge)
+		}
+	}
+	return result
+}
+
+// GetEdgesWithTarget will return all edges where the target type parameter is the To of the edge
+func (kb EdgeKB) GetEdgesWithTarget(target reflect.Type) []Edge {
+	result := []Edge{}
+	for edge := range kb {
+		if edge.Destination == target {
+			result = append(result, edge)
+		}
+	}
+	return result
+}
+
+// FindPaths takes in a source and destination type and finds all valid paths to get from source to destination.
+//
+// Find paths does a Depth First Search to search through all edges in the knowledge base.
+// The function tracks visited edges to prevent cycles during execution
+// It also checks the ValidDestinations for each edge against the original destination node to ensure that the edge is allowed to be used in the instance of the path generation
+//
+// The method will return all paths found
+func (kb EdgeKB) FindPaths(source reflect.Type, dest reflect.Type) []Path {
+	zap.S().Debugf("Finding Paths from %s -> %s", source, dest)
+	visitedEdges := map[reflect.Type]bool{}
+	stack := []Edge{}
+	return kb.findPaths(source, dest, stack, visitedEdges)
+}
+
+// findPaths performs the recursive calls of the parent FindPath function
+//
+// It works under the assumption that an edge is bidirectional and uses the edges ValidDestinations field to determine when that assumption is incorrect
+func (kb EdgeKB) findPaths(source reflect.Type, dest reflect.Type, stack []Edge, visited map[reflect.Type]bool) (result []Path) {
+	visited[source] = true
+	if source == dest {
+		result = append(result, stack)
+	} else {
+		for _, e := range kb.GetEdgesWithSource(source) {
+			if e.Source == source && !visited[e.Destination] && kb.isValidForPath(e, dest) {
+				result = append(result, kb.findPaths(e.Destination, dest, append(stack, e), visited)...)
+			}
+		}
+		for _, e := range kb.GetEdgesWithTarget(source) {
+			if e.Destination == source && !visited[e.Source] && kb.isValidForPath(e, dest) {
+				result = append(result, kb.findPaths(e.Source, dest, append(stack, e), visited)...)
+			}
+		}
+	}
+	delete(visited, source)
+	return result
+}
+
+// isValidForPath determines if an edge is valid for an instance of path generation.
+//
+// The criteria is:
+//   - if there is no expansion function and no ValidDestinations, assume all destinations are valid.
+//   - otherwise check to see if the path generations destination is valid for the edge
+func (kb EdgeKB) isValidForPath(edge Edge, dest reflect.Type) bool {
+	edgeDetail := kb.GetEdgeDetails(edge.Source, edge.Destination)
+	if edgeDetail.ExpansionFunc == nil || len(edgeDetail.ValidDestinations) == 0 {
+		return true
+	}
+	for _, validDest := range edgeDetail.ValidDestinations {
+		if validDest == dest {
+			return true
+		}
+	}
+	return false
+}
+
+// ExpandEdges performs calculations to determine the proper path to be inserted into the ResourceGraph.
+//
+// The workflow of the edge expansion is as follows:
+//   - Find all valid paths from the dependencies source node to the dependencies target node
+//   - Check each of the valid paths against constraints passed in on the edge data
+//   - At this point we should only have 1 valid path (If we have more than 1 edge choose direct connection otherwise error)
+//   - Iterate through each edge in path calling expansion function on edge
+func (kb EdgeKB) ExpandEdges(dag *core.ResourceGraph) (err error) {
+	zap.S().Debug("Expanding Edges")
+	var merr multierr.Error
+	// It does not matter what order we go in as each edge should be expanded independently. They can still reuse resources since the create methods should be idempotent if resources are the same.
+	for _, dep := range dag.ListDependencies() {
+		zap.S().Debug("Expanding Edge for %s -> %s", dep.Source.Id(), dep.Destination.Id())
+
+		// We want to retrieve the edge data from the edge in the resource graph to use during expansion
+		edgeData := EdgeData{}
+		data, ok := dep.Properties.Data.(EdgeData)
+		if !ok && dep.Properties.Data != nil {
+			merr.Append(fmt.Errorf("edge properties for edge %s -> %s, do not satisfy edge data format", dep.Source.Id(), dep.Destination.Id()))
+		} else if dep.Properties.Data != nil {
+			edgeData = data
+		}
+		// Find all possible paths given the initial source and destination node
+		paths := kb.FindPaths(reflect.TypeOf(dep.Source), reflect.TypeOf(dep.Destination))
+		validPaths := [][]Edge{}
+		for _, path := range paths {
+
+			// Ensure that the path satisfies the NodeMustExist edge constraint
+			if edgeData.Constraint.NodeMustExist != nil {
+				nodeFound := false
+				for _, res := range path {
+					for _, mustExistRes := range edgeData.Constraint.NodeMustExist {
+						if res.Source == reflect.TypeOf(mustExistRes) || res.Destination == reflect.TypeOf(mustExistRes) {
+							nodeFound = true
+						}
+					}
+				}
+				if !nodeFound {
+					continue
+				}
+			}
+
+			// Ensure that the path satisfies the NodeMustNotExist edge constraint
+			if edgeData.Constraint.NodeMustNotExist != nil {
+				nodeFound := false
+				for _, res := range path {
+					for _, mustNotExistRes := range edgeData.Constraint.NodeMustNotExist {
+						if res.Source == reflect.TypeOf(mustNotExistRes) || res.Destination == reflect.TypeOf(mustNotExistRes) {
+							nodeFound = true
+						}
+					}
+				}
+				if nodeFound {
+					continue
+				}
+			}
+			validPaths = append(validPaths, path)
+		}
+
+		zap.S().Debugf("Found valid paths %s", validPaths)
+		var validPath []Edge
+
+		// If we have more than 1 valid path we will always default to the direct path if it exists, otherwise we will raise an error since we cannot determine which path we are supposed to use.
+		if len(validPaths) > 1 {
+			for _, p := range validPaths {
+				if len(p) == 2 {
+					zap.S().Debug("Defaulting to direct path")
+					validPath = p
+				}
+			}
+			if len(validPath) == 0 {
+				merr.Append(fmt.Errorf("found multiple paths which satisfy constraints for edge %s -> %s. \n Paths: %s", dep.Source.Id(), dep.Destination.Id(), validPaths))
+			}
+		} else if len(validPaths) == 0 {
+			merr.Append(fmt.Errorf("found no paths which satisfy constraints for edge %s -> %s. \n Paths: %s", dep.Source.Id(), dep.Destination.Id(), validPaths))
+		} else {
+			validPath = validPaths[0]
+
+			// If the valid path is not the original direct path, we want to remove the initial direct dependency so we can fill in the new edges with intermediate nodes
+			if len(validPath) > 2 {
+				zap.S().Debugf("Removing dependency from %s -> %s", dep.Source.Id(), dep.Destination.Id())
+				err := dag.RemoveDependency(dep.Source.Id().String(), dep.Destination.Id().String())
+				if err != nil {
+					merr.Append(err)
+					continue
+				}
+			}
+
+			// resourceCache is used to always pass the graphs nodes into the Expand functions if they exist. We do this so that we operate on nodes which already exist
+			resourceCache := map[reflect.Type]core.Resource{}
+			for _, edge := range validPath {
+				source := edge.Source
+				dest := edge.Destination
+				edgeDetail := kb.GetEdgeDetails(source, dest)
+				sourceNode := resourceCache[source]
+				if source == reflect.TypeOf(dep.Source) {
+					sourceNode = dep.Source
+				}
+				if sourceNode == nil {
+					sourceNode = reflect.New(source.Elem()).Interface().(core.Resource)
+				}
+
+				destNode := resourceCache[dest]
+				if dest == reflect.TypeOf(dep.Destination) {
+					destNode = dep.Destination
+				}
+				if destNode == nil {
+					destNode = reflect.New(dest.Elem()).Interface().(core.Resource)
+				}
+
+				if edgeDetail.ExpansionFunc != nil {
+					err := edgeDetail.ExpansionFunc(sourceNode, destNode, dag, edgeData)
+					merr.Append(err)
+				}
+
+				if sourceNode != nil {
+					resourceCache[source] = sourceNode
+				}
+				sourceNodeInGraph := dag.GetResource(sourceNode.Id())
+				if sourceNodeInGraph != nil {
+					resourceCache[source] = sourceNodeInGraph
+				}
+				if destNode != nil {
+					resourceCache[dest] = destNode
+				}
+				destNodeInGraph := dag.GetResource(destNode.Id())
+				if destNodeInGraph != nil {
+					resourceCache[dest] = destNodeInGraph
+				}
+			}
+		}
+
+	}
+	return merr.ErrOrNil()
+}
+
+// ConfigureFromEdgeData calls each edges configure function.
+func (kb EdgeKB) ConfigureFromEdgeData(dag *core.ResourceGraph) (err error) {
+	zap.S().Debug("Configuring Edges")
+	var merr multierr.Error
+	for _, dep := range dag.ListDependencies() {
+		zap.S().Debugf("Configuring Edge for %s -> %s", dep.Source.Id(), dep.Destination.Id())
+		to := reflect.TypeOf(dep.Source)
+		from := reflect.TypeOf(dep.Destination)
+		edgeData := EdgeData{}
+		data, ok := dep.Properties.Data.(EdgeData)
+		if !ok && dep.Properties.Data != nil {
+			merr.Append(fmt.Errorf("edge properties for edge %s -> %s, do not satisfy edge data format", dep.Source.Id(), dep.Destination.Id()))
+		} else if dep.Properties.Data != nil {
+			edgeData = data
+		}
+		edgeDetail := kb.GetEdgeDetails(to, from)
+		if edgeDetail.Configure != nil {
+			err := edgeDetail.Configure(dep.Source, dep.Destination, dag, edgeData)
+			merr.Append(err)
+		}
+	}
+	return merr.ErrOrNil()
+}
diff --git a/pkg/lang/python/imports.go b/pkg/lang/python/imports.go
index 9d53ec90..299391f7 100644
--- a/pkg/lang/python/imports.go
+++ b/pkg/lang/python/imports.go
@@ -15,43 +15,37 @@ import (
 // TODO: capture import scope (e.g. IsGlobal/IsLocal)
 type Import struct {
 	ParentModule string
-	Name         string
+	// Name is the unqualified name of the actual module. This is not necessarily the name by which the python source
+	// will refer to this module, because of aliases and qualified imports. For any analysis of the user's source code,
+	// you should use UsedAs instead.
+	Name string
 	// ImportedAttributes is a map from attribute name, to information about that attribute.
 	// Given a statement `from foo import bar as the_bar` the attribute name is `bar`.
 	ImportedAttributes map[string]Attribute
 	Node               *sitter.Node
-	Alias              string
+	// UsedAs is the names of the module as it will be used in the (python) code. These are either the (possibly
+	// qualified) module name, or the aliases. If this module was imported several times, each one will have an entry
+	// in this set (removing duplicates, of course).
+	UsedAs map[string]struct{}
 }
 
 type Attribute struct {
-	Name  string
-	Node  *sitter.Node
-	Alias string
+	// Name is the module attribute's name. This is not necessarily the name by which the python source will refer to
+	// the attribute, because of aliases. For any analysis of the user's source code, you should use UsedAs instead.
+	Name string
+	Node *sitter.Node
+	// UsedAs is the same as [Import.UsedAs], but for attributes
+	UsedAs map[string]struct{}
 }
 
 type Imports map[string]Import
 
-// ImportedAs returns the name of the module as it will be used in the (python) code. This is either the (possibly
-// qualified) module name, or the alias.
-func (imp Import) ImportedAs() string {
-	if imp.Alias != "" {
-		return imp.Alias
-	} else {
-		name := imp.Name
-		if imp.ParentModule != "" {
-			name = imp.ParentModule + "." + name
-		}
-		return name
-	}
-}
-
-func (imp Import) ModuleDir() string {
-	moduleRoot := ""
-	if imp.ParentModule != "" {
-		moduleRoot = imp.ParentModule
-	}
+func (imp Import) FullyQualifiedModule() string {
+	moduleRoot := imp.ParentModule
 	if imp.Name != "" {
-		if moduleRoot != "" {
+		if moduleRoot != "" && !strings.HasSuffix(moduleRoot, ".") {
+			// Only add a '.' delimiter if one doesn't already exist.
+			// It should only exist for `.ParentModule` in ['.' and '..'] cases
 			moduleRoot += "."
 		}
 		moduleRoot += imp.Name
@@ -59,12 +53,8 @@ func (imp Import) ModuleDir() string {
 	return moduleRoot
 }
 
-func (attr Attribute) UsedAs() string {
-	if attr.Alias != "" {
-		return attr.Alias
-	} else {
-		return attr.Name
-	}
+func FindFileImports(file *core.SourceFile) Imports {
+	return FindImports(file.Tree().RootNode())
 }
 
 // FindImports returns a map containing each import statement within the file, as a map keyed by the import's qualified
@@ -75,8 +65,8 @@ func (attr Attribute) UsedAs() string {
 //	import module_a                     # key is "module_a"
 //	import module_b as the_b            # key is "module_b"
 //	import module_c.module_cc as the_c  # key is "module_c.module_cc"
-func FindImports(file *core.SourceFile) Imports {
-	nextMatch := DoQuery(file.Tree().RootNode(), findImports)
+func FindImports(node *sitter.Node) Imports {
+	nextMatch := DoQuery(node, findImports)
 	fileImports := Imports{}
 	for {
 		match, found := nextMatch()
@@ -146,21 +136,31 @@ func FindImports(file *core.SourceFile) Imports {
 			qualifiedModuleName += "." + moduleName
 		}
 		i := fileImports[qualifiedModuleName]
-		// technically, this may be a submodule, but we can't tell without deeper analysis of the imported file
+		// this may be a submodule, but we can't tell without deeper analysis of the imported file
 		if attribute != nil {
 			attributeName := attribute.Content()
 			ia := i.ImportedAttributes
 			if ia == nil {
 				ia = map[string]Attribute{}
 			}
-			aliasFrom := ""
-			if alias != nil {
-				aliasFrom = alias.Content()
+			// Upsert a "UsedAs" map for this attribute
+			var attributeAliases map[string]struct{}
+			if existingIa, exists := ia[attributeName]; exists {
+				attributeAliases = existingIa.UsedAs
+			}
+			if attributeAliases == nil {
+				attributeAliases = make(map[string]struct{})
+			}
+			// Insert either the attribute name, or the alias if it exists
+			if alias == nil {
+				attributeAliases[attributeName] = struct{}{}
+			} else {
+				attributeAliases[alias.Content()] = struct{}{}
 			}
 			ia[attributeName] = Attribute{
-				Name:  attributeName,
-				Node:  attribute,
-				Alias: aliasFrom,
+				Name:   attributeName,
+				Node:   attribute,
+				UsedAs: attributeAliases,
 			}
 			i.ImportedAttributes = ia
 		} else {
@@ -169,7 +169,20 @@ func FindImports(file *core.SourceFile) Imports {
 
 		i.ParentModule = parent
 		i.Name = moduleName
-		i.Alias = aliasName
+
+		if len(i.ImportedAttributes) == 0 {
+			if aliasName == "" {
+				// convert it into the (possibly qualified) module name.
+				aliasName = moduleName
+				if parent != "" {
+					aliasName = parent + "." + aliasName
+				}
+			}
+			if i.UsedAs == nil {
+				i.UsedAs = make(map[string]struct{})
+			}
+			i.UsedAs[aliasName] = struct{}{}
+		}
 
 		fileImports[qualifiedModuleName] = i
 	}
@@ -198,7 +211,7 @@ func ResolveFileDependencies(files map[string]core.File) (core.FileDependencies,
 			imported[initPy] = core.References{}
 		}
 
-		imports := FindImports(pyFile)
+		imports := FindFileImports(pyFile)
 		for _, importSpec := range imports {
 			deps, err := dependenciesForImport(filePath, importSpec, files)
 			if err != nil {
@@ -217,60 +230,30 @@ func ResolveFileDependencies(files map[string]core.File) (core.FileDependencies,
 // dependenciesForImport returns all imports specified by spec
 func dependenciesForImport(relativeToPath string, spec Import, files map[string]core.File) (core.Imported, error) {
 	deps := make(core.Imported)
+	moduleDir := spec.FullyQualifiedModule()
+	importerFile := files[relativeToPath].(*core.SourceFile)
 
-	moduleRoot := spec.ModuleDir()
-
-	importedModule, err := findImportedFile(spec.Name, relativeToPath, files)
+	rootModule, err := findImportedFile(moduleDir, relativeToPath, files)
 	if err != nil {
 		return nil, err
 	}
-
-	if importedModule == "" {
-		importedModule, err = findImportedFile(moduleRoot, relativeToPath, files)
-		if err != nil {
-			return nil, err
-		}
+	if rootModule != "" {
+		deps[rootModule] = referencesForImport(importerFile.Tree().RootNode(), spec.UsedAs)
 	}
 
-	if importedModule != "" {
-		refs, ok := deps[importedModule]
-		if !ok {
-			refs = make(core.References)
-			deps[importedModule] = refs
-		}
-
-		if len(spec.ImportedAttributes) == 0 {
-			sourceFile := files[relativeToPath].(*core.SourceFile)
-			importRefs := referencesForImport(sourceFile.Tree().RootNode(), spec.ImportedAs())
-			refs.AddAll(importRefs)
-		} else {
-			for _, attr := range spec.ImportedAttributes {
-				refs.Add(attr.Name)
-			}
-		}
-		return deps, nil
-	}
-
-	if moduleRoot != "" && !strings.HasSuffix(moduleRoot, ".") {
-		moduleRoot += "."
-	}
 	for _, attr := range spec.ImportedAttributes {
-		modulePath, err := findImportedFile(moduleRoot+attr.Name, relativeToPath, files)
+		attrImport := Import{ParentModule: moduleDir, Name: attr.Name}
+		attrModule, err := findImportedFile(attrImport.FullyQualifiedModule(), relativeToPath, files)
 		if err != nil {
 			return nil, err
 		}
-		if modulePath == "" {
-			continue
-		}
-		moduleFile := files[modulePath].(*core.SourceFile)
-		refs, ok := deps[modulePath]
-		if !ok {
-			refs = make(core.References)
-			deps[modulePath] = refs
+		if attrModule == "" {
+			if rootRefs, ok := deps[rootModule]; ok {
+				rootRefs.Add(attr.Name)
+			}
+		} else {
+			deps[attrModule] = referencesForImport(importerFile.Tree().RootNode(), attr.UsedAs)
 		}
-
-		importRefs := referencesForImport(moduleFile.Tree().RootNode(), attr.UsedAs())
-		refs.AddAll(importRefs)
 	}
 
 	return deps, nil
@@ -278,7 +261,7 @@ func dependenciesForImport(relativeToPath string, spec Import, files map[string]
 
 // referencesForImport returns all references of importModule within the program. If the import is aliased, importModule should be the alias
 // and not the real module name.
-func referencesForImport(program *sitter.Node, importModule string) core.References {
+func referencesForImport(program *sitter.Node, importModules map[string]struct{}) core.References {
 	refs := make(core.References)
 	nextAttrUsage := DoQuery(program, FindQualifiedAttrUsage)
 	for {
@@ -287,7 +270,7 @@ func referencesForImport(program *sitter.Node, importModule string) core.Referen
 			break
 		}
 		objName, attrName := attrUsage["obj_name"], attrUsage["attr_name"]
-		if objName.Content() == importModule {
+		if _, found := importModules[objName.Content()]; found {
 			attrNameStr := attrName.Content()
 			refs[attrNameStr] = struct{}{}
 		}
diff --git a/pkg/lang/python/plugin_persist.go b/pkg/lang/python/plugin_persist.go
index f3ee2296..203f2654 100644
--- a/pkg/lang/python/plugin_persist.go
+++ b/pkg/lang/python/plugin_persist.go
@@ -428,7 +428,7 @@ type persistResult struct {
 func (p *persister) queryKV(file *core.SourceFile, annotation *core.Annotation, enableWarnings bool) *persistResult {
 	log := zap.L().With(logging.FileField(file), logging.AnnotationField(annotation))
 
-	imports := FindImports(file)
+	imports := FindFileImports(file)
 
 	aiocacheImport, ok := imports["aiocache"]
 	if !ok {
@@ -436,8 +436,6 @@ func (p *persister) queryKV(file *core.SourceFile, annotation *core.Annotation,
 	}
 	aiocacheImported := len(aiocacheImport.ImportedAttributes) == 0
 	cacheImport, cacheImported := aiocacheImport.ImportedAttributes["Cache"]
-	functionHostName := aiocacheImport.Name
-	cacheFunction := cacheImport.Name
 
 	nextMatch := DoQuery(annotation.Node, persistKV)
 
@@ -449,12 +447,12 @@ func (p *persister) queryKV(file *core.SourceFile, annotation *core.Annotation,
 	expression, name, functionHost, function := match["expression"], match["name"], match["functionHost"], match["function"]
 
 	// this assignment/invocation is unrelated to aiocache.Cache instantiation
-	if !aiocacheImported && !query.NodeContentEquals(function, cacheFunction) {
+	if !aiocacheImported && !query.NodeContentIn(function, cacheImport.UsedAs) {
 		return nil
 	}
 
 	// this Cache() invocation belongs to an object other the aiocache module
-	if aiocacheImported && functionHost != nil && !query.NodeContentEquals(functionHost, functionHostName) {
+	if aiocacheImported && functionHost != nil && !query.NodeContentIn(functionHost, aiocacheImport.UsedAs) {
 		return nil
 	}
 
@@ -489,19 +487,17 @@ func (p *persister) queryKV(file *core.SourceFile, annotation *core.Annotation,
 func (p *persister) queryFS(file *core.SourceFile, annotation *core.Annotation, enableWarnings bool) *persistResult {
 	log := zap.L().With(logging.FileField(file), logging.AnnotationField(annotation))
 
-	imports := FindImports(file)
+	imports := FindImports(annotation.Node)
 
 	fsSpecImport, ok := imports["aiofiles"]
 	if !ok {
 		return nil
 	}
 
-	varName := ""
-	if fsSpecImport.Alias != "" {
-		varName = fsSpecImport.Alias
-	} else if len(fsSpecImport.ImportedAttributes) == 0 {
-		varName = fsSpecImport.Name
-	} else {
+	varNames := fsSpecImport.UsedAs
+	if len(varNames) == 0 {
+		// this means it's an attribute-import: "from aiofiles import f", which we don't support
+		log.Warn(`Unsupported import. Use "import aiofiles" (with no "from") instead.`)
 		return nil
 	}
 
@@ -515,11 +511,15 @@ func (p *persister) queryFS(file *core.SourceFile, annotation *core.Annotation,
 	module, aliasedModule, alias, importStatement := match["module"], match["aliasedModule"], match["alias"], match["importStatement"]
 
 	// this assignment/invocation is unrelated to aiofile instantiation found from the matching import
+	var varName string
 	if aliasedModule != nil {
-		if !query.NodeContentEquals(alias, varName) {
-			return nil
+		if query.NodeContentIn(alias, varNames) {
+			varName = alias.Content()
 		}
-	} else if !query.NodeContentEquals(module, varName) {
+	} else if query.NodeContentIn(module, varNames) {
+		varName = module.Content()
+	}
+	if varName == "" {
 		return nil
 	}
 
@@ -539,22 +539,14 @@ func (p *persister) queryFS(file *core.SourceFile, annotation *core.Annotation,
 func (p *persister) queryORM(file *core.SourceFile, annotation *core.Annotation, enableWarnings bool) *persistResult {
 	log := zap.L().With(logging.FileField(file), logging.AnnotationField(annotation))
 
-	imports := FindImports(file)
+	imports := FindFileImports(file)
 
 	sqlalchemyImport, ok := imports["sqlalchemy"]
 	if !ok {
 		return nil
 	}
 	sqlalchemyImported := len(sqlalchemyImport.ImportedAttributes) == 0
-	sqlalchemyImportName := sqlalchemyImport.Name
 	engineImport, engineImported := sqlalchemyImport.ImportedAttributes["create_engine"]
-	engineFunction := engineImport.Name
-	if engineImport.Alias != "" {
-		engineFunction = engineImport.Alias
-	}
-	if sqlalchemyImport.Alias != "" {
-		sqlalchemyImportName = sqlalchemyImport.Alias
-	}
 
 	nextMatch := DoQuery(annotation.Node, orm)
 
@@ -566,12 +558,12 @@ func (p *persister) queryORM(file *core.SourceFile, annotation *core.Annotation,
 	engineVar, funcCall, connString, module := match["engineVar"], match["funcCall"], match["connString"], match["module"]
 
 	// this assignment/invocation is unrelated to sqlAlchemy.create_engine instantiation
-	if !sqlalchemyImported && !query.NodeContentEquals(funcCall, engineFunction) {
+	if !sqlalchemyImported && !query.NodeContentIn(funcCall, engineImport.UsedAs) {
 		return nil
 	}
 
 	// this create_engine() invocation belongs to an object other the aiocache module
-	if sqlalchemyImported && module != nil && !query.NodeContentEquals(module, sqlalchemyImportName) {
+	if sqlalchemyImported && module != nil && !query.NodeContentIn(module, sqlalchemyImport.UsedAs) {
 		return nil
 	}
 
@@ -640,7 +632,7 @@ func (p *persister) querySecret(file *core.SourceFile, name string) ([]string, e
 func (p *persister) queryRedis(file *core.SourceFile, annotation *core.Annotation, enableWarnings bool) *persistResult {
 	log := zap.L().With(logging.FileField(file), logging.AnnotationField(annotation))
 
-	imports := FindImports(file)
+	imports := FindFileImports(file)
 
 	redisImport, ok := imports["redis"]
 	redisClusterImport, cok := imports["redis.cluster"]
@@ -648,29 +640,18 @@ func (p *persister) queryRedis(file *core.SourceFile, annotation *core.Annotatio
 		return nil
 	}
 	redisImported := len(redisClusterImport.ImportedAttributes) == 0
-	redisImportName := redisImport.Name
 	constructorImport, constructorImported := redisImport.ImportedAttributes["Redis"]
 	clusterConstructorImport, clusterConstructorImported := redisClusterImport.ImportedAttributes["RedisCluster"]
 	clustermoduleImport, clusterModuleImported := redisImport.ImportedAttributes["cluster"]
-	clusterRedisFunction := clusterConstructorImport.Name
-	clustermoduleImportName := clustermoduleImport.Name
 
-	redisFunction := constructorImport.Name
-	if redisFunction == "" {
-		redisFunction = "Redis"
-	} else if constructorImport.Alias != "" {
-		redisFunction = constructorImport.Alias
-	}
-	if clusterRedisFunction == "" {
-		clusterRedisFunction = "RedisCluster"
-	} else if clusterConstructorImport.Alias != "" {
-		clusterRedisFunction = clusterConstructorImport.Alias
-	}
-	if redisImport.Alias != "" {
-		redisImportName = redisImport.Alias
+	redisFunctions := constructorImport.UsedAs
+	if redisFunctions == nil {
+		// This is when there's an "import redis" (as opposed to "from redis import Redis")
+		redisFunctions = map[string]struct{}{"Redis": {}}
 	}
-	if clustermoduleImport.Alias != "" {
-		clustermoduleImportName = clustermoduleImport.Alias
+	clusterRedisFunctions := clusterConstructorImport.UsedAs
+	if clusterRedisFunctions == nil {
+		clusterRedisFunctions = map[string]struct{}{"RedisCluster": {}}
 	}
 
 	nextMatch := DoQuery(annotation.Node, redis)
@@ -683,17 +664,17 @@ func (p *persister) queryRedis(file *core.SourceFile, annotation *core.Annotatio
 	redisVar, funcCall, args, module, subModule := match["redisVar"], match["funcCall"], match["args"], match["module"], match["subModule"]
 
 	// this Redis() or RedisCluster() invocation belongs to an object other the redis module
-	if redisImported && !clusterModuleImported && module != nil && !query.NodeContentEquals(module, redisImportName) {
+	if redisImported && !clusterModuleImported && module != nil && !query.NodeContentIn(module, redisImport.UsedAs) {
 		return nil
 	}
 
 	// import is similar to `from redis import cluster` and the RedisCluster call does not use cluster module
-	if clusterModuleImported && !query.NodeContentEquals(module, clustermoduleImportName) {
+	if clusterModuleImported && !query.NodeContentIn(module, clustermoduleImport.UsedAs) {
 		return nil
 	}
 
 	// Redis is not self imported and the function call does not match the redis or redis cluster function call from the import
-	if !redisImported && (!query.NodeContentEquals(funcCall, redisFunction) && (!query.NodeContentEquals(funcCall, clusterRedisFunction))) {
+	if !redisImported && (!query.NodeContentIn(funcCall, redisFunctions) && (!query.NodeContentIn(funcCall, clusterRedisFunctions))) {
 		return nil
 	}
 
@@ -710,7 +691,7 @@ func (p *persister) queryRedis(file *core.SourceFile, annotation *core.Annotatio
 	var construct core.Construct
 
 	construct = &core.RedisNode{}
-	if funcCall.Content() == clusterRedisFunction {
+	if _, isClusterRedisFunc := clusterRedisFunctions[funcCall.Content()]; isClusterRedisFunc {
 		construct = &core.RedisCluster{}
 	}
 
diff --git a/pkg/lang/python/queries/find_imports.scm b/pkg/lang/python/queries/find_imports.scm
index 291de7e8..1f4c44ce 100644
--- a/pkg/lang/python/queries/find_imports.scm
+++ b/pkg/lang/python/queries/find_imports.scm
@@ -1,22 +1,22 @@
 [
-  (import_statement [
-      name: (dotted_name) @module
-      name: (aliased_import
+  (import_statement
+    name: [
+      (dotted_name) @module
+      (aliased_import
         name: (dotted_name) @aliasedModule
         alias: (identifier) @alias)
     ]
   ) @standardImport
   (import_from_statement
     . module_name: [
-                     (dotted_name) @module
-                     (relative_import . (import_prefix) @importPrefix (dotted_name) ? @module)
-                     ]
-    [
-      name: (dotted_name) @attribute
-      name: (aliased_import
-              name: (dotted_name) @attribute
-              alias: (identifier) @alias)
-
-      ]
-    ) @fromImport
-  ] @importStatement
+      (dotted_name) @module
+      (relative_import . (import_prefix) @importPrefix (dotted_name) ? @module)
+    ]
+    name: [
+      (dotted_name) @attribute
+      (aliased_import
+        name: (dotted_name) @attribute
+        alias: (identifier) @alias)
+    ]
+  ) @fromImport
+] @importStatement
diff --git a/pkg/provider/aws/execution_unit.go b/pkg/provider/aws/execution_unit.go
index 4656efe9..155e9f74 100644
--- a/pkg/provider/aws/execution_unit.go
+++ b/pkg/provider/aws/execution_unit.go
@@ -12,6 +12,52 @@ import (
 	"go.uber.org/zap"
 )
 
+// expandExecutionUnit takes in a single execution unit and expands the generic construct into a set of resource's based on the units configuration.
+func (a *AWS) expandExecutionUnit(dag *core.ResourceGraph, unit *core.ExecutionUnit) error {
+	switch a.Config.GetExecutionUnit(unit.ID).Type {
+	case Lambda:
+		lambda, err := core.CreateResource[*resources.LambdaFunction](dag, resources.LambdaCreateParams{
+			AppName: a.Config.AppName,
+			Refs:    []core.AnnotationKey{unit.AnnotationKey},
+			Name:    unit.ID,
+		})
+		if err != nil {
+			return err
+		}
+		err = a.MapResourceToConstruct(lambda, unit)
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("unsupported execution unit type %s", a.Config.GetExecutionUnit(unit.ID).Type)
+	}
+	return nil
+}
+
+func (a *AWS) getLambdaConfiguration(result *core.ConstructGraph, dag *core.ResourceGraph, refs []core.AnnotationKey) (resources.LambdaFunctionConfigureParams, error) {
+	if len(refs) != 1 {
+		return resources.LambdaFunctionConfigureParams{}, fmt.Errorf("lambda must only have one construct reference")
+	}
+	lambdaConfig := resources.LambdaFunctionConfigureParams{}
+	construct := result.GetConstruct(refs[0].ToId())
+	if construct == nil {
+		return resources.LambdaFunctionConfigureParams{}, fmt.Errorf("construct with id %s does not exist", refs[0].ToId())
+	}
+	unit, ok := construct.(*core.ExecutionUnit)
+	if !ok {
+		return resources.LambdaFunctionConfigureParams{}, fmt.Errorf("lambda must only have a construct reference to an execution unit")
+	}
+	for _, env := range unit.EnvironmentVariables {
+		if env.Construct == nil {
+			lambdaConfig.EnvironmentVariables = append(lambdaConfig.EnvironmentVariables, env)
+		}
+	}
+	cfg := config.ConvertFromInfraParams[config.ServerlessTypeParams](a.Config.GetExecutionUnit(refs[0].ID).InfraParams)
+	lambdaConfig.MemorySize = cfg.Memory
+	lambdaConfig.Timeout = cfg.Timeout
+	return lambdaConfig, nil
+}
+
 // GenerateExecUnitResources generates the necessary AWS resources for a given execution unit and adds them to the resource graph
 func (a *AWS) GenerateExecUnitResources(unit *core.ExecutionUnit, result *core.ConstructGraph, dag *core.ResourceGraph) error {
 	log := zap.S()
diff --git a/pkg/provider/aws/knowledgebase/edges.go b/pkg/provider/aws/knowledgebase/edges.go
new file mode 100644
index 00000000..5bc2a32c
--- /dev/null
+++ b/pkg/provider/aws/knowledgebase/edges.go
@@ -0,0 +1,245 @@
+package knowledgebase
+
+import (
+	"fmt"
+	"reflect"
+
+	"github.com/klothoplatform/klotho/pkg/core"
+	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base"
+	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
+)
+
+var AwsKB = knowledgebase.EdgeKB{
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.LambdaFunction{}), Destination: reflect.TypeOf(&resources.RdsInstance{})}: knowledgebase.EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			lambda := source.(*resources.LambdaFunction)
+			instance := dest.(*resources.RdsInstance)
+			if len(lambda.Subnets) == 0 {
+				lambda.Subnets = instance.SubnetGroup.Subnets
+			}
+			if len(lambda.SecurityGroups) == 0 {
+				lambda.SecurityGroups = instance.SecurityGroups
+			}
+			return nil
+		},
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			lambda := source.(*resources.LambdaFunction)
+			instance := dest.(*resources.RdsInstance)
+			if len(lambda.Subnets) == 0 {
+				return fmt.Errorf("unable Destination expand edge [%s -> %s]: lambda function [%s] is not in a VPC",
+					lambda.Id().String(), instance.Id().String(), lambda.Id().String())
+			}
+			inlinePol := resources.NewIamInlinePolicy(fmt.Sprintf("%s-connectionpolicy", instance.Name), core.DedupeAnnotationKeys(append(lambda.ConstructsRef, instance.ConstructsRef...)), instance.GetConnectionPolicyDocument())
+			lambda.Role.InlinePolicies = append(lambda.Role.InlinePolicies, inlinePol)
+
+			for _, env := range data.EnvironmentVariables {
+				lambda.EnvironmentVariables[env.GetName()] = core.IaCValue{Resource: instance, Property: env.GetValue()}
+			}
+			return nil
+		},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.LambdaFunction{}), Destination: reflect.TypeOf(&resources.RdsProxy{})}: knowledgebase.EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			lambda := source.(*resources.LambdaFunction)
+			proxy := dest.(*resources.RdsProxy)
+			if len(lambda.Subnets) == 0 {
+				lambda.Subnets = proxy.Subnets
+			}
+			if len(lambda.SecurityGroups) == 0 {
+				lambda.SecurityGroups = proxy.SecurityGroups
+			}
+			return nil
+		},
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			lambda := source.(*resources.LambdaFunction)
+			proxy := dest.(*resources.RdsProxy)
+			if len(lambda.Subnets) == 0 {
+				return fmt.Errorf("unable Destination expand edge [%s -> %s]: lambda function [%s] is not in a VPC",
+					lambda.Id().String(), proxy.Id().String(), lambda.Id().String())
+			}
+
+			upstreamResources := dag.GetUpstreamResources(proxy)
+			for _, res := range upstreamResources {
+				if tg, ok := res.(*resources.RdsProxyTargetGroup); ok {
+					for _, tgUpstream := range dag.GetDownstreamResources(tg) {
+						if instance, ok := tgUpstream.(*resources.RdsInstance); ok {
+							inlinePol := resources.NewIamInlinePolicy(fmt.Sprintf("%s-connectionpolicy", instance.Name),
+								core.DedupeAnnotationKeys(append(lambda.ConstructsRef, instance.ConstructsRef...)), instance.GetConnectionPolicyDocument())
+							lambda.Role.InlinePolicies = append(lambda.Role.InlinePolicies, inlinePol)
+						}
+					}
+				}
+			}
+
+			for _, env := range data.EnvironmentVariables {
+				lambda.EnvironmentVariables[env.GetName()] = core.IaCValue{Resource: proxy, Property: env.GetValue()}
+			}
+			return nil
+		},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.RdsProxyTargetGroup{}), Destination: reflect.TypeOf(&resources.RdsInstance{})}: knowledgebase.EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			instance := dest.(*resources.RdsInstance)
+			targetGroup := source.(*resources.RdsProxyTargetGroup)
+			if targetGroup.Name == "" {
+				proxyTargetGroup, err := core.CreateResource[*resources.RdsProxyTargetGroup](dag, resources.RdsProxyTargetGroupCreateParams{
+					AppName: data.AppName,
+					Refs:    instance.ConstructsRef,
+					Name:    instance.Name,
+				})
+				if err != nil {
+					return err
+				}
+				dag.AddDependency(proxyTargetGroup, instance)
+			}
+			return nil
+		},
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			instance := dest.(*resources.RdsInstance)
+			targetGroup := source.(*resources.RdsProxyTargetGroup)
+
+			if targetGroup.RdsInstance == nil {
+				targetGroup.RdsInstance = instance
+			} else if targetGroup.RdsInstance != instance {
+				return fmt.Errorf("target group, %s, has edge Destination instance, %s, but internal property is set Destination a different instance", targetGroup.Name, instance.Name)
+			}
+
+			secret := targetGroup.RdsProxy.Auths[0].SecretArn.Resource.(*resources.Secret)
+			for _, res := range dag.GetDownstreamResources(secret) {
+				if secretVersion, ok := res.(*resources.SecretVersion); ok {
+					secretVersion.Path = instance.CredentialsFile.Path()
+				}
+			}
+			return nil
+		},
+		ValidDestinations: []reflect.Type{reflect.TypeOf(&resources.RdsInstance{})},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.RdsProxyTargetGroup{}), Destination: reflect.TypeOf(&resources.RdsProxy{})}: knowledgebase.EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			proxy := dest.(*resources.RdsProxy)
+			targetGroup := source.(*resources.RdsProxyTargetGroup)
+			destination := data.Destination.(*resources.RdsInstance)
+			if proxy.Name == "" {
+				var err error
+				proxy, err = core.CreateResource[*resources.RdsProxy](dag, resources.RdsProxyCreateParams{
+					AppName: data.AppName,
+					Refs:    targetGroup.ConstructsRef,
+					Name:    destination.Name,
+				})
+				if err != nil {
+					return err
+				}
+			}
+
+			if targetGroup.Name == "" {
+				proxyTargetGroup, err := core.CreateResource[*resources.RdsProxyTargetGroup](dag, resources.RdsProxyTargetGroupCreateParams{
+					AppName: data.AppName,
+					Refs:    proxy.ConstructsRef,
+					Name:    destination.Name,
+				})
+				if err != nil {
+					return err
+				}
+				dag.AddDependency(proxyTargetGroup, proxy)
+			}
+			secretVersion, err := core.CreateResource[*resources.SecretVersion](dag, resources.SecretVersionCreateParams{
+				AppName: data.AppName,
+				Refs:    core.DedupeAnnotationKeys(append(source.KlothoConstructRef(), dest.KlothoConstructRef()...)),
+				Name:    proxy.Name,
+			})
+			if err != nil {
+				return err
+			}
+			proxy.Auths = append(proxy.Auths, &resources.ProxyAuth{
+				AuthScheme: "SECRETS",
+				IamAuth:    "DISABLED",
+				SecretArn:  core.IaCValue{Resource: secretVersion.Secret, Property: resources.ARN_IAC_VALUE},
+			})
+			dag.AddDependency(proxy, secretVersion)
+
+			secretPolicy, err := core.CreateResource[*resources.IamPolicy](dag, resources.IamPolicyCreateParams{
+				AppName: data.AppName,
+				Name:    fmt.Sprintf("%s-ormsecretpolicy", proxy.Name),
+				Refs:    proxy.ConstructsRef,
+			})
+			if err != nil {
+				return err
+			}
+			dag.AddDependency(secretPolicy, secretVersion)
+			dag.AddDependency(proxy.Role, secretPolicy)
+			dag.AddDependenciesReflect(proxy)
+			return nil
+		},
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			proxy := dest.(*resources.RdsProxy)
+			targetGroup := source.(*resources.RdsProxyTargetGroup)
+
+			if targetGroup.RdsProxy == nil {
+				targetGroup.RdsProxy = proxy
+			} else if targetGroup.RdsProxy != proxy {
+				return fmt.Errorf("target group, %s, has edge Destination proxy, %s, but internal property is set Destination a different proxy", targetGroup.Name, proxy.Name)
+			}
+			return nil
+		},
+		ValidDestinations: []reflect.Type{reflect.TypeOf(&resources.RdsInstance{})},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.RdsProxy{}), Destination: reflect.TypeOf(&resources.SecretVersion{})}: knowledgebase.EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			proxy := dest.(*resources.RdsProxy)
+			secretVersion, err := core.CreateResource[*resources.SecretVersion](dag, resources.SecretVersionCreateParams{
+				AppName: data.AppName,
+				Refs:    core.DedupeAnnotationKeys(append(source.KlothoConstructRef(), dest.KlothoConstructRef()...)),
+				Name:    proxy.Name,
+			})
+			if err != nil {
+				return err
+			}
+			dag.AddDependency(proxy, secretVersion)
+
+			secretPolicy, err := core.CreateResource[*resources.IamPolicy](dag, resources.IamPolicyCreateParams{
+				AppName: data.AppName,
+				Name:    fmt.Sprintf("%s-ormsecretpolicy", proxy.Name),
+				Refs:    proxy.ConstructsRef,
+			})
+			if err != nil {
+				return err
+			}
+			dag.AddDependency(secretPolicy, secretVersion)
+			dag.AddDependency(proxy.Role, secretPolicy)
+			return nil
+		},
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			secretVersion := dest.(*resources.SecretVersion)
+			secretVersion.Type = "string"
+			return nil
+		},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.IamPolicy{}), Destination: reflect.TypeOf(&resources.SecretVersion{})}: knowledgebase.EdgeDetails{
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			policy := source.(*resources.IamPolicy)
+			secretVersion := dest.(*resources.SecretVersion)
+			secretPolicyDoc := resources.CreateAllowPolicyDocument([]string{"secretsmanager:GetSecretValue"}, []core.IaCValue{{Resource: secretVersion.Secret, Property: resources.ARN_IAC_VALUE}})
+			if policy.Policy == nil {
+				policy.Policy = secretPolicyDoc
+			} else {
+				policy.Policy.Statement = append(policy.Policy.Statement, secretPolicyDoc.Statement...)
+			}
+			return nil
+		},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.IamRole{}), Destination: reflect.TypeOf(&resources.IamPolicy{})}: knowledgebase.EdgeDetails{
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			policy := dest.(*resources.IamPolicy)
+			role := source.(*resources.IamRole)
+			role.ManagedPolicies = append(role.ManagedPolicies, core.IaCValue{Resource: policy, Property: resources.ARN_IAC_VALUE})
+			return nil
+		},
+	},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.RdsInstance{}), Destination: reflect.TypeOf(&resources.RdsSubnetGroup{})}: knowledgebase.EdgeDetails{},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.RdsInstance{}), Destination: reflect.TypeOf(&resources.SecurityGroup{})}:  knowledgebase.EdgeDetails{},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.LambdaFunction{}), Destination: reflect.TypeOf(&resources.IamRole{})}:     knowledgebase.EdgeDetails{},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.LambdaFunction{}), Destination: reflect.TypeOf(&resources.EcrImage{})}:    knowledgebase.EdgeDetails{},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.EcrImage{}), Destination: reflect.TypeOf(&resources.EcrRepository{})}:     knowledgebase.EdgeDetails{},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.SecurityGroup{}), Destination: reflect.TypeOf(&resources.Vpc{})}:          knowledgebase.EdgeDetails{},
+	knowledgebase.Edge{Source: reflect.TypeOf(&resources.Subnet{}), Destination: reflect.TypeOf(&resources.Vpc{})}:                 knowledgebase.EdgeDetails{},
+}
diff --git a/pkg/provider/aws/kv.go b/pkg/provider/aws/kv.go
index 75a4585a..9a70a093 100644
--- a/pkg/provider/aws/kv.go
+++ b/pkg/provider/aws/kv.go
@@ -46,7 +46,7 @@ func (a *AWS) GenerateKvResources(kv *core.Kv, result *core.ConstructGraph, dag
 				{Resource: table, Property: resources.DYNAMODB_TABLE_STREAM_IAC_VALUE},
 			}
 			policyDoc := resources.CreateAllowPolicyDocument(actions, policyResources)
-			policy := resources.NewIamInlinePolicy(fmt.Sprintf("%s-kv-dynamodb", unit.ID), kv.Provenance(), policyDoc)
+			policy := resources.NewIamInlinePolicy(fmt.Sprintf("%s-kv-dynamodb", unit.ID), []core.AnnotationKey{kv.Provenance()}, policyDoc)
 			a.PolicyGenerator.AddInlinePolicyToUnit(unit.Id(), policy)
 		}
 	}
diff --git a/pkg/provider/aws/orm.go b/pkg/provider/aws/orm.go
index c78b5147..eaa121bc 100644
--- a/pkg/provider/aws/orm.go
+++ b/pkg/provider/aws/orm.go
@@ -7,6 +7,28 @@ import (
 	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
 )
 
+// expandOrm takes in a single orm construct and expands the generic construct into a set of resource's based on the units configuration.
+func (a *AWS) expandOrm(dag *core.ResourceGraph, orm *core.Orm) error {
+	switch a.Config.GetPersistOrm(orm.ID).Type {
+	case Rds_postgres:
+		instance, err := core.CreateResource[*resources.RdsInstance](dag, resources.RdsInstanceCreateParams{
+			AppName: a.Config.AppName,
+			Refs:    []core.AnnotationKey{orm.AnnotationKey},
+			Name:    orm.ID,
+		})
+		if err != nil {
+			return err
+		}
+		err = a.MapResourceToConstruct(instance, orm)
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("unsupported orm type %s", a.Config.GetPersistOrm(orm.ID).Type)
+	}
+	return nil
+}
+
 func (a *AWS) GenerateOrmResources(construct *core.Orm, result *core.ConstructGraph, dag *core.ResourceGraph) error {
 	vpc := resources.GetVpc(a.Config, dag)
 	securityGroups := []*resources.SecurityGroup{resources.GetSecurityGroup(a.Config, dag)}
@@ -25,7 +47,7 @@ func (a *AWS) GenerateOrmResources(construct *core.Orm, result *core.ConstructGr
 		unit, ok := res.(*core.ExecutionUnit)
 		if ok {
 			a.PolicyGenerator.AddInlinePolicyToUnit(unit.Id(),
-				resources.NewIamInlinePolicy(fmt.Sprintf("%s-connectionpolicy", instance.Name), unit.Provenance(), policyDoc))
+				resources.NewIamInlinePolicy(fmt.Sprintf("%s-connectionpolicy", instance.Name), []core.AnnotationKey{unit.Provenance()}, policyDoc))
 		}
 	}
 	return nil
diff --git a/pkg/provider/aws/persist.go b/pkg/provider/aws/persist.go
index 8ae1b6b4..795b43bc 100644
--- a/pkg/provider/aws/persist.go
+++ b/pkg/provider/aws/persist.go
@@ -17,7 +17,7 @@ func (a *AWS) GenerateFsResources(construct core.Construct, result *core.Constru
 		{Resource: bucket, Property: resources.ALL_BUCKET_DIRECTORY_IAC_VALUE},
 	}
 	policyDoc := resources.CreateAllowPolicyDocument(actions, policyResources)
-	policy := resources.NewIamInlinePolicy(fmt.Sprintf("%s-s3", construct.Id()), construct.Provenance(), policyDoc)
+	policy := resources.NewIamInlinePolicy(fmt.Sprintf("%s-s3", construct.Id()), []core.AnnotationKey{construct.Provenance()}, policyDoc)
 	upstreamResources := result.GetUpstreamConstructs(construct)
 	for _, res := range upstreamResources {
 		unit, ok := res.(*core.ExecutionUnit)
diff --git a/pkg/provider/aws/plugin.go b/pkg/provider/aws/plugin.go
index 9d3b1cab..b81304b5 100644
--- a/pkg/provider/aws/plugin.go
+++ b/pkg/provider/aws/plugin.go
@@ -5,12 +5,81 @@ import (
 
 	"github.com/klothoplatform/klotho/pkg/core"
 	"github.com/klothoplatform/klotho/pkg/infra/kubernetes"
+	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base"
 	"github.com/klothoplatform/klotho/pkg/multierr"
 	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
 	"github.com/pkg/errors"
 	"go.uber.org/zap"
 )
 
+// ExpandConstructs looks at all existing constructs in the construct graph and turns them into their respective AWS Resources
+func (a *AWS) ExpandConstructs(result *core.ConstructGraph, dag *core.ResourceGraph) (err error) {
+	log := zap.S()
+	var merr multierr.Error
+	for _, construct := range result.ListConstructs() {
+		log.Debugf("Converting construct with id, %s, to aws resources", construct.Id())
+		switch construct := construct.(type) {
+		case *core.ExecutionUnit:
+			merr.Append(a.expandExecutionUnit(dag, construct))
+		case *core.Orm:
+			merr.Append(a.expandOrm(dag, construct))
+		}
+	}
+	return merr.ErrOrNil()
+}
+
+// CopyConstructEdgesToDag looks at the dependencies which existed in the construct graph and copies those dependencies into the resource graph so that the edges can be later expanded on
+func (a *AWS) CopyConstructEdgesToDag(result *core.ConstructGraph, dag *core.ResourceGraph) (err error) {
+	var merr multierr.Error
+	for _, dep := range result.ListDependencies() {
+		sourceResource := a.GetResourceTiedToConstruct(dep.Source)
+		if sourceResource == nil {
+			merr.Append(errors.Errorf("unable to copy edge, no resource tied to construct %s", dep.Source.Id()))
+			continue
+		}
+		targetResource := a.GetResourceTiedToConstruct(dep.Destination)
+		if targetResource == nil {
+			merr.Append(errors.Errorf("unable to copy edge, no resource tied to construct %s", dep.Destination.Id()))
+			continue
+		}
+
+		data := knowledgebase.EdgeData{AppName: a.Config.AppName, Source: sourceResource, Destination: targetResource}
+		if unit, ok := dep.Source.(*core.ExecutionUnit); ok {
+			if _, ok := dep.Destination.(*core.Orm); ok {
+				data.Constraint = knowledgebase.EdgeConstraint{
+					NodeMustExist:    []core.Resource{&resources.RdsProxy{}},
+					NodeMustNotExist: []core.Resource{&resources.IamRole{}},
+				}
+			}
+			for _, envVar := range unit.EnvironmentVariables {
+				if envVar.Construct == dep.Destination {
+					data.EnvironmentVariables = []core.EnvironmentVariable{envVar}
+				}
+			}
+		}
+		dag.AddDependencyWithData(sourceResource, targetResource, data)
+	}
+	return merr.ErrOrNil()
+}
+
+// configureResources calls every resource's Configure method, for resources that exist in the graph
+func (a *AWS) configureResources(result *core.ConstructGraph, dag *core.ResourceGraph) (err error) {
+	var merr multierr.Error
+	for _, resource := range dag.ListResources() {
+		var configuration any
+		switch res := resource.(type) {
+		case *resources.LambdaFunction:
+			configuration, err = a.getLambdaConfiguration(result, dag, res.ConstructsRef)
+			if err != nil {
+				merr.Append(err)
+				continue
+			}
+		}
+		merr.Append(dag.CallConfigure(resource, configuration))
+	}
+	return merr.ErrOrNil()
+}
+
 func (a *AWS) Translate(result *core.ConstructGraph, dag *core.ResourceGraph) (links []core.CloudResourceLink, err error) {
 	log := zap.S()
 
diff --git a/pkg/provider/aws/provider.go b/pkg/provider/aws/provider.go
index af06c9e5..7f957bac 100644
--- a/pkg/provider/aws/provider.go
+++ b/pkg/provider/aws/provider.go
@@ -11,6 +11,7 @@ import (
 type AWS struct {
 	Config                 *config.Application
 	constructIdToResources map[string][]core.Resource
+	constructIdToResource  map[string]core.Resource
 	PolicyGenerator        *resources.PolicyGenerator
 }
 
@@ -47,3 +48,16 @@ func (a *AWS) GetResourcesDirectlyTiedToConstruct(construct core.Construct) ([]c
 	resources, found := a.constructIdToResources[construct.Id()]
 	return resources, found
 }
+
+func (a *AWS) MapResourceToConstruct(resource core.Resource, construct core.Construct) error {
+	if a.constructIdToResource == nil {
+		a.constructIdToResource = make(map[string]core.Resource)
+	}
+	a.constructIdToResource[construct.Id()] = resource
+	return nil
+}
+
+func (a *AWS) GetResourceTiedToConstruct(construct core.Construct) core.Resource {
+	resource := a.constructIdToResource[construct.Id()]
+	return resource
+}
diff --git a/pkg/provider/aws/resources/cloudwatch.go b/pkg/provider/aws/resources/cloudwatch.go
index f4f9df0a..1b3694c9 100644
--- a/pkg/provider/aws/resources/cloudwatch.go
+++ b/pkg/provider/aws/resources/cloudwatch.go
@@ -20,6 +20,26 @@ type (
 	}
 )
 
+type CloudwatchLogGroupCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	Name    string
+}
+
+func (logGroup *LogGroup) Create(dag *core.ResourceGraph, params CloudwatchLogGroupCreateParams) error {
+	logGroup.Name = logGroupSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	logGroup.ConstructsRef = params.Refs
+
+	existingLogGroup := dag.GetResource(logGroup.Id())
+	if existingLogGroup != nil {
+		graphLogGroup := existingLogGroup.(*LogGroup)
+		graphLogGroup.ConstructsRef = core.DedupeAnnotationKeys(append(graphLogGroup.KlothoConstructRef(), params.Refs...))
+	} else {
+		dag.AddResource(logGroup)
+	}
+	return nil
+}
+
 func NewLogGroup(appName string, logGroupName string, ref core.AnnotationKey, retention int) *LogGroup {
 	return &LogGroup{
 		Name:            logGroupSanitizer.Apply(fmt.Sprintf("%s-%s", appName, logGroupName)),
diff --git a/pkg/provider/aws/resources/ecr.go b/pkg/provider/aws/resources/ecr.go
index 6e872eae..dc5500d7 100644
--- a/pkg/provider/aws/resources/ecr.go
+++ b/pkg/provider/aws/resources/ecr.go
@@ -30,6 +30,69 @@ type (
 	}
 )
 
+type RepoCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+}
+
+func (repo *EcrRepository) Create(dag *core.ResourceGraph, params RepoCreateParams) error {
+	repo.Name = params.AppName
+	repo.ConstructsRef = params.Refs
+
+	existingRepo := dag.GetResource(repo.Id())
+	if existingRepo != nil {
+		graphRepo := existingRepo.(*EcrRepository)
+		graphRepo.ConstructsRef = core.DedupeAnnotationKeys(append(graphRepo.KlothoConstructRef(), params.Refs...))
+	} else {
+		dag.AddResource(repo)
+	}
+	return nil
+}
+
+type EcrRepositoryConfigureParams struct {
+	ForceDelete bool
+}
+
+func (repo *EcrRepository) Configure(params EcrRepositoryConfigureParams) error {
+	repo.ForceDelete = true
+	return nil
+}
+
+type ImageCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	Name    string
+}
+
+func (image *EcrImage) Create(dag *core.ResourceGraph, params ImageCreateParams) error {
+	name := fmt.Sprintf("%s-%s", params.AppName, params.Name)
+	image.Name = name
+	image.ConstructsRef = params.Refs
+
+	existingImage := dag.GetResource(image.Id())
+	if existingImage != nil {
+		return fmt.Errorf("ecr image with name %s already exists", name)
+	}
+
+	err := dag.CreateDependencies(image, map[string]any{
+		"Repo": RepoCreateParams{
+			AppName: params.AppName,
+			Refs:    params.Refs,
+		},
+	})
+	return err
+}
+
+type EcrImageConfigureParams struct {
+	ExtraOptions []string
+}
+
+// Configure sets the intristic characteristics of a vpc based on parameters passed in
+func (image *EcrImage) Configure(params EcrImageConfigureParams) error {
+	image.ExtraOptions = []string{"--platform", "linux/amd64", "--quiet"}
+	return nil
+}
+
 func GenerateEcrRepoAndImage(appName string, unit *core.ExecutionUnit, dag *core.ResourceGraph) (*EcrImage, error) {
 	// See if we have already created an ecr repository for the app and if not create one, otherwise add a ref to this exec unit
 	var repo *EcrRepository
diff --git a/pkg/provider/aws/resources/eks.go b/pkg/provider/aws/resources/eks.go
index 3f5f58de..a3630f40 100644
--- a/pkg/provider/aws/resources/eks.go
+++ b/pkg/provider/aws/resources/eks.go
@@ -586,6 +586,11 @@ func (cluster *EksCluster) InstallAlbController(references []core.AnnotationKey,
 			"podLabels": map[string]string{
 				"app": "aws-lb-controller",
 			},
+			// objectSelector is used to select pods to inject the pod readiness gate into
+			// (see https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.4/deploy/pod_readiness_gate/)
+			"objectSelector": map[string]any{"matchLabels": map[string]any{"elbv2.k8s.aws/pod-readiness-gate-inject": "enabled"}},
+			// webhookNamespaceSelector is set to an empty matchExpressions to allow the pod readiness gate to be installed in any namespace
+			"webhookNamespaceSelectors": map[string]any{"matchExpressions": []any{}},
 		},
 	}
 	dag.AddDependenciesReflect(albChart)
@@ -748,7 +753,7 @@ func createClusterAdminRole(appName string, roleName string, refs []core.Annotat
 func createPodExecutionRole(appName string, roleName string, refs []core.AnnotationKey) *IamRole {
 	fargateRole := NewIamRole(appName, roleName, refs, EKS_FARGATE_ASSUME_ROLE_POLICY)
 	fargateRole.InlinePolicies = []*IamInlinePolicy{
-		NewIamInlinePolicy("fargate-pod-execution-policy", refs[0],
+		NewIamInlinePolicy("fargate-pod-execution-policy", refs,
 			&PolicyDocument{Version: VERSION, Statement: []StatementEntry{
 				{
 					Effect: "Allow",
diff --git a/pkg/provider/aws/resources/iam.go b/pkg/provider/aws/resources/iam.go
index 3ebbbf2a..ab085d04 100644
--- a/pkg/provider/aws/resources/iam.go
+++ b/pkg/provider/aws/resources/iam.go
@@ -147,6 +147,43 @@ type (
 	}
 )
 
+type RoleCreateParams struct {
+	AppName string
+	Name    string
+	Refs    []core.AnnotationKey
+}
+
+// Create takes in an all necessary parameters to generate the Roles name and ensure that the Role is correlated to the constructs which required its creation.
+func (role *IamRole) Create(dag *core.ResourceGraph, params RoleCreateParams) error {
+	role.Name = roleSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	role.ConstructsRef = params.Refs
+
+	existingRole := dag.GetResource(role.Id())
+	if existingRole != nil {
+		return fmt.Errorf("iam role with name %s already exists", role.Name)
+	}
+
+	dag.AddResource(role)
+	return nil
+}
+
+type IamPolicyCreateParams struct {
+	AppName string
+	Name    string
+	Refs    []core.AnnotationKey
+}
+
+// Create takes in an all necessary parameters to generate the IamPolicy name and ensure that the IamPolicy is correlated to the constructs which required its creation.
+func (policy *IamPolicy) Create(dag *core.ResourceGraph, params IamPolicyCreateParams) error {
+	policy.Name = policySanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	policy.ConstructsRef = params.Refs
+	existingPolicy := dag.GetResource(policy.Id())
+	if existingPolicy != nil {
+		return fmt.Errorf("iam policy with name %s already exists", policy.Name)
+	}
+	dag.AddResource(policy)
+	return nil
+}
 func NewPolicyGenerator() *PolicyGenerator {
 	p := &PolicyGenerator{
 		unitsPolicies:       make(map[string][]*IamPolicy),
@@ -259,10 +296,10 @@ func NewIamPolicy(appName string, policyName string, ref core.AnnotationKey, pol
 	}
 }
 
-func NewIamInlinePolicy(policyName string, ref core.AnnotationKey, policy *PolicyDocument) *IamInlinePolicy {
+func NewIamInlinePolicy(policyName string, refs []core.AnnotationKey, policy *PolicyDocument) *IamInlinePolicy {
 	return &IamInlinePolicy{
 		Name:          policySanitizer.Apply(policyName),
-		ConstructsRef: []core.AnnotationKey{ref},
+		ConstructsRef: refs,
 		Policy:        policy,
 	}
 }
diff --git a/pkg/provider/aws/resources/lambda.go b/pkg/provider/aws/resources/lambda.go
index 1f094665..7ca936a7 100644
--- a/pkg/provider/aws/resources/lambda.go
+++ b/pkg/provider/aws/resources/lambda.go
@@ -39,6 +39,70 @@ type (
 	}
 )
 
+type LambdaCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	Name    string
+}
+
+func (lambda *LambdaFunction) Create(dag *core.ResourceGraph, params LambdaCreateParams) error {
+
+	name := lambdaFunctionSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	lambda.Name = name
+	lambda.ConstructsRef = params.Refs
+
+	existingLambda := dag.GetResourceByVertexId(lambda.Id().String())
+	if existingLambda != nil {
+		return fmt.Errorf("lambda with name %s already exists", name)
+	}
+
+	logGroup, err := core.CreateResource[*LogGroup](dag, params)
+	if err != nil {
+		return err
+	}
+	dag.AddDependency(lambda, logGroup)
+	subParams := map[string]any{
+		"Role": RoleCreateParams{
+			AppName: params.AppName,
+			Name:    fmt.Sprintf("%s-ExecutionRole", params.Name),
+			Refs:    lambda.ConstructsRef,
+		},
+		"Image": params,
+	}
+
+	err = dag.CreateDependencies(lambda, subParams)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+type LambdaFunctionConfigureParams struct {
+	Timeout              int
+	MemorySize           int
+	EnvironmentVariables core.EnvironmentVariables
+}
+
+func (lambda *LambdaFunction) Configure(params LambdaFunctionConfigureParams) error {
+	lambda.Timeout = 180
+	lambda.MemorySize = 512
+	if lambda.EnvironmentVariables == nil {
+		lambda.EnvironmentVariables = make(EnvironmentVariables)
+	}
+
+	if params.Timeout != 0 {
+		lambda.Timeout = params.Timeout
+	}
+	if params.MemorySize != 0 {
+		lambda.MemorySize = params.MemorySize
+	}
+	for _, env := range params.EnvironmentVariables {
+		lambda.EnvironmentVariables[env.GetName()] = core.IaCValue{Property: env.GetValue()}
+	}
+
+	return nil
+}
+
 func NewLambdaFunction(unit *core.ExecutionUnit, cfg *config.Application, role *IamRole, image *EcrImage) *LambdaFunction {
 	params := config.ConvertFromInfraParams[config.ServerlessTypeParams](cfg.GetExecutionUnit(unit.ID).InfraParams)
 	return &LambdaFunction{
diff --git a/pkg/provider/aws/resources/rds.go b/pkg/provider/aws/resources/rds.go
index b2be4a9c..fed25287 100644
--- a/pkg/provider/aws/resources/rds.go
+++ b/pkg/provider/aws/resources/rds.go
@@ -107,6 +107,210 @@ type (
 	}
 )
 
+type RdsInstanceCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	Name    string
+}
+
+func (instance *RdsInstance) Create(dag *core.ResourceGraph, params RdsInstanceCreateParams) error {
+
+	name := rdsInstanceSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	instance.Name = name
+	instance.ConstructsRef = params.Refs
+
+	existingInstance := dag.GetResource(instance.Id())
+	if existingInstance != nil {
+		return fmt.Errorf("RdsInstance with name %s already exists", name)
+	}
+
+	instance.SecurityGroups = make([]*SecurityGroup, 1)
+	subParams := map[string]any{
+		"SecurityGroups": []SecurityGroupCreateParams{
+			{
+				AppName: params.AppName,
+				Refs:    params.Refs,
+			},
+		},
+		"SubnetGroup": params,
+	}
+	err := dag.CreateDependencies(instance, subParams)
+	return err
+}
+
+type RdsInstanceConfigureParams struct {
+	DatabaseName      string
+	Username          string
+	Password          string
+	Engine            string
+	EngineVersion     string
+	InstanceClass     string
+	SkipFinalSnapshot bool
+	AllocatedStorage  int
+}
+
+func (instance *RdsInstance) Configure(params RdsInstanceConfigureParams) error {
+	instance.IamDatabaseAuthenticationEnabled = true
+	instance.SkipFinalSnapshot = true
+	instance.DatabaseName = params.DatabaseName
+	instance.Username = generateUsername()
+	instance.Password = generatePassword()
+
+	instance.Engine = "postgres"
+	instance.EngineVersion = "13.7"
+	instance.InstanceClass = "db.t4g.micro"
+	instance.AllocatedStorage = 20
+	credsBytes := []byte(fmt.Sprintf("{\n\"username\": \"%s\",\n\"password\": \"%s\"\n}", instance.Username, instance.Password))
+	credsPath := fmt.Sprintf("secrets/%s", instance.Name)
+	instance.CredentialsFile = &core.RawFile{
+		FPath:   credsPath,
+		Content: credsBytes,
+	}
+	return nil
+}
+
+type RdsSubnetGroupCreateParams struct {
+	AppName string
+	Name    string
+	Refs    []core.AnnotationKey
+}
+
+func (subnetGroup *RdsSubnetGroup) Create(dag *core.ResourceGraph, params RdsSubnetGroupCreateParams) error {
+	subnetGroup.Name = rdsSubnetSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	subnetGroup.ConstructsRef = params.Refs
+
+	existingSubnetGroup := dag.GetResource(subnetGroup.Id())
+	if existingSubnetGroup != nil {
+		graphSubnetGroup := existingSubnetGroup.(*RdsSubnetGroup)
+		graphSubnetGroup.ConstructsRef = core.DedupeAnnotationKeys(append(graphSubnetGroup.KlothoConstructRef(), params.Refs...))
+		return nil
+	} else {
+		subnetGroup.Subnets = make([]*Subnet, 2)
+		err := dag.CreateDependencies(subnetGroup, map[string]any{
+			"Subnets": []SubnetCreateParams{
+				{
+					AppName: params.AppName,
+					Refs:    params.Refs,
+					AZ:      "0",
+					Type:    PrivateSubnet,
+				},
+				{
+					AppName: params.AppName,
+					Refs:    params.Refs,
+					AZ:      "1",
+					Type:    PrivateSubnet,
+				},
+			},
+		})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type RdsProxyCreateParams struct {
+	AppName string
+	Name    string
+	Refs    []core.AnnotationKey
+}
+
+func (proxy *RdsProxy) Create(dag *core.ResourceGraph, params RdsProxyCreateParams) error {
+	proxy.Name = rdsSubnetSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	proxy.ConstructsRef = params.Refs
+
+	existingProxy := dag.GetResource(proxy.Id())
+	if existingProxy != nil {
+		graphProxy := existingProxy.(*RdsProxy)
+		graphProxy.ConstructsRef = core.DedupeAnnotationKeys(append(graphProxy.KlothoConstructRef(), params.Refs...))
+		return nil
+	} else {
+		proxy.Subnets = make([]*Subnet, 2)
+		proxy.SecurityGroups = make([]*SecurityGroup, 1)
+		err := dag.CreateDependencies(proxy, map[string]any{
+			"Role": RoleCreateParams{
+				AppName: params.AppName,
+				Name:    fmt.Sprintf("%s-ProxyRole", params.Name),
+				Refs:    proxy.ConstructsRef,
+			},
+			"SecurityGroups": []SecurityGroupCreateParams{
+				{
+					AppName: params.AppName,
+					Refs:    params.Refs,
+				},
+			},
+			"Subnets": []SubnetCreateParams{
+				{
+					AppName: params.AppName,
+					Refs:    params.Refs,
+					AZ:      "0",
+					Type:    PrivateSubnet,
+				},
+				{
+					AppName: params.AppName,
+					Refs:    params.Refs,
+					AZ:      "1",
+					Type:    PrivateSubnet,
+				},
+			},
+		})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type RdsProxyConfigureParams struct {
+	EngineFamily      string
+	DebugLogging      bool
+	IdleClientTimeout int
+	RequireTls        bool
+}
+
+func (proxy *RdsProxy) Configure(params RdsProxyConfigureParams) error {
+	proxy.DebugLogging = false
+	proxy.EngineFamily = "POSTGRESQL"
+	proxy.IdleClientTimeout = 1800
+	proxy.RequireTls = false
+	return nil
+}
+
+type RdsProxyTargetGroupCreateParams struct {
+	AppName string
+	Name    string
+	Refs    []core.AnnotationKey
+}
+
+func (tg *RdsProxyTargetGroup) Create(dag *core.ResourceGraph, params RdsProxyTargetGroupCreateParams) error {
+
+	tg.Name = rdsProxySanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	tg.ConstructsRef = params.Refs
+	existingTG := dag.GetResource(tg.Id())
+	if existingTG != nil {
+		graphTG := existingTG.(*RdsProxyTargetGroup)
+		graphTG.ConstructsRef = core.DedupeAnnotationKeys(append(graphTG.KlothoConstructRef(), params.Refs...))
+		return nil
+	} else {
+		dag.AddResource(tg)
+	}
+	return nil
+}
+
+type RdsProxyTargetGroupConfigureParams struct {
+	ConnectionPoolConfigurationInfo ConnectionPoolConfigurationInfo
+}
+
+// Configure sets the intristic characteristics of a vpc based on parameters passed in
+func (targetGroup *RdsProxyTargetGroup) Configure(params RdsProxyTargetGroupConfigureParams) error {
+	targetGroup.ConnectionPoolConfigurationInfo = &ConnectionPoolConfigurationInfo{
+		ConnectionBorrowTimeout:   120,
+		MaxConnectionsPercent:     100,
+		MaxIdleConnectionsPercent: 50,
+	}
+	return nil
+}
+
 // CreateRdsInstance takes in an orm construct and creates the necessary resources to support creating a functional RDS Orm implementation
 //
 // If proxy is enabled, a corresponding proxy, secret, and remaining resources will be created.
diff --git a/pkg/provider/aws/resources/region.go b/pkg/provider/aws/resources/region.go
index 125dc966..3c0c8be7 100644
--- a/pkg/provider/aws/resources/region.go
+++ b/pkg/provider/aws/resources/region.go
@@ -4,11 +4,6 @@ import (
 	"github.com/klothoplatform/klotho/pkg/core"
 )
 
-const REGION_TYPE = "region"
-const AVAILABILITY_ZONES_TYPE = "availability_zones"
-const ACCOUNT_ID_TYPE = "account_id"
-const ARN_IAC_VALUE = "arn"
-
 type (
 	Region struct {
 		Name          string
@@ -26,9 +21,19 @@ type (
 	}
 )
 
+const (
+	REGION_NAME             = "region"
+	AVAILABILITY_ZONES_NAME = "AvailabilityZones"
+	ACCOUNT_ID_NAME         = "AccountId"
+	REGION_TYPE             = "region"
+	AVAILABILITY_ZONES_TYPE = "availability_zones"
+	ACCOUNT_ID_TYPE         = "account_id"
+	ARN_IAC_VALUE           = "arn"
+)
+
 func NewRegion() *Region {
 	return &Region{
-		Name:          "region",
+		Name:          REGION_NAME,
 		ConstructsRef: []core.AnnotationKey{},
 	}
 }
@@ -43,13 +48,13 @@ func (region *Region) Id() core.ResourceId {
 	return core.ResourceId{
 		Provider: AWS_PROVIDER,
 		Type:     REGION_TYPE,
-		Name:     region.Name,
+		Name:     REGION_NAME,
 	}
 }
 
 func NewAvailabilityZones() *AvailabilityZones {
 	return &AvailabilityZones{
-		Name:          "AvailabilityZones",
+		Name:          AVAILABILITY_ZONES_NAME,
 		ConstructsRef: []core.AnnotationKey{},
 	}
 }
@@ -64,13 +69,13 @@ func (azs *AvailabilityZones) Id() core.ResourceId {
 	return core.ResourceId{
 		Provider: AWS_PROVIDER,
 		Type:     AVAILABILITY_ZONES_TYPE,
-		Name:     azs.Name,
+		Name:     AVAILABILITY_ZONES_NAME,
 	}
 }
 
 func NewAccountId() *AccountId {
 	return &AccountId{
-		Name:          "AccountId",
+		Name:          ACCOUNT_ID_NAME,
 		ConstructsRef: []core.AnnotationKey{},
 	}
 }
@@ -85,6 +90,6 @@ func (id *AccountId) Id() core.ResourceId {
 	return core.ResourceId{
 		Provider: AWS_PROVIDER,
 		Type:     ACCOUNT_ID_TYPE,
-		Name:     id.Name,
+		Name:     ACCOUNT_ID_NAME,
 	}
 }
diff --git a/pkg/provider/aws/resources/secret.go b/pkg/provider/aws/resources/secret.go
index 72021292..ffbac366 100644
--- a/pkg/provider/aws/resources/secret.go
+++ b/pkg/provider/aws/resources/secret.go
@@ -1,6 +1,8 @@
 package resources
 
 import (
+	"fmt"
+
 	"github.com/klothoplatform/klotho/pkg/core"
 	"github.com/klothoplatform/klotho/pkg/sanitization/aws"
 )
@@ -13,18 +15,74 @@ type (
 	}
 
 	SecretVersion struct {
-		SecretName            string
-		Secret                *Secret
-		Path                  string
-		ConstructsRef         []core.AnnotationKey
-		secretNameUnSanitized string
-		Type                  string
+		SecretName    string
+		Secret        *Secret
+		Path          string
+		ConstructsRef []core.AnnotationKey
+		Name          string
+		Type          string
 	}
 )
 
 const SECRET_TYPE = "secret"
 const SECRET_VERSION_TYPE = "secret_version"
 
+type SecretCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	Name    string
+}
+
+// Create takes in an all necessary parameters to generate the Secret name and ensure that the Secret is correlated to the constructs which required its creation.
+func (secret *Secret) Create(dag *core.ResourceGraph, params SecretCreateParams) error {
+	secret.Name = aws.SecretSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	secret.ConstructsRef = params.Refs
+	existingSecret := dag.GetResource(secret.Id())
+	if existingSecret != nil {
+		return fmt.Errorf("Secret with name %s already exists", secret.Name)
+	}
+	dag.AddResource(secret)
+	return nil
+}
+
+type SecretVersionCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	Name    string
+}
+
+// Create takes in an all necessary parameters to generate the SecretVersion name and ensure that the SecretVersion is correlated to the constructs which required its creation.
+//
+// This method will also create dependent resources which are necessary for functionality. Those resources are:
+//   - Secret
+func (sv *SecretVersion) Create(dag *core.ResourceGraph, params SecretVersionCreateParams) error {
+	sv.Name = aws.SecretSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+	sv.ConstructsRef = params.Refs
+	existingSecret := dag.GetResource(sv.Id())
+	if existingSecret != nil {
+		return fmt.Errorf("SecretVersion with name %s already exists", sv.Name)
+	}
+	err := dag.CreateDependencies(sv, map[string]any{
+		"Secret": params,
+	})
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+type SecretVersionConfigureParams struct {
+	Type string
+	Path string
+}
+
+// Configure sets the intristic characteristics of a vpc based on parameters passed in
+func (sv *SecretVersion) Configure(params SecretVersionConfigureParams) error {
+	sv.Type = params.Type
+	sv.Path = params.Path
+	return nil
+}
+
 func NewSecret(annot core.AnnotationKey, secretName string, appName string) *Secret {
 	plainName := appName
 	if secretName != "" {
@@ -39,11 +97,11 @@ func NewSecret(annot core.AnnotationKey, secretName string, appName string) *Sec
 
 func NewSecretVersion(secret *Secret, filePath string) *SecretVersion {
 	return &SecretVersion{
-		SecretName:            secret.SecretName,
-		Secret:                secret,
-		Path:                  filePath,
-		ConstructsRef:         secret.ConstructsRef,
-		secretNameUnSanitized: secret.Name,
+		SecretName:    secret.SecretName,
+		Secret:        secret,
+		Path:          filePath,
+		ConstructsRef: secret.ConstructsRef,
+		Name:          secret.Name,
 	}
 }
 
@@ -67,6 +125,6 @@ func (sv *SecretVersion) Id() core.ResourceId {
 	return core.ResourceId{
 		Provider: AWS_PROVIDER,
 		Type:     SECRET_VERSION_TYPE,
-		Name:     sv.secretNameUnSanitized,
+		Name:     sv.Name,
 	}
 }
diff --git a/pkg/provider/aws/resources/securitygroup.go b/pkg/provider/aws/resources/securitygroup.go
index 3a6a122a..d506b264 100644
--- a/pkg/provider/aws/resources/securitygroup.go
+++ b/pkg/provider/aws/resources/securitygroup.go
@@ -25,6 +25,30 @@ type (
 
 const SG_TYPE = "security_group"
 
+type SecurityGroupCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+}
+
+func (sg *SecurityGroup) Create(dag *core.ResourceGraph, params SecurityGroupCreateParams) error {
+
+	sg.Name = params.AppName
+	sg.ConstructsRef = params.Refs
+	err := dag.CreateDependencies(sg, map[string]any{
+		"Vpc": params,
+	})
+	if err != nil {
+		return err
+	}
+
+	existingSG := dag.GetResource(sg.Id())
+	if existingSG != nil {
+		graphSG := existingSG.(*SecurityGroup)
+		graphSG.ConstructsRef = core.DedupeAnnotationKeys(append(graphSG.ConstructsRef, params.Refs...))
+	}
+	return nil
+}
+
 // GetSecurityGroup returns the security group if one exists, otherwise creates one, then returns it
 func GetSecurityGroup(cfg *config.Application, dag *core.ResourceGraph) *SecurityGroup {
 	for _, r := range dag.ListResources() {
diff --git a/pkg/provider/aws/resources/vpc.go b/pkg/provider/aws/resources/vpc.go
index e894ca54..7d448b1c 100644
--- a/pkg/provider/aws/resources/vpc.go
+++ b/pkg/provider/aws/resources/vpc.go
@@ -88,6 +88,240 @@ type (
 	}
 )
 
+type VpcCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+}
+
+func (vpc *Vpc) Create(dag *core.ResourceGraph, params VpcCreateParams) error {
+
+	vpc.Name = aws.VpcSanitizer.Apply(params.AppName)
+	vpc.ConstructsRef = params.Refs
+
+	existingVpc := dag.GetResource(vpc.Id())
+	if existingVpc != nil {
+		graphVpc := existingVpc.(*Vpc)
+		graphVpc.ConstructsRef = append(graphVpc.KlothoConstructRef(), params.Refs...)
+	} else {
+		dag.AddResource(vpc)
+	}
+
+	return nil
+}
+
+type VpcConfigureParams struct {
+}
+
+func (vpc *Vpc) Configure(params VpcConfigureParams) error {
+	vpc.CidrBlock = "10.0.0.0/16"
+	vpc.EnableDnsSupport = true
+	vpc.EnableDnsHostnames = true
+	return nil
+}
+
+type EipCreateParams struct {
+	AppName string
+	IpName  string
+	Refs    []core.AnnotationKey
+}
+
+func (eip *ElasticIp) Create(dag *core.ResourceGraph, params EipCreateParams) error {
+	eip.Name = elasticIpSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.IpName))
+	eip.ConstructsRef = params.Refs
+	existingEip := dag.GetResourceByVertexId(eip.Id().String())
+	if existingEip != nil {
+		graphEip := existingEip.(*ElasticIp)
+		graphEip.ConstructsRef = append(graphEip.ConstructsRef, params.Refs...)
+	} else {
+		dag.AddResource(eip)
+	}
+	return nil
+}
+
+type IgwCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+}
+
+func (igw *InternetGateway) Create(dag *core.ResourceGraph, params IgwCreateParams) error {
+
+	igw.Name = igwSanitizer.Apply(fmt.Sprintf("%s-igw", params.AppName))
+	igw.ConstructsRef = params.Refs
+
+	existingIgw := dag.GetResourceByVertexId(igw.Id().String())
+
+	if existingIgw != nil {
+		graphIgw := existingIgw.(*InternetGateway)
+		graphIgw.ConstructsRef = append(graphIgw.ConstructsRef, params.Refs...)
+	} else {
+		err := dag.CreateDependencies(igw, map[string]any{
+			"Vpc": params,
+		})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type NatCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	AZ      string
+}
+
+func (nat *NatGateway) Create(dag *core.ResourceGraph, params NatCreateParams) error {
+
+	nat.Name = natGatewaySanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.AZ))
+	nat.ConstructsRef = params.Refs
+
+	existingNat := dag.GetResourceByVertexId(nat.Id().String())
+	if existingNat != nil {
+		graphNat := existingNat.(*NatGateway)
+		graphNat.ConstructsRef = append(graphNat.ConstructsRef, params.Refs...)
+	} else {
+		subResourceParams := map[string]any{
+			"Subnet": SubnetCreateParams{
+				AppName: params.AppName,
+				Refs:    params.Refs,
+				AZ:      params.AZ,
+				Type:    PublicSubnet,
+			},
+			"ElasticIp": EipCreateParams{
+				AppName: params.AppName,
+				Refs:    params.Refs,
+				IpName:  params.AZ,
+			},
+		}
+		err := dag.CreateDependencies(nat, subResourceParams)
+		return err
+	}
+	return nil
+}
+
+type SubnetCreateParams struct {
+	AppName string
+	Refs    []core.AnnotationKey
+	AZ      string
+	Type    string
+}
+
+func (subnet *Subnet) Create(dag *core.ResourceGraph, params SubnetCreateParams) error {
+	subnet.Name = subnetSanitizer.Apply(fmt.Sprintf("%s-%s%s", params.AppName, params.Type, params.AZ))
+	subnet.ConstructsRef = params.Refs
+	subnet.AvailabilityZone = core.IaCValue{Resource: NewAvailabilityZones(), Property: params.AZ}
+	subnet.Type = params.Type
+
+	routeTableParams := RouteTableCreateParams{
+		AppName: params.AppName,
+		Refs:    params.Refs,
+	}
+	if subnet.Type == PrivateSubnet {
+		routeTableParams.Name = fmt.Sprintf("%s%s", params.Type, params.AZ)
+	} else {
+		routeTableParams.Name = fmt.Sprintf(params.Type)
+	}
+	rt := &RouteTable{}
+	err := rt.Create(dag, routeTableParams)
+	if err != nil {
+		return err
+	}
+	if subnet.Type == PrivateSubnet {
+		nat := &NatGateway{}
+		natParams := NatCreateParams{
+			AppName: params.AppName,
+			Refs:    params.Refs,
+			AZ:      params.AZ,
+		}
+		err := nat.Create(dag, natParams)
+		if err != nil {
+			return err
+		}
+		dag.AddDependency(rt, nat)
+	} else if subnet.Type == PublicSubnet {
+		igw := &InternetGateway{}
+		igwParams := IgwCreateParams{
+			AppName: params.AppName,
+			Refs:    params.Refs,
+		}
+		err := igw.Create(dag, igwParams)
+		if err != nil {
+			return err
+		}
+		dag.AddDependency(rt, igw)
+	}
+
+	err = dag.CreateDependencies(subnet, map[string]any{
+		"Vpc": params,
+	})
+	if err != nil {
+		return err
+	}
+	dag.AddDependency(subnet, NewAvailabilityZones())
+	dag.AddDependency(rt, subnet)
+
+	// We must check to see if there is an existent subnet after calling create dependencies because the id of the subnet has a namespace based on the vpc
+	existingSubnet := dag.GetResourceByVertexId(subnet.Id().String())
+	if existingSubnet != nil {
+		graphSubnet := existingSubnet.(*Subnet)
+		graphSubnet.ConstructsRef = core.DedupeAnnotationKeys(append(graphSubnet.ConstructsRef, params.Refs...))
+	}
+	return nil
+}
+
+type SubnetConfigureParams struct {
+}
+
+func (subnet *Subnet) Configure(params SubnetConfigureParams) error {
+	if subnet.Type == PrivateSubnet {
+		if subnet.AvailabilityZone.Property == "0" {
+			subnet.CidrBlock = "10.0.0.0/18"
+		} else if subnet.AvailabilityZone.Property == "1" {
+			subnet.CidrBlock = "10.0.64.0/18"
+		}
+	} else if subnet.Type == PublicSubnet {
+		if subnet.AvailabilityZone.Property == "0" {
+			subnet.CidrBlock = "10.0.128.0/18"
+		} else if subnet.AvailabilityZone.Property == "1" {
+			subnet.CidrBlock = "10.0.192.0/18"
+
+		}
+		subnet.MapPublicIpOnLaunch = true
+	}
+	return nil
+}
+
+type RouteTableCreateParams struct {
+	AppName string
+	Name    string
+	Refs    []core.AnnotationKey
+}
+
+func (rt *RouteTable) Create(dag *core.ResourceGraph, params RouteTableCreateParams) error {
+
+	rt.Name = subnetSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
+
+	subParams := map[string]any{
+		"Vpc": VpcCreateParams{
+			AppName: params.AppName,
+			Refs:    params.Refs,
+		},
+	}
+	err := dag.CreateDependencies(rt, subParams)
+	if err != nil {
+		return err
+	}
+	dag.AddDependenciesReflect(rt)
+
+	// We must check to see if there is an existent route table after calling create dependencies because the id of the subnet can contain a namespace based on the vpc
+	existingRt := dag.GetResourceByVertexId(rt.Id().String())
+	if existingRt != nil {
+		graphRt := existingRt.(*RouteTable)
+		graphRt.ConstructsRef = core.DedupeAnnotationKeys(append(graphRt.ConstructsRef, params.Refs...))
+	}
+	return nil
+}
+
 // CreateNetwork takes in a config and uses the appName to create an aws network and inject it into the dag.
 //
 // The network consists of:
@@ -398,13 +632,10 @@ func (subnet *Subnet) KlothoConstructRef() []core.AnnotationKey {
 // Id returns the id of the cloud resource
 func (subnet *Subnet) Id() core.ResourceId {
 	id := core.ResourceId{
-		Provider: AWS_PROVIDER,
-		Type:     VPC_SUBNET_TYPE_PREFIX + subnet.Type,
-		Name:     subnet.Name,
-	}
-	if subnet.Vpc != nil {
-		// Realistically, this should only be the case for tests
-		id.Namespace = subnet.Vpc.Name
+		Provider:  AWS_PROVIDER,
+		Type:      VPC_SUBNET_TYPE_PREFIX + subnet.Type,
+		Namespace: subnet.Vpc.Name,
+		Name:      subnet.Name,
 	}
 	return id
 }
diff --git a/pkg/provider/aws/secret.go b/pkg/provider/aws/secret.go
index 3b56dfd0..fdcb1503 100644
--- a/pkg/provider/aws/secret.go
+++ b/pkg/provider/aws/secret.go
@@ -36,7 +36,7 @@ func (a *AWS) generateSecret(construct core.Construct, result *core.ConstructGra
 			Property: resources.ARN_IAC_VALUE,
 		}}
 		policyDoc := resources.CreateAllowPolicyDocument(actions, policyResources)
-		policy := resources.NewIamInlinePolicy(fmt.Sprintf("%s-secretsmanager", secret.Name), construct.Provenance(), policyDoc)
+		policy := resources.NewIamInlinePolicy(fmt.Sprintf("%s-secretsmanager", secret.Name), []core.AnnotationKey{construct.Provenance()}, policyDoc)
 		a.PolicyGenerator.AddInlinePolicyToUnit(unit.Id(), policy)
 	}
 	return nil
diff --git a/pkg/query/node.go b/pkg/query/node.go
index f7fe6bc5..f4687ad8 100644
--- a/pkg/query/node.go
+++ b/pkg/query/node.go
@@ -24,6 +24,12 @@ func NodeContentEquals(node *sitter.Node, s string) bool {
 	return false
 }
 
+// NodeContentIn returns whether the node's content is a key in the given set.
+func NodeContentIn(node *sitter.Node, m map[string]struct{}) bool {
+	_, keyExists := m[node.Content()]
+	return keyExists
+}
+
 func NodeContentRegex(node *sitter.Node, regex *regexp.Regexp) bool {
 	content := node.Content()
 	return regex.MatchString(content)


Test patch:
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 2cb5d4a0..94837ab4 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -7,6 +7,8 @@ concurrency:
   cancel-in-progress: true
   group: unit-tests-${{ github.ref }}
 name: unit tests
+env:
+  KLOTHO_VIZ_URL_BASE: https://viz-dev.klo.dev
 jobs:
   test:
     runs-on: ubuntu-latest
diff --git a/pkg/core/construct_graph_test.go b/pkg/core/construct_graph_test.go
index 29f267e3..77be27a8 100644
--- a/pkg/core/construct_graph_test.go
+++ b/pkg/core/construct_graph_test.go
@@ -144,7 +144,7 @@ func Test_GetDownstreamDependencies(t *testing.T) {
 			g.AddVertex(tt.construct)
 			for _, c := range tt.deps {
 				g.AddVertex(c)
-				g.AddEdge(tt.construct.Id(), c.Id())
+				g.AddEdge(tt.construct.Id(), c.Id(), nil)
 			}
 			deps := constructGraph.GetDownstreamDependencies(tt.construct)
 			if tt.want != nil && !assert.NotNil(deps) {
@@ -214,7 +214,7 @@ func Test_GetUpstreamDependencies(t *testing.T) {
 			g.AddVertex(tt.construct)
 			for _, c := range tt.deps {
 				g.AddVertex(c)
-				g.AddEdge(c.Id(), tt.construct.Id())
+				g.AddEdge(c.Id(), tt.construct.Id(), nil)
 			}
 			deps := constructGraph.GetUpstreamDependencies(tt.construct)
 			if tt.want != nil && !assert.NotNil(deps) {
diff --git a/pkg/core/resource_graph_test.go b/pkg/core/resource_graph_test.go
index 62f35753..f8bf6c53 100644
--- a/pkg/core/resource_graph_test.go
+++ b/pkg/core/resource_graph_test.go
@@ -3,13 +3,15 @@ package core
 import (
 	"testing"
 
+	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 )
 
 type NestedResources struct {
-	Resource      Resource
-	ResourceArray []Resource
-	ResourceMap   map[string]Resource
+	Resource           Resource
+	SpecificDependency *testResource
+	ResourceArray      []Resource
+	ResourceMap        map[string]Resource
 }
 
 type testResource struct {
@@ -39,6 +41,16 @@ func (tr *testResource) KlothoConstructRef() []AnnotationKey {
 	return nil
 }
 
+type testResourceParams struct {
+	Name string
+}
+
+func (tr *testResource) Create(dag *ResourceGraph, metadata testResourceParams) error {
+	currName := metadata.Name + uuid.NewString()
+	*tr = testResource{Name: currName}
+	return nil
+}
+
 // Id returns the id of the cloud resource
 func (tr *testResource) Id() ResourceId {
 	return ResourceId{
@@ -121,3 +133,127 @@ func TestResourceGraph_AddDependenciesReflect(t *testing.T) {
 	}
 	assert.Nil(dag.GetDependencyByVertexIds(tr.Id().String(), (&testResource{Name: "nested_single"}).Id().String()))
 }
+
+func TestResourceGraph_CreateDependencies(t *testing.T) {
+	tr := &testResource{
+		Name: "id",
+
+		NestedResources: NestedResources{
+			Resource:      &testResource{Name: "nested"},
+			ResourceArray: []Resource{&testResource{Name: "nested_arr1"}, &testResource{Name: "nested_arr2"}},
+			ResourceMap: map[string]Resource{
+				"one": &testResource{Name: "nested_map1"},
+				"two": &testResource{Name: "nested_map2"},
+			},
+		},
+
+		SingleDependency: &testResource{Name: "single", NestedResources: NestedResources{Resource: &testResource{Name: "nested_single"}}},
+
+		DependencyArray:  []Resource{&testResource{Name: "arr1"}, &testResource{Name: "arr2"}},
+		SpecificDepArray: []*testResource{{Name: "spec_arr1"}, {Name: "spec_arr2"}},
+
+		DependencyMap: map[string]Resource{
+			"one": &testResource{Name: "map1"},
+			"two": &testResource{Name: "map2"},
+		},
+		SpecificDepMap: map[string]*testResource{
+			"one": {Name: "spec_map1"},
+			"two": {Name: "spec_map2"},
+		},
+
+		IacValue:    IaCValue{Resource: &testResource{Name: "value1"}},
+		IacValuePtr: &IaCValue{Resource: &testResource{Name: "value2"}},
+		IacValueArr: []IaCValue{
+			{Resource: &testResource{Name: "value_arr1"}},
+			{Resource: &testResource{Name: "value_arr2"}},
+		},
+		IacValuePtrArr: []*IaCValue{
+			{Resource: &testResource{Name: "value_ptr_arr1"}},
+			{Resource: &testResource{Name: "value_ptr_arr2"}},
+		},
+		IacValueMap: map[string]IaCValue{
+			"one": {Resource: &testResource{Name: "value_map1"}},
+			"two": {Resource: &testResource{Name: "value_map2"}},
+		},
+		IacValuePtrMap: map[string]*IaCValue{
+			"one": {Resource: &testResource{Name: "value_ptr_map1"}},
+			"two": {Resource: &testResource{Name: "value_ptr_map2"}},
+		},
+	}
+
+	subParams := map[string]any{
+		"NestedResources": map[string]any{
+			"Resource":           testResourceParams{Name: "nested"},
+			"SpecificDependency": testResourceParams{Name: "nested_specific"},
+			"ResourceArray":      []testResourceParams{{Name: "nested_arr1"}, {Name: "nested_arr2"}},
+			"ResourceMap": map[string]testResourceParams{
+				"one": {Name: "nested_map1"},
+				"two": {Name: "nested_map2"},
+			},
+		},
+
+		"SingleDependency":   testResourceParams{Name: "single"},
+		"SpecificDependency": testResourceParams{Name: "specific"},
+		"DependencyArray":    []testResourceParams{{Name: "arr1"}, {Name: "arr2"}},
+		"SpecificDepArray":   []testResourceParams{{Name: "spec_arr1"}, {Name: "spec_arr2"}},
+
+		"DependencyMap": map[string]testResourceParams{
+			"one": {Name: "map1"},
+			"two": {Name: "map2"},
+		},
+		"SpecificDepMap": map[string]testResourceParams{
+			"one": {Name: "spec_map1"},
+			"two": {Name: "spec_map2"},
+		},
+
+		"IacValue":    testResourceParams{Name: "value1"},
+		"IacValuePtr": testResourceParams{Name: "value2"},
+		"IacValueArr": []testResourceParams{
+			{Name: "value_arr1"},
+			{Name: "value_arr2"},
+		},
+		"IacValuePtrArr": []testResourceParams{
+			{Name: "value_ptr_arr1"},
+			{Name: "value_ptr_arr2"},
+		},
+		"IacValueMap": map[string]testResourceParams{
+			"one": {Name: "value_map1"},
+			"two": {Name: "value_map2"},
+		},
+		"IacValuePtrMap": map[string]testResourceParams{
+			"one": {Name: "value_ptr_map1"},
+			"two": {Name: "value_ptr_map2"},
+		},
+	}
+
+	dag := NewResourceGraph()
+
+	err := dag.CreateDependencies(tr, subParams)
+
+	assert := assert.New(t)
+
+	if !assert.NoError(err) {
+		return
+	}
+	assert.Len(dag.ListResources(), 27)
+	assert.Len(dag.ListDependencies(), 26)
+	assert.NotNil(tr.SpecificDependency)
+	assert.NotNil(tr.NestedResources.SpecificDependency)
+	for _, target := range []string{
+		"single", "single_specific",
+		"arr1", "arr2",
+		"spec_arr1", "spec_arr2",
+		"map1", "map2",
+		"spec_map1", "spec_map2",
+		"value1", "value2",
+		"value_arr1", "value_arr2",
+		"value_ptr_arr1", "value_ptr_arr2",
+		"value_map1", "value_map2",
+		"value_ptr_map1", "value_ptr_map2",
+		"nested",
+		"nested_arr1", "nested_arr2",
+		"nested_map1", "nested_map2",
+	} {
+		assert.Nil(dag.GetDependencyByVertexIds(tr.Id().String(), (&testResource{Name: target}).Id().String()), "source -> %s", target)
+	}
+}
diff --git a/pkg/graph/graph_test.go b/pkg/graph/graph_test.go
index 51da4fcb..c0070e12 100644
--- a/pkg/graph/graph_test.go
+++ b/pkg/graph/graph_test.go
@@ -20,8 +20,8 @@ func TestSimpleGraph(t *testing.T) {
 	d.AddVertex(a)
 	d.AddVertex(b)
 	d.AddVertex(c)
-	d.AddEdge(a.Id(), b.Id())
-	d.AddEdge(a.Id(), c.Id())
+	d.AddEdge(a.Id(), b.Id(), nil)
+	d.AddEdge(a.Id(), c.Id(), nil)
 
 	test(t, "roots", func(assert *assert.Assertions) {
 		assert.Equal([]DummyVertex{a}, d.Roots())
@@ -50,7 +50,7 @@ func TestCycleToSelf(t *testing.T) {
 	d := NewDirected(DummyVertex.Id)
 	v := DummyVertex("dummy")
 	d.AddVertex(v)
-	d.AddEdge(v.Id(), v.Id())
+	d.AddEdge(v.Id(), v.Id(), nil)
 	assert.Equal(
 		[]Edge[DummyVertex]{
 			{
@@ -68,8 +68,8 @@ func TestCycle(t *testing.T) {
 	v2 := DummyVertex("world")
 	d.AddVertex(v1)
 	d.AddVertex(v2)
-	d.AddEdge(v1.Id(), v2.Id())
-	d.AddEdge(v2.Id(), v1.Id())
+	d.AddEdge(v1.Id(), v2.Id(), nil)
+	d.AddEdge(v2.Id(), v1.Id(), nil)
 	assert.Equal(
 		[]Edge[DummyVertex]{
 			{
@@ -102,8 +102,8 @@ func TestNegativeCases(t *testing.T) {
 		v2 := DummyVertex("world")
 		d.AddVertex(v1)
 		d.AddVertex(v2)
-		d.AddEdge(v1.Id(), v2.Id())
-		d.AddEdge(v1.Id(), v2.Id())
+		d.AddEdge(v1.Id(), v2.Id(), nil)
+		d.AddEdge(v1.Id(), v2.Id(), nil)
 		assert.Equal(
 			[]Edge[DummyVertex]{
 				{
diff --git a/pkg/infra/iac2/templates_compiler_test.go b/pkg/infra/iac2/templates_compiler_test.go
index 75cd5b6e..448e99bd 100644
--- a/pkg/infra/iac2/templates_compiler_test.go
+++ b/pkg/infra/iac2/templates_compiler_test.go
@@ -191,6 +191,7 @@ func TestResolveStructInput(t *testing.T) {
 
 func Test_renderGlueVars(t *testing.T) {
 	unit := &core.ExecutionUnit{}
+	vpc := &resources.Vpc{Name: "vpc"}
 	cases := []struct {
 		name                 string
 		subResource          core.Resource
@@ -228,20 +229,21 @@ func Test_renderGlueVars(t *testing.T) {
 			name:        "routeTableAssociation",
 			subResource: &resources.RouteTable{Name: "rt1"},
 			nodes: []core.Resource{
+				vpc,
 				&resources.RouteTable{Name: "rt1"},
-				&resources.Subnet{Name: "s1"},
+				&resources.Subnet{Name: "s1", Vpc: vpc},
 			},
 			edges: []graph.Edge[core.Resource]{
 				{
 					Source:      &resources.RouteTable{Name: "rt1"},
-					Destination: &resources.Subnet{Name: "s1"},
+					Destination: &resources.Subnet{Name: "s1", Vpc: vpc},
 				},
 			},
 			resourceVarNamesById: map[core.ResourceId]string{
 				{Provider: "aws", Type: "route_table", Name: "rt1"}: "testRouteTable",
 				{Provider: "aws", Type: "vpc_subnet", Name: "s1"}:   "subnet1",
 			},
-			want: "\n\nconst routeTableAssociationS1 = new aws.ec2.RouteTableAssociation(`s1`, {\n\t\t\t\tsubnetId: subnetS1.id,\n\t\t\trouteTableId: testRouteTable.id,\n\t\t\t});\n\n",
+			want: "\n\nconst routeTableAssociationS1 = new aws.ec2.RouteTableAssociation(`s1`, {\n\t\t\t\tsubnetId: vpcSubnetS1.id,\n\t\t\trouteTableId: testRouteTable.id,\n\t\t\t});\n\n",
 		},
 	}
 	for _, tt := range cases {
@@ -320,7 +322,7 @@ func Test_handleIaCValue(t *testing.T) {
 			resourceVarNamesById: map[core.ResourceId]string{
 				{Provider: "aws", Type: "availability_zones", Name: "AvailabilityZones"}: "azs",
 			},
-			want: "availabilityZones.names[2]",
+			want: "azs.names[2]",
 		},
 		{
 			name: "nlb uri",
diff --git a/pkg/infra/kubernetes/exec_unit_test.go b/pkg/infra/kubernetes/exec_unit_test.go
index 0b66aefe..5b2d945c 100644
--- a/pkg/infra/kubernetes/exec_unit_test.go
+++ b/pkg/infra/kubernetes/exec_unit_test.go
@@ -5,7 +5,7 @@ import (
 	"testing"
 
 	apps "k8s.io/api/apps/v1"
-	autoscaling "k8s.io/api/autoscaling/v2beta2"
+	autoscaling "k8s.io/api/autoscaling/v2"
 	corev1 "k8s.io/api/core/v1"
 	yaml2 "k8s.io/apimachinery/pkg/util/yaml"
 	k8s_yaml "sigs.k8s.io/yaml"
@@ -727,7 +727,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 		{
 			name: "Basic HPA, no cfg",
 			file: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   name: testUnit
@@ -739,7 +739,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				values: nil,
 				newFile: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   creationTimestamp: null
@@ -751,9 +751,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
                     kind: Deployment
                     name: testUnit
                 status:
-                  conditions: null
                   currentMetrics: null
-                  currentReplicas: 0
                   desiredReplicas: 0`),
 			},
 		},
@@ -763,7 +761,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 				"replicas": 13,
 			}},
 			file: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   name: testUnit
@@ -775,7 +773,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				values: nil,
 				newFile: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   creationTimestamp: null
@@ -788,9 +786,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
                     kind: Deployment
                     name: testUnit
                 status:
-                  conditions: null
                   currentMetrics: null
-                  currentReplicas: 0
                   desiredReplicas: 0`),
 			},
 		},
@@ -802,7 +798,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 				},
 			}},
 			file: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   name: testUnit
@@ -815,7 +811,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				values: nil,
 				newFile: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   creationTimestamp: null
@@ -828,9 +824,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
                     kind: Deployment
                     name: testUnit
                 status:
-                  conditions: null
                   currentMetrics: null
-                  currentReplicas: 0
                   desiredReplicas: 0`),
 			},
 		},
@@ -842,7 +836,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 				},
 			}},
 			file: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   name: testUnit
@@ -854,7 +848,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				values: nil,
 				newFile: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   creationTimestamp: null
@@ -873,9 +867,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
                     kind: Deployment
                     name: testUnit
                 status:
-                  conditions: null
                   currentMetrics: null
-                  currentReplicas: 0
                   desiredReplicas: 0`),
 			},
 		},
@@ -887,7 +879,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 				},
 			}},
 			file: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   name: testUnit
@@ -904,7 +896,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				values: nil,
 				newFile: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   creationTimestamp: null
@@ -923,9 +915,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
                     kind: Deployment
                     name: testUnit
                 status:
-                  conditions: null
                   currentMetrics: null
-                  currentReplicas: 0
                   desiredReplicas: 0`),
 			},
 		},
@@ -937,7 +927,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 				},
 			}},
 			file: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   name: testUnit
@@ -949,7 +939,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				values: nil,
 				newFile: testutil.UnIndent(`
-                apiVersion: autoscaling/v2beta2
+                apiVersion: autoscaling/v2
                 kind: HorizontalPodAutoscaler
                 metadata:
                   creationTimestamp: null
@@ -968,9 +958,7 @@ func Test_transformHorizontalPodAutoscaler(t *testing.T) {
                     kind: Deployment
                     name: testUnit
                 status:
-                  conditions: null
                   currentMetrics: null
-                  currentReplicas: 0
                   desiredReplicas: 0`),
 			},
 		},
diff --git a/pkg/infra/kubernetes/helm_chart_test.go b/pkg/infra/kubernetes/helm_chart_test.go
index 36a7981c..11331f88 100644
--- a/pkg/infra/kubernetes/helm_chart_test.go
+++ b/pkg/infra/kubernetes/helm_chart_test.go
@@ -120,7 +120,7 @@ func Test_AssignFilesToUnits(t *testing.T) {
 			units: []string{"unit1"},
 			fileUnits: map[string]string{
 				"HorizontalPodAutoscaler.yaml": testutil.UnIndent(`
-                    apiVersion: autoscaling/v2beta2
+                    apiVersion: autoscaling/v2
                     kind: HorizontalPodAutoscaler
                     metadata:
                       name: example-hpa
@@ -231,7 +231,7 @@ func Test_AssignFilesToUnits(t *testing.T) {
 			units: []string{"unit1", "unit2"},
 			fileUnits: map[string]string{
 				"HorizontalPodAutoscaler.yaml": testutil.UnIndent(`
-                    apiVersion: autoscaling/v2beta2
+                    apiVersion: autoscaling/v2
                     kind: HorizontalPodAutoscaler
                     metadata:
                       name: unit1
@@ -788,7 +788,7 @@ func Test_addHorizontalPodAutoscaler(t *testing.T) {
 			want: result{
 				hpaPath: "test/templates/unit-horizontal-pod-autoscaler.yaml",
 				hpaFile: testutil.UnIndent(`
-                    apiVersion: autoscaling/v2beta2
+                    apiVersion: autoscaling/v2
                     kind: HorizontalPodAutoscaler
                     metadata:
                       creationTimestamp: null
@@ -816,9 +816,7 @@ func Test_addHorizontalPodAutoscaler(t *testing.T) {
                         kind: Deployment
                         name: unit
                     status:
-                      conditions: null
                       currentMetrics: null
-                      currentReplicas: 0
                       desiredReplicas: 0`),
 				values: nil,
 			},
diff --git a/pkg/knowledge_base/edge_kb_test.go b/pkg/knowledge_base/edge_kb_test.go
new file mode 100644
index 00000000..43c10785
--- /dev/null
+++ b/pkg/knowledge_base/edge_kb_test.go
@@ -0,0 +1,227 @@
+package knowledgebase
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/klothoplatform/klotho/pkg/core"
+
+	klothograph "github.com/klothoplatform/klotho/pkg/graph"
+
+	"github.com/dominikbraun/graph"
+	"github.com/stretchr/testify/assert"
+)
+
+var TestKnowledgeBase = &EdgeKB{
+	Edge{Source: reflect.TypeOf(&A{}), Destination: reflect.TypeOf(&B{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			b := dest.(*B)
+			b.Name = "B"
+			dag.AddDependency(source, dest)
+			return nil
+		},
+		Configure: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			a := source.(*A)
+			a.Name = "name"
+			return nil
+		},
+	},
+	Edge{Source: reflect.TypeOf(&A{}), Destination: reflect.TypeOf(&E{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			dag.AddDependency(source, dest)
+			return nil
+		},
+		ValidDestinations: []reflect.Type{typeE},
+	},
+	Edge{Source: reflect.TypeOf(&B{}), Destination: reflect.TypeOf(&C{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			dag.AddDependency(source, dest)
+			return nil
+		},
+	},
+	Edge{Source: reflect.TypeOf(&C{}), Destination: reflect.TypeOf(&D{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			dag.AddDependency(source, dest)
+			return nil
+		},
+		ValidDestinations: []reflect.Type{typeE, typeD},
+	},
+	Edge{Source: reflect.TypeOf(&C{}), Destination: reflect.TypeOf(&E{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			dag.AddDependency(source, dest)
+			return nil
+		},
+		ValidDestinations: []reflect.Type{typeE},
+	},
+	Edge{Source: reflect.TypeOf(&D{}), Destination: reflect.TypeOf(&B{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			dag.AddDependency(source, dest)
+			return nil
+		},
+		ValidDestinations: []reflect.Type{typeC},
+	},
+	Edge{Source: reflect.TypeOf(&D{}), Destination: reflect.TypeOf(&E{})}: EdgeDetails{
+		ExpansionFunc: func(source, dest core.Resource, dag *core.ResourceGraph, data EdgeData) error {
+			dag.AddDependency(source, dest)
+			return nil
+		},
+		ValidDestinations: []reflect.Type{typeE},
+	},
+}
+
+var typeA = reflect.TypeOf(&A{})
+var typeB = reflect.TypeOf(&B{})
+var typeC = reflect.TypeOf(&C{})
+var typeD = reflect.TypeOf(&D{})
+var typeE = reflect.TypeOf(&E{})
+
+func Test_FindPaths(t *testing.T) {
+	cases := []struct {
+		name   string
+		source core.Resource
+		dest   core.Resource
+		want   []Path
+	}{
+		{
+			name:   "paths from a",
+			source: &A{},
+			dest:   &E{},
+			want: []Path{
+				{{typeA, typeB}, {typeB, typeC}, {typeC, typeD}, {typeD, typeE}},
+				{{typeA, typeB}, {typeB, typeC}, {typeC, typeE}},
+				{{typeA, typeE}},
+			},
+		},
+		{
+			name:   "paths from b",
+			source: &B{},
+			dest:   &E{},
+			want: []Path{
+				{{typeB, typeC}, {typeC, typeD}, {typeD, typeE}},
+				{{typeB, typeC}, {typeC, typeE}},
+				{{typeA, typeB}, {typeA, typeE}},
+			},
+		},
+		{
+			name:   "paths from d to c",
+			source: &D{},
+			dest:   &C{},
+			want: []Path{
+				{{typeD, typeB}, {typeB, typeC}},
+			},
+		},
+		{
+			name:   "no paths from e",
+			source: &E{},
+			dest:   &A{},
+			want:   []Path{},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+
+			result := TestKnowledgeBase.FindPaths(reflect.TypeOf(tt.source), reflect.TypeOf(tt.dest))
+			assert.ElementsMatch(tt.want, result)
+		})
+	}
+}
+
+func Test_ConfigureFromEdgeData(t *testing.T) {
+	cases := []struct {
+		name   string
+		source core.Resource
+		dest   core.Resource
+		data   EdgeData
+		want   []klothograph.Edge[core.Resource]
+	}{
+		{
+			name:   "node must and must not exist",
+			source: &A{},
+			dest:   &B{},
+			want: []klothograph.Edge[core.Resource]{
+				{Source: &A{Name: "name"}, Destination: &B{}, Properties: graph.EdgeProperties{Attributes: map[string]string{}, Data: EdgeData{}}},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			dag.AddDependencyWithData(tt.source, tt.dest, tt.data)
+			err := TestKnowledgeBase.ConfigureFromEdgeData(dag)
+			assert.NoError(err)
+			assert.ElementsMatch(tt.want, dag.ListDependencies())
+		})
+	}
+}
+
+func Test_ExpandEdges(t *testing.T) {
+	cases := []struct {
+		name   string
+		source core.Resource
+		dest   core.Resource
+		data   EdgeData
+		want   []klothograph.Edge[core.Resource]
+	}{
+		{
+			name:   "node must and must not exist",
+			source: &A{Name: "A"},
+			dest:   &E{Name: "E"},
+			data: EdgeData{
+				Constraint: EdgeConstraint{
+					NodeMustExist:    []core.Resource{&C{}},
+					NodeMustNotExist: []core.Resource{&D{}},
+				},
+			},
+			want: []klothograph.Edge[core.Resource]{
+				{Source: &A{Name: "A"}, Destination: &B{Name: "B"}, Properties: graph.EdgeProperties{Attributes: map[string]string{}}},
+				{Source: &B{Name: "B"}, Destination: &C{}, Properties: graph.EdgeProperties{Attributes: map[string]string{}}},
+				{Source: &C{}, Destination: &E{Name: "E"}, Properties: graph.EdgeProperties{Attributes: map[string]string{}}},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			dag.AddDependencyWithData(tt.source, tt.dest, tt.data)
+			err := TestKnowledgeBase.ExpandEdges(dag)
+			assert.NoError(err)
+			assert.ElementsMatch(tt.want, dag.ListDependencies())
+		})
+	}
+}
+
+type (
+	A struct {
+		Name string
+	}
+	B struct {
+		Name string
+	}
+	C struct {
+		Name string
+	}
+	D struct {
+		Name string
+	}
+	E struct {
+		Name string
+	}
+)
+
+func (f *A) Id() core.ResourceId                      { return core.ResourceId{Name: "A" + f.Name} }
+func (f *A) KlothoConstructRef() []core.AnnotationKey { return nil }
+
+func (b B) Id() core.ResourceId                      { return core.ResourceId{Name: "B" + b.Name} }
+func (f B) KlothoConstructRef() []core.AnnotationKey { return nil }
+
+func (p *C) Id() core.ResourceId                      { return core.ResourceId{Name: "C" + p.Name} }
+func (f *C) KlothoConstructRef() []core.AnnotationKey { return nil }
+
+func (p *D) Id() core.ResourceId                      { return core.ResourceId{Name: "D" + p.Name} }
+func (f *D) KlothoConstructRef() []core.AnnotationKey { return nil }
+
+func (p *E) Id() core.ResourceId                      { return core.ResourceId{Name: "E" + p.Name} }
+func (f *E) KlothoConstructRef() []core.AnnotationKey { return nil }
diff --git a/pkg/lang/python/imports_test.go b/pkg/lang/python/imports_test.go
index 0687ca2c..8009dfe5 100644
--- a/pkg/lang/python/imports_test.go
+++ b/pkg/lang/python/imports_test.go
@@ -1,11 +1,13 @@
 package python
 
 import (
+	"bytes"
 	"fmt"
 	"strings"
 	"testing"
 
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/testutil"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -18,83 +20,110 @@ func TestFindImports(t *testing.T) {
 		{
 			name:   "import module",
 			source: "import mymodule",
-			want: map[string]Import{
-				"mymodule": {Name: "mymodule"},
+			want: Imports{
+				"mymodule": {Name: "mymodule", UsedAs: testutil.NewSet("mymodule")},
 			},
 		},
 		{
 			name:   "import module aliased",
 			source: "import mymodule as m",
-			want: map[string]Import{
-				"mymodule": {Name: "mymodule", Alias: "m"},
+			want: Imports{
+				"mymodule": {Name: "mymodule", UsedAs: testutil.NewSet("m")},
 			},
 		},
 		{
 			name:   "import modules",
 			source: "import mymodule1, mymodule2",
-			want: map[string]Import{
-				"mymodule1": {Name: "mymodule1"},
-				"mymodule2": {Name: "mymodule2"},
+			want: Imports{
+				"mymodule1": {Name: "mymodule1", UsedAs: testutil.NewSet("mymodule1")},
+				"mymodule2": {Name: "mymodule2", UsedAs: testutil.NewSet("mymodule2")},
 			},
 		},
 		{
 			name:   "import modules aliased",
 			source: "import mymodule1 as m1, mymodule2 as m2",
-			want: map[string]Import{
-				"mymodule1": {Name: "mymodule1", Alias: "m1"},
-				"mymodule2": {Name: "mymodule2", Alias: "m2"},
+			want: Imports{
+				"mymodule1": {Name: "mymodule1", UsedAs: testutil.NewSet("m1")},
+				"mymodule2": {Name: "mymodule2", UsedAs: testutil.NewSet("m2")},
 			},
 		},
 		{
 			name:   "import submodule",
 			source: "import mymodule.submodule",
-			want: map[string]Import{
-				"mymodule.submodule": {ParentModule: "mymodule", Name: "submodule"},
+			want: Imports{
+				"mymodule.submodule": {
+					ParentModule: "mymodule",
+					Name:         "submodule",
+					UsedAs:       testutil.NewSet("mymodule.submodule")},
 			},
 		},
 		{
 			name:   "import submodule aliased",
 			source: "import mymodule.submodule as w",
-			want: map[string]Import{
-				"mymodule.submodule": {ParentModule: "mymodule", Name: "submodule", Alias: "w"},
+			want: Imports{
+				"mymodule.submodule": {ParentModule: "mymodule", Name: "submodule", UsedAs: testutil.NewSet("w")},
 			},
 		},
 		{
 			name:   "import aliased nested submodule",
 			source: "import mymodule.submodule1.submodule2 as w",
-			want: map[string]Import{
+			want: Imports{
 				"mymodule.submodule1.submodule2": {
 					ParentModule: "mymodule.submodule1",
 					Name:         "submodule2",
-					Alias:        "w",
+					UsedAs:       testutil.NewSet("w"),
 				},
 			},
 		},
 		{
-			name:   "import relative module",
+			name:   "import relative parent module",
 			source: "from .. import mymodule\nfrom ..parent import child.attribute",
-			want: map[string]Import{
+			want: Imports{
 				"..": {
 					ParentModule: "..",
 					Name:         "",
 					ImportedAttributes: map[string]Attribute{
-						"mymodule": {Name: "mymodule"},
+						"mymodule": {Name: "mymodule", UsedAs: testutil.NewSet("mymodule")},
 					}},
 				"..parent": {
 					ParentModule:       "..",
 					Name:               "parent",
-					ImportedAttributes: map[string]Attribute{"child.attribute": {Name: "child.attribute"}},
+					ImportedAttributes: map[string]Attribute{"child.attribute": {Name: "child.attribute", UsedAs: testutil.NewSet("child.attribute")}},
 				},
 			},
 		},
+		{
+			name:   "import relative sibling module",
+			source: "from . import foo\nfrom .foo import bar\nfrom .x.y.z import a",
+			want: Imports{
+				".": {
+					ParentModule: ".",
+					Name:         "",
+					ImportedAttributes: map[string]Attribute{
+						"foo": {Name: "foo", UsedAs: testutil.NewSet("foo")},
+					}},
+				".foo": {
+					ParentModule: ".",
+					Name:         "foo",
+					ImportedAttributes: map[string]Attribute{
+						"bar": {Name: "bar", UsedAs: testutil.NewSet("bar")},
+					}},
+				".x.y.z": {
+					ParentModule: ".x.y",
+					Name:         "z",
+					ImportedAttributes: map[string]Attribute{
+						"a": {Name: "a", UsedAs: testutil.NewSet("a")},
+					}},
+			},
+		},
 		{
 			name:   "from module import attribute",
 			source: "from mymodule import attribute",
-			want: map[string]Import{
+			want: Imports{
 				"mymodule": {
 					Name: "mymodule",
 					ImportedAttributes: map[string]Attribute{
-						"attribute": {Name: "attribute"},
+						"attribute": {Name: "attribute", UsedAs: testutil.NewSet("attribute")},
 					}},
 			},
 		},
@@ -102,44 +131,52 @@ func TestFindImports(t *testing.T) {
 			name: "import multiple attributes from module in separate imports",
 			source: `from mymodule import attribute1
 			         from mymodule import attribute2`,
-			want: map[string]Import{
+			want: Imports{
 				"mymodule": {
 					Name: "mymodule",
 					ImportedAttributes: map[string]Attribute{
-						"attribute1": {Name: "attribute1"},
-						"attribute2": {Name: "attribute2"},
+						"attribute1": {Name: "attribute1", UsedAs: testutil.NewSet("attribute1")},
+						"attribute2": {Name: "attribute2", UsedAs: testutil.NewSet("attribute2")},
 					}},
 			},
 		},
 		{
 			name:   "from module import attributes",
 			source: "from mymodule import attribute1, attribute2",
-			want: map[string]Import{
+			want: Imports{
 				"mymodule": {
 					Name: "mymodule",
 					ImportedAttributes: map[string]Attribute{
-						"attribute1": {Name: "attribute1"},
-						"attribute2": {Name: "attribute2"},
+						"attribute1": {Name: "attribute1", UsedAs: testutil.NewSet("attribute1")},
+						"attribute2": {Name: "attribute2", UsedAs: testutil.NewSet("attribute2")},
 					}},
 			},
 		},
 		{
 			name:   "from module import attributes aliased",
 			source: "from mymodule import attribute1 as a1, attribute2 as a2",
-			want: map[string]Import{
+			want: Imports{
 				"mymodule": {
 					Name: "mymodule",
 					ImportedAttributes: map[string]Attribute{
-						"attribute1": {Name: "attribute1", Alias: "a1"},
-						"attribute2": {Name: "attribute2", Alias: "a2"},
+						"attribute1": {Name: "attribute1", UsedAs: testutil.NewSet("a1")},
+						"attribute2": {Name: "attribute2", UsedAs: testutil.NewSet("a2")},
 					}},
 			},
 		},
 		{
-			name:   "import sibling",
-			source: "from . import mymodule",
-			want: map[string]Import{
-				".": {ParentModule: ".", ImportedAttributes: map[string]Attribute{"mymodule": {Name: "mymodule"}}},
+			name: "from module import attributes aliased multiple times",
+			source: testutil.UnIndent(`
+				from mymodule import attribute1
+				from mymodule import attribute1 as a1
+				from mymodule import attribute1 as a2
+				`),
+			want: Imports{
+				"mymodule": {
+					Name: "mymodule",
+					ImportedAttributes: map[string]Attribute{
+						"attribute1": {Name: "attribute1", UsedAs: testutil.NewSet("attribute1", "a1", "a2")},
+					}},
 			},
 		},
 		{
@@ -150,39 +187,50 @@ func TestFindImports(t *testing.T) {
 					from module3 import attribute1, attribute2
 					from .. import attribute1
 			`,
-			want: map[string]Import{
+			want: Imports{
 				"module1": {
-					Name: "module1",
+					Name:   "module1",
+					UsedAs: testutil.NewSet("module1"),
 				},
 				"module2.submodule1": {
 					ParentModule: "module2",
 					Name:         "submodule1",
+					UsedAs:       testutil.NewSet("module2.submodule1"),
 				},
 				"module3": {
 					Name: "module3",
 					ImportedAttributes: map[string]Attribute{
-						"attribute1": {Name: "attribute1"},
-						"attribute2": {Name: "attribute2"},
+						"attribute1": {Name: "attribute1", UsedAs: testutil.NewSet("attribute1")},
+						"attribute2": {Name: "attribute2", UsedAs: testutil.NewSet("attribute2")},
 					},
 				},
 				"..": {
 					ParentModule: "..",
 					Name:         "",
 					ImportedAttributes: map[string]Attribute{
-						"attribute1": {Name: "attribute1"},
+						"attribute1": {Name: "attribute1", UsedAs: testutil.NewSet("attribute1")},
 					},
 				},
 			},
 		},
-		{ // TODO https://github.com/klothoplatform/klotho/issues/567
+		{
 			name: "import module aliased twice",
 			source: `
 				import module1 as a
 				import module1 as b
 `,
-			want: map[string]Import{
-				"module1": {Name: "module1", Alias: "a"},
-				// missing alias 'b'
+			want: Imports{
+				"module1": {Name: "module1", UsedAs: testutil.NewSet("a", "b")},
+			},
+		},
+		{
+			name: "imported twice once with alias",
+			source: `
+				import module1
+				import module1 as a
+`,
+			want: Imports{
+				"module1": {Name: "module1", UsedAs: testutil.NewSet("module1", "a")},
 			},
 		},
 	}
@@ -195,20 +243,51 @@ func TestFindImports(t *testing.T) {
 				return
 			}
 
-			imports := FindImports(f)
-			if len(tt.want) != len(imports) {
-				fmt.Println(imports)
-			}
-			assert.Equal(len(tt.want), len(imports))
-			for qualifiedName, i := range imports {
-				if expected, ok := tt.want[qualifiedName]; assert.Truef(ok, "import not found for name: %s", qualifiedName) {
-					validateImport(assert, f.Program(), expected, i)
-				}
-			}
+			imports := FindFileImports(f)
+			assertImportsEqual(t, f.Program(), tt.want, imports)
 		})
 	}
 }
 
+func assertImportsEqual(t *testing.T, program []byte, expect, actual Imports) {
+	t.Helper()
+	assert := assert.New(t)
+	expectKeys := make([]string, 0, len(expect))
+	actualKeys := make([]string, 0, len(actual))
+	for k := range expect {
+		expectKeys = append(expectKeys, k)
+	}
+	for k := range actual {
+		actualKeys = append(actualKeys, k)
+	}
+	if !assert.ElementsMatch(expectKeys, actualKeys, "import keys") {
+		return
+	}
+
+	for k, expectV := range expect {
+		actualV := actual[k]
+		validateImport(t, program, expectV, actualV)
+	}
+}
+
+func validateImport(t *testing.T, content []byte, expected Import, actual Import) {
+	t.Helper()
+	assert := assert.New(t)
+	assert.Equal(expected.ParentModule, actual.ParentModule, "ParentModule")
+	assert.Equal(expected.Name, actual.Name, "Name")
+	assert.Equal(expected.UsedAs, actual.UsedAs, "UsedAs")
+
+	expectedAttrs := make([]string, 0, len(expected.ImportedAttributes))
+	actualAttrs := make([]string, 0, len(actual.ImportedAttributes))
+	for i := range expected.ImportedAttributes {
+		expectedAttrs = append(expectedAttrs, expected.ImportedAttributes[i].Name)
+	}
+	for i := range actual.ImportedAttributes {
+		actualAttrs = append(actualAttrs, actual.ImportedAttributes[i].Name)
+	}
+	assert.ElementsMatch(expectedAttrs, actualAttrs, "imported attributes")
+}
+
 func TestResolveFileDependencies(t *testing.T) {
 	cases := []struct {
 		name   string
@@ -225,7 +304,7 @@ func TestResolveFileDependencies(t *testing.T) {
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet("my_method"),
+					"other.py": testutil.NewSet("my_method"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -238,7 +317,7 @@ func TestResolveFileDependencies(t *testing.T) {
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"shared/other.py": NewSet[string](),
+					"shared/other.py": testutil.NewSet[string](),
 				},
 				"shared/other.py": map[string]core.References{},
 			},
@@ -251,7 +330,7 @@ func TestResolveFileDependencies(t *testing.T) {
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet("method_a", "method_2"),
+					"other.py": testutil.NewSet("method_a", "method_2"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -264,7 +343,19 @@ func TestResolveFileDependencies(t *testing.T) {
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet("method_a"),
+					"other.py": testutil.NewSet("method_a"),
+				},
+				"other.py": map[string]core.References{},
+			},
+		},
+		{
+			input: map[string]string{
+				"main.py":  `from other import method_a as aaa`,
+				"other.py": `pass`,
+			},
+			expect: map[string]core.Imported{
+				"main.py": map[string]core.References{
+					"other.py": testutil.NewSet("method_a"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -280,7 +371,7 @@ other.hello_world()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet("hello_world"),
+					"other.py": testutil.NewSet("hello_world"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -296,7 +387,7 @@ print(other.hello_world)
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet("hello_world"),
+					"other.py": testutil.NewSet("hello_world"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -309,7 +400,7 @@ print(other.hello_world)
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet[string](),
+					"other.py": testutil.NewSet[string](),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -325,7 +416,7 @@ some_other.hello_world()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet("hello_world"),
+					"other.py": testutil.NewSet("hello_world"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -338,7 +429,7 @@ some_other.hello_world()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other/hello.py": NewSet[string](),
+					"other/hello.py": testutil.NewSet[string](),
 				},
 				"other/hello.py": map[string]core.References{},
 			},
@@ -351,7 +442,7 @@ some_other.hello_world()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other/hello.py": NewSet("a"),
+					"other/hello.py": testutil.NewSet("a"),
 				},
 				"other/hello.py": map[string]core.References{},
 			},
@@ -368,7 +459,7 @@ other.hello.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other/hello.py": NewSet("say_hi"),
+					"other/hello.py": testutil.NewSet("say_hi"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -385,7 +476,7 @@ other.hello.world.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other/hello/world.py": NewSet("say_hi"),
+					"other/hello/world.py": testutil.NewSet("say_hi"),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -399,8 +490,8 @@ other.hello.world.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"foo.py":  NewSet("bar"),
-					"fizz.py": NewSet("buzz"),
+					"foo.py":  testutil.NewSet("bar"),
+					"fizz.py": testutil.NewSet("buzz"),
 				},
 				"foo.py":  map[string]core.References{},
 				"fizz.py": map[string]core.References{},
@@ -425,7 +516,7 @@ other.hello.world.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet[string](),
+					"other.py": testutil.NewSet[string](),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -438,7 +529,7 @@ other.hello.world.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"mod/main.py": map[string]core.References{
-					"other.py": NewSet[string](),
+					"other.py": testutil.NewSet[string](),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -451,7 +542,7 @@ other.hello.world.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"main.py": map[string]core.References{
-					"other.py": NewSet[string](),
+					"other.py": testutil.NewSet[string](),
 				},
 				"other.py": map[string]core.References{},
 			},
@@ -464,11 +555,27 @@ other.hello.world.say_hi()
 			},
 			expect: map[string]core.Imported{
 				"mod/main.py": map[string]core.References{
-					"other.py": NewSet[string](),
+					"other.py": testutil.NewSet[string](),
 				},
 				"other.py": map[string]core.References{},
 			},
 		},
+		{
+			name: "import multiple with submodule",
+			input: map[string]string{
+				"main.py":    `from foo import bar, baz`,
+				"foo.py":     `pass`,
+				"foo/bar.py": `pass`,
+			},
+			expect: map[string]core.Imported{
+				"main.py": map[string]core.References{
+					"foo.py":     testutil.NewSet("baz"),
+					"foo/bar.py": testutil.NewSet[string](),
+				},
+				"foo.py":     map[string]core.References{},
+				"foo/bar.py": map[string]core.References{},
+			},
+		},
 	}
 	for _, tt := range cases {
 		t.Run(tt.name, func(t *testing.T) {
@@ -604,56 +711,33 @@ func TestFindImportedFile(t *testing.T) {
 	}
 }
 
-func validateImport(assert *assert.Assertions, content []byte, expected Import, actual Import) {
-	assert.Equal(expected.ParentModule, actual.ParentModule)
-	assert.Equal(expected.Name, actual.Name)
-
-	assert.Equal(len(expected.ImportedAttributes), len(actual.ImportedAttributes))
-	for i := range expected.ImportedAttributes {
-		validateAttribute(assert, content, expected.ImportedAttributes[i], actual.ImportedAttributes[i])
-	}
-}
-
-func validateAttribute(assert *assert.Assertions, content []byte, expected Attribute, actual Attribute) {
-	assert.Equal(expected.Name, actual.Name)
-
-}
-
-func NewSet[K comparable](elements ...K) map[K]struct{} {
-	result := make(map[K]struct{}, len(elements))
-	for _, elem := range elements {
-		result[elem] = struct{}{}
-	}
-	return result
-}
-
 func Test_referencesForImport(t *testing.T) {
 	tests := []struct {
-		name         string
-		program      string
-		importModule string
-		want         core.References
+		name          string
+		program       string
+		importModules map[string]struct{}
+		want          core.References
 	}{
 		{
 			name: "imported function call",
 			program: `import blah
 blah.foo()`,
-			importModule: "blah",
-			want:         core.References{"foo": {}},
+			importModules: testutil.NewSet("blah"),
+			want:          core.References{"foo": {}},
 		},
 		{
 			name: "imported constant",
 			program: `import blah
 b = blah.a + 1`,
-			importModule: "blah",
-			want:         core.References{"a": {}},
+			importModules: testutil.NewSet("blah"),
+			want:          core.References{"a": {}},
 		},
 		{
 			name: "imported subproperty",
 			program: `import blah
 blah.a.b()`,
-			importModule: "blah",
-			want:         core.References{"a": {}},
+			importModules: testutil.NewSet("blah"),
+			want:          core.References{"a": {}},
 		},
 	}
 	for _, tt := range tests {
@@ -665,7 +749,7 @@ blah.a.b()`,
 				return
 			}
 
-			got := referencesForImport(parsed.Tree().RootNode(), tt.importModule)
+			got := referencesForImport(parsed.Tree().RootNode(), tt.importModules)
 			assert.Equal(tt.want, got)
 		})
 	}
@@ -766,3 +850,189 @@ func Test_pythonModuleToPath(t *testing.T) {
 		})
 	}
 }
+
+func Test_dependenciesForImport(t *testing.T) {
+	// For ease of understanding, all test cases operate in the following directory structure:
+	// .
+	// ├─ app/
+	// │  ├─ models/
+	// │  │  └─ data.py
+	// │  └─ main.py
+	// ├─ shared/
+	// │  ├─ util.py
+	// │  └─ blah.py
+	// ├─ foo.py
+	// └─ bar.py
+	baseFiles := map[string]core.File{
+		"app/main.py":        nil,
+		"app/models/data.py": nil,
+		"shared/util.py":     nil,
+		"shared/blah.py":     nil,
+		"foo.py":             nil,
+		"bar.py":             nil,
+	}
+	tests := []struct {
+		name           string
+		relativeToPath string
+		spec           Import
+		want           core.Imported
+		wantErr        bool
+	}{
+		{
+			name: "direct import",
+			// import foo
+			spec:           Import{Name: "foo"},
+			relativeToPath: "bar.py",
+			want:           core.Imported{"foo.py": {}},
+		},
+		{
+			name: "reference imports",
+			// import foo
+			spec:           Import{Name: "foo"},
+			relativeToPath: "bar.py",
+			want:           core.Imported{"foo.py": {"x": {}}},
+		},
+		{
+			name: "import non-module attributes",
+			// from foo import x
+			spec:           Import{Name: "foo", ImportedAttributes: map[string]Attribute{"x": {}}},
+			relativeToPath: "bar.py",
+			want:           core.Imported{"foo.py": {"x": {}}},
+		},
+		{
+			name: "import sibling module",
+			// from . import foo
+			spec:           Import{ParentModule: ".", ImportedAttributes: map[string]Attribute{"foo": {}}},
+			relativeToPath: "bar.py",
+			want:           core.Imported{"foo.py": {}},
+		},
+		{
+			name: "import module attributes",
+			// from .models import data
+			spec:           Import{ParentModule: ".", Name: "models", ImportedAttributes: map[string]Attribute{"data": {}}},
+			relativeToPath: "app/main.py",
+			want:           core.Imported{"app/models/data.py": {}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+
+			// Make a copy of the base files so the test can add the 'relativeToPath' file
+			// with content based on the expected references.
+			files := make(map[string]core.File)
+			for p, f := range baseFiles {
+				files[p] = f
+			}
+
+			if tt.spec.UsedAs == nil {
+				tt.spec.UsedAs = map[string]struct{}{"test": {}}
+			}
+
+			// Create a file with the expected references for use by any
+			// referencesForImport calls.
+			fileBuf := new(bytes.Buffer)
+			for used := range tt.spec.UsedAs {
+				fmt.Fprintf(fileBuf, "import %s as %s\n", tt.spec.FullyQualifiedModule(), used)
+				for _, refs := range tt.want {
+					for ref := range refs {
+						fmt.Fprintf(fileBuf, "%s.%s\n", used, ref)
+					}
+				}
+			}
+
+			// Fill in the ImportedAttributes to allow more short-hand specification
+			// in the test cases.
+			for attrName, attr := range tt.spec.ImportedAttributes {
+				attr.Name = attrName
+				if len(attr.UsedAs) == 0 {
+					attr.UsedAs = map[string]struct{}{attr.Name: {}}
+				}
+				tt.spec.ImportedAttributes[attrName] = attr
+			}
+
+			file, err := NewFile(tt.relativeToPath, fileBuf)
+			if !assert.NoError(err) {
+				return
+			}
+			files[tt.relativeToPath] = file
+
+			got, err := dependenciesForImport(tt.relativeToPath, tt.spec, files)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			} else if !assert.NoError(err) {
+				return
+			}
+			assertImportedEqual(t, tt.want, got)
+		})
+	}
+}
+
+func assertImportedEqual(t *testing.T, want, got core.Imported) {
+	t.Helper()
+
+	assert := assert.New(t)
+
+	gotKeys := make([]string, 0, len(got))
+	wantKeys := make([]string, 0, len(want))
+	for k := range got {
+		gotKeys = append(gotKeys, k)
+	}
+	for k := range want {
+		wantKeys = append(wantKeys, k)
+	}
+	if !assert.ElementsMatch(wantKeys, gotKeys) {
+		return
+	}
+
+	for k, wantV := range want {
+		gotV := got[k]
+		assert.Equal(wantV, gotV, "key '%s'", k)
+	}
+}
+
+func TestImport_FullyQualifiedModule(t *testing.T) {
+	tests := []struct {
+		name string
+		imp  Import
+		want string
+	}{
+		{
+			name: "name only",
+			imp:  Import{Name: "foo"},
+			want: "foo",
+		},
+		{
+			name: "parent only",
+			imp:  Import{ParentModule: "."},
+			want: ".",
+		},
+		{
+			name: "name and parent relative",
+			imp:  Import{ParentModule: ".", Name: "foo"},
+			want: ".foo",
+		},
+		{
+			name: "name and parent absolute",
+			imp:  Import{ParentModule: "blah", Name: "foo"},
+			want: "blah.foo",
+		},
+		{
+			name: "many parents",
+			imp:  Import{ParentModule: "x.y.z", Name: "foo"},
+			want: "x.y.z.foo",
+		},
+		{
+			name: "many parents relative",
+			imp:  Import{ParentModule: ".x.y.z", Name: "foo"},
+			want: ".x.y.z.foo",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.imp.FullyQualifiedModule()
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
diff --git a/pkg/lang/python/plugin_persist_test.go b/pkg/lang/python/plugin_persist_test.go
index 246c36d6..32e2cd50 100644
--- a/pkg/lang/python/plugin_persist_test.go
+++ b/pkg/lang/python/plugin_persist_test.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/testutil"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -29,11 +30,18 @@ func Test_persister_queryKV(t *testing.T) {
 			matchName:       "myCache",
 			matchExpression: "myCache=aiocache.Cache(aiocache.Cache.MEMORY)",
 		},
+		{
+			name: "aiocache import with alias",
+			source: testutil.UnIndent(`
+				import aiocache as a
+				myCache=a.Cache(a.Cache.MEMORY)`),
+			matchName:       "myCache",
+			matchExpression: "myCache=a.Cache(a.Cache.MEMORY)",
+		},
 		{
 			name:   "other 'Cache' function not matched",
 			source: "import other\nmyCache=other.Cache(aiocache.Cache.MEMORY)\nmyCache=Cache(aiocache.Cache.MEMORY)",
 		},
-		// TODO: add cases for import aliases when adding alias support
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -162,54 +170,127 @@ myCache = Cache(cache_class=keyvalue.KVStore, my_arg="value", serializer=keyvalu
 
 // FS Tests
 func Test_persister_queryFs(t *testing.T) {
-	tests := []struct {
-		name            string
-		source          string
+	type result struct {
 		matchName       string
 		matchExpression string
+	}
+	tests := []struct {
+		name    string
+		imports []string
+		// want is a slice of results, each corresponding to one top-level node in the source
+		want []result
 	}{
 		{
-			name:            "aiofiles import match",
-			source:          "import aiofiles",
-			matchName:       "aiofiles",
-			matchExpression: "import aiofiles",
+			name:    "aiofiles import match",
+			imports: []string{"import aiofiles"},
+			want: []result{
+				{
+					matchName:       "aiofiles",
+					matchExpression: "import aiofiles",
+				},
+			},
+		},
+		{
+			name:    "aiofiles import alias match",
+			imports: []string{"import aiofiles as fs"},
+			want: []result{
+				{
+					matchName:       "fs",
+					matchExpression: "import aiofiles as fs",
+				},
+			},
+		},
+		{
+			name:    "other 'import not matched",
+			imports: []string{"import other"},
+			want:    []result{{}},
+		},
+		{
+			name:    "imported with alias",
+			imports: []string{`import aiofiles as fs`},
+			want: []result{
+				{
+					matchName:       "fs",
+					matchExpression: `import aiofiles as fs`,
+				},
+			},
+		},
+		{
+			name: "imported twice with different aliases",
+			imports: []string{
+				`import aiofiles as first`,
+				`import aiofiles as second`,
+			},
+			want: []result{
+				{
+					matchName:       "first",
+					matchExpression: `import aiofiles as first`,
+				},
+				{
+					matchName:       "second",
+					matchExpression: `import aiofiles as second`,
+				},
+			},
+		},
+		{
+			name:    "attribute import",
+			imports: []string{`from aiofiles import open`},
+			want: []result{
+				{},
+			},
 		},
 		{
-			name:            "aiofiles import alias match",
-			source:          "import aiofiles as fs",
-			matchName:       "fs",
-			matchExpression: "import aiofiles as fs",
+			name:    "attribute import with alias",
+			imports: []string{`from aiofiles import open as o`},
+			want: []result{
+				{},
+			},
 		},
 		{
-			name:   "other 'import not matched",
-			source: "import other",
+			name: "both supported and unsupported imports",
+			imports: []string{
+				`import aiofiles`,
+				`from aiofiles import open`,
+			},
+			want: []result{
+				{
+					matchName:       "aiofiles",
+					matchExpression: `import aiofiles`,
+				},
+				{},
+			},
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			assert := assert.New(t)
 
-			f, err := NewFile("test.py", strings.NewReader(tt.source))
+			f, err := NewFile("test.py", strings.NewReader(strings.Join(tt.imports, "\n")))
 			if !assert.NoError(err) {
 				return
 			}
 
-			cap := &core.Annotation{
-				Capability: &annotation.Capability{Name: annotation.PersistCapability},
-				Node:       f.Tree().RootNode(),
-			}
+			for idx, imp := range tt.imports {
+				childNode := testutil.FindNodeByContent(f.Tree().RootNode(), imp)
+				want := tt.want[idx]
 
-			p := persister{}
+				cap := &core.Annotation{
+					Capability: &annotation.Capability{Name: annotation.PersistCapability},
+					Node:       childNode,
+				}
 
-			kvResult := p.queryFS(f, cap, true)
+				p := persister{}
 
-			if tt.matchExpression != "" || tt.matchName != "" {
-				if assert.NotNil(kvResult) {
-					assert.Equal(tt.matchExpression, kvResult.expression)
-					assert.Equal(tt.matchName, kvResult.name)
+				kvResult := p.queryFS(f, cap, true)
+
+				if want.matchExpression != "" || want.matchName != "" {
+					if assert.NotNil(kvResult) {
+						assert.Equal(want.matchExpression, kvResult.expression)
+						assert.Equal(want.matchName, kvResult.name)
+					}
+				} else {
+					assert.Nilf(kvResult, "for item %d", idx)
 				}
-			} else {
-				assert.Nil(kvResult)
 			}
 		})
 	}
@@ -467,110 +548,132 @@ import klotho_runtime.secret as fs`,
 func Test_persister_queryOrm(t *testing.T) {
 	tests := []struct {
 		name            string
-		source          string
+		imports         string
+		annotatedLine   string
 		matchName       string
 		matchExpression string
 	}{
 		{
-			name: "create engine constructor import match",
-			source: `
-			from sqlalchemy import create_engine
-			engine = create_engine("sqlite://", echo=True, future=True)
-			`,
+			name:            "create engine constructor import match",
+			imports:         `from sqlalchemy import create_engine`,
+			annotatedLine:   `engine = create_engine("sqlite://", echo=True, future=True)`,
 			matchName:       "engine",
 			matchExpression: "\"sqlite://\"",
 		},
 		{
-			name: "sqlalchemy import match",
-			source: `
-			import sqlalchemy
-			engine = sqlalchemy.create_engine("sqlite://", echo=True, future=True)
-			`,
+			name:            "sqlalchemy import match",
+			imports:         `import sqlalchemy`,
+			annotatedLine:   `engine = sqlalchemy.create_engine("sqlite://", echo=True, future=True)`,
 			matchName:       "engine",
 			matchExpression: "\"sqlite://\"",
 		},
 		{
-			name: "sqlalchemy import match as alias",
-			source: `
-			import sqlalchemy as sql
-			engine = sql.create_engine("sqlite://", echo=True, future=True)
-			`,
+			name:            "sqlalchemy import match as alias",
+			imports:         `import sqlalchemy as sql`,
+			annotatedLine:   `engine = sql.create_engine("sqlite://", echo=True, future=True)`,
 			matchName:       "engine",
 			matchExpression: "\"sqlite://\"",
 		},
 		{
-			name: "sqlalchemy import match as attribute alias",
-			source: `
-			from sqlalchemy import create_engine as eng
-			engine = eng("sqlite://", echo=True, future=True)
-			`,
+			name:            "sqlalchemy import match as attribute alias",
+			imports:         `from sqlalchemy import create_engine as eng`,
+			annotatedLine:   `engine = eng("sqlite://", echo=True, future=True)`,
 			matchName:       "engine",
 			matchExpression: "\"sqlite://\"",
 		},
 		{
-			name: "create engine constructor import match only conn string",
-			source: `
-			from sqlalchemy import create_engine
-			engine = create_engine("sqlite://")
-			`,
+			name:            "create engine constructor import match only conn string",
+			imports:         `from sqlalchemy import create_engine`,
+			annotatedLine:   `engine = create_engine("sqlite://")`,
 			matchName:       "engine",
 			matchExpression: "\"sqlite://\"",
 		},
 		{
-			name: "create engine constructor import match no conn string",
-			source: `
-			from sqlalchemy import create_engine
-			engine = create_engine()
-			`,
+			name:          "create engine constructor import match no conn string",
+			imports:       `from sqlalchemy import create_engine`,
+			annotatedLine: `engine = create_engine()`,
 		},
 		{
-			name: "create engine constructor import match only conn string as fstring",
-			source: `
-			from sqlalchemy import create_engine
-			engine = create_engine(f'sqlite://{foo}')
-			`,
+			name:            "create engine constructor import match only conn string as fstring",
+			imports:         `from sqlalchemy import create_engine`,
+			annotatedLine:   `engine = create_engine(f'sqlite://{foo}')`,
 			matchName:       "engine",
 			matchExpression: "f'sqlite://{foo}'",
 		},
 		{
-			name: "create engine constructor import match only conn string as method",
-			source: `
-			from sqlalchemy import create_engine
-			engine = create_engine(method())
-			`,
+			name:            "create engine constructor import match only conn string as method",
+			imports:         `from sqlalchemy import create_engine`,
+			annotatedLine:   `engine = create_engine(method())`,
 			matchName:       "engine",
 			matchExpression: "method()",
 		},
 		{
 			name: "other import function not matched",
-			source: `
-			import other
-			import sqlalchemy
-			myCache = other.create_engine("sqlite://", echo=True, future=True)
-			`,
+			imports: testutil.UnIndent(`
+				import other
+				import sqlalchemy`),
+			annotatedLine: `myCache = other.create_engine("sqlite://", echo=True, future=True)`,
 		},
 		{
 			name: "other 'create_engine' function not matched",
-			source: `
-			from other import create_engine
-			from sqlalchemy import something
-			myCache = other.create_engine("sqlite://", echo=True, future=True)
-			`,
+			imports: testutil.UnIndent(`
+				from other import create_engine
+				from sqlalchemy import something`),
+			annotatedLine: `myCache = other.create_engine("sqlite://", echo=True, future=True)`,
+		},
+		{
+			name:            "import sqlalchemy with alias",
+			imports:         `import sqlalchemy as s`,
+			annotatedLine:   `engine = s.create_engine("sqlite://", echo=True, future=True)`,
+			matchName:       "engine",
+			matchExpression: "\"sqlite://\"",
+		},
+		{
+			name:            "import sqlalchemy with alias",
+			imports:         `import sqlalchemy as s`,
+			annotatedLine:   `engine = s.create_engine("sqlite://", echo=True, future=True)`,
+			matchName:       "engine",
+			matchExpression: "\"sqlite://\"",
+		},
+		{
+			name:            "import create_engine with alias",
+			imports:         `from sqlalchemy import create_engine as ce`,
+			annotatedLine:   `engine = ce(method())`,
+			matchName:       "engine",
+			matchExpression: "method()",
+		},
+		{
+			name: "multiple imports with alias",
+			imports: testutil.UnIndent(`
+				import sqlalchemy
+				from sqlalchemy import create_engine as something
+				from sqlalchemy import create_engine as ce`),
+			annotatedLine:   `engine = ce(method())`,
+			matchName:       "engine",
+			matchExpression: "method()",
 		},
-		// TODO: add cases for import aliases when adding alias support
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			assert := assert.New(t)
 
-			f, err := NewFile("test.py", strings.NewReader(tt.source))
+			if !assert.NotContains(tt.annotatedLine, "\n", `bug in the test: "annotated"" line must be a single line`) {
+				return
+			}
+
+			f, err := NewFile("test.py", strings.NewReader(tt.imports+"\n"+tt.annotatedLine))
 			if !assert.NoError(err) {
 				return
 			}
 
+			annotatedNode := testutil.FindNodeByContent(f.Tree().RootNode(), tt.annotatedLine)
+			if !assert.NotNil(annotatedNode, "bug in the test: couldn't find annotated node") {
+				return
+			}
+
 			cap := &core.Annotation{
 				Capability: &annotation.Capability{Name: annotation.PersistCapability},
-				Node:       f.Tree().RootNode(),
+				Node:       annotatedNode,
 			}
 
 			p := persister{}
diff --git a/pkg/provider/aws/execution_unit_test.go b/pkg/provider/aws/execution_unit_test.go
index f43be46e..81b8caee 100644
--- a/pkg/provider/aws/execution_unit_test.go
+++ b/pkg/provider/aws/execution_unit_test.go
@@ -1,6 +1,7 @@
 package aws
 
 import (
+	"fmt"
 	"testing"
 
 	"github.com/klothoplatform/klotho/pkg/annotation"
@@ -13,6 +14,60 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_ExpandExecutionUnit(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}, DockerfilePath: "path"}
+	cases := []struct {
+		name   string
+		unit   *core.ExecutionUnit
+		chart  *kubernetes.HelmChart
+		config *config.Application
+		want   coretesting.ResourcesExpectation
+	}{
+		{
+			name:   "single lambda exec unit",
+			unit:   eu,
+			config: &config.Application{AppName: "my-app", Defaults: config.Defaults{ExecutionUnit: config.KindDefaults{Type: Lambda}}},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:ecr_image:my-app-test",
+					"aws:ecr_repo:my-app",
+					"aws:iam_role:my-app-test-ExecutionRole",
+					"aws:lambda_function:my-app-test",
+					"aws:log_group:my-app-test",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:ecr_image:my-app-test", Destination: "aws:ecr_repo:my-app"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:ecr_image:my-app-test"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:iam_role:my-app-test-ExecutionRole"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:log_group:my-app-test"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.chart != nil {
+				dag.AddResource(tt.chart)
+			}
+
+			aws := AWS{
+				Config: tt.config,
+			}
+			err := aws.expandExecutionUnit(dag, tt.unit)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+			fmt.Println(coretesting.ResoucesFromDAG(dag).GoString())
+			resource := aws.GetResourceTiedToConstruct(tt.unit)
+			assert.NotNil(resource)
+		})
+	}
+}
+
 func Test_GenerateExecUnitResources(t *testing.T) {
 	unit := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
 	fs := &core.Fs{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
diff --git a/pkg/provider/aws/knowledgebase/edge_configure_test.go b/pkg/provider/aws/knowledgebase/edge_configure_test.go
new file mode 100644
index 00000000..f5b7bc8f
--- /dev/null
+++ b/pkg/provider/aws/knowledgebase/edge_configure_test.go
@@ -0,0 +1,181 @@
+package knowledgebase
+
+import (
+	"testing"
+
+	dgraph "github.com/dominikbraun/graph"
+	"github.com/klothoplatform/klotho/pkg/annotation"
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/graph"
+	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base"
+	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_ConfigureEdge(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	cases := []struct {
+		name string
+		edge []graph.Edge[core.Resource]
+		want []core.Resource
+	}{
+		{
+			name: "single rds lambda",
+			edge: []graph.Edge[core.Resource]{
+				{
+					Source:      &resources.LambdaFunction{Name: "lambda", Subnets: []*resources.Subnet{{Name: "sub1"}}, Role: &resources.IamRole{}, EnvironmentVariables: make(resources.EnvironmentVariables)},
+					Destination: &resources.RdsInstance{Name: "rds"},
+					Properties: dgraph.EdgeProperties{
+						Data: knowledgebase.EdgeData{
+							AppName:              "my-app",
+							Source:               &resources.LambdaFunction{Name: "lambda"},
+							Destination:          &resources.RdsInstance{Name: "rds"},
+							EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
+						},
+					},
+				},
+			},
+			want: []core.Resource{&resources.LambdaFunction{
+				Name:    "lambda",
+				Subnets: []*resources.Subnet{{Name: "sub1"}},
+				Role: &resources.IamRole{
+					InlinePolicies: []*resources.IamInlinePolicy{
+						{
+							Name:          "rds-connectionpolicy",
+							ConstructsRef: []core.AnnotationKey{},
+							Policy: &resources.PolicyDocument{
+								Version: "2012-10-17",
+								Statement: []resources.StatementEntry{
+									{
+										Effect:   "Allow",
+										Action:   []string{"rds-db:connect"},
+										Resource: []core.IaCValue{{Resource: &resources.RdsInstance{Name: "rds"}, Property: resources.RDS_CONNECTION_ARN_IAC_VALUE}},
+									},
+								},
+							},
+						},
+					},
+				},
+				EnvironmentVariables: resources.EnvironmentVariables{"TEST_PERSIST_ORM_CONNECTION": core.IaCValue{Resource: &resources.RdsInstance{Name: "rds"}, Property: string(core.CONNECTION_STRING)}},
+			},
+				&resources.RdsInstance{Name: "rds"},
+			},
+		},
+		{
+			name: "single rds proxy and lambda",
+			edge: []graph.Edge[core.Resource]{
+				{
+					Source:      &resources.LambdaFunction{Name: "lambda", Subnets: []*resources.Subnet{{Name: "sub1"}}, Role: &resources.IamRole{}, EnvironmentVariables: make(resources.EnvironmentVariables)},
+					Destination: &resources.RdsProxy{Name: "rds", Role: &resources.IamRole{Name: "ProxyRole"}, Auths: []*resources.ProxyAuth{{SecretArn: core.IaCValue{Resource: &resources.Secret{Name: "Secret"}}}}},
+					Properties: dgraph.EdgeProperties{
+						Data: knowledgebase.EdgeData{
+							AppName:              "my-app",
+							Source:               &resources.LambdaFunction{Name: "lambda"},
+							Destination:          &resources.RdsInstance{Name: "rds"},
+							EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
+						},
+					},
+				},
+				{
+					Source:      &resources.RdsProxyTargetGroup{Name: "rds"},
+					Destination: &resources.RdsProxy{Name: "rds", Role: &resources.IamRole{Name: "ProxyRole"}, Auths: []*resources.ProxyAuth{{SecretArn: core.IaCValue{Resource: &resources.Secret{Name: "Secret"}}}}},
+					Properties: dgraph.EdgeProperties{
+						Data: knowledgebase.EdgeData{
+							AppName:              "my-app",
+							Source:               &resources.LambdaFunction{Name: "lambda"},
+							Destination:          &resources.RdsInstance{Name: "rds"},
+							EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
+						},
+					},
+				},
+				{
+					Source:      &resources.RdsProxy{Name: "rds"},
+					Destination: &resources.Secret{Name: "Secret"},
+				},
+				{
+					Source:      &resources.Secret{Name: "Secret"},
+					Destination: &resources.SecretVersion{Name: "sv"},
+				},
+				{
+					Source:      &resources.RdsProxy{Name: "rds"},
+					Destination: &resources.Secret{Name: "Secret"},
+				},
+				{
+					Source: &resources.RdsProxyTargetGroup{
+						Name: "rds",
+						RdsProxy: &resources.RdsProxy{
+							Name:  "rds",
+							Auths: []*resources.ProxyAuth{{SecretArn: core.IaCValue{Resource: &resources.Secret{Name: "Secret"}}}},
+						},
+					},
+					Destination: &resources.RdsInstance{Name: "instance", CredentialsFile: &core.FileRef{FPath: "rds"}},
+					Properties: dgraph.EdgeProperties{
+						Data: knowledgebase.EdgeData{
+							AppName:              "my-app",
+							Source:               &resources.LambdaFunction{Name: "lambda"},
+							Destination:          &resources.RdsInstance{Name: "rds"},
+							EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
+						},
+					},
+				},
+			},
+			want: []core.Resource{
+				&resources.LambdaFunction{
+					Name:    "lambda",
+					Subnets: []*resources.Subnet{{Name: "sub1"}},
+					Role: &resources.IamRole{
+						InlinePolicies: []*resources.IamInlinePolicy{
+							{
+								Name:          "instance-connectionpolicy",
+								ConstructsRef: []core.AnnotationKey{},
+								Policy: &resources.PolicyDocument{
+									Version: "2012-10-17",
+									Statement: []resources.StatementEntry{
+										{
+											Effect:   "Allow",
+											Action:   []string{"rds-db:connect"},
+											Resource: []core.IaCValue{{Resource: &resources.RdsInstance{Name: "instance", CredentialsFile: &core.FileRef{FPath: "rds"}}, Property: resources.RDS_CONNECTION_ARN_IAC_VALUE}},
+										},
+									},
+								},
+							},
+						},
+					},
+					EnvironmentVariables: resources.EnvironmentVariables{"TEST_PERSIST_ORM_CONNECTION": core.IaCValue{
+						Resource: &resources.RdsProxy{
+							Name:  "rds",
+							Role:  &resources.IamRole{Name: "ProxyRole"},
+							Auths: []*resources.ProxyAuth{{SecretArn: core.IaCValue{Resource: &resources.Secret{Name: "Secret"}}}},
+						},
+						Property: string(core.CONNECTION_STRING),
+					},
+					},
+				},
+				&resources.RdsProxy{
+					Name:  "rds",
+					Role:  &resources.IamRole{Name: "ProxyRole"},
+					Auths: []*resources.ProxyAuth{{SecretArn: core.IaCValue{Resource: &resources.Secret{Name: "Secret"}}}},
+				},
+				&resources.SecretVersion{Name: "sv", Path: "rds"},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			for _, edge := range tt.edge {
+				dag.AddDependencyWithData(edge.Source, edge.Destination, edge.Properties.Data)
+			}
+
+			err := AwsKB.ConfigureFromEdgeData(dag)
+			if !assert.NoError(err) {
+				return
+			}
+			for _, res := range tt.want {
+				assert.Equal(res, dag.GetResource(res.Id()))
+			}
+		})
+	}
+}
diff --git a/pkg/provider/aws/knowledgebase/edge_expansion_test.go b/pkg/provider/aws/knowledgebase/edge_expansion_test.go
new file mode 100644
index 00000000..9a487993
--- /dev/null
+++ b/pkg/provider/aws/knowledgebase/edge_expansion_test.go
@@ -0,0 +1,121 @@
+package knowledgebase
+
+import (
+	"testing"
+
+	dgraph "github.com/dominikbraun/graph"
+	"github.com/klothoplatform/klotho/pkg/annotation"
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
+	"github.com/klothoplatform/klotho/pkg/graph"
+	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base"
+	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_ExpandEdges(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	cases := []struct {
+		name string
+		edge graph.Edge[core.Resource]
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "single rds orm",
+			edge: graph.Edge[core.Resource]{
+				Source:      &resources.LambdaFunction{Name: "lambda"},
+				Destination: &resources.RdsInstance{Name: "rds"},
+				Properties: dgraph.EdgeProperties{
+					Data: knowledgebase.EdgeData{
+						AppName:     "my-app",
+						Source:      &resources.LambdaFunction{Name: "lambda"},
+						Destination: &resources.RdsInstance{Name: "rds"},
+						Constraint: knowledgebase.EdgeConstraint{
+							NodeMustExist:    []core.Resource{&resources.RdsProxy{}},
+							NodeMustNotExist: []core.Resource{&resources.IamRole{}},
+						},
+						EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
+					},
+				},
+			},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:elastic_ip:my_app_1",
+					"aws:iam_policy:my-app-my-app-rds-ormsecretpolicy",
+					"aws:iam_role:my-app-rds-ProxyRole",
+					"aws:internet_gateway:my_app_igw",
+					"aws:lambda_function:lambda",
+					"aws:nat_gateway:my_app_0",
+					"aws:nat_gateway:my_app_1",
+					"aws:rds_instance:rds",
+					"aws:rds_proxy:my-app-rds",
+					"aws:rds_proxy_target_group:my-app-rds",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:secret:my-app-my-app-rds",
+					"aws:secret_version:my-app-my-app-rds",
+					"aws:security_group:my_app:my-app",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:subnet_public:my_app:my_app_public1",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:iam_policy:my-app-my-app-rds-ormsecretpolicy", Destination: "aws:secret_version:my-app-my-app-rds"},
+					{Source: "aws:iam_role:my-app-rds-ProxyRole", Destination: "aws:iam_policy:my-app-my-app-rds-ormsecretpolicy"},
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:rds_proxy:my-app-rds", Destination: "aws:iam_role:my-app-rds-ProxyRole"},
+					{Source: "aws:rds_proxy:my-app-rds", Destination: "aws:secret:my-app-my-app-rds"},
+					{Source: "aws:rds_proxy:my-app-rds", Destination: "aws:secret_version:my-app-my-app-rds"},
+					{Source: "aws:rds_proxy:my-app-rds", Destination: "aws:security_group:my_app:my-app"},
+					{Source: "aws:rds_proxy:my-app-rds", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:rds_proxy:my-app-rds", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:rds_proxy_target_group:my-app-rds", Destination: "aws:rds_instance:rds"},
+					{Source: "aws:rds_proxy_target_group:my-app-rds", Destination: "aws:rds_proxy:my-app-rds"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:secret_version:my-app-my-app-rds", Destination: "aws:secret:my-app-my-app-rds"},
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			dag.AddDependencyWithData(tt.edge.Source, tt.edge.Destination, tt.edge.Properties.Data)
+
+			err := AwsKB.ExpandEdges(dag)
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+		})
+	}
+}
diff --git a/pkg/provider/aws/orm_test.go b/pkg/provider/aws/orm_test.go
index 37f0b1d8..c07b3a88 100644
--- a/pkg/provider/aws/orm_test.go
+++ b/pkg/provider/aws/orm_test.go
@@ -3,12 +3,98 @@ package aws
 import (
 	"testing"
 
+	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
 	"github.com/klothoplatform/klotho/pkg/core/coretesting"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_ExpandOrm(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	cases := []struct {
+		name   string
+		orm    *core.Orm
+		config *config.Application
+		want   coretesting.ResourcesExpectation
+	}{
+		{
+			name:   "single rds orm",
+			orm:    orm,
+			config: &config.Application{AppName: "my-app", Defaults: config.Defaults{PersistOrm: config.KindDefaults{Type: Rds_postgres}}},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:elastic_ip:my_app_1",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_0",
+					"aws:nat_gateway:my_app_1",
+					"aws:rds_instance:my-app-test",
+					"aws:rds_subnet_group:my-app-test",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:security_group:my_app:my-app",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:subnet_public:my_app:my_app_public1",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:rds_instance:my-app-test", Destination: "aws:rds_subnet_group:my-app-test"},
+					{Source: "aws:rds_instance:my-app-test", Destination: "aws:security_group:my_app:my-app"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			aws := AWS{
+				Config: tt.config,
+			}
+			err := aws.expandOrm(dag, tt.orm)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+			resource := aws.GetResourceTiedToConstruct(tt.orm)
+			assert.NotNil(resource)
+		})
+	}
+}
+
 func Test_GenerateOrmResources(t *testing.T) {
 	appName := "test-app"
 	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test"}}
diff --git a/pkg/provider/aws/plugin_test.go b/pkg/provider/aws/plugin_test.go
index c0c4c2d7..265ddbca 100644
--- a/pkg/provider/aws/plugin_test.go
+++ b/pkg/provider/aws/plugin_test.go
@@ -4,14 +4,253 @@ import (
 	"fmt"
 	"testing"
 
+	dgraph "github.com/dominikbraun/graph"
 	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
+	"github.com/klothoplatform/klotho/pkg/graph"
 	"github.com/klothoplatform/klotho/pkg/infra/kubernetes"
+	knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base"
 	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_ExpandConstructs(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}, DockerfilePath: "path"}
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	cases := []struct {
+		name       string
+		constructs []core.Construct
+		config     *config.Application
+		want       coretesting.ResourcesExpectation
+	}{
+		{
+			name:       "lambda and rds",
+			constructs: []core.Construct{eu, orm},
+			config: &config.Application{
+				AppName: "my-app",
+				Defaults: config.Defaults{
+					ExecutionUnit: config.KindDefaults{Type: Lambda},
+					PersistOrm:    defaultConfig.PersistOrm,
+				},
+			},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:ecr_image:my-app-test",
+					"aws:ecr_repo:my-app",
+					"aws:elastic_ip:my_app_0",
+					"aws:elastic_ip:my_app_1",
+					"aws:iam_role:my-app-test-ExecutionRole",
+					"aws:internet_gateway:my_app_igw",
+					"aws:lambda_function:my-app-test",
+					"aws:log_group:my-app-test",
+					"aws:nat_gateway:my_app_0",
+					"aws:nat_gateway:my_app_1",
+					"aws:rds_instance:my-app-test",
+					"aws:rds_subnet_group:my-app-test",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:security_group:my_app:my-app",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:subnet_public:my_app:my_app_public1",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:ecr_image:my-app-test", Destination: "aws:ecr_repo:my-app"},
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:ecr_image:my-app-test"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:iam_role:my-app-test-ExecutionRole"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:log_group:my-app-test"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:rds_instance:my-app-test", Destination: "aws:rds_subnet_group:my-app-test"},
+					{Source: "aws:rds_instance:my-app-test", Destination: "aws:security_group:my_app:my-app"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			result := core.NewConstructGraph()
+
+			for _, construct := range tt.constructs {
+				result.AddConstruct(construct)
+			}
+
+			aws := AWS{
+				Config: tt.config,
+			}
+			err := aws.ExpandConstructs(result, dag)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+		})
+	}
+}
+
+func Test_CopyConstructEdgesToDag(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+
+	eu := &core.ExecutionUnit{
+		AnnotationKey:        core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability},
+		EnvironmentVariables: core.EnvironmentVariables{core.GenerateOrmConnStringEnvVar(orm)},
+	}
+	cases := []struct {
+		name                 string
+		constructs           []graph.Edge[core.Construct]
+		config               *config.Application
+		constructResourceMap map[string]core.Resource
+		want                 []*graph.Edge[core.Resource]
+	}{
+		{
+			name: "lambda and rds",
+			constructs: []graph.Edge[core.Construct]{
+				{Source: eu, Destination: orm},
+			},
+			config: &config.Application{
+				AppName: "my-app",
+			},
+			constructResourceMap: map[string]core.Resource{
+				"execution_unit:test": &resources.LambdaFunction{Name: "lambda"},
+				"persist:test":        &resources.RdsInstance{Name: "rds"},
+			},
+			want: []*graph.Edge[core.Resource]{
+				{Source: &resources.LambdaFunction{Name: "lambda"}, Destination: &resources.RdsInstance{Name: "rds"}, Properties: dgraph.EdgeProperties{
+					Attributes: make(map[string]string),
+					Data: knowledgebase.EdgeData{
+						AppName:     "my-app",
+						Source:      &resources.LambdaFunction{Name: "lambda"},
+						Destination: &resources.RdsInstance{Name: "rds"},
+						Constraint: knowledgebase.EdgeConstraint{
+							NodeMustExist:    []core.Resource{&resources.RdsProxy{}},
+							NodeMustNotExist: []core.Resource{&resources.IamRole{}},
+						},
+						EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
+					},
+				}},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			result := core.NewConstructGraph()
+
+			for _, dep := range tt.constructs {
+				result.AddConstruct(dep.Source)
+				result.AddConstruct(dep.Destination)
+				result.AddDependency(dep.Source.Id(), dep.Destination.Id())
+			}
+			for _, res := range tt.constructResourceMap {
+				dag.AddResource(res)
+			}
+			aws := AWS{
+				Config:                tt.config,
+				constructIdToResource: tt.constructResourceMap,
+			}
+			err := aws.CopyConstructEdgesToDag(result, dag)
+
+			if !assert.NoError(err) {
+				return
+			}
+			for _, dep := range tt.want {
+				edge := dag.GetDependency(dep.Source, dep.Destination)
+				assert.Equal(edge, dep)
+			}
+		})
+	}
+}
+
+func Test_configureResources(t *testing.T) {
+
+	cases := []struct {
+		name       string
+		config     *config.Application
+		constructs []core.Construct
+		resources  []core.Resource
+		want       []core.Resource
+	}{
+		{
+			name: "lambda and rds",
+			config: &config.Application{
+				AppName: "my-app",
+				ExecutionUnits: map[string]*config.ExecutionUnit{
+					"test": &config.ExecutionUnit{
+						InfraParams: config.ConvertToInfraParams(config.ServerlessTypeParams{Timeout: 100, Memory: 200}),
+					},
+				},
+			},
+			constructs: []core.Construct{
+				&core.ExecutionUnit{
+					AnnotationKey:        core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability},
+					EnvironmentVariables: core.EnvironmentVariables{core.NewEnvironmentVariable("env1", nil, "val1")}},
+			},
+			resources: []core.Resource{&resources.LambdaFunction{Name: "lambda", ConstructsRef: []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}}}, &resources.RdsProxy{Name: "rds"}},
+			want: []core.Resource{
+				&resources.LambdaFunction{Name: "lambda", Timeout: 100, MemorySize: 200, ConstructsRef: []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}}, EnvironmentVariables: resources.EnvironmentVariables{"env1": core.IaCValue{Property: "val1"}}},
+				&resources.RdsProxy{Name: "rds", EngineFamily: "POSTGRESQL", IdleClientTimeout: 1800}},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			result := core.NewConstructGraph()
+			for _, construct := range tt.constructs {
+				result.AddConstruct(construct)
+			}
+			for _, res := range tt.resources {
+				dag.AddResource(res)
+			}
+			aws := AWS{
+				Config: tt.config,
+			}
+			err := aws.configureResources(result, dag)
+
+			if !assert.NoError(err) {
+				return
+			}
+			for _, res := range tt.want {
+				graphRes := dag.GetResource(res.Id())
+				assert.Equal(graphRes, res)
+			}
+		})
+	}
+}
+
 func Test_shouldCreateNetwork(t *testing.T) {
 	cases := []struct {
 		name       string
diff --git a/pkg/provider/aws/provider_test.go b/pkg/provider/aws/provider_test.go
index 98f6bb5a..9168a85f 100644
--- a/pkg/provider/aws/provider_test.go
+++ b/pkg/provider/aws/provider_test.go
@@ -50,6 +50,26 @@ func TestAwsMapResourceDirectlyToConstruct(t *testing.T) {
 	})
 }
 
+func TestAwsMapResourceToConstruct(t *testing.T) {
+	t.Run("empty AWS struct, MapResourceDirectlyToConstruct", func(t *testing.T) {
+		assert := assert.New(t)
+		a := AWS{}
+		err := a.MapResourceToConstruct(dummyResource("res"), dummyConstruct("cons"))
+		assert.NoError(err)
+		assert.Equal(
+			map[string]core.Resource{
+				"cons": dummyResource("res"),
+			},
+			a.constructIdToResource)
+	})
+	t.Run("empty AWS struct, MapResourceDirectlyToConstruct", func(t *testing.T) {
+		assert := assert.New(t)
+		a := AWS{}
+		res := a.GetResourceTiedToConstruct(dummyConstruct("cons"))
+		assert.Nil(res)
+	})
+}
+
 type (
 	dummyResource  string
 	dummyConstruct string
@@ -60,6 +80,9 @@ func (dr dummyResource) KlothoConstructRef() []core.AnnotationKey { return nil }
 func (dr dummyResource) Id() core.ResourceId {
 	return core.ResourceId{Provider: "test", Type: "", Name: string(dr)}
 }
+func (f dummyResource) Create(dag *core.ResourceGraph, metadata map[string]any) (core.Resource, error) {
+	return nil, nil
+}
 
 func (dc dummyConstruct) Provenance() core.AnnotationKey { return core.AnnotationKey{} }
 
diff --git a/pkg/provider/aws/resources/cloudwatch_test.go b/pkg/provider/aws/resources/cloudwatch_test.go
new file mode 100644
index 00000000..844805b3
--- /dev/null
+++ b/pkg/provider/aws/resources/cloudwatch_test.go
@@ -0,0 +1,73 @@
+package resources
+
+import (
+	"testing"
+
+	"github.com/klothoplatform/klotho/pkg/annotation"
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_CloudwatchLogGroupCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name     string
+		logGroup *LogGroup
+		want     coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil repo",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:log_group:my-app-log-group",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name:     "existing repo",
+			logGroup: &LogGroup{Name: "my-app-log-group", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:log_group:my-app-log-group",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.logGroup != nil {
+				dag.AddResource(tt.logGroup)
+			}
+			metadata := CloudwatchLogGroupCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+				Name:    "log-group",
+			}
+
+			logGroup := &LogGroup{}
+			err := logGroup.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+
+			tt.want.Assert(t, dag)
+
+			graphLogGroup := dag.GetResourceByVertexId(logGroup.Id().String())
+			logGroup = graphLogGroup.(*LogGroup)
+
+			assert.Equal(logGroup.Name, "my-app-log-group")
+			if tt.logGroup == nil {
+				assert.ElementsMatch(logGroup.ConstructsRef, metadata.Refs)
+			} else {
+				assert.Equal(logGroup, tt.logGroup)
+				assert.ElementsMatch(logGroup.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
diff --git a/pkg/provider/aws/resources/ecr_test.go b/pkg/provider/aws/resources/ecr_test.go
index 9d989921..e8eb3ba7 100644
--- a/pkg/provider/aws/resources/ecr_test.go
+++ b/pkg/provider/aws/resources/ecr_test.go
@@ -5,9 +5,128 @@ import (
 
 	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_RepositoryCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		repo *EcrRepository
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil repo",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:ecr_repo:my-app",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name: "existing repo",
+			repo: &EcrRepository{Name: "my-app", ConstructsRef: initialRefs, ForceDelete: true},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:ecr_repo:my-app",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.repo != nil {
+				dag.AddResource(tt.repo)
+			}
+			metadata := RepoCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+			}
+
+			repo := &EcrRepository{}
+			err := repo.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+
+			tt.want.Assert(t, dag)
+			graphRepo := dag.GetResource(repo.Id())
+			repo = graphRepo.(*EcrRepository)
+			assert.Equal(repo.Name, "my-app")
+			if tt.repo == nil {
+				assert.ElementsMatch(repo.ConstructsRef, metadata.Refs)
+			} else {
+				assert.Equal(repo, tt.repo)
+				assert.ElementsMatch(repo.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
+func Test_ImageCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name    string
+		image   *EcrImage
+		want    coretesting.ResourcesExpectation
+		wantErr bool
+	}{
+		{
+			name: "nil repo",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:ecr_image:my-app-test-unit",
+					"aws:ecr_repo:my-app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:ecr_image:my-app-test-unit", Destination: "aws:ecr_repo:my-app"},
+				},
+			},
+		},
+		{
+			name:    "existing image",
+			image:   &EcrImage{Name: "my-app-test-unit", ConstructsRef: initialRefs},
+			wantErr: true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.image != nil {
+				dag.AddResource(tt.image)
+			}
+			metadata := ImageCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+				Name:    "test-unit",
+			}
+			image := &EcrImage{}
+			err := image.Create(dag, metadata)
+
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+			graphImage := dag.GetResource(image.Id())
+			image = graphImage.(*EcrImage)
+
+			assert.Equal(image.Name, "my-app-test-unit")
+			assert.Equal(image.ConstructsRef, metadata.Refs)
+		})
+	}
+}
+
 func Test_GenerateExecUnitResources(t *testing.T) {
 	appName := "test-app"
 	unit := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
diff --git a/pkg/provider/aws/resources/eks_test.go b/pkg/provider/aws/resources/eks_test.go
index 52f5e3ce..f8f7ca20 100644
--- a/pkg/provider/aws/resources/eks_test.go
+++ b/pkg/provider/aws/resources/eks_test.go
@@ -236,11 +236,12 @@ func Test_createNodeRole(t *testing.T) {
 
 func Test_createNodeGroups(t *testing.T) {
 	cluster := &EksCluster{Name: "cluster"}
+	vpc := &Vpc{Name: "vpc"}
 	subnets := []*Subnet{
-		{Name: "private 1", Type: PrivateSubnet},
-		{Name: "private 2", Type: PrivateSubnet},
-		{Name: "public 1", Type: PublicSubnet},
-		{Name: "public 2", Type: PublicSubnet},
+		{Name: "private 1", Type: PrivateSubnet, Vpc: vpc},
+		{Name: "private 2", Type: PrivateSubnet, Vpc: vpc},
+		{Name: "public 1", Type: PublicSubnet, Vpc: vpc},
+		{Name: "public 2", Type: PublicSubnet, Vpc: vpc},
 	}
 	type NodeGroupExpect struct {
 		Name     string
diff --git a/pkg/provider/aws/resources/iam_test.go b/pkg/provider/aws/resources/iam_test.go
index a26e206e..6f132040 100644
--- a/pkg/provider/aws/resources/iam_test.go
+++ b/pkg/provider/aws/resources/iam_test.go
@@ -3,10 +3,118 @@ package resources
 import (
 	"testing"
 
+	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_RoleCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name    string
+		role    *IamRole
+		want    coretesting.ResourcesExpectation
+		wantErr bool
+	}{
+		{
+			name: "nil role",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:iam_role:my-app-executionRole",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name:    "existing role",
+			role:    &IamRole{Name: "my-app-executionRole", ConstructsRef: initialRefs},
+			wantErr: true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.role != nil {
+				dag.AddResource(tt.role)
+			}
+			metadata := RoleCreateParams{
+				AppName: "my-app",
+				Name:    "executionRole",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+			}
+			role := &IamRole{}
+			err := role.Create(dag, metadata)
+
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			assert.Equal(role.Name, "my-app-executionRole")
+			assert.Equal(role.ConstructsRef, metadata.Refs)
+		})
+	}
+}
+
+func Test_PolicyCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name    string
+		policy  *IamPolicy
+		want    coretesting.ResourcesExpectation
+		wantErr bool
+	}{
+		{
+			name: "nil policy",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:iam_policy:my-app-policy",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name:    "existing policy",
+			policy:  &IamPolicy{Name: "my-app-policy", ConstructsRef: initialRefs},
+			wantErr: true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.policy != nil {
+				dag.AddResource(tt.policy)
+			}
+			metadata := IamPolicyCreateParams{
+				AppName: "my-app",
+				Name:    "policy",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+			}
+			policy := &IamPolicy{}
+			err := policy.Create(dag, metadata)
+
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			assert.Equal(policy.Name, "my-app-policy")
+			assert.Equal(policy.ConstructsRef, metadata.Refs)
+		})
+	}
+}
+
 func Test_AddAllowPolicyToUnit(t *testing.T) {
 	bucket := NewS3Bucket(&core.Fs{}, "test-app")
 	unitId := "testUnit"
diff --git a/pkg/provider/aws/resources/lambda_test.go b/pkg/provider/aws/resources/lambda_test.go
new file mode 100644
index 00000000..2a669bc2
--- /dev/null
+++ b/pkg/provider/aws/resources/lambda_test.go
@@ -0,0 +1,77 @@
+package resources
+
+import (
+	"testing"
+
+	"github.com/klothoplatform/klotho/pkg/annotation"
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_LambdaCreate(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
+	cases := []struct {
+		name    string
+		lambda  *LambdaFunction
+		want    coretesting.ResourcesExpectation
+		wantErr bool
+	}{
+		{
+			name: "nil lambda",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:ecr_image:my-app-test",
+					"aws:ecr_repo:my-app",
+					"aws:iam_role:my-app-test-ExecutionRole",
+					"aws:lambda_function:my-app-test",
+					"aws:log_group:my-app-test",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:ecr_image:my-app-test", Destination: "aws:ecr_repo:my-app"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:ecr_image:my-app-test"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:iam_role:my-app-test-ExecutionRole"},
+					{Source: "aws:lambda_function:my-app-test", Destination: "aws:log_group:my-app-test"},
+				},
+			},
+		},
+		{
+			name:    "existing lambda",
+			lambda:  &LambdaFunction{Name: "my-app-test"},
+			wantErr: true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.lambda != nil {
+				dag.AddResource(tt.lambda)
+			}
+
+			metadata := LambdaCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{eu.AnnotationKey},
+				Name:    eu.ID,
+			}
+			lambda := &LambdaFunction{}
+			err := lambda.Create(dag, metadata)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphLambda := dag.GetResource(lambda.Id())
+			lambda = graphLambda.(*LambdaFunction)
+
+			assert.Equal(lambda.Name, "my-app-test")
+			assert.ElementsMatch(lambda.ConstructsRef, []core.AnnotationKey{eu.AnnotationKey})
+		})
+	}
+}
diff --git a/pkg/provider/aws/resources/rds_test.go b/pkg/provider/aws/resources/rds_test.go
index 83c83ed9..a4d7a47f 100644
--- a/pkg/provider/aws/resources/rds_test.go
+++ b/pkg/provider/aws/resources/rds_test.go
@@ -4,12 +4,397 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
 	"github.com/klothoplatform/klotho/pkg/core/coretesting"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_RdsInstanceCreate(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	cases := []struct {
+		name     string
+		instance *RdsInstance
+		want     coretesting.ResourcesExpectation
+		wantErr  bool
+	}{
+		{
+			name: "nil instance",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:elastic_ip:my_app_1",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_0",
+					"aws:nat_gateway:my_app_1",
+					"aws:rds_instance:my-app-test",
+					"aws:rds_subnet_group:my-app-test",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:security_group:my_app:my-app",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:subnet_public:my_app:my_app_public1",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:rds_instance:my-app-test", Destination: "aws:rds_subnet_group:my-app-test"},
+					{Source: "aws:rds_instance:my-app-test", Destination: "aws:security_group:my_app:my-app"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+		{
+			name:     "existing instance",
+			instance: &RdsInstance{Name: "my-app-test"},
+			wantErr:  true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.instance != nil {
+				dag.AddResource(tt.instance)
+			}
+
+			metadata := RdsInstanceCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{orm.AnnotationKey},
+				Name:    orm.ID,
+			}
+			instance := &RdsInstance{}
+			err := instance.Create(dag, metadata)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphInstance := dag.GetResource(instance.Id())
+			instance = graphInstance.(*RdsInstance)
+
+			assert.Equal(instance.Name, "my-app-test")
+			assert.ElementsMatch(instance.ConstructsRef, []core.AnnotationKey{orm.AnnotationKey})
+		})
+	}
+}
+
+func Test_RdsSubnetGroupCreate(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name        string
+		subnetGroup *RdsSubnetGroup
+		want        coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil instance",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:elastic_ip:my_app_1",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_0",
+					"aws:nat_gateway:my_app_1",
+					"aws:rds_subnet_group:my-app-test",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:subnet_public:my_app:my_app_public1",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:rds_subnet_group:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+		{
+			name:        "existing instance",
+			subnetGroup: &RdsSubnetGroup{Name: "my-app-test", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:rds_subnet_group:my-app-test",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.subnetGroup != nil {
+				dag.AddResource(tt.subnetGroup)
+			}
+
+			metadata := RdsSubnetGroupCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{orm.AnnotationKey},
+				Name:    orm.ID,
+			}
+			subnetGroup := &RdsSubnetGroup{}
+			err := subnetGroup.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphSG := dag.GetResource(subnetGroup.Id())
+			subnetGroup = graphSG.(*RdsSubnetGroup)
+
+			assert.Equal(subnetGroup.Name, "my-app-test")
+			if tt.subnetGroup == nil {
+				assert.Len(subnetGroup.Subnets, 2)
+				assert.ElementsMatch(subnetGroup.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(subnetGroup.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}))
+			}
+		})
+	}
+}
+
+func Test_RdsProxyCreate(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name  string
+		proxy *RdsProxy
+		want  coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil proxy",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:elastic_ip:my_app_1",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_0",
+					"aws:nat_gateway:my_app_1",
+					"aws:rds_proxy:my-app-test",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:security_group:my_app:my-app",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:subnet_public:my_app:my_app_public1",
+					"aws:vpc:my_app",
+					"aws:iam_role:my-app-test-ProxyRole",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:rds_proxy:my-app-test", Destination: "aws:security_group:my_app:my-app"},
+					{Source: "aws:rds_proxy:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:rds_proxy:my-app-test", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:rds_proxy:my-app-test", Destination: "aws:iam_role:my-app-test-ProxyRole"},
+				},
+			},
+		},
+		{
+			name:  "existing instance",
+			proxy: &RdsProxy{Name: "my-app-test", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:rds_proxy:my-app-test",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.proxy != nil {
+				dag.AddResource(tt.proxy)
+			}
+
+			metadata := RdsProxyCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{orm.AnnotationKey},
+				Name:    orm.ID,
+			}
+			proxy := &RdsProxy{}
+			err := proxy.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphSG := dag.GetResource(proxy.Id())
+			proxy = graphSG.(*RdsProxy)
+
+			assert.Equal(proxy.Name, "my-app-test")
+			if tt.proxy == nil {
+				assert.Len(proxy.Subnets, 2)
+				assert.Len(proxy.SecurityGroups, 1)
+				assert.NotNil(proxy.Role)
+				assert.Equal(proxy.ConstructsRef, metadata.Refs)
+			} else {
+				assert.Equal(proxy.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}))
+			}
+		})
+	}
+}
+
+func Test_RdsProxyTargetGroupCreate(t *testing.T) {
+	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}}
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name        string
+		targetGroup *RdsProxyTargetGroup
+		want        coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil proxy",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:rds_proxy_target_group:my-app-test",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name:        "existing instance",
+			targetGroup: &RdsProxyTargetGroup{Name: "my-app-test", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:rds_proxy_target_group:my-app-test",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.targetGroup != nil {
+				dag.AddResource(tt.targetGroup)
+			}
+
+			metadata := RdsProxyTargetGroupCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{orm.AnnotationKey},
+				Name:    orm.ID,
+			}
+			targetGroup := &RdsProxyTargetGroup{}
+			err := targetGroup.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphSG := dag.GetResource(targetGroup.Id())
+			targetGroup = graphSG.(*RdsProxyTargetGroup)
+
+			assert.Equal(targetGroup.Name, "my-app-test")
+			if tt.targetGroup == nil {
+				assert.Equal(targetGroup.ConstructsRef, metadata.Refs)
+			} else {
+				assert.Equal(targetGroup.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.PersistCapability}))
+			}
+		})
+	}
+}
+
 func Test_CreateRdsInstance(t *testing.T) {
 	appName := "test-app"
 	orm := &core.Orm{AnnotationKey: core.AnnotationKey{ID: "test", Capability: "orm"}}
diff --git a/pkg/provider/aws/resources/secret_test.go b/pkg/provider/aws/resources/secret_test.go
new file mode 100644
index 00000000..146f2f08
--- /dev/null
+++ b/pkg/provider/aws/resources/secret_test.go
@@ -0,0 +1,127 @@
+package resources
+
+import (
+	"testing"
+
+	"github.com/klothoplatform/klotho/pkg/annotation"
+	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_SecretCreate(t *testing.T) {
+	cases := []struct {
+		name    string
+		secret  *Secret
+		want    coretesting.ResourcesExpectation
+		wantErr bool
+	}{
+		{
+			name: "nil secret",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:secret:my-app-test",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name:    "existing secret",
+			secret:  &Secret{Name: "my-app-test"},
+			wantErr: true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.secret != nil {
+				dag.AddResource(tt.secret)
+			}
+
+			metadata := SecretCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.PersistCapability}},
+				Name:    "test",
+			}
+			secret := &Secret{}
+			err := secret.Create(dag, metadata)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphInstance := dag.GetResource(secret.Id())
+			secret = graphInstance.(*Secret)
+
+			assert.Equal(secret.Name, "my-app-test")
+			assert.ElementsMatch(secret.ConstructsRef, metadata.Refs)
+		})
+	}
+}
+
+func Test_SecretVersionCreate(t *testing.T) {
+	cases := []struct {
+		name    string
+		secret  *SecretVersion
+		want    coretesting.ResourcesExpectation
+		wantErr bool
+	}{
+		{
+			name: "nil secret",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:secret:my-app-test",
+					"aws:secret_version:my-app-test",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:secret_version:my-app-test", Destination: "aws:secret:my-app-test"},
+				},
+			},
+		},
+		{
+			name:    "existing secret",
+			secret:  &SecretVersion{Name: "my-app-test"},
+			wantErr: true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+
+			if tt.secret != nil {
+				dag.AddResource(tt.secret)
+			}
+
+			metadata := SecretVersionCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.PersistCapability}},
+				Name:    "test",
+			}
+			secret := &SecretVersion{}
+			err := secret.Create(dag, metadata)
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphInstance := dag.GetResource(secret.Id())
+			secret = graphInstance.(*SecretVersion)
+
+			assert.Equal(secret.Name, "my-app-test")
+			assert.ElementsMatch(secret.ConstructsRef, metadata.Refs)
+		})
+	}
+}
diff --git a/pkg/provider/aws/resources/securitygroup_test.go b/pkg/provider/aws/resources/securitygroup_test.go
index 4cc86fb4..b2c64932 100644
--- a/pkg/provider/aws/resources/securitygroup_test.go
+++ b/pkg/provider/aws/resources/securitygroup_test.go
@@ -3,11 +3,78 @@ package resources
 import (
 	"testing"
 
+	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/core/coretesting"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_SecurityGroupCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		sg   *SecurityGroup
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil sg",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:security_group:my_app:my-app",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+		{
+			name: "existing sg",
+			sg:   &SecurityGroup{Name: "my-app", ConstructsRef: initialRefs, Vpc: &Vpc{Name: "my_app"}},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:security_group:my_app:my-app",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:security_group:my_app:my-app", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.sg != nil {
+				dag.AddDependenciesReflect(tt.sg)
+			}
+			metadata := SecurityGroupCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+			}
+			sg := &SecurityGroup{}
+			err := sg.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphSG := dag.GetResourceByVertexId(sg.Id().String())
+			sg = graphSG.(*SecurityGroup)
+
+			assert.Equal(sg.Name, "my-app")
+			if tt.sg == nil {
+				assert.ElementsMatch(sg.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(sg.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
 func Test_GetSecurityGroup(t *testing.T) {
 	vpc := NewVpc("test")
 	type result struct {
diff --git a/pkg/provider/aws/resources/vpc_test.go b/pkg/provider/aws/resources/vpc_test.go
index a42dbdb9..74ce1a04 100644
--- a/pkg/provider/aws/resources/vpc_test.go
+++ b/pkg/provider/aws/resources/vpc_test.go
@@ -4,12 +4,522 @@ import (
 	"fmt"
 	"testing"
 
+	"github.com/klothoplatform/klotho/pkg/annotation"
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
 	"github.com/klothoplatform/klotho/pkg/core/coretesting"
 	"github.com/stretchr/testify/assert"
 )
 
+func Test_VpcCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		vpc  *Vpc
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil vpc",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name: "existing vpc",
+			vpc:  &Vpc{Name: "my_app", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.vpc != nil {
+				dag.AddResource(tt.vpc)
+			}
+			metadata := VpcCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+			}
+
+			vpc := &Vpc{}
+			err := vpc.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphVpc := dag.GetResourceByVertexId(vpc.Id().String())
+			vpc = graphVpc.(*Vpc)
+			assert.Equal(vpc.Name, "my_app")
+			if tt.vpc == nil {
+				assert.ElementsMatch(vpc.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(vpc.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
+func Test_ElasticIpCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		eip  *ElasticIp
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil eip",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:elastic_ip:my_app_ip0",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+		{
+			name: "existing eip",
+			eip:  &ElasticIp{Name: "my_app_ip0", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:elastic_ip:my_app_ip0",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.eip != nil {
+				dag.AddResource(tt.eip)
+			}
+			metadata := EipCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+				IpName:  "ip0",
+			}
+
+			eip := &ElasticIp{}
+			err := eip.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphEip := dag.GetResourceByVertexId(eip.Id().String())
+			eip = graphEip.(*ElasticIp)
+			assert.Equal(eip.Name, "my_app_ip0")
+			if tt.eip == nil {
+				assert.ElementsMatch(eip.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(eip.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
+func Test_InternetGatewayCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		igw  *InternetGateway
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil eip",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:internet_gateway:my_app_igw",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+		{
+			name: "existing eip",
+			igw:  &InternetGateway{Name: "my_app_igw", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:internet_gateway:my_app_igw",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.igw != nil {
+				dag.AddResource(tt.igw)
+			}
+			metadata := IgwCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+			}
+
+			igw := &InternetGateway{}
+			err := igw.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphIgw := dag.GetResourceByVertexId(igw.Id().String())
+			igw = graphIgw.(*InternetGateway)
+			assert.Equal(igw.Name, "my_app_igw")
+			if tt.igw == nil {
+				assert.NotNil(igw.Vpc)
+				assert.ElementsMatch(igw.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(igw.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
+func Test_NatGatewayCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		nat  *NatGateway
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil nat",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_0",
+					"aws:route_table:my_app_public",
+					"aws:vpc:my_app",
+					"aws:subnet_public:my_app:my_app_public0",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+		{
+			name: "existing nat",
+			nat:  &NatGateway{Name: "my_app_0", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:nat_gateway:my_app_0",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.nat != nil {
+				dag.AddResource(tt.nat)
+			}
+			metadata := NatCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+				AZ:      "0",
+			}
+
+			nat := &NatGateway{}
+			err := nat.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+			graphNat := dag.GetResourceByVertexId(nat.Id().String())
+			nat = graphNat.(*NatGateway)
+
+			assert.Equal(nat.Name, "my_app_0")
+			if tt.nat == nil {
+				assert.NotNil(nat.Subnet)
+				assert.NotNil(nat.ElasticIp)
+				assert.ElementsMatch(nat.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(nat.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
+func Test_SubnetCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name     string
+		subnet   *Subnet
+		addToDag bool
+		want     coretesting.ResourcesExpectation
+		wantErr  bool
+	}{
+		{
+			name:     "private subnet az0",
+			subnet:   &Subnet{Type: PrivateSubnet, AvailabilityZone: core.IaCValue{Property: "0"}, CidrBlock: "10.0.0.0/18"},
+			addToDag: false,
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_0",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_0",
+					"aws:route_table:my_app_private0",
+					"aws:route_table:my_app_public",
+					"aws:vpc:my_app",
+					"aws:subnet_private:my_app:my_app_private0",
+					"aws:subnet_public:my_app:my_app_public0",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:elastic_ip:my_app_0"},
+					{Source: "aws:nat_gateway:my_app_0", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:nat_gateway:my_app_0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:subnet_private:my_app:my_app_private0"},
+				},
+			},
+		},
+		{
+			name:     "private subnet az1",
+			subnet:   &Subnet{Type: PrivateSubnet, AvailabilityZone: core.IaCValue{Property: "1"}, CidrBlock: "10.0.64.0/18"},
+			addToDag: false,
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:elastic_ip:my_app_1",
+					"aws:internet_gateway:my_app_igw",
+					"aws:nat_gateway:my_app_1",
+					"aws:route_table:my_app_private1",
+					"aws:route_table:my_app_public",
+					"aws:vpc:my_app",
+					"aws:subnet_private:my_app:my_app_private1",
+					"aws:subnet_public:my_app:my_app_public1",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:elastic_ip:my_app_1"},
+					{Source: "aws:nat_gateway:my_app_1", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:nat_gateway:my_app_1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_private:my_app:my_app_private1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+					{Source: "aws:route_table:my_app_private1", Destination: "aws:subnet_private:my_app:my_app_private1"},
+				},
+			},
+		},
+		{
+			name:     "public subnet az0",
+			subnet:   &Subnet{Type: PublicSubnet, AvailabilityZone: core.IaCValue{Property: "0"}, CidrBlock: "10.0.128.0/18"},
+			addToDag: false,
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:internet_gateway:my_app_igw",
+					"aws:availability_zones:AvailabilityZones",
+					"aws:route_table:my_app_public",
+					"aws:vpc:my_app",
+					"aws:subnet_public:my_app:my_app_public0",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+				},
+			},
+		},
+		{
+			name:     "public subnet az1",
+			subnet:   &Subnet{Type: PublicSubnet, AvailabilityZone: core.IaCValue{Property: "1"}, CidrBlock: "10.0.192.0/18"},
+			addToDag: false,
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:internet_gateway:my_app_igw",
+					"aws:route_table:my_app_public",
+					"aws:availability_zones:AvailabilityZones",
+					"aws:vpc:my_app",
+					"aws:subnet_public:my_app:my_app_public1",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public1", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public1"},
+				},
+			},
+		},
+		{
+			name:     "existing subnet",
+			subnet:   &Subnet{Name: "my_app_public0", Type: PublicSubnet, AvailabilityZone: core.IaCValue{Property: "0"}, ConstructsRef: initialRefs, Vpc: &Vpc{Name: "my_app"}, CidrBlock: "10.0.128.0/18"},
+			addToDag: true,
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:availability_zones:AvailabilityZones",
+					"aws:internet_gateway:my_app_igw",
+					"aws:route_table:my_app_public",
+					"aws:subnet_public:my_app:my_app_public0",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:internet_gateway:my_app_igw", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:internet_gateway:my_app_igw"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:vpc:my_app"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:availability_zones:AvailabilityZones"},
+					{Source: "aws:subnet_public:my_app:my_app_public0", Destination: "aws:vpc:my_app"},
+					{Source: "aws:route_table:my_app_public", Destination: "aws:subnet_public:my_app:my_app_public0"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.addToDag {
+				dag.AddResource(tt.subnet)
+			}
+			metadata := SubnetCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+				AZ:      tt.subnet.AvailabilityZone.Property,
+				Type:    tt.subnet.Type,
+			}
+			subnet := &Subnet{}
+			err := subnet.Create(dag, metadata)
+
+			if tt.wantErr {
+				assert.Error(err)
+				return
+			}
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphSubnet := dag.GetResourceByVertexId(subnet.Id().String())
+			subnet = graphSubnet.(*Subnet)
+
+			assert.Equal(subnet.Name, fmt.Sprintf("my_app_%s%s", tt.subnet.Type, tt.subnet.AvailabilityZone.Property))
+			assert.Equal(subnet.Type, tt.subnet.Type)
+			assert.Equal(subnet.AvailabilityZone.Property, tt.subnet.AvailabilityZone.Property)
+			if tt.addToDag == false {
+				assert.ElementsMatch(subnet.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(subnet.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
+func Test_RouteTableCreate(t *testing.T) {
+	initialRefs := []core.AnnotationKey{{ID: "first"}}
+	cases := []struct {
+		name string
+		rt   *RouteTable
+		want coretesting.ResourcesExpectation
+	}{
+		{
+			name: "nil route table ",
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route_table:my_app_private0",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+		{
+			name: "existing rt",
+			rt:   &RouteTable{Name: "my_app_private0", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route_table:my_app_private0",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:route_table:my_app_private0", Destination: "aws:vpc:my_app"},
+				},
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := assert.New(t)
+			dag := core.NewResourceGraph()
+			if tt.rt != nil {
+				dag.AddResource(tt.rt)
+			}
+			metadata := RouteTableCreateParams{
+				AppName: "my-app",
+				Refs:    []core.AnnotationKey{{ID: "test", Capability: annotation.ExecutionUnitCapability}},
+				Name:    "private0",
+			}
+
+			rt := &RouteTable{}
+			err := rt.Create(dag, metadata)
+
+			if !assert.NoError(err) {
+				return
+			}
+			tt.want.Assert(t, dag)
+
+			graphRT := dag.GetResourceByVertexId(rt.Id().String())
+			rt = graphRT.(*RouteTable)
+
+			assert.Equal(rt.Name, "my_app_private0")
+			if tt.rt == nil {
+				assert.NotNil(rt.Vpc)
+				assert.ElementsMatch(rt.ConstructsRef, metadata.Refs)
+			} else {
+				assert.ElementsMatch(rt.KlothoConstructRef(), append(initialRefs, core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}))
+			}
+		})
+	}
+}
+
 func Test_CreateNetwork(t *testing.T) {
 	appName := "test-app"
 	cases := []struct {
diff --git a/pkg/testutil/set.go b/pkg/testutil/set.go
new file mode 100644
index 00000000..5385ceff
--- /dev/null
+++ b/pkg/testutil/set.go
@@ -0,0 +1,9 @@
+package testutil
+
+func NewSet[T comparable](ss ...T) map[T]struct{} {
+	set := make(map[T]struct{}, len(ss))
+	for _, s := range ss {
+		set[s] = struct{}{}
+	}
+	return set
+}
diff --git a/pkg/testutil/sitter.go b/pkg/testutil/sitter.go
new file mode 100644
index 00000000..cb861d81
--- /dev/null
+++ b/pkg/testutil/sitter.go
@@ -0,0 +1,20 @@
+package testutil
+
+import (
+	sitter "github.com/smacker/go-tree-sitter"
+)
+
+// FindNodeByContent returns a node whose content is the given content string. This could be a descendent of the given
+// node. If there are multiple such nodes, this will return one of them, but it's unspecified which.
+func FindNodeByContent(node *sitter.Node, content string) *sitter.Node {
+	if node.Content() == content {
+		return node
+	}
+	for childIdx := 0; childIdx < int(node.ChildCount()); childIdx += 1 {
+		child := node.Child(childIdx)
+		if result := FindNodeByContent(child, content); result != nil {
+			return result
+		}
+	}
+	return nil
+}
diff --git a/pkg/testutil/strings.go b/pkg/testutil/strings.go
index 138092b2..9f26d3e1 100644
--- a/pkg/testutil/strings.go
+++ b/pkg/testutil/strings.go
@@ -2,9 +2,10 @@ package testutil
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath"
 	"gopkg.in/yaml.v3"
-	"strings"
 )
 
 // UnIndent removes a level of indentation from the given string. The rules are very simple:
diff --git a/pkg/visualizer/all_types_test.go b/pkg/visualizer/all_types_test.go
index f495c051..31bb2d9d 100644
--- a/pkg/visualizer/all_types_test.go
+++ b/pkg/visualizer/all_types_test.go
@@ -2,6 +2,7 @@ package visualizer
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"net/http"
 	"testing"
@@ -25,16 +26,38 @@ func TestAllTypesHaveIcons(t *testing.T) {
 	testedTypes := make(coretesting.TypeRefSet)
 
 	api := visApi{client: http.DefaultClient}
+	typeNamesBuf := bytes.Buffer{}
+	for _, res := range allResources {
+		_, typeNames := typeNamesForResource(res)
+		for _, typeName := range typeNames {
+			typeNamesBuf.WriteString(typeName + "\n")
+		}
+	}
+	validationBytes, err := api.request(http.MethodPost, `validate-types`, `application/text`, "", &typeNamesBuf)
+	if !assert.NoError(t, err) {
+		statusCode, isBadStatus := err.(httpStatusBad)
+		if !isBadStatus || (statusCode != 400) {
+			// If it's a 400 we can keep going — that just means there were unvalidated types, which we'll check below.
+			// Anything else is unrecoverable.
+			return
+		}
+	}
+
+	var validations map[string]bool
+	err = json.Unmarshal(validationBytes, &validations)
+	if !assert.NoError(t, err) {
+		return
+	}
+
 	for _, res := range allResources {
 		provider, typeNames := typeNamesForResource(res)
 		for _, typeName := range typeNames {
 			t.Run(fmt.Sprintf("%s:%s", provider, typeName), func(t *testing.T) {
 				testedTypes.Add(res)
-				assert := assert.New(t)
-				typeNameBuf := bytes.Buffer{}
-				typeNameBuf.WriteString(typeName)
-				_, err := api.request(http.MethodGet, `validate-types`, `application/text`, ``, &typeNameBuf)
-				assert.NoError(err)
+				known, found := validations[typeName]
+				if assert.True(t, found) {
+					assert.True(t, known)
+				}
 			})
 		}
 	}