Repository: klothoplatform/klotho
Pull request #654 (2023-05-30 18:26:32)

add post-compile hooks
These are on a file extension basis. They let you do something like:

    klotho --set-option output.post-write-hooks.ts='npx prettier -w {}'

## Example

### Initially

~Out of the box, we don't~ Previously, we wouldn't do any formatting.

```
$ cat ~/.klotho/options.yaml
ui:
    disable-logo: true
```

```
$ klotho -c klotho.yaml
Adding resource internal:InternalKlothoPayloads
Adding resource execution_unit:main
searching for http imports in main.go
main.go: Found 2 route(s) on app 'r'
main.go: Found 3 route(s) on mounted router 'routes.ExtraRoutes'
Adding resource expose:app
```

The output is pretty ugly:

```typescript
// $ cat compiled/index.ts| tail -n 15
cachedMethods: [`HEAD`,`GET`],
targetOriginId: `app`,
forwardedValues: {queryString: true,
cookies: {forward: `none`,
},
},
defaultTtl: 3600,
maxTtl: 86400,
viewerProtocolPolicy: `allow-all`,
},
        restrictions: {geoRestriction: {restrictionType: `none`,
},
},
        defaultRootObject: ``,
    })
```

### Add a formatter

Now, we do apply formatting. Optionally, you can use `--set-option` to change it:

```
$ klotho --set-option output.post-write-hooks.ts='npx prettier -w {}'
$ cat ~/.klotho/options.yaml
ui:
    disable-logo: true
output:
    post-write-hooks:
        ts: npx prettier -w {}
```

Now, when we compile, we get a notification about the hook:

```
klotho -c klotho.yaml
Adding resource internal:InternalKlothoPayloads
Adding resource execution_unit:main
searching for http imports in main.go
main.go: Found 2 route(s) on app 'r'
main.go: Found 3 route(s) on mounted router 'routes.ExtraRoutes'
Adding resource expose:app
index.ts: running post-output hook: npx prettier -w index.ts        # <--- HERE
```

And the output is a lot nicer:

```typescript
// $ cat compiled/index.ts| tail -n 15
        `OPTIONS`,
        `PATCH`,
        `POST`,
        `PUT`,
      ],
      cachedMethods: [`HEAD`, `GET`],
      targetOriginId: `app`,
      forwardedValues: { queryString: true, cookies: { forward: `none` } },
      defaultTtl: 3600,
      maxTtl: 86400,
      viewerProtocolPolicy: `allow-all`,
    },
    restrictions: { geoRestriction: { restrictionType: `none` } },
    defaultRootObject: ``,
  });
```

### Negative path case

```
$ klotho --set-option output.post-write-hooks.ts='npx yuvalisblah -w {}'

$ klotho -c klotho.yaml
Adding resource internal:InternalKlothoPayloads
Adding resource execution_unit:main
searching for http imports in main.go
main.go: Found 2 route(s) on app 'r'
main.go: Found 3 route(s) on mounted router 'routes.ExtraRoutes'
Adding resource expose:app
index.ts: running post-output hook: npx yuvalisblah -w index.ts
index.ts: failed to apply post-output hook to index.ts: exit status 1

$ echo $?
0
```

The failure comes out as a warning, and does not cause the compilation to fail.

## Standard checks

- **Unit tests**: not added
- **Docs**: not documented
- **Backwards compatibility**: no issues


yuval-klotho: Sorry, I missed this high-level comment above:

> I have some reservations about running a formatter over users' code(1), even if it's opt-in - it may degrade their ability to 
recognize the output.

Speaking for myself it, it makes it a lot _easier_ to read the output.

> I also worry that this will cause us to not prioritize making sure the output files are formatted nicely for users without a post-compile formatter.

I don't see that formatting as a core value-add for Klotho, so personally I'm very happy to offload it. Sure, it'd be even nicer if it were done totally "in house" in the Go code; but isn't that letting the perfect be the enemy of the good?
 
> (1) I know the CompilationDocument.OutputFiles are only IaC files at the moment, but the intention is/was to move away from exec units from having their own OutputTo and instead be handled at IaC layer.

If/when this happens, we can remove or rework this undocumented feature.

---

Overall, I think this makes Klotho easier to use for me. But there's not consensus on that, I can close the PR. Lmk.
yuval-klotho: It just occurred to me:

> I have some reservations about running a formatter over users' code(1), even if it's opt-in - it may degrade their ability to recognize the output.

Is your concern that this would mangle the user's `.ts` files that they wrote?

This only affects our generated files. The user's files are in a separate struct (the `core.ExecutionUnit`'s `.Files`), and are unaffected by this option. Those are written in [`(*core.ExecutionUnit).OutputTo(string)`](https://github.com/klothoplatform/klotho/blob/main/pkg/core/exec_unit.go#L86)
gordon-klotho: > Speaking for myself it, it makes it a lot _easier_ to read the output. If a user doesn't feel that's true for them, then doesn't the opt-in nature of this provide a lot of protection?

Yeah, I just worry about the signal of "if you want it to look nice, you have to go install XYZ and go out of your way to configure it like ABC".
 
> I don't see that formatting as a core value-add for Klotho, so personally I'm very happy to offload it. Sure, it'd be even nicer if it were done totally "in house" in the Go code; but isn't that letting the perfect be the enemy of the good?

I agree that we should offload what we can, but I see formatting, and other UX, as part of a core value-add.

> Is your concern that this would mangle the user's .ts files that they wrote (or .js files, if they added an option for that)?
> 
> This only affects our generated files. The user's files are in a separate struct (the core.ExecutionUnit's .Files), and are unaffected by this option. Those are written in [(*core.ExecutionUnit).OutputTo(string)](https://github.com/klothoplatform/klotho/blob/main/pkg/core/exec_unit.go#L86)

Yeah, that's my concern and what the footnote (1) is covering - that the intention of OutputFiles was to be all output files, and the existence of ExecutionUnit.OutputTo is a carryover from the previous design. We can revisit the desire to move to OutputFiles, given that we haven't spent any effort in standardizing, but I just won't want to accidentally change this behaviour in the future, and it'd be hard to catch.

The strongest concern and the closest (but not quite qualifying, to be clear) of a blocker is the UX around "you need to install <yet another tool> for Klotho to be useful". We've already heard concerns about Docker and pulumi. It's better than nothing, but if we could spend like 2 person-hours on fixing it in the generation code/template, I'm sure we could get it to an acceptable state.
yuval-klotho: Okay, I will close this.

Patch:
diff --git a/go.mod b/go.mod
index f11675ba..1341cb18 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
 	github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
 	github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54
 	github.com/lithammer/dedent v1.1.0
+	github.com/mitchellh/mapstructure v1.5.0
 	github.com/pborman/ansi v1.0.0
 	github.com/pelletier/go-toml/v2 v2.0.8-0.20230509155657-d34104d49374
 	github.com/pkg/errors v0.9.1
@@ -42,7 +43,6 @@ 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
@@ -87,7 +87,7 @@ require (
 	github.com/google/gnostic v0.6.9 // indirect
 	github.com/google/go-cmp v0.5.9 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
-	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
+	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
 	github.com/gorilla/mux v1.8.0 // indirect
 	github.com/gosuri/uitable v0.0.4 // indirect
 	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
diff --git a/pkg/cli/cli_options.go b/pkg/cli/cli_options.go
index a1380782..4d5904e8 100644
--- a/pkg/cli/cli_options.go
+++ b/pkg/cli/cli_options.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 
 	"github.com/klothoplatform/klotho/pkg/cli_config"
+	"github.com/klothoplatform/klotho/pkg/compiler"
 	"github.com/klothoplatform/klotho/pkg/yaml_util"
 	"github.com/pkg/errors"
 	"go.uber.org/zap"
@@ -17,8 +18,9 @@ const configFileName = "options.yaml"
 
 type (
 	Options struct {
-		Update UpdateOptions `yaml:",omitempty"`
-		UI     UIOptions     `yaml:",omitempty"`
+		Update UpdateOptions          `yaml:",omitempty"`
+		UI     UIOptions              `yaml:",omitempty"`
+		Output compiler.OutputOptions `yaml:",omitempty"`
 	}
 
 	UpdateOptions struct {
diff --git a/pkg/cli/klothomain.go b/pkg/cli/klothomain.go
index e0d6efd1..f5ebf051 100644
--- a/pkg/cli/klothomain.go
+++ b/pkg/cli/klothomain.go
@@ -417,6 +417,7 @@ func (km KlothoMain) run(cmd *cobra.Command, args []string) (err error) {
 		Constructs:       constructs,
 		Configuration:    &appCfg,
 		Resources:        core.NewResourceGraph(),
+		OutputOptions:    options.Output,
 	}
 
 	compiler := compiler.Compiler{
diff --git a/pkg/collectionutil/maps.go b/pkg/collectionutil/maps.go
index d0318782..5558816a 100644
--- a/pkg/collectionutil/maps.go
+++ b/pkg/collectionutil/maps.go
@@ -18,3 +18,21 @@ func GetOneEntry[K comparable, V any](m map[K]V) (K, V) {
 	var v V
 	return k, v
 }
+
+type ExtendingMap[K comparable, V any] map[K]V
+
+func Extend[K comparable, V any](m map[K]V) ExtendingMap[K, V] {
+	return ExtendingMap[K, V](m)
+}
+
+func (source ExtendingMap[K, V]) Into(target map[K]V) {
+	for k, v := range source {
+		target[k] = v
+	}
+}
+
+func CopyMap[K comparable, V any](m map[K]V) map[K]V {
+	cp := make(map[K]V, len(m))
+	Extend(m).Into(cp)
+	return cp
+}
diff --git a/pkg/compiler/compilation_document.go b/pkg/compiler/compilation_document.go
index 76f47745..3e196c73 100644
--- a/pkg/compiler/compilation_document.go
+++ b/pkg/compiler/compilation_document.go
@@ -3,11 +3,18 @@ package compiler
 import (
 	"encoding/json"
 	"os"
+	"os/exec"
 	"path"
 	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
 
+	"github.com/google/shlex"
+	"github.com/klothoplatform/klotho/pkg/collectionutil"
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
+	"github.com/klothoplatform/klotho/pkg/logging"
 	"github.com/klothoplatform/klotho/pkg/multierr"
 	"github.com/pkg/errors"
 	"go.uber.org/zap"
@@ -21,10 +28,21 @@ type (
 		Configuration    *config.Application
 		Resources        *core.ResourceGraph
 		OutputFiles      []core.File
+		OutputOptions    OutputOptions
+	}
+
+	OutputOptions struct {
+		PostWriteHooks map[string]string `yaml:"post-write-hooks,omitempty"`
 	}
 )
 
+var unquotedCharsRe = regexp.MustCompile(`^[\w.{}:>=<@/-]*$`)
+
 func (doc *CompilationDocument) OutputTo(dest string) error {
+
+	postWriteHooks := newDefaultPostWriteHooksMap()
+	collectionutil.Extend(doc.OutputOptions.PostWriteHooks).Into(postWriteHooks)
+
 	errs := make(chan error)
 	files := doc.OutputFiles
 	for idx := range files {
@@ -53,6 +71,18 @@ func (doc *CompilationDocument) OutputTo(dest string) error {
 			}
 			_, err = f.WriteTo(file)
 			file.Close()
+
+			fileExt := filepath.Ext(path)
+			if fileExt != "" {
+				fileExt = strings.TrimPrefix(fileExt, ".")
+				if hook, found := postWriteHooks[fileExt]; found {
+					hookErr := postCompileHook(dest, f, hook)
+					if hookErr != nil {
+						zap.S().Warnf(`failed to apply post-output hook to %s/%s: %s`, dest, f.Path(), hookErr.Error())
+					}
+				}
+			}
+
 			errs <- err
 		}(files[idx])
 	}
@@ -66,6 +96,36 @@ func (doc *CompilationDocument) OutputTo(dest string) error {
 	return nil
 }
 
+func postCompileHook(dir string, file core.File, hook string) error {
+	hookSegments, err := shlex.Split(hook)
+	if err != nil {
+		return err
+	}
+	if len(hookSegments) == 0 {
+		return errors.New(`empty formatter command`)
+	}
+	var args []string
+	for _, arg := range hookSegments[1:] {
+		if arg == "{}" {
+			arg = file.Path()
+		}
+		args = append(args, arg)
+	}
+
+	cmd := exec.Command(hookSegments[0], args...)
+	cmd.Dir = dir
+
+	quotedArgs := cmd.Args
+	for i, arg := range quotedArgs {
+		if !unquotedCharsRe.MatchString(arg) {
+			quotedArgs[i] = strconv.Quote(arg)
+		}
+	}
+	zap.S().With(logging.FileField(file)).Infof(`running post-output hook: %s`, strings.Join(quotedArgs, " "))
+
+	return cmd.Run()
+}
+
 func (document *CompilationDocument) OutputResources() (resourceCounts map[string]int, err error) {
 	outDir := document.Configuration.OutDir
 	result := document.Constructs
@@ -139,3 +199,9 @@ func (document *CompilationDocument) OutputHelpers(outDir string) error {
 	}
 	return merr.ErrOrNil()
 }
+
+func newDefaultPostWriteHooksMap() map[string]string {
+	return map[string]string{
+		"ts": `npx prettier -w {}`,
+	}
+}
diff --git a/pkg/core/resource_graph.go b/pkg/core/resource_graph.go
index 75931f76..88404afb 100644
--- a/pkg/core/resource_graph.go
+++ b/pkg/core/resource_graph.go
@@ -57,6 +57,9 @@ func (rg *ResourceGraph) AddDependency(deployedSecond Resource, deployedFirst Re
 // 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) {
+	if deployedSecond.Id() == deployedFirst.Id() {
+		return
+	}
 	rg.AddResource(deployedSecond)
 	rg.AddResource(deployedFirst)
 	if cycle, _ := rg.underlying.CreatesCycle(deployedSecond.Id().String(), deployedFirst.Id().String()); cycle {
diff --git a/pkg/infra/iac2/templates/route53_health_check/factory.ts b/pkg/infra/iac2/templates/route53_health_check/factory.ts
new file mode 100644
index 00000000..3b505793
--- /dev/null
+++ b/pkg/infra/iac2/templates/route53_health_check/factory.ts
@@ -0,0 +1,31 @@
+import * as aws from '@pulumi/aws'
+
+interface Args {
+    Name: string
+    Type: string
+    Disabled: boolean
+    FailureThreshold: number
+    Fqdn: string
+    IpAddress: string
+    Port: number
+    RequestInterval: number
+    ResourcePath: string
+}
+
+// noinspection JSUnusedLocalSymbols
+function create(args: Args): aws.route53.HealthCheck {
+    return new aws.route53.HealthCheck(args.Name, {
+        type: args.Type,
+        //TMPL {{- if .Fqdn.Raw }}
+        fqdn: args.Fqdn,
+        //TMPL {{- end}}
+        //TMPL {{- if .IpAddress.Raw }}
+        ipAddress: args.IpAddress,
+        //TMPL {{- end}}
+        disabled: args.Disabled,
+        failureThreshold: args.FailureThreshold,
+        port: args.Port,
+        requestInterval: args.RequestInterval,
+        resourcePath: args.ResourcePath,
+    })
+}
diff --git a/pkg/infra/iac2/templates/route53_health_check/package.json b/pkg/infra/iac2/templates/route53_health_check/package.json
new file mode 100644
index 00000000..1d68f8fb
--- /dev/null
+++ b/pkg/infra/iac2/templates/route53_health_check/package.json
@@ -0,0 +1,6 @@
+{
+    "name": "route53_health_check",
+    "dependencies": {
+        "@pulumi/aws": "^5.37.0"
+    }
+}
diff --git a/pkg/infra/iac2/templates/route53_hosted_zone/factory.ts b/pkg/infra/iac2/templates/route53_hosted_zone/factory.ts
new file mode 100644
index 00000000..382b823c
--- /dev/null
+++ b/pkg/infra/iac2/templates/route53_hosted_zone/factory.ts
@@ -0,0 +1,17 @@
+import * as aws from '@pulumi/aws'
+
+interface Args {
+    Name: string
+    Vpc: aws.ec2.Vpc
+    ForceDestroy: boolean
+}
+
+// noinspection JSUnusedLocalSymbols
+function create(args: Args): aws.route53.Zone {
+    return new aws.route53.Zone(args.Name, {
+        //TMPL {{- if .Vpc.Raw }}
+        vpcs: [{ vpcId: args.Vpc.id }],
+        //TMPL {{- end}}
+        forceDestroy: args.ForceDestroy,
+    })
+}
diff --git a/pkg/infra/iac2/templates/route53_hosted_zone/package.json b/pkg/infra/iac2/templates/route53_hosted_zone/package.json
new file mode 100644
index 00000000..31cf69e3
--- /dev/null
+++ b/pkg/infra/iac2/templates/route53_hosted_zone/package.json
@@ -0,0 +1,6 @@
+{
+    "name": "route53_hosted_zone",
+    "dependencies": {
+        "@pulumi/aws": "^5.37.0"
+    }
+}
diff --git a/pkg/infra/iac2/templates/route53_record/factory.ts b/pkg/infra/iac2/templates/route53_record/factory.ts
new file mode 100644
index 00000000..00dff442
--- /dev/null
+++ b/pkg/infra/iac2/templates/route53_record/factory.ts
@@ -0,0 +1,25 @@
+import * as aws from '@pulumi/aws'
+import * as pulumi from '@pulumi/pulumi'
+interface Args {
+    Name: string
+    DomainName: string
+    Zone: aws.route53.Zone
+    Type: string
+    Records: pulumi.Output<string>[]
+    HealthCheck: aws.route53.HealthCheck
+    TTL: number
+}
+
+// noinspection JSUnusedLocalSymbols
+function create(args: Args): aws.route53.Record {
+    return new aws.route53.Record(args.Name, {
+        //TMPL {{- if .HealthCheck.Raw }}
+        healthCheckId: args.HealthCheck.id,
+        //TMPL {{- end}}
+        zoneId: args.Zone.id,
+        type: args.Type,
+        records: args.Records,
+        ttl: args.TTL,
+        name: args.DomainName,
+    })
+}
diff --git a/pkg/infra/iac2/templates/route53_record/package.json b/pkg/infra/iac2/templates/route53_record/package.json
new file mode 100644
index 00000000..967fcb35
--- /dev/null
+++ b/pkg/infra/iac2/templates/route53_record/package.json
@@ -0,0 +1,7 @@
+{
+    "name": "route53_record",
+    "dependencies": {
+        "@pulumi/aws": "^5.37.0",
+        "@pulumi/pulumi": "3.68.0"
+    }
+}
diff --git a/pkg/infra/iac2/templates_compiler.go b/pkg/infra/iac2/templates_compiler.go
index c9f4b471..98576093 100644
--- a/pkg/infra/iac2/templates_compiler.go
+++ b/pkg/infra/iac2/templates_compiler.go
@@ -643,7 +643,7 @@ func (tc TemplatesCompiler) handleIaCValue(v core.IaCValue, appliedOutputs *[]Ap
 		if !ok {
 			return "", errors.Errorf("Unable to handle iac value for %s on type %s", resources.NLB_INTEGRATION_URI_IAC_VALUE, resourceVal.Type().Name())
 		}
-		return fmt.Sprintf("pulumi.interpolate`http://${%s.dnsName}%s`", tc.getVarName(resource), integration.Route), nil
+		return fmt.Sprintf("pulumi.interpolate`http://${%s.dnsName}%s`", tc.getVarName(resource), strings.ReplaceAll(integration.Route, "+", "")), nil
 	case resources.RDS_CONNECTION_ARN_IAC_VALUE:
 		switch res := v.Resource.(type) {
 		case *resources.RdsInstance:
diff --git a/pkg/knowledge_base/edge_kb.go b/pkg/knowledge_base/edge_kb.go
index e165f4f1..b527537d 100644
--- a/pkg/knowledge_base/edge_kb.go
+++ b/pkg/knowledge_base/edge_kb.go
@@ -58,6 +58,9 @@ type (
 		Destination core.Resource
 		// Routes refers to any api routes being satisfied by the edge
 		Routes []core.Route
+		// SourceRef denotes the source annotation being used during expansion or configuration
+		// This is a temporary field due to helm chart being the lowest level of kubernetes resource at the moment
+		SourceRef core.AnnotationKey
 	}
 
 	Path []Edge
diff --git a/pkg/provider/aws/execution_unit.go b/pkg/provider/aws/execution_unit.go
index 5d14aac7..469f2507 100644
--- a/pkg/provider/aws/execution_unit.go
+++ b/pkg/provider/aws/execution_unit.go
@@ -6,6 +6,7 @@ import (
 	"github.com/klothoplatform/klotho/pkg/config"
 	"github.com/klothoplatform/klotho/pkg/core"
 	"github.com/klothoplatform/klotho/pkg/infra/kubernetes"
+	"github.com/klothoplatform/klotho/pkg/provider/aws/knowledgebase"
 	"github.com/klothoplatform/klotho/pkg/provider/aws/resources"
 )
 
@@ -189,7 +190,7 @@ func (a *AWS) getNodeGroupConfiguration(result *core.ConstructGraph, dag *core.R
 		construct := result.GetConstruct(ref.ToId())
 		unit, ok := construct.(*core.ExecutionUnit)
 		if !ok {
-			return nodeGroupConfig, fmt.Errorf("node group must only have construct references to ExecutionUnits but got %T", construct)
+			continue
 		}
 		cfg := config.ConvertFromInfraParams[config.KubernetesTypeParams](a.Config.GetExecutionUnit(unit.ID).InfraParams)
 
@@ -200,6 +201,38 @@ func (a *AWS) getNodeGroupConfiguration(result *core.ConstructGraph, dag *core.R
 	return nodeGroupConfig, nil
 }
 
+// handleEksProxy creates the necessary dependencies and resources for pods within the same helm chart to be able to use cloudmap to communicate
+func (a *AWS) handleEksProxy(source, dest *core.ExecutionUnit, chart *kubernetes.HelmChart, dag *core.ResourceGraph) error {
+	refs := core.AnnotationKeySetOf(source.AnnotationKey, dest.AnnotationKey)
+	privateDnsNamespace, err := core.CreateResource[*resources.PrivateDnsNamespace](dag, resources.PrivateDnsNamespaceCreateParams{
+		Refs:    refs,
+		AppName: a.Config.AppName,
+	})
+	if err != nil {
+		return err
+	}
+	dag.AddDependency(chart, privateDnsNamespace)
+	unitsRole := knowledgebase.GetIamRoleForUnit(chart, source.AnnotationKey)
+	if unitsRole == nil {
+		return fmt.Errorf("no role found for chart %s and source reference %s", chart.Id(), source.Id())
+	}
+	role := dag.GetResource(unitsRole.Id())
+	if role == nil {
+		return fmt.Errorf("no role found for chart %s based on source reference %s, for role %s", chart.Id(), source.Id(), unitsRole.Id())
+	}
+	policy, err := core.CreateResource[*resources.IamPolicy](dag, resources.IamPolicyCreateParams{
+		AppName: a.Config.AppName,
+		Name:    "servicediscovery",
+		Refs:    refs,
+	})
+	if err != nil {
+		return err
+	}
+	dag.AddDependency(policy, privateDnsNamespace)
+	dag.AddDependency(role, policy)
+	return err
+}
+
 func findUnitsHelmChart(unit *core.ExecutionUnit, dag *core.ResourceGraph) (*kubernetes.HelmChart, error) {
 	for _, res := range dag.ListResources() {
 		if r, ok := res.(*kubernetes.HelmChart); ok {
@@ -212,86 +245,3 @@ func findUnitsHelmChart(unit *core.ExecutionUnit, dag *core.ResourceGraph) (*kub
 	}
 	return nil, fmt.Errorf("helm chart not found for unit with id, %s", unit.ID)
 }
-
-// convertExecUnitParams transforms the execution units environment variables to a map of key names and their corresponding core.IaCValue struct.
-//
-// If an environment variable does not pertain to a construct and is just a key, value string, the resource of the IaCValue will be left null.
-func (a *AWS) convertExecUnitParams(result *core.ConstructGraph, dag *core.ResourceGraph) error {
-	execUnits := core.GetConstructsOfType[*core.ExecutionUnit](result)
-	for _, unit := range execUnits {
-
-		resourceEnvVars := make(resources.EnvironmentVariables)
-
-		// This set of environment variables correspond to the specific needs of the execution units and its dependencies
-		for _, envVar := range unit.EnvironmentVariables {
-			if envVar.Construct != nil {
-				resList, ok := a.GetResourcesDirectlyTiedToConstruct(envVar.GetConstruct())
-				if !ok {
-					return fmt.Errorf("resource not found for env var construct with id, %s", envVar.GetConstruct().Id())
-				}
-				for _, resource := range resList {
-					resourceEnvVars[envVar.Name] = core.IaCValue{
-						Resource: resource,
-						Property: envVar.Value,
-					}
-
-				}
-			} else {
-				resourceEnvVars[envVar.Name] = core.IaCValue{
-					Property: envVar.Value,
-				}
-			}
-		}
-
-		// Retrieve the actual resource and set the environment variables on it
-		resList, _ := a.GetResourcesDirectlyTiedToConstruct(unit)
-
-		// If the unit is a kubernetes unit, the helm chart wont be directly tied to the unit so we need to ensure we grab it
-		if a.Config.GetExecutionUnit(unit.ID).Type == kubernetes.KubernetesType {
-			chart, err := findUnitsHelmChart(unit, dag)
-			if err != nil {
-				return err
-			}
-			resList = append(resList, chart)
-		}
-		if len(resList) == 0 {
-			return fmt.Errorf("resource not found for construct with id, %s", unit.Id())
-		}
-		for _, resource := range resList {
-			switch r := resource.(type) {
-			case *resources.LambdaFunction:
-				r.EnvironmentVariables = resourceEnvVars
-			case *kubernetes.HelmChart:
-				for evName, evVal := range resourceEnvVars {
-					for _, val := range r.ProviderValues {
-						if val.EnvironmentVariable != nil && evName == val.EnvironmentVariable.GetName() {
-							r.Values[val.Key] = evVal
-							if evVal.Resource != nil && evVal.Resource != resource {
-								dag.AddDependency(resource, evVal.Resource)
-							}
-						}
-					}
-				}
-			}
-			dag.AddDependenciesReflect(resource)
-		}
-	}
-	return nil
-}
-
-// GetAssumeRolePolicyForType returns an assume role policy doc as a string, for the execution units corresponding IAM role
-func GetAssumeRolePolicyForType(cfg config.ExecutionUnit) *resources.PolicyDocument {
-	switch cfg.Type {
-	case Lambda:
-		return resources.LAMBDA_ASSUMER_ROLE_POLICY
-	case Ecs:
-		return resources.ECS_ASSUMER_ROLE_POLICY
-	case kubernetes.KubernetesType:
-		eksConfig := cfg.GetExecutionUnitParamsAsKubernetes()
-		if eksConfig.NodeType == string(resources.Fargate) {
-			return resources.EKS_FARGATE_ASSUME_ROLE_POLICY
-		}
-		return resources.EC2_ASSUMER_ROLE_POLICY
-	}
-	return nil
-}
diff --git a/pkg/provider/aws/knowledgebase/api_gateway.go b/pkg/provider/aws/knowledgebase/api_gateway.go
index ca631be8..9d59b641 100644
--- a/pkg/provider/aws/knowledgebase/api_gateway.go
+++ b/pkg/provider/aws/knowledgebase/api_gateway.go
@@ -16,18 +16,12 @@ var ApiGatewayKB = knowledgebase.Build(
 	knowledgebase.EdgeBuilder[*resources.ApiMethod, *resources.RestApi]{},
 	knowledgebase.EdgeBuilder[*resources.ApiDeployment, *resources.ApiMethod]{
 		Configure: func(deployment *resources.ApiDeployment, method *resources.ApiMethod, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
-			if deployment.Triggers == nil {
-				deployment.Triggers = make(map[string]string)
-			}
 			deployment.Triggers[method.Id().Name] = method.Id().Name
 			return nil
 		},
 	},
 	knowledgebase.EdgeBuilder[*resources.ApiDeployment, *resources.ApiIntegration]{
 		Configure: func(deployment *resources.ApiDeployment, integration *resources.ApiIntegration, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
-			if deployment.Triggers == nil {
-				deployment.Triggers = make(map[string]string)
-			}
 			deployment.Triggers[integration.Id().Name] = integration.Id().Name
 			return nil
 		},
@@ -98,6 +92,8 @@ var ApiGatewayKB = knowledgebase.Build(
 				for _, res := range dag.GetUpstreamResources(restApi) {
 					switch resource := res.(type) {
 					case *resources.ApiDeployment:
+						// Add trigger about the integration uri reosurce so that we trigger on compute changes
+						resource.Triggers[fmt.Sprintf("%s-target", integration.Name)] = function.Id().Name
 						dag.AddDependency(resource, integration.Method)
 						dag.AddDependency(resource, integration)
 					}
@@ -116,9 +112,6 @@ var ApiGatewayKB = knowledgebase.Build(
 				return nil
 			}
 			restApi, ok := data.Source.(*resources.RestApi)
-			refs := core.AnnotationKeySet{}
-			refs.AddAll(lb.ConstructsRef)
-			refs.AddAll(restApi.ConstructsRef)
 			if !ok {
 				return fmt.Errorf("source of eks to api integration expansion must be a rest api resource")
 			}
@@ -154,7 +147,7 @@ var ApiGatewayKB = knowledgebase.Build(
 				var err error
 				integration, err = core.CreateResource[*resources.ApiIntegration](dag, resources.ApiIntegrationCreateParams{
 					AppName:    data.AppName,
-					Refs:       refs,
+					Refs:       lb.ConstructsRef.CloneWith(restApi.ConstructsRef),
 					Path:       route.Path,
 					ApiName:    restApi.Name,
 					HttpMethod: strings.ToUpper(string(route.Verb)),
@@ -190,6 +183,8 @@ var ApiGatewayKB = knowledgebase.Build(
 				for _, res := range dag.GetUpstreamResources(restApi) {
 					switch resource := res.(type) {
 					case *resources.ApiDeployment:
+						// Add trigger about the integration uri reosurce so that we trigger on compute changes
+						resource.Triggers[fmt.Sprintf("%s-target", integration.Name)] = listener.LoadBalancer.Id().Name
 						dag.AddDependency(resource, integration.Method)
 						dag.AddDependency(resource, integration)
 					}
diff --git a/pkg/provider/aws/knowledgebase/edges.go b/pkg/provider/aws/knowledgebase/edges.go
index 72dcbfff..f528fa42 100644
--- a/pkg/provider/aws/knowledgebase/edges.go
+++ b/pkg/provider/aws/knowledgebase/edges.go
@@ -39,4 +39,5 @@ var AwsExtraEdgesKB = knowledgebase.Build(
 	knowledgebase.EdgeBuilder[*resources.SecretVersion, *resources.Secret]{},
 	knowledgebase.EdgeBuilder[*resources.EcrImage, *resources.EcrRepository]{},
 	knowledgebase.EdgeBuilder[*resources.OpenIdConnectProvider, *resources.Region]{},
+	knowledgebase.EdgeBuilder[*resources.PrivateDnsNamespace, *resources.Vpc]{},
 )
diff --git a/pkg/provider/aws/knowledgebase/eks.go b/pkg/provider/aws/knowledgebase/eks.go
index 2b680cee..d77fbf29 100644
--- a/pkg/provider/aws/knowledgebase/eks.go
+++ b/pkg/provider/aws/knowledgebase/eks.go
@@ -1,6 +1,7 @@
 package knowledgebase
 
 import (
+	"fmt"
 	"strings"
 
 	"github.com/klothoplatform/klotho/pkg/core"
@@ -65,6 +66,22 @@ var EksKB = knowledgebase.Build(
 	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.EksNodeGroup]{},
 	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.EcrImage]{},
 	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *kubernetes.HelmChart]{},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *kubernetes.KustomizeDirectory]{},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.PrivateDnsNamespace]{
+		Expand: func(chart *kubernetes.HelmChart, namespace *resources.PrivateDnsNamespace, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			clusterProvider := chart.ClustersProvider.Resource
+			cluster, ok := clusterProvider.(*resources.EksCluster)
+			if !ok {
+				return fmt.Errorf("cluster provider resource for %s, must be an eks cluster, was %T", chart.Id(), clusterProvider)
+			}
+			cmController, err := cluster.InstallCloudMapController(chart.ConstructRefs, dag)
+			if err != nil {
+				return err
+			}
+			dag.AddDependency(chart, cmController)
+			return nil
+		},
+	},
 	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.TargetGroup]{
 		Expand: func(chart *kubernetes.HelmChart, targetGroup *resources.TargetGroup, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
 			clusterProviderResource := chart.ClustersProvider.Resource
@@ -75,7 +92,7 @@ var EksKB = knowledgebase.Build(
 						return err
 					}
 				}
-				albChart, err := cluster.InstallAlbController(chart.ConstructRefs, dag)
+				albChart, err := cluster.InstallAlbController(chart.ConstructRefs, dag, data.AppName)
 				if err != nil {
 					return err
 				}
@@ -86,9 +103,126 @@ var EksKB = knowledgebase.Build(
 	},
 	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.Region]{},
 	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.Vpc]{},
+	knowledgebase.EdgeBuilder[*kubernetes.KustomizeDirectory, *resources.EksCluster]{},
+	knowledgebase.EdgeBuilder[*kubernetes.KustomizeDirectory, *resources.EksNodeGroup]{},
+	knowledgebase.EdgeBuilder[*kubernetes.Manifest, *kubernetes.KustomizeDirectory]{},
 	knowledgebase.EdgeBuilder[*kubernetes.Manifest, *resources.EksCluster]{},
 	knowledgebase.EdgeBuilder[*kubernetes.Manifest, *resources.EksFargateProfile]{},
 	knowledgebase.EdgeBuilder[*kubernetes.Manifest, *resources.EksNodeGroup]{},
 	knowledgebase.EdgeBuilder[*kubernetes.Manifest, *kubernetes.Manifest]{},
 	knowledgebase.EdgeBuilder[*kubernetes.Manifest, *resources.Region]{},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.DynamodbTable]{
+		Expand: func(chart *kubernetes.HelmChart, table *resources.DynamodbTable, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			role := GetIamRoleForUnit(chart, data.SourceRef)
+			if role == nil {
+				return fmt.Errorf("no role found for chart %s and source reference %s in HelmChart to ddb DynamodbTable expansion", chart.Id(), data.SourceRef.ToId())
+			}
+			dag.AddDependency(role, table)
+			return nil
+		},
+		Configure: func(chart *kubernetes.HelmChart, table *resources.DynamodbTable, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+
+			for _, env := range data.EnvironmentVariables {
+				addEnvVarToChart(chart, table, env)
+			}
+			return nil
+		},
+	},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.ElasticacheCluster]{
+		Configure: func(chart *kubernetes.HelmChart, cluster *resources.ElasticacheCluster, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			for _, env := range data.EnvironmentVariables {
+				addEnvVarToChart(chart, cluster, env)
+			}
+			return nil
+		},
+	},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.S3Bucket]{
+		Expand: func(chart *kubernetes.HelmChart, bucket *resources.S3Bucket, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			role := GetIamRoleForUnit(chart, data.SourceRef)
+			if role == nil {
+				return fmt.Errorf("no role found for chart %s and source reference %s in HelmChart to S3Bucket expansion", chart.Id(), data.SourceRef.ToId())
+			}
+			dag.AddDependency(role, bucket)
+			return nil
+		},
+		Configure: func(chart *kubernetes.HelmChart, bucket *resources.S3Bucket, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			for _, env := range data.EnvironmentVariables {
+				addEnvVarToChart(chart, bucket, env)
+			}
+			return nil
+		},
+	},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.RdsInstance]{
+		Configure: func(chart *kubernetes.HelmChart, instance *resources.RdsInstance, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+
+			role := GetIamRoleForUnit(chart, data.SourceRef)
+			if role == nil {
+				return fmt.Errorf("no role found for chart %s and source reference %s in HelmChart to ddb RdsInstance expansion", chart.Id(), data.SourceRef.ToId())
+			}
+			refs := role.ConstructsRef.CloneWith(instance.ConstructsRef)
+			inlinePol := resources.NewIamInlinePolicy(fmt.Sprintf("%s-connectionpolicy", instance.Name), refs, instance.GetConnectionPolicyDocument())
+			role.InlinePolicies = append(role.InlinePolicies, inlinePol)
+
+			for _, env := range data.EnvironmentVariables {
+				addEnvVarToChart(chart, instance, env)
+			}
+			return nil
+		},
+	},
+	knowledgebase.EdgeBuilder[*kubernetes.HelmChart, *resources.RdsProxy]{
+		Configure: func(chart *kubernetes.HelmChart, proxy *resources.RdsProxy, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			role := GetIamRoleForUnit(chart, data.SourceRef)
+			if role == nil {
+				return fmt.Errorf("no role found for chart %s and source reference %s in HelmChart to ddb RdsProxy expansion", chart.Id(), data.SourceRef.ToId())
+			}
+			upstreamResources := dag.GetUpstreamResources(proxy)
+			for _, res := range upstreamResources {
+				tg, ok := res.(*resources.RdsProxyTargetGroup)
+				if !ok {
+					continue
+				}
+				for _, tgUpstream := range dag.GetDownstreamResources(tg) {
+					instance, ok := tgUpstream.(*resources.RdsInstance)
+					if !ok {
+						continue
+					}
+					refs := role.ConstructsRef.Clone()
+					refs.AddAll(instance.ConstructsRef)
+					inlinePol := resources.NewIamInlinePolicy(fmt.Sprintf("%s-connectionpolicy", instance.Name), refs, instance.GetConnectionPolicyDocument())
+					role.InlinePolicies = append(role.InlinePolicies, inlinePol)
+					dag.AddDependency(role, instance)
+				}
+			}
+			for _, env := range data.EnvironmentVariables {
+				addEnvVarToChart(chart, proxy, env)
+			}
+			return nil
+		},
+		ValidDestinations: []core.Resource{&resources.RdsInstance{}},
+	},
 )
+
+func GetIamRoleForUnit(chart *kubernetes.HelmChart, ref core.AnnotationKey) *resources.IamRole {
+	rolePlaceholder := kubernetes.GenerateRoleArnPlaceholder(ref.ID)
+	for key, val := range chart.Values {
+		if rolePlaceholder == key {
+			if iacVal, ok := val.(core.IaCValue); ok {
+				if role, ok := iacVal.Resource.(*resources.IamRole); ok {
+					return role
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func addEnvVarToChart(chart *kubernetes.HelmChart, resource core.Resource, env core.EnvironmentVariable) {
+	for _, val := range chart.ProviderValues {
+		if val.EnvironmentVariable != nil && env.GetName() == val.EnvironmentVariable.GetName() {
+			chart.Values[val.Key] = core.IaCValue{
+				Resource: resource,
+				Property: env.GetValue(),
+			}
+		}
+	}
+}
diff --git a/pkg/provider/aws/knowledgebase/iam.go b/pkg/provider/aws/knowledgebase/iam.go
index 17c33069..3ea981b1 100644
--- a/pkg/provider/aws/knowledgebase/iam.go
+++ b/pkg/provider/aws/knowledgebase/iam.go
@@ -27,12 +27,7 @@ var IamKB = knowledgebase.Build(
 	},
 	knowledgebase.EdgeBuilder[*resources.IamPolicy, *resources.Secret]{
 		Configure: func(policy *resources.IamPolicy, secret *resources.Secret, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
-			secretPolicyDoc := resources.CreateAllowPolicyDocument([]string{"secretsmanager:DescribeSecret", "secretsmanager:GetSecretValue"}, []core.IaCValue{{Resource: secret, Property: resources.ARN_IAC_VALUE}})
-			if policy.Policy == nil {
-				policy.Policy = secretPolicyDoc
-			} else {
-				policy.Policy.Statement = append(policy.Policy.Statement, secretPolicyDoc.Statement...)
-			}
+			policy.AddPolicyDocument(resources.CreateAllowPolicyDocument([]string{"secretsmanager:DescribeSecret", "secretsmanager:GetSecretValue"}, []core.IaCValue{{Resource: secret, Property: resources.ARN_IAC_VALUE}}))
 			return nil
 		},
 	},
@@ -149,6 +144,10 @@ var IamKB = knowledgebase.Build(
 	},
 	knowledgebase.EdgeBuilder[*resources.IamRole, *resources.OpenIdConnectProvider]{
 		Configure: func(role *resources.IamRole, oidc *resources.OpenIdConnectProvider, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			if strings.Contains(role.Name, "alb-controller") {
+				role.AssumeRolePolicyDoc = resources.GetServiceAccountAssumeRolePolicy("aws-load-balancer-controller", oidc)
+				return nil
+			}
 			ref, oneRef := role.ConstructsRef.GetSingle()
 			if !oneRef {
 				return fmt.Errorf("iam role %s must only have one construct ref, but has %d, %s", role.Name, len(role.ConstructsRef), role.ConstructsRef)
@@ -173,15 +172,16 @@ var IamKB = knowledgebase.Build(
 	},
 	knowledgebase.EdgeBuilder[*resources.IamPolicy, *resources.LambdaFunction]{
 		Configure: func(policy *resources.IamPolicy, function *resources.LambdaFunction, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
-			statement := resources.CreateAllowPolicyDocument([]string{"lambda:InvokeFunction"}, []core.IaCValue{{Resource: function, Property: resources.ARN_IAC_VALUE}})
-			if policy.Policy == nil {
-				policy.Policy = statement
-			} else {
-				policy.Policy.Statement = append(policy.Policy.Statement, statement.Statement...)
-			}
+			policy.AddPolicyDocument(resources.CreateAllowPolicyDocument([]string{"lambda:InvokeFunction"}, []core.IaCValue{{Resource: function, Property: resources.ARN_IAC_VALUE}}))
 			return nil
 		},
 	},
 	knowledgebase.EdgeBuilder[*resources.RolePolicyAttachment, *resources.IamRole]{},
 	knowledgebase.EdgeBuilder[*resources.RolePolicyAttachment, *resources.IamPolicy]{},
+	knowledgebase.EdgeBuilder[*resources.IamPolicy, *resources.PrivateDnsNamespace]{
+		Configure: func(policy *resources.IamPolicy, namespace *resources.PrivateDnsNamespace, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			policy.AddPolicyDocument(resources.CreateAllowPolicyDocument([]string{"servicediscovery:DiscoverInstances"}, []core.IaCValue{{Property: core.ALL_RESOURCES_IAC_VALUE}}))
+			return nil
+		},
+	},
 )
diff --git a/pkg/provider/aws/knowledgebase/lambda.go b/pkg/provider/aws/knowledgebase/lambda.go
index 096b3675..ad222b55 100644
--- a/pkg/provider/aws/knowledgebase/lambda.go
+++ b/pkg/provider/aws/knowledgebase/lambda.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"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/provider/aws/resources"
 )
@@ -174,4 +175,70 @@ var LambdaKB = knowledgebase.Build(
 			return nil
 		},
 	},
+	knowledgebase.EdgeBuilder[*resources.LambdaFunction, *kubernetes.HelmChart]{
+		Expand: func(lambda *resources.LambdaFunction, destination *kubernetes.HelmChart, dag *core.ResourceGraph, data knowledgebase.EdgeData) error {
+			if len(lambda.Subnets) == 0 {
+				lambda.Subnets = make([]*resources.Subnet, 2)
+				subparams := map[string]any{
+					"Subnets": []resources.SubnetCreateParams{
+						{
+							AppName: data.AppName,
+							Refs:    lambda.ConstructsRef,
+							AZ:      "0",
+							Type:    resources.PrivateSubnet,
+						},
+						{
+							AppName: data.AppName,
+							Refs:    lambda.ConstructsRef,
+							AZ:      "1",
+							Type:    resources.PrivateSubnet,
+						},
+					},
+				}
+				if len(lambda.SecurityGroups) == 0 {
+					lambda.SecurityGroups = make([]*resources.SecurityGroup, 1)
+					subparams["SecurityGroups"] = []resources.SecurityGroupCreateParams{
+						{
+							AppName: data.AppName,
+							Refs:    lambda.ConstructsRef,
+						},
+					}
+				}
+				err := dag.CreateDependencies(lambda, subparams)
+				if err != nil {
+					return err
+				}
+			}
+			refs := lambda.ConstructsRef.CloneWith(destination.ConstructRefs)
+			privateDnsNamespace, err := core.CreateResource[*resources.PrivateDnsNamespace](dag, resources.PrivateDnsNamespaceCreateParams{
+				Refs:    refs,
+				AppName: data.AppName,
+			})
+			if err != nil {
+				return err
+			}
+			dag.AddDependency(destination, privateDnsNamespace)
+			policy, err := core.CreateResource[*resources.IamPolicy](dag, resources.IamPolicyCreateParams{
+				AppName: data.AppName,
+				Name:    "servicediscovery",
+				Refs:    refs,
+			})
+			if err != nil {
+				return err
+			}
+			dag.AddDependency(policy, privateDnsNamespace)
+			dag.AddDependency(lambda.Role, policy)
+			if err != nil {
+				return err
+			}
+			clusterProvider := destination.ClustersProvider.Resource
+			cluster, ok := clusterProvider.(*resources.EksCluster)
+			if !ok {
+				return fmt.Errorf("cluster provider resource for %s, must be an eks cluster, was %T", destination.Id(), clusterProvider)
+			}
+			cmController, err := cluster.InstallCloudMapController(refs, dag)
+			dag.AddDependency(destination, cmController)
+			return err
+		},
+	},
 )
diff --git a/pkg/provider/aws/plugin.go b/pkg/provider/aws/plugin.go
index 7830b5c8..18260da1 100644
--- a/pkg/provider/aws/plugin.go
+++ b/pkg/provider/aws/plugin.go
@@ -74,23 +74,54 @@ func (a *AWS) copyConstructEdgeToDag(
 	dep graph.Edge[core.Construct],
 	dag *core.ResourceGraph) error {
 	data := knowledgebase.EdgeData{AppName: a.Config.AppName, Source: sourceResource, Destination: destinationResource}
+
 	switch construct := dep.Source.(type) {
 	case *core.ExecutionUnit:
-		switch dep.Destination.(type) {
+		if a.Config.GetExecutionUnit(construct.ID).Type == kubernetes.KubernetesType {
+			data.SourceRef = construct.AnnotationKey
+		}
+		for _, envVar := range construct.EnvironmentVariables {
+			if envVar.Construct != nil && envVar.Construct.Id() == dep.Destination.Id() {
+				data.EnvironmentVariables = append(data.EnvironmentVariables, envVar)
+			}
+		}
+		switch dest := dep.Destination.(type) {
 		case *core.Orm:
 			data.Constraint = knowledgebase.EdgeConstraint{
 				NodeMustExist: []core.Resource{&resources.RdsProxy{}},
 			}
+			// Because we dont understand resource types, our edges cant depict what is truly valid and what isnt yet. Example
+			// 	found multiple paths which satisfy constraints for edge aws:lambda_function:lambda -> aws:rds_instance:rds.
+			// Paths: [[{*resources.LambdaFunction *resources.RdsProxy} {*resources.RdsProxyTargetGroup *resources.RdsProxy} {*resources.RdsProxyTargetGroup *resources.RdsInstance}]
+			// [{*resources.LambdaFunction *resources.RdsProxy} {*kubernetes.HelmChart *resources.RdsProxy} {*kubernetes.HelmChart *resources.RdsInstance}]]
+			// When we can classify lambda and helm as compute then we can understand that there is no need for the data flow in this manner
+			if a.Config.GetExecutionUnit(construct.ID).Type == kubernetes.KubernetesType {
+				data.Constraint.NodeMustNotExist = append(data.Constraint.NodeMustNotExist, &resources.LambdaFunction{})
+			} else {
+				data.Constraint.NodeMustNotExist = append(data.Constraint.NodeMustNotExist, &kubernetes.HelmChart{})
+			}
 		case *core.Kv:
 			data.Constraint = knowledgebase.EdgeConstraint{
 				NodeMustNotExist: []core.Resource{&resources.IamPolicy{}},
 			}
-		}
-		for _, envVar := range construct.EnvironmentVariables {
-			if envVar.Construct != nil && envVar.Construct.Id() == dep.Destination.Id() {
-				data.EnvironmentVariables = append(data.EnvironmentVariables, envVar)
+		case *core.ExecutionUnit:
+			// We have to handle this case here since we dont understand what exists within a helm chart yet outside of the notion of constructs
+			// We will be able to move this to edges once we build a better understanding of kubernetes resources
+			if a.Config.GetExecutionUnit(dest.ID).Type == kubernetes.KubernetesType && a.Config.GetExecutionUnit(construct.ID).Type == kubernetes.KubernetesType {
+				if chart, ok := destinationResource.(*kubernetes.HelmChart); ok {
+					err := a.handleEksProxy(construct, dest, chart, dag)
+					if err != nil {
+						return err
+					}
+				} else {
+					return errors.Errorf("unable to copy edge %s -> %s, target resource must be a helm chart for kubernetes proxy", dep.Source.Id(), dep.Destination.Id())
+				}
 			}
 		}
+	case *core.Kv:
+		data.Constraint = knowledgebase.EdgeConstraint{
+			NodeMustNotExist: []core.Resource{&resources.IamPolicy{}},
+		}
 	case *core.Gateway:
 		for _, route := range construct.Routes {
 			if route.ExecUnitName == dep.Destination.Provenance().ID {
@@ -119,6 +150,7 @@ func (a *AWS) copyConstructEdgeToDag(
 			}
 		}
 	}
+
 	dag.AddDependencyWithData(sourceResource, destinationResource, data)
 	return nil
 }
diff --git a/pkg/provider/aws/resources/api_gateway.go b/pkg/provider/aws/resources/api_gateway.go
index 2b03de01..1a552434 100644
--- a/pkg/provider/aws/resources/api_gateway.go
+++ b/pkg/provider/aws/resources/api_gateway.go
@@ -203,6 +203,7 @@ func (integration *ApiIntegration) Create(dag *core.ResourceGraph, params ApiInt
 	name := apiResourceSanitizer.Apply(fmt.Sprintf("%s-%s-%s", params.AppName, params.Path, params.HttpMethod))
 	integration.Name = name
 	integration.ConstructsRef = params.Refs
+	integration.Route = convertPath(params.Path, false)
 
 	existingResource := dag.GetResource(integration.Id())
 	if existingResource != nil {
@@ -300,7 +301,9 @@ func (deployment *ApiDeployment) Create(dag *core.ResourceGraph, params ApiDeplo
 	name := apiResourceSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name))
 	deployment.Name = name
 	deployment.ConstructsRef = params.Refs
-
+	if deployment.Triggers == nil {
+		deployment.Triggers = make(map[string]string)
+	}
 	existingDeployment := dag.GetResource(deployment.Id())
 	if existingDeployment != nil {
 		graphDeployment := existingDeployment.(*ApiDeployment)
diff --git a/pkg/provider/aws/resources/cloud_map.go b/pkg/provider/aws/resources/cloud_map.go
index 006b81fa..9b240ad6 100644
--- a/pkg/provider/aws/resources/cloud_map.go
+++ b/pkg/provider/aws/resources/cloud_map.go
@@ -19,12 +19,28 @@ type (
 	}
 )
 
-func NewPrivateDnsNamespace(appName string, refs core.AnnotationKeySet, vpc *Vpc) *PrivateDnsNamespace {
-	return &PrivateDnsNamespace{
-		Name:          privateDnsNamespaceSanitizer.Apply(appName),
-		ConstructsRef: refs,
-		Vpc:           vpc,
+type PrivateDnsNamespaceCreateParams struct {
+	AppName string
+	Refs    core.AnnotationKeySet
+}
+
+func (namespace *PrivateDnsNamespace) Create(dag *core.ResourceGraph, params PrivateDnsNamespaceCreateParams) error {
+	namespace.Name = privateDnsNamespaceSanitizer.Apply(params.AppName)
+	namespace.ConstructsRef = params.Refs
+
+	existingNamespace, found := core.GetResource[*PrivateDnsNamespace](dag, namespace.Id())
+	if found {
+		existingNamespace.ConstructsRef.AddAll(params.Refs)
+	} else {
+		err := dag.CreateDependencies(namespace, map[string]any{"Vpc": VpcCreateParams{
+			AppName: params.AppName,
+			Refs:    params.Refs.Clone(),
+		}})
+		if err != nil {
+			return err
+		}
 	}
+	return nil
 }
 
 // KlothoConstructRef returns AnnotationKey of the klotho resource the cloud resource is correlated to
diff --git a/pkg/provider/aws/resources/ecr.go b/pkg/provider/aws/resources/ecr.go
index d71485ba..844e0303 100644
--- a/pkg/provider/aws/resources/ecr.go
+++ b/pkg/provider/aws/resources/ecr.go
@@ -37,7 +37,7 @@ type RepoCreateParams struct {
 
 func (repo *EcrRepository) Create(dag *core.ResourceGraph, params RepoCreateParams) error {
 	repo.Name = params.AppName
-	repo.ConstructsRef = params.Refs
+	repo.ConstructsRef = params.Refs.Clone()
 
 	existingRepo := dag.GetResource(repo.Id())
 	if existingRepo != nil {
@@ -66,7 +66,7 @@ type ImageCreateParams struct {
 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
+	image.ConstructsRef = params.Refs.Clone()
 
 	existingImage := dag.GetResource(image.Id())
 	if existingImage != nil {
diff --git a/pkg/provider/aws/resources/eks.go b/pkg/provider/aws/resources/eks.go
index 7e3e0b3c..a5d4c258 100644
--- a/pkg/provider/aws/resources/eks.go
+++ b/pkg/provider/aws/resources/eks.go
@@ -317,12 +317,11 @@ func (nodeGroup *EksNodeGroup) Create(dag *core.ResourceGraph, params EksNodeGro
 
 	name := NodeGroupName(params.ClusterName, params.NetworkType, params.InstanceType)
 	nodeGroup.Name = fmt.Sprintf("%s_%s", params.AppName, name)
-
 	existingNodeGroup, found := core.GetResource[*EksNodeGroup](dag, nodeGroup.Id())
 	if found {
 		existingNodeGroup.ConstructsRef.AddAll(params.Refs)
 	} else {
-		nodeGroup.ConstructsRef = params.Refs
+		nodeGroup.ConstructsRef = params.Refs.Clone()
 		nodeGroup.InstanceTypes = []string{params.InstanceType}
 		nodeGroup.Labels = map[string]string{
 			"network_placement": params.NetworkType,
@@ -568,10 +567,10 @@ func (cluster *EksCluster) InstallFluentBit(references core.AnnotationKeySet, da
 	return nil
 }
 
-func (cluster *EksCluster) InstallCloudMapController(ref core.AnnotationKey, dag *core.ResourceGraph) (*kubernetes.KustomizeDirectory, error) {
+func (cluster *EksCluster) InstallCloudMapController(refs core.AnnotationKeySet, dag *core.ResourceGraph) (*kubernetes.KustomizeDirectory, error) {
 	cloudMapController := &kubernetes.KustomizeDirectory{
 		Name:          fmt.Sprintf("%s-cloudmap-controller", cluster.Name),
-		ConstructRefs: core.AnnotationKeySetOf(ref),
+		ConstructRefs: refs,
 		Directory:     "https://github.com/aws/aws-cloud-map-mcs-controller-for-k8s/config/controller_install_release",
 		ClustersProvider: core.IaCValue{
 			Resource: cluster,
@@ -582,7 +581,7 @@ func (cluster *EksCluster) InstallCloudMapController(ref core.AnnotationKey, dag
 	if controller := dag.GetResource(cloudMapController.Id()); controller != nil {
 		if cm, ok := controller.(*kubernetes.KustomizeDirectory); ok {
 			cloudMapController = cm
-			cm.ConstructRefs.Add(ref)
+			cm.ConstructRefs.AddAll(refs)
 		} else {
 			return nil, errors.Errorf("Expected resource with id, %s, to be of type HelmChart, but was %s",
 				controller.Id(), reflect.ValueOf(controller).Type().Name())
@@ -595,7 +594,7 @@ func (cluster *EksCluster) InstallCloudMapController(ref core.AnnotationKey, dag
 		}
 		clusterSet := &kubernetes.Manifest{
 			Name:          fmt.Sprintf("%s-%s", cluster.Name, "cluster-set"),
-			ConstructRefs: core.AnnotationKeySetOf(ref),
+			ConstructRefs: refs,
 			FilePath:      clusterSetOutputPath,
 			Transformations: map[string]core.IaCValue{
 				`spec["value"]`: {Resource: cluster, Property: NAME_IAC_VALUE},
@@ -618,7 +617,7 @@ func (cluster *EksCluster) InstallCloudMapController(ref core.AnnotationKey, dag
 	return cloudMapController, nil
 }
 
-func (cluster *EksCluster) InstallAlbController(references core.AnnotationKeySet, dag *core.ResourceGraph) (*kubernetes.HelmChart, error) {
+func (cluster *EksCluster) InstallAlbController(references core.AnnotationKeySet, dag *core.ResourceGraph, appName string) (*kubernetes.HelmChart, error) {
 	serviceAccountName := "aws-load-balancer-controller"
 	saPath := "aws-load-balancer-controller-service-account.yaml"
 	outputPath := path.Join(MANIFEST_PATH_PREFIX, saPath)
@@ -628,24 +627,25 @@ func (cluster *EksCluster) InstallAlbController(references core.AnnotationKeySet
 	}
 
 	role, err := core.CreateResource[*IamRole](dag, RoleCreateParams{
-		AppName: cluster.Name,
+		AppName: appName,
 		Name:    "alb-controller",
 		Refs:    references,
 	})
 	if err != nil {
 		return nil, err
 	}
-	assumeRolePolicyDoc := GetServiceAccountAssumeRolePolicy(saName, cluster.getOidc(dag))
-	if err != nil {
-		return nil, err
-	}
+	oidc, err := core.CreateResource[*OpenIdConnectProvider](dag, OidcCreateParams{
+		AppName:     appName,
+		ClusterName: strings.TrimLeft(cluster.Name, fmt.Sprintf("%s-", appName)),
+		Refs:        role.ConstructsRef.Clone(),
+	})
 	var aRef core.AnnotationKey
 	for ref := range references {
 		aRef = ref
 		break
 	}
+	dag.AddDependency(role, oidc)
 	policy := createAlbControllerPolicy(cluster.Name, aRef)
-	role.AssumeRolePolicyDoc = assumeRolePolicyDoc
 	dag.AddDependency(role, policy)
 	if err != nil {
 		return nil, err
@@ -786,14 +786,6 @@ func createEKSKubeconfig(cluster *EksCluster, region *Region) *kubernetes.Kubeco
 	}
 }
 
-func (cluster *EksCluster) getOidc(dag *core.ResourceGraph) *OpenIdConnectProvider {
-	for _, res := range dag.GetUpstreamResources(cluster) {
-		if oidc, ok := res.(*OpenIdConnectProvider); ok {
-			return oidc
-		}
-	}
-	return nil
-}
 func GetServiceAccountAssumeRolePolicy(serviceAccountName string, oidc *OpenIdConnectProvider) *PolicyDocument {
 	return &PolicyDocument{
 		Version: VERSION,
diff --git a/pkg/provider/aws/resources/iam.go b/pkg/provider/aws/resources/iam.go
index ea2be105..57bfcf03 100644
--- a/pkg/provider/aws/resources/iam.go
+++ b/pkg/provider/aws/resources/iam.go
@@ -163,7 +163,7 @@ type RoleCreateParams struct {
 
 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
+	role.ConstructsRef.AddAll(params.Refs)
 
 	existingRole := dag.GetResource(role.Id())
 	if existingRole != nil {
@@ -182,10 +182,11 @@ type IamPolicyCreateParams struct {
 
 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)
+	policy.ConstructsRef = params.Refs.Clone()
+	existingPolicy, found := core.GetResource[*IamPolicy](dag, policy.Id())
+	if found {
+		existingPolicy.ConstructsRef.AddAll(params.Refs)
+		return nil
 	}
 	dag.AddResource(policy)
 	return nil
@@ -205,7 +206,7 @@ func (oidc *OpenIdConnectProvider) Create(dag *core.ResourceGraph, params OidcCr
 		graphOidc := existingOidc.(*OpenIdConnectProvider)
 		graphOidc.ConstructsRef.AddAll(params.Refs)
 	} else {
-		oidc.ConstructsRef = params.Refs
+		oidc.ConstructsRef = params.Refs.Clone()
 		oidc.Region = NewRegion()
 		subParams := map[string]any{
 			"Cluster": EksClusterCreateParams{
@@ -362,6 +363,16 @@ func NewIamInlinePolicy(policyName string, refs core.AnnotationKeySet, policy *P
 	}
 }
 
+func (policy *IamPolicy) AddPolicyDocument(doc *PolicyDocument) {
+	if policy.Policy == nil {
+		policy.Policy = doc
+		return
+	}
+	statement := doc.Statement
+	policy.Policy.Statement = append(policy.Policy.Statement, statement...)
+	policy.Policy.Deduplicate()
+}
+
 // KlothoConstructRef returns AnnotationKey of the klotho resource the cloud resource is correlated to
 func (policy *IamPolicy) KlothoConstructRef() core.AnnotationKeySet {
 	return policy.ConstructsRef
diff --git a/pkg/provider/aws/resources/list_all.go b/pkg/provider/aws/resources/list_all.go
index 72291828..84889678 100644
--- a/pkg/provider/aws/resources/list_all.go
+++ b/pkg/provider/aws/resources/list_all.go
@@ -45,6 +45,9 @@ func ListAll() []core.Resource {
 		&RestApi{},
 		&RolePolicyAttachment{},
 		&RouteTable{},
+		&Route53HealthCheck{},
+		&Route53HostedZone{},
+		&Route53Record{},
 		&S3BucketPolicy{},
 		&S3Bucket{},
 		&S3Object{},
diff --git a/pkg/provider/aws/resources/manifests/fluent_bit_cluster_info.yaml b/pkg/provider/aws/resources/manifests/fluent_bit_cluster_info.yaml
index 2e67759f..790be679 100644
--- a/pkg/provider/aws/resources/manifests/fluent_bit_cluster_info.yaml
+++ b/pkg/provider/aws/resources/manifests/fluent_bit_cluster_info.yaml
@@ -8,7 +8,7 @@ metadata:
 data:
   cluster.name: REPLACE_ME
   logs.region: REPLACE_ME
-  http.server: "On"
-  http.port: "2020"
-  read.head: "Off"
-  read.tail: "On"
+  http.server: 'On'
+  http.port: '2020'
+  read.head: 'Off'
+  read.tail: 'On'
diff --git a/pkg/provider/aws/resources/route53.go b/pkg/provider/aws/resources/route53.go
new file mode 100644
index 00000000..91828341
--- /dev/null
+++ b/pkg/provider/aws/resources/route53.go
@@ -0,0 +1,219 @@
+package resources
+
+import (
+	"fmt"
+
+	"github.com/klothoplatform/klotho/pkg/core"
+)
+
+const (
+	HOSTED_ZONE_TYPE           = "route53_hosted_zone"
+	ROUTE_53_RECORD_TYPE       = "route53_record"
+	ROUTE_53_HEALTH_CHECK_TYPE = "route53_health_check"
+)
+
+type (
+	Route53HostedZone struct {
+		Name          string
+		ConstructsRef core.AnnotationKeySet
+		Vpc           *Vpc
+		ForceDestroy  bool
+	}
+
+	Route53Record struct {
+		Name          string
+		DomainName    string
+		ConstructsRef core.AnnotationKeySet
+		Zone          *Route53HostedZone
+		Type          string
+		Records       []core.IaCValue
+		HealthCheck   *Route53HealthCheck
+		TTL           int
+	}
+
+	Route53HealthCheck struct {
+		Name             string
+		ConstructsRef    core.AnnotationKeySet
+		Type             string
+		Disabled         bool
+		FailureThreshold int
+		Fqdn             string
+		IpAddress        string
+		Port             int
+		RequestInterval  int
+		ResourcePath     string
+	}
+)
+
+type Route53HostedZoneCreateParams struct {
+	AppName string
+	Refs    core.AnnotationKeySet
+	Name    string
+	Type    string
+}
+
+func (zone *Route53HostedZone) Create(dag *core.ResourceGraph, params Route53HostedZoneCreateParams) error {
+	zone.Name = fmt.Sprintf("%s-%s", params.AppName, params.Name)
+	zone.ConstructsRef = params.Refs
+
+	existingZone, found := core.GetResource[*Route53HostedZone](dag, zone.Id())
+	if found {
+		existingZone.ConstructsRef.AddAll(params.Refs)
+		return nil
+	}
+	subParams := map[string]any{}
+	if params.Type == "private" {
+		subParams["Vpc"] = VpcCreateParams{
+			AppName: params.AppName,
+			Refs:    params.Refs.Clone(),
+		}
+	}
+	err := dag.CreateDependencies(zone, subParams)
+	return err
+}
+
+type Route53HostedZoneConfigureParams struct {
+}
+
+func (zone *Route53HostedZone) Configure(params Route53HostedZoneConfigureParams) error {
+	zone.ForceDestroy = true
+	return nil
+}
+
+type Route53RecordCreateParams struct {
+	Refs       core.AnnotationKeySet
+	DomainName string
+	Zone       *Route53HostedZone
+}
+
+func (record *Route53Record) Create(dag *core.ResourceGraph, params Route53RecordCreateParams) error {
+	record.Name = fmt.Sprintf("%s-%s", params.Zone.Name, params.DomainName)
+	record.ConstructsRef = params.Refs
+	record.DomainName = params.DomainName
+
+	existingRecord, found := core.GetResource[*Route53Record](dag, record.Id())
+	if found {
+		existingRecord.ConstructsRef.AddAll(params.Refs)
+		return nil
+	}
+	record.Zone = params.Zone
+	dag.AddDependenciesReflect(record)
+	return nil
+}
+
+type Route53RecordConfigureParams struct {
+	Type        string
+	HealthCheck *Route53HealthCheck
+	TTL         int
+}
+
+func (record *Route53Record) Configure(params Route53RecordConfigureParams) error {
+	if params.Type != "" {
+		record.Type = params.Type
+	}
+	if params.HealthCheck != nil {
+		record.HealthCheck = params.HealthCheck
+	}
+	if params.TTL != 0 {
+		record.TTL = params.TTL
+	}
+	return nil
+}
+
+type Route53HealthCheckCreateParams struct {
+	Refs      core.AnnotationKeySet
+	AppName   string
+	Fqdn      string
+	IpAddress string
+}
+
+func (healthCheck *Route53HealthCheck) Create(dag *core.ResourceGraph, params Route53HealthCheckCreateParams) error {
+	if params.Fqdn != "" && params.IpAddress != "" {
+		return fmt.Errorf("cannot set fully qualified domain name and ip address on route53 health check")
+	}
+	name := fmt.Sprintf("%s-%s", params.AppName, params.IpAddress)
+	healthCheck.IpAddress = params.IpAddress
+	if params.IpAddress == "" {
+		name = fmt.Sprintf("%s-%s", params.AppName, params.Fqdn)
+		healthCheck.Fqdn = params.Fqdn
+	}
+	healthCheck.Name = name
+	healthCheck.ConstructsRef = params.Refs
+
+	existingHealthCheck, found := core.GetResource[*Route53HealthCheck](dag, healthCheck.Id())
+	if found {
+		existingHealthCheck.ConstructsRef.AddAll(params.Refs)
+		return nil
+	}
+	dag.AddDependenciesReflect(healthCheck)
+	return nil
+}
+
+type Route53HealthCheckConfigureParams struct {
+	Type             string
+	Disabled         bool
+	FailureThreshold int
+	Port             int
+	RequestInterval  int
+	ResourcePath     string
+}
+
+func (healthCheck *Route53HealthCheck) Configure(params Route53HealthCheckConfigureParams) error {
+	if params.Type != "" {
+		healthCheck.Type = params.Type
+	}
+	healthCheck.Disabled = params.Disabled
+	if params.FailureThreshold != 0 {
+		healthCheck.FailureThreshold = params.FailureThreshold
+	}
+	if params.Port != 0 {
+		healthCheck.Port = params.Port
+	}
+	if params.RequestInterval != 0 {
+		healthCheck.RequestInterval = params.RequestInterval
+	}
+	if params.ResourcePath != "" {
+		healthCheck.ResourcePath = params.ResourcePath
+	}
+	return nil
+}
+
+// KlothoConstructRef returns AnnotationKey of the klotho resource the cloud resource is correlated to
+func (zone *Route53HostedZone) KlothoConstructRef() core.AnnotationKeySet {
+	return zone.ConstructsRef
+}
+
+// Id returns the id of the cloud resource
+func (zone *Route53HostedZone) Id() core.ResourceId {
+	return core.ResourceId{
+		Provider: AWS_PROVIDER,
+		Type:     HOSTED_ZONE_TYPE,
+		Name:     zone.Name,
+	}
+}
+
+func (record *Route53Record) KlothoConstructRef() core.AnnotationKeySet {
+	return record.ConstructsRef
+}
+
+// Id returns the id of the cloud resource
+func (record *Route53Record) Id() core.ResourceId {
+	return core.ResourceId{
+		Provider: AWS_PROVIDER,
+		Type:     ROUTE_53_RECORD_TYPE,
+		Name:     record.Name,
+	}
+}
+
+func (hc *Route53HealthCheck) KlothoConstructRef() core.AnnotationKeySet {
+	return hc.ConstructsRef
+}
+
+// Id returns the id of the cloud resource
+func (hc *Route53HealthCheck) Id() core.ResourceId {
+	return core.ResourceId{
+		Provider: AWS_PROVIDER,
+		Type:     ROUTE_53_HEALTH_CHECK_TYPE,
+		Name:     hc.Name,
+	}
+}


Test patch:
diff --git a/pkg/provider/aws/execution_unit_test.go b/pkg/provider/aws/execution_unit_test.go
index 9c6acae9..b7d26d61 100644
--- a/pkg/provider/aws/execution_unit_test.go
+++ b/pkg/provider/aws/execution_unit_test.go
@@ -427,192 +427,3 @@ func Test_handleHelmChartAwsValues(t *testing.T) {
 		})
 	}
 }
-
-func Test_convertExecUnitParams(t *testing.T) {
-	s3Bucket := resources.NewS3Bucket(&core.Fs{AnnotationKey: core.AnnotationKey{ID: "bucket"}}, "test-app")
-	cases := []struct {
-		name                    string
-		construct               core.Construct
-		resources               []core.Resource
-		defaultType             string
-		execUnitResource        core.Resource
-		wants                   resources.EnvironmentVariables
-		constructIdToResourceId map[string][]core.Resource
-		wantErr                 bool
-	}{
-		{
-			name: `lambda`,
-			construct: &core.ExecutionUnit{
-				AnnotationKey: core.AnnotationKey{ID: "unit"},
-				EnvironmentVariables: core.EnvironmentVariables{
-					core.GenerateBucketEnvVar(&core.Fs{AnnotationKey: core.AnnotationKey{ID: "bucket"}}),
-				},
-			},
-			defaultType: Lambda,
-			resources: []core.Resource{
-				s3Bucket,
-			},
-			constructIdToResourceId: map[string][]core.Resource{
-				":bucket": {s3Bucket},
-			},
-			execUnitResource: &resources.LambdaFunction{},
-			wants: resources.EnvironmentVariables{
-				"BUCKET_BUCKET_NAME": core.IaCValue{Resource: s3Bucket, Property: "bucket_name"},
-			},
-		},
-		{
-			name: `lambda with sample key value`,
-			construct: &core.ExecutionUnit{
-				AnnotationKey: core.AnnotationKey{ID: "unit"},
-				EnvironmentVariables: core.EnvironmentVariables{
-					core.NewEnvironmentVariable("TestVar", nil, "TestValue"),
-				},
-			},
-			defaultType:             Lambda,
-			constructIdToResourceId: make(map[string][]core.Resource),
-			execUnitResource:        &resources.LambdaFunction{},
-			wants: resources.EnvironmentVariables{
-				"TestVar": core.IaCValue{Resource: nil, Property: "TestValue"},
-			},
-		},
-		{
-			name: `kubernetes`,
-			construct: &core.ExecutionUnit{
-				AnnotationKey: core.AnnotationKey{ID: "unit"},
-				EnvironmentVariables: core.EnvironmentVariables{
-					core.GenerateBucketEnvVar(&core.Fs{AnnotationKey: core.AnnotationKey{ID: "bucket"}}),
-					core.NewEnvironmentVariable("TestVar", nil, "TestValue"),
-				},
-			},
-			defaultType: kubernetes.KubernetesType,
-			resources: []core.Resource{
-				s3Bucket,
-			},
-			constructIdToResourceId: map[string][]core.Resource{
-				":bucket": {s3Bucket},
-			},
-			execUnitResource: &kubernetes.HelmChart{
-				Name:           "chart",
-				ExecutionUnits: []*kubernetes.HelmExecUnit{{Name: "unit"}},
-				ProviderValues: []kubernetes.HelmChartValue{
-					{
-						EnvironmentVariable: core.GenerateBucketEnvVar(&core.Fs{AnnotationKey: core.AnnotationKey{ID: "bucket"}}),
-						Key:                 "BUCKETBUCKETNAME",
-					},
-				},
-				ConstructRefs: core.AnnotationKeySetOf(core.AnnotationKey{ID: "unit"}),
-				Values:        make(map[string]any),
-			},
-			wants: resources.EnvironmentVariables{
-				"BUCKETBUCKETNAME": core.IaCValue{Resource: s3Bucket, Property: "bucket_name"},
-			},
-		},
-	}
-
-	for _, tt := range cases {
-		t.Run(tt.name, func(t *testing.T) {
-			assert := assert.New(t)
-			aws := AWS{
-				Config:                 &config.Application{AppName: "test", Defaults: config.Defaults{ExecutionUnit: config.KindDefaults{Type: tt.defaultType}}},
-				constructIdToResources: tt.constructIdToResourceId,
-			}
-			if _, ok := tt.execUnitResource.(*kubernetes.HelmChart); !ok {
-				aws.constructIdToResources[":unit"] = []core.Resource{tt.execUnitResource}
-			}
-
-			result := core.NewConstructGraph()
-			result.AddConstruct(tt.construct)
-
-			dag := core.NewResourceGraph()
-			dag.AddResource(tt.execUnitResource)
-			for _, res := range tt.resources {
-				dag.AddResource(res)
-			}
-
-			err := aws.convertExecUnitParams(result, dag)
-			if tt.wantErr {
-				assert.Error(err)
-				return
-			}
-			if !assert.NoError(err) {
-				return
-			}
-			switch res := tt.execUnitResource.(type) {
-			case *resources.LambdaFunction:
-				assert.Equal(tt.wants, res.EnvironmentVariables)
-			case *kubernetes.HelmChart:
-				wantAsMap := map[string]any{}
-				for key, val := range tt.wants {
-					wantAsMap[key] = val
-				}
-				assert.Equal(wantAsMap, res.Values)
-			default:
-				assert.Failf(`test error`, `unrecognized test resource: %v`, res)
-			}
-		})
-
-	}
-}
-
-func Test_GetAssumeRolePolicyForType(t *testing.T) {
-	cases := []struct {
-		name string
-		cfg  config.ExecutionUnit
-		want resources.StatementEntry
-	}{
-		{
-			name: `lambda`,
-			cfg:  config.ExecutionUnit{Type: Lambda},
-			want: resources.StatementEntry{
-				Action: []string{"sts:AssumeRole"},
-				Principal: &resources.Principal{
-					Service: "lambda.amazonaws.com",
-				},
-				Effect: "Allow",
-			},
-		},
-		{
-			name: `ecs`,
-			cfg:  config.ExecutionUnit{Type: Ecs},
-			want: resources.StatementEntry{
-				Action: []string{"sts:AssumeRole"},
-				Principal: &resources.Principal{
-					Service: "ecs-tasks.amazonaws.com",
-				},
-				Effect: "Allow",
-			},
-		},
-		{
-			name: `eks fargate`,
-			cfg:  config.ExecutionUnit{Type: kubernetes.KubernetesType, InfraParams: config.ConvertToInfraParams(config.KubernetesTypeParams{NodeType: string(resources.Fargate)})},
-			want: resources.StatementEntry{
-				Action: []string{"sts:AssumeRole"},
-				Principal: &resources.Principal{
-					Service: "eks-fargate-pods.amazonaws.com",
-				},
-				Effect: "Allow",
-			},
-		},
-		{
-			name: `eks node`,
-			cfg:  config.ExecutionUnit{Type: kubernetes.KubernetesType, InfraParams: config.ConvertToInfraParams(config.KubernetesTypeParams{NodeType: string(resources.Node)})},
-			want: resources.StatementEntry{
-				Action: []string{"sts:AssumeRole"},
-				Principal: &resources.Principal{
-					Service: "ec2.amazonaws.com",
-				},
-				Effect: "Allow",
-			},
-		},
-	}
-
-	for _, tt := range cases {
-		t.Run(tt.name, func(t *testing.T) {
-			assert := assert.New(t)
-			actual := GetAssumeRolePolicyForType(tt.cfg)
-
-			assert.Equal(tt.want, actual.Statement[0])
-		})
-
-	}
-}
diff --git a/pkg/provider/aws/knowledgebase/edge_expansion_test.go b/pkg/provider/aws/knowledgebase/edge_expansion_test.go
index 63d38fd2..7588ae42 100644
--- a/pkg/provider/aws/knowledgebase/edge_expansion_test.go
+++ b/pkg/provider/aws/knowledgebase/edge_expansion_test.go
@@ -8,6 +8,7 @@ import (
 	"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"
@@ -33,7 +34,7 @@ func Test_ExpandEdges(t *testing.T) {
 							SubnetGroup: &resources.RdsSubnetGroup{Subnets: []*resources.Subnet{{Name: "subnet1", Vpc: &resources.Vpc{}}}}},
 						Constraint: knowledgebase.EdgeConstraint{
 							NodeMustExist:    []core.Resource{&resources.RdsProxy{}},
-							NodeMustNotExist: []core.Resource{&resources.IamRole{}},
+							NodeMustNotExist: []core.Resource{&kubernetes.HelmChart{}},
 						},
 						EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
 					},
diff --git a/pkg/provider/aws/plugin_test.go b/pkg/provider/aws/plugin_test.go
index 92b735d8..26decf04 100644
--- a/pkg/provider/aws/plugin_test.go
+++ b/pkg/provider/aws/plugin_test.go
@@ -153,7 +153,8 @@ func Test_CopyConstructEdgesToDag(t *testing.T) {
 						Source:      &resources.LambdaFunction{Name: "lambda"},
 						Destination: &resources.RdsInstance{Name: "rds"},
 						Constraint: knowledgebase.EdgeConstraint{
-							NodeMustExist: []core.Resource{&resources.RdsProxy{}},
+							NodeMustExist:    []core.Resource{&resources.RdsProxy{}},
+							NodeMustNotExist: []core.Resource{&kubernetes.HelmChart{}},
 						},
 						EnvironmentVariables: []core.EnvironmentVariable{core.GenerateOrmConnStringEnvVar(orm)},
 					},
diff --git a/pkg/provider/aws/resources/cloud_map_test.go b/pkg/provider/aws/resources/cloud_map_test.go
new file mode 100644
index 00000000..24ff11f7
--- /dev/null
+++ b/pkg/provider/aws/resources/cloud_map_test.go
@@ -0,0 +1,58 @@
+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_PrivateDnsNamespaceCreate(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
+	initialRefs := core.AnnotationKeySetOf(core.AnnotationKey{ID: "first"})
+	cases := []coretesting.CreateCase[PrivateDnsNamespaceCreateParams, *PrivateDnsNamespace]{
+		{
+			Name: "nil profile",
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:private_dns_namespace:my-app",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:private_dns_namespace:my-app", Destination: "aws:vpc:my_app"},
+				},
+			},
+			Check: func(assert *assert.Assertions, namespace *PrivateDnsNamespace) {
+				assert.Equal(namespace.Name, "my-app")
+				assert.NotNil(namespace.Vpc)
+				assert.Equal(namespace.ConstructsRef, core.AnnotationKeySetOf(eu.AnnotationKey))
+			},
+		},
+		{
+			Name:     "existing lambda",
+			Existing: &PrivateDnsNamespace{Name: "my-app", ConstructsRef: initialRefs},
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:private_dns_namespace:my-app",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Check: func(assert *assert.Assertions, namespace *PrivateDnsNamespace) {
+				assert.Equal(namespace.Name, "my-app")
+				initialRefs.Add(eu.AnnotationKey)
+				assert.Equal(namespace.ConstructsRef, initialRefs)
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.Name, func(t *testing.T) {
+			tt.Params = PrivateDnsNamespaceCreateParams{
+				AppName: "my-app",
+				Refs:    core.AnnotationKeySetOf(eu.AnnotationKey),
+			}
+			tt.Run(t)
+		})
+	}
+}
diff --git a/pkg/provider/aws/resources/iam_test.go b/pkg/provider/aws/resources/iam_test.go
index ba5464ca..c110326a 100644
--- a/pkg/provider/aws/resources/iam_test.go
+++ b/pkg/provider/aws/resources/iam_test.go
@@ -80,9 +80,14 @@ func Test_PolicyCreate(t *testing.T) {
 			},
 		},
 		{
-			name:    "existing policy",
-			policy:  &IamPolicy{Name: "my-app-policy", ConstructsRef: initialRefs},
-			wantErr: true,
+			name:   "existing policy",
+			policy: &IamPolicy{Name: "my-app-policy", ConstructsRef: initialRefs},
+			want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:iam_policy:my-app-policy",
+				},
+				Deps: []coretesting.StringDep{},
+			},
 		},
 	}
 	for _, tt := range cases {
@@ -109,8 +114,15 @@ func Test_PolicyCreate(t *testing.T) {
 			}
 			tt.want.Assert(t, dag)
 
+			policy, _ = core.GetResource[*IamPolicy](dag, policy.Id())
 			assert.Equal(policy.Name, "my-app-policy")
-			assert.Equal(policy.ConstructsRef, metadata.Refs)
+			if tt.policy == nil {
+				assert.Equal(policy.ConstructsRef, metadata.Refs)
+			} else {
+				initialRefs.Add(core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability})
+				assert.Equal(policy.ConstructsRef, initialRefs)
+
+			}
 		})
 	}
 }
diff --git a/pkg/provider/aws/resources/route53_test.go b/pkg/provider/aws/resources/route53_test.go
new file mode 100644
index 00000000..9f6adb30
--- /dev/null
+++ b/pkg/provider/aws/resources/route53_test.go
@@ -0,0 +1,193 @@
+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_Route53HostedZoneCreate(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
+	initialRefs := core.AnnotationKeySetOf(core.AnnotationKey{ID: "first"})
+	cases := []coretesting.CreateCase[Route53HostedZoneCreateParams, *Route53HostedZone]{
+		{
+			Name: "nil private zone",
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_hosted_zone:my-app-zone",
+					"aws:vpc:my_app",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:route53_hosted_zone:my-app-zone", Destination: "aws:vpc:my_app"},
+				},
+			},
+			Params: Route53HostedZoneCreateParams{Type: "private"},
+			Check: func(assert *assert.Assertions, zone *Route53HostedZone) {
+				assert.Equal(zone.Name, "my-app-zone")
+				assert.NotNil(zone.Vpc)
+				assert.Equal(zone.ConstructsRef, core.AnnotationKeySetOf(eu.AnnotationKey))
+			},
+		},
+		{
+			Name: "nil public zone",
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_hosted_zone:my-app-zone",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Params: Route53HostedZoneCreateParams{Type: "public"},
+			Check: func(assert *assert.Assertions, zone *Route53HostedZone) {
+				assert.Equal(zone.Name, "my-app-zone")
+				assert.Nil(zone.Vpc)
+				assert.Equal(zone.ConstructsRef, core.AnnotationKeySetOf(eu.AnnotationKey))
+			},
+		},
+		{
+			Name:     "existing load balancer",
+			Existing: &Route53HostedZone{Name: "my-app-zone", ConstructsRef: initialRefs},
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_hosted_zone:my-app-zone",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Check: func(assert *assert.Assertions, zone *Route53HostedZone) {
+				assert.Equal(zone.Name, "my-app-zone")
+				initialRefs.Add(eu.AnnotationKey)
+				assert.Equal(zone.ConstructsRef, initialRefs)
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.Name, func(t *testing.T) {
+			tt.Params = Route53HostedZoneCreateParams{
+				AppName: "my-app",
+				Refs:    core.AnnotationKeySetOf(eu.AnnotationKey),
+				Type:    tt.Params.Type,
+				Name:    "zone",
+			}
+			tt.Run(t)
+		})
+	}
+}
+
+func Test_Route53RecordCreate(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
+	initialRefs := core.AnnotationKeySetOf(core.AnnotationKey{ID: "first"})
+	cases := []coretesting.CreateCase[Route53RecordCreateParams, *Route53Record]{
+		{
+			Name: "nil private zone",
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_hosted_zone:my-app-zone",
+					"aws:route53_record:my-app-zone-record",
+				},
+				Deps: []coretesting.StringDep{
+					{Source: "aws:route53_record:my-app-zone-record", Destination: "aws:route53_hosted_zone:my-app-zone"},
+				},
+			},
+			Check: func(assert *assert.Assertions, record *Route53Record) {
+				assert.Equal(record.Name, "my-app-zone-record")
+				assert.NotNil(record.Zone)
+				assert.Equal(record.ConstructsRef, core.AnnotationKeySetOf(eu.AnnotationKey))
+			},
+		},
+		{
+			Name:     "existing load balancer",
+			Existing: &Route53Record{Name: "my-app-zone-record", ConstructsRef: initialRefs},
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_record:my-app-zone-record",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Check: func(assert *assert.Assertions, record *Route53Record) {
+				assert.Equal(record.Name, "my-app-zone-record")
+				initialRefs.Add(eu.AnnotationKey)
+				assert.Equal(record.ConstructsRef, initialRefs)
+			},
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.Name, func(t *testing.T) {
+			tt.Params = Route53RecordCreateParams{
+				Refs:       core.AnnotationKeySetOf(eu.AnnotationKey),
+				Zone:       &Route53HostedZone{Name: "my-app-zone", ConstructsRef: initialRefs},
+				DomainName: "record",
+			}
+			tt.Run(t)
+		})
+	}
+}
+
+func Test_Route53HealthCheckCreate(t *testing.T) {
+	eu := &core.ExecutionUnit{AnnotationKey: core.AnnotationKey{ID: "test", Capability: annotation.ExecutionUnitCapability}}
+	initialRefs := core.AnnotationKeySetOf(core.AnnotationKey{ID: "first"})
+	cases := []coretesting.CreateCase[Route53HealthCheckCreateParams, *Route53HealthCheck]{
+		{
+			Name: "nil check ip",
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_health_check:my-app-10.0.0.0",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Params: Route53HealthCheckCreateParams{IpAddress: "10.0.0.0"},
+			Check: func(assert *assert.Assertions, healthCheck *Route53HealthCheck) {
+				assert.Equal(healthCheck.Name, "my-app-10.0.0.0")
+				assert.Equal(healthCheck.ConstructsRef, core.AnnotationKeySetOf(eu.AnnotationKey))
+			},
+		},
+		{
+			Name: "nil check fqdn",
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_health_check:my-app-example.com",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Params: Route53HealthCheckCreateParams{Fqdn: "example.com"},
+			Check: func(assert *assert.Assertions, healthCheck *Route53HealthCheck) {
+				assert.Equal(healthCheck.Name, "my-app-example.com")
+				assert.Equal(healthCheck.ConstructsRef, core.AnnotationKeySetOf(eu.AnnotationKey))
+			},
+		},
+		{
+			Name:     "existing health check",
+			Existing: &Route53HealthCheck{Name: "my-app-check", ConstructsRef: initialRefs},
+			Params:   Route53HealthCheckCreateParams{Fqdn: "check"},
+			Want: coretesting.ResourcesExpectation{
+				Nodes: []string{
+					"aws:route53_health_check:my-app-check",
+				},
+				Deps: []coretesting.StringDep{},
+			},
+			Check: func(assert *assert.Assertions, healthCheck *Route53HealthCheck) {
+				assert.Equal(healthCheck.Name, "my-app-check")
+				initialRefs.Add(eu.AnnotationKey)
+				assert.Equal(healthCheck.ConstructsRef, initialRefs)
+			},
+		},
+		{
+			Name:     "ip and fqdn error",
+			Params:   Route53HealthCheckCreateParams{IpAddress: "10.0.0.0", Fqdn: "example.com"},
+			Existing: &Route53HealthCheck{Name: "my-app-check", ConstructsRef: initialRefs},
+			WantErr:  true,
+		},
+	}
+	for _, tt := range cases {
+		t.Run(tt.Name, func(t *testing.T) {
+			tt.Params = Route53HealthCheckCreateParams{
+				Refs:      core.AnnotationKeySetOf(eu.AnnotationKey),
+				AppName:   "my-app",
+				IpAddress: tt.Params.IpAddress,
+				Fqdn:      tt.Params.Fqdn,
+			}
+			tt.Run(t)
+		})
+	}
+}