From 51239399b1b48c46d6e9e44a3abf7cecd4e66e7a Mon Sep 17 00:00:00 2001 From: gordon-klotho Date: Tue, 19 Sep 2023 09:46:13 -0400 Subject: [PATCH 1/4] Use go templates in edge and resource templates --- .golangci.toml | 6 + pkg/construct/resource_graph.go | 22 +- pkg/construct/resource_id.go | 79 +++- pkg/construct/resource_id_test.go | 198 ++++++++ pkg/construct/resources.go | 23 + .../classification/base_classifications.go | 50 +- pkg/engine/classification/classification.go | 11 +- pkg/engine/cli.go | 7 + pkg/engine/constraints/constraint.go | 2 +- pkg/engine/constraints/resource_constraint.go | 57 ++- pkg/engine/construct_expansion.go | 99 ++-- pkg/engine/dataflow.go | 11 - pkg/engine/decisions.go | 31 +- pkg/engine/edge_configuration.go | 65 ++- pkg/engine/engine.go | 40 +- .../enginetesting/classification_mock.go | 8 +- pkg/engine/errors.go | 93 ++-- pkg/engine/operational_resources.go | 311 +++++++++---- pkg/engine/operational_resources_test.go | 326 +++---------- pkg/engine/reconciler.go | 110 +++-- pkg/engine/reconciler_resources.go | 213 +++++++++ pkg/engine/resource_configuration.go | 174 +++++-- pkg/engine/resource_configuration_test.go | 8 +- pkg/graph/graph.go | 44 +- pkg/infra/iac2/templates/.gitignore | 2 +- pkg/knowledge_base/edge_kb_test.go | 32 ++ pkg/knowledge_base/operational_funcs.go | 433 ++++++++++++++++++ pkg/knowledge_base/operational_funcs_test.go | 142 ++++++ pkg/knowledge_base/resource.go | 6 +- .../edges/api_deployment-api_integration.yaml | 6 +- .../aws/edges/api_deployment-api_method.yaml | 6 +- .../aws/edges/api_deployment-rest_api.yaml | 4 +- .../api_integration-lambda_function.yaml | 14 +- .../edges/api_integration-load_balancer.yaml | 24 +- .../aws/edges/api_integration-vpc_link.yaml | 12 +- .../aws/edges/api_method-api_integration.yaml | 18 +- .../edges/api_resource-api_integration.yaml | 4 +- .../aws/edges/api_resource-api_method.yaml | 8 +- .../aws/edges/api_resource-api_resource.yaml | 4 +- .../aws/edges/api_stage-api_deployment.yaml | 4 +- .../aws/edges/api_stage-rest_api.yaml | 4 +- .../app_runner_service-dynamodb_table.yaml | 6 +- .../edges/app_runner_service-ecr_image.yaml | 4 +- .../edges/app_runner_service-s3_bucket.yaml | 6 +- .../aws/edges/app_runner_service-secret.yaml | 6 +- ...app_runner_service-ses_email_identity.yaml | 6 +- pkg/provider/aws/edges/ec2_instance-ami.yaml | 4 +- .../edges/ec2_instance-dynamodb_table.yaml | 6 +- .../edges/ec2_instance-efs_mount_target.yaml | 6 +- .../ec2_instance-elasticache_cluster.yaml | 4 +- .../edges/ec2_instance-instance_profile.yaml | 4 +- .../aws/edges/ec2_instance-rds_instance.yaml | 4 +- .../aws/edges/ec2_instance-rds_proxy.yaml | 4 +- .../aws/edges/ec2_instance-s3_bucket.yaml | 6 +- .../aws/edges/ec2_instance-secret.yaml | 6 +- .../edges/ec2_instance-security_group.yaml | 4 +- .../aws/edges/ec2_instance-sns_topic.yaml | 4 +- .../aws/edges/ec2_instance-sqs_queue.yaml | 4 +- .../edges/ec2_instance-subnet_private.yaml | 4 +- .../aws/edges/ec2_instance-subnet_public.yaml | 4 +- .../aws/edges/ecr_image-docker_image.yaml | 8 +- .../aws/edges/ecr_image-ecr_repo.yaml | 4 +- .../aws/edges/ecs_service-dynamodb_table.yaml | 6 +- .../aws/edges/ecs_service-ecs_cluster.yaml | 4 +- .../ecs_service-ecs_task_definition.yaml | 4 +- .../ecs_service-elasticache_cluster.yaml | 4 +- .../aws/edges/ecs_service-rds_instance.yaml | 4 +- .../aws/edges/ecs_service-rds_proxy.yaml | 4 +- .../aws/edges/ecs_service-s3_bucket.yaml | 6 +- .../aws/edges/ecs_service-secret.yaml | 6 +- .../aws/edges/ecs_service-security_group.yaml | 4 +- .../aws/edges/ecs_service-sns_topic.yaml | 4 +- .../aws/edges/ecs_service-sqs_queue.yaml | 4 +- .../aws/edges/ecs_service-subnet_private.yaml | 4 +- .../aws/edges/ecs_service-subnet_public.yaml | 4 +- pkg/provider/aws/edges/ecs_service-vpc.yaml | 4 +- .../ecs_task_definition-docker_image.yaml | 4 +- .../edges/ecs_task_definition-ecr_image.yaml | 4 +- .../edges/ecs_task_definition-log_group.yaml | 4 +- .../aws/edges/ecs_task_definition-region.yaml | 4 +- .../efs_access_point-efs_file_system.yaml | 4 +- .../efs_access_point-efs_mount_target.yaml | 4 +- .../efs_file_system-availability_zones.yaml | 4 +- .../aws/edges/efs_file_system-kms_key.yaml | 4 +- .../efs_mount_target-efs_file_system.yaml | 4 +- .../efs_mount_target-security_group.yaml | 4 +- .../efs_mount_target-subnet_private.yaml | 4 +- .../edges/efs_mount_target-subnet_public.yaml | 4 +- .../aws/edges/eks_add_on-eks_cluster.yaml | 4 +- .../aws/edges/eks_cluster-security_group.yaml | 6 +- .../aws/edges/eks_cluster-subnet_private.yaml | 4 +- .../aws/edges/eks_cluster-subnet_public.yaml | 4 +- pkg/provider/aws/edges/eks_cluster-vpc.yaml | 4 +- .../eks_fargate_profile-subnet_private.yaml | 4 +- .../eks_fargate_profile-subnet_public.yaml | 4 +- .../edges/eks_node_group-subnet_private.yaml | 4 +- .../edges/eks_node_group-subnet_public.yaml | 4 +- ...cache_cluster-elasticache_subnetgroup.yaml | 4 +- .../edges/elasticache_cluster-log_group.yaml | 6 +- .../elasticache_cluster-security_group.yaml | 4 +- ...lasticache_subnetgroup-subnet_private.yaml | 4 +- .../edges/iam_oidc_provider-eks_cluster.yaml | 6 +- .../aws/edges/iam_oidc_provider-region.yaml | 4 +- .../aws/edges/internet_gateway-vpc.yaml | 4 +- ...kubernetes_deployment-lambda_function.yaml | 4 +- .../kubernetes_deployment-sns_topic.yaml | 4 +- .../kubernetes_deployment-sqs_queue.yaml | 4 +- .../kubernetes_kubeconfig-eks_cluster.yaml | 4 +- .../edges/kubernetes_pod-lambda_function.yaml | 4 +- .../aws/edges/kubernetes_pod-sns_topic.yaml | 4 +- .../aws/edges/kubernetes_pod-sqs_queue.yaml | 4 +- .../edges/lambda_function-dynamodb_table.yaml | 6 +- .../aws/edges/lambda_function-ecr_image.yaml | 4 +- .../lambda_function-elasticache_cluster.yaml | 6 +- .../lambda_function-lambda_function.yaml | 6 +- .../aws/edges/lambda_function-log_group.yaml | 6 +- .../edges/lambda_function-rds_instance.yaml | 6 +- .../aws/edges/lambda_function-rds_proxy.yaml | 6 +- .../aws/edges/lambda_function-s3_bucket.yaml | 6 +- .../aws/edges/lambda_function-secret.yaml | 6 +- .../edges/lambda_function-security_group.yaml | 4 +- .../lambda_function-ses_email_identity.yaml | 6 +- .../aws/edges/lambda_function-sns_topic.yaml | 4 +- .../aws/edges/lambda_function-sqs_queue.yaml | 4 +- .../edges/lambda_function-subnet_private.yaml | 4 +- .../edges/lambda_function-subnet_public.yaml | 4 +- .../lambda_permission-lambda_function.yaml | 4 +- .../load_balancer-load_balancer_listener.yaml | 14 +- .../edges/load_balancer-security_group.yaml | 4 +- .../edges/load_balancer-subnet_private.yaml | 4 +- .../edges/load_balancer-subnet_public.yaml | 4 +- .../load_balancer_listener-target_group.yaml | 14 +- .../aws/edges/nat_gateway-elastic_ip.yaml | 4 +- .../aws/edges/nat_gateway-subnet_public.yaml | 4 +- .../aws/edges/private_dns_namespace-vpc.yaml | 4 +- .../edges/rds_instance-rds_subnet_group.yaml | 4 +- .../edges/rds_instance-security_group.yaml | 4 +- .../rds_proxy-rds_proxy_target_group.yaml | 10 +- pkg/provider/aws/edges/rds_proxy-secret.yaml | 8 +- .../aws/edges/rds_proxy-security_group.yaml | 4 +- .../rds_proxy_target_group-rds_instance.yaml | 8 +- ...rds_subnet_group-group-subnet_private.yaml | 4 +- .../aws/edges/rest_api-api_integration.yaml | 4 +- .../aws/edges/rest_api-api_method.yaml | 4 +- .../aws/edges/rest_api-api_resource.yaml | 4 +- .../aws/edges/rest_api-lambda_permission.yaml | 12 +- .../aws/edges/route53_hosted_zone-vpc.yaml | 4 +- .../edges/route_table-internet_gateway.yaml | 8 +- .../aws/edges/route_table-nat_gateway.yaml | 8 +- pkg/provider/aws/edges/route_table-vpc.yaml | 4 +- ...icy-cloudfront_origin_access_identity.yaml | 6 +- .../aws/edges/s3_bucket_policy-s3_bucket.yaml | 8 +- .../aws/edges/s3_object-s3_bucket.yaml | 4 +- .../aws/edges/secret-secret_version.yaml | 4 +- .../edges/secret_version-rds_instance.yaml | 8 +- .../aws/edges/security_group-vpc.yaml | 10 +- .../edges/sns_subscription-ec2_instance.yaml | 4 +- .../edges/sns_subscription-ecs_service.yaml | 4 +- ...ns_subscription-kubernetes_deployment.yaml | 4 +- .../sns_subscription-kubernetes_pod.yaml | 4 +- .../sns_subscription-lambda_function.yaml | 4 +- .../aws/edges/sns_topic-sns_subscription.yaml | 4 +- .../aws/edges/sqs_queue-ec2_instance.yaml | 4 +- .../aws/edges/sqs_queue-ecs_service.yaml | 4 +- .../sqs_queue-kubernetes_deployment.yaml | 4 +- .../aws/edges/sqs_queue-kubernetes_pod.yaml | 4 +- .../aws/edges/sqs_queue-lambda_function.yaml | 4 +- .../aws/edges/sqs_queue-sqs_queue_policy.yaml | 4 +- .../subnet_private-availability_zones.yaml | 4 +- .../aws/edges/subnet_private-route_table.yaml | 4 +- .../aws/edges/subnet_private-vpc.yaml | 4 +- .../subnet_public-availability_zones.yaml | 4 +- .../aws/edges/subnet_public-route_table.yaml | 4 +- pkg/provider/aws/edges/subnet_public-vpc.yaml | 4 +- .../aws/edges/target_group-ec2_instance.yaml | 14 +- .../aws/edges/target_group-ecs_service.yaml | 10 +- pkg/provider/aws/edges/target_group-vpc.yaml | 4 +- .../aws/edges/vpc_link-load_balancer.yaml | 8 +- pkg/provider/aws/knowledgebase/api_gateway.go | 42 +- pkg/provider/aws/provider.go | 14 +- pkg/provider/aws/resources/api_gateway.go | 44 +- .../resources/templates/api_deployment.yaml | 6 +- .../resources/templates/api_integration.yaml | 19 +- .../aws/resources/templates/api_method.yaml | 6 +- .../aws/resources/templates/api_resource.yaml | 35 +- 185 files changed, 2382 insertions(+), 1179 deletions(-) create mode 100644 .golangci.toml create mode 100644 pkg/construct/resource_id_test.go create mode 100644 pkg/engine/reconciler_resources.go create mode 100644 pkg/knowledge_base/operational_funcs.go create mode 100644 pkg/knowledge_base/operational_funcs_test.go diff --git a/.golangci.toml b/.golangci.toml new file mode 100644 index 000000000..490200b36 --- /dev/null +++ b/.golangci.toml @@ -0,0 +1,6 @@ +[linters-settings.lll] +line-length = 120 +tab-width = 2 + +[linters] +enable = ["lll"] diff --git a/pkg/construct/resource_graph.go b/pkg/construct/resource_graph.go index bb9c76555..8975397c3 100644 --- a/pkg/construct/resource_graph.go +++ b/pkg/construct/resource_graph.go @@ -14,7 +14,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "go.uber.org/zap" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ) type ( @@ -491,6 +491,26 @@ func (rg *ResourceGraph) ReplaceConstruct(resource Resource, new Resource) error return rg.RemoveResource(resource) } +func (rg *ResourceGraph) ReplaceConstructId(oldId ResourceId, new Resource) error { + rg.AddResource(new) + // Since its a construct we just assume every single edge can be removed + for _, edge := range rg.underlying.OutgoingEdgesById(oldId.String()) { + rg.AddDependency(new, edge.Destination) + err := rg.RemoveDependency(edge.Source.Id(), edge.Destination.Id()) + if err != nil { + return err + } + } + for _, edge := range rg.underlying.IncomingEdgesById(oldId.String()) { + rg.AddDependency(edge.Source, new) + err := rg.RemoveDependency(edge.Source.Id(), edge.Destination.Id()) + if err != nil { + return err + } + } + return rg.underlying.RemoveVertex(oldId.String()) +} + // 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 diff --git a/pkg/construct/resource_id.go b/pkg/construct/resource_id.go index 2556fabb8..d040d64e1 100644 --- a/pkg/construct/resource_id.go +++ b/pkg/construct/resource_id.go @@ -17,16 +17,32 @@ type ResourceId struct { Name string `yaml:"name" toml:"name"` } +var zeroId = ResourceId{} + func (id ResourceId) IsZero() bool { - return id == ResourceId{} + return id == zeroId } func (id ResourceId) String() string { - s := id.Provider + ":" + id.Type + if id.IsZero() { + return "" + } + + sb := strings.Builder{} + sb.Grow(len(id.Provider) + len(id.Type) + len(id.Namespace) + len(id.Name) + 3) + + sb.WriteString(id.Provider) + sb.WriteByte(':') + sb.WriteString(id.Type) if id.Namespace != "" || strings.Contains(id.Name, ":") { - s += ":" + id.Namespace + sb.WriteByte(':') + sb.WriteString(id.Namespace) + } + if id.Name != "" { + sb.WriteByte(':') + sb.WriteString(id.Name) } - return s + ":" + id.Name + return sb.String() } func (id ResourceId) QualifiedTypeName() string { @@ -37,28 +53,51 @@ func (id ResourceId) MarshalText() ([]byte, error) { return []byte(id.String()), nil } +// Matches uses `id` (the receiver) as a filter for `other` (the argument) and returns true if all the non-empty fields from +// `id` match the corresponding fields in `other`. +func (id ResourceId) Matches(other ResourceId) bool { + if id.Provider != "" && id.Provider != other.Provider { + return false + } + if id.Type != "" && id.Type != other.Type { + return false + } + if id.Namespace != "" && id.Namespace != other.Namespace { + return false + } + if id.Name != "" && id.Name != other.Name { + return false + } + return true +} + var ( resourceProviderPattern = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) resourceTypePattern = regexp.MustCompile(`^[a-zA-Z0-9_]+$`) - resourceNamespacePattern = regexp.MustCompile(`^[a-zA-Z0-9_#./\-:\[\]]*$`) - resourceNamePattern = regexp.MustCompile(`^[a-zA-Z0-9_#./\-:\[\]]*$`) + resourceNamespacePattern = regexp.MustCompile(`^[a-zA-Z0-9_./\-\[\]]*$`) // like name, but `:` not allowed + resourceNamePattern = regexp.MustCompile(`^[a-zA-Z0-9_./\-:\[\]#]*$`) ) func (id *ResourceId) UnmarshalText(data []byte) error { - parts := strings.Split(string(data), ":") - if len(parts) < 3 { - return fmt.Errorf("invalid number of parts (%d) in resource id '%s'", len(parts), string(data)) - } - if len(parts) > 4 { - parts = append(parts[:3], strings.Join(parts[3:], ":")) - } - id.Provider = parts[0] - id.Type = parts[1] - if len(parts) == 4 { - id.Namespace = parts[2] + parts := strings.SplitN(string(data), ":", 4) + switch len(parts) { + case 4: id.Name = parts[3] - } else { - id.Name = parts[2] + fallthrough + case 3: + if len(parts) == 4 { + id.Namespace = parts[2] + } else { + id.Name = parts[2] + } + fallthrough + case 2: + id.Type = parts[1] + id.Provider = parts[0] + case 1: + if parts[0] != "" { + return fmt.Errorf("must have trailing ':' for provider-only ID") + } } if id.IsZero() { return nil @@ -67,7 +106,7 @@ func (id *ResourceId) UnmarshalText(data []byte) error { if !resourceProviderPattern.MatchString(id.Provider) { err = errors.Join(err, fmt.Errorf("invalid provider '%s' (must match %s)", id.Provider, resourceProviderPattern)) } - if !resourceTypePattern.MatchString(id.Type) { + if id.Type != "" && !resourceTypePattern.MatchString(id.Type) { err = errors.Join(err, fmt.Errorf("invalid type '%s' (must match %s)", id.Type, resourceTypePattern)) } if id.Namespace != "" && !resourceNamespacePattern.MatchString(id.Namespace) { diff --git a/pkg/construct/resource_id_test.go b/pkg/construct/resource_id_test.go new file mode 100644 index 000000000..296041cf5 --- /dev/null +++ b/pkg/construct/resource_id_test.go @@ -0,0 +1,198 @@ +package construct + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResourceId_UnmarshalText(t *testing.T) { + tests := []struct { + name string + str string + want ResourceId + wantErr bool + }{ + { + name: "full id", + str: "aws:subnet:my_vpc:my_subnet", + want: ResourceId{ + Provider: "aws", + Type: "subnet", + Namespace: "my_vpc", + Name: "my_subnet", + }, + }, + { + name: "no namespace", + str: "aws:subnet:my_subnet", + want: ResourceId{ + Provider: "aws", + Type: "subnet", + Name: "my_subnet", + }, + }, + { + name: "namespace with colon in name", + str: "aws:subnet:my_vpc:my_subnet:with:colons", + want: ResourceId{ + Provider: "aws", + Type: "subnet", + Namespace: "my_vpc", + Name: "my_subnet:with:colons", + }, + }, + { + name: "no namespace with colon in name", + str: "aws:subnet::my_subnet:with:colons", + want: ResourceId{ + Provider: "aws", + Type: "subnet", + Name: "my_subnet:with:colons", + }, + }, + { + name: "no name or namespace", + str: "aws:subnet", + want: ResourceId{ + Provider: "aws", + Type: "subnet", + }, + }, + { + name: "no type", + str: "aws:", + want: ResourceId{ + Provider: "aws", + }, + }, + { + name: "no trailing colon", + str: "aws", + wantErr: true, + }, + { + name: "empty is zero id", + str: "", + want: ResourceId{}, + }, + { + name: "invalid provider", + str: "aws$:subnet:my_subnet", + wantErr: true, + }, + { + name: "invalid type", + str: "aws:subnet$:my_subnet", + wantErr: true, + }, + { + name: "invalid namespace", + str: "aws:subnet:my_vpc$:my_subnet", + wantErr: true, + }, + { + name: "invalid name", + str: "aws:subnet:my_vpc:my_subnet$", + wantErr: true, + }, + { + name: "missing provider", + str: ":subnet:my_vpc:my_subnet", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + var id ResourceId + err := id.UnmarshalText([]byte(tt.str)) + if tt.wantErr { + assert.Error(err) + return + } + if !assert.NoError(err) { + return + } + assert.Equal(tt.want, id) + // Test the round trip to make sure that String() matches exactly the input string + assert.Equal(tt.str, id.String()) + }) + } +} + +func TestResourceId_Matches(t *testing.T) { + tests := []struct { + name string + selector string + resource string + want bool + }{ + { + name: "provider match", + selector: "a:", + resource: "a:b:c", + want: true, + }, + { + name: "provider mismatch", + selector: "a:", + resource: "x:b:c", + want: false, + }, + { + name: "type match", + selector: "a:b", + resource: "a:b:c", + want: true, + }, + { + name: "type mismatch", + selector: "a:b", + resource: "a:x:c", + want: false, + }, + { + name: "namespace match", + selector: "a:b:c:", + resource: "a:b:c:d", + want: true, + }, + { + name: "namespace mismatch", + selector: "a:b:c:", + resource: "a:b:x:d", + want: false, + }, + { + name: "name match", + selector: "a:b:c:d", + resource: "a:b:c:d", + want: true, + }, + { + name: "name mismatch", + selector: "a:b:c:d", + resource: "a:b:c:x", + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + var sel, res ResourceId + err := sel.UnmarshalText([]byte(tt.selector)) + if !assert.NoError(err) { + return + } + err = res.UnmarshalText([]byte(tt.resource)) + if !assert.NoError(err) { + return + } + + assert.Equal(tt.want, sel.Matches(res)) + }) + } +} diff --git a/pkg/construct/resources.go b/pkg/construct/resources.go index af60bb7f5..df7890146 100644 --- a/pkg/construct/resources.go +++ b/pkg/construct/resources.go @@ -1,6 +1,8 @@ package construct import ( + "bytes" + "fmt" "reflect" "github.com/klothoplatform/klotho/pkg/io" @@ -313,3 +315,24 @@ func getNestedResources(resolver resourceResolver, source BaseConstruct, targetV } return } + +func (v IaCValue) String() string { + return v.ResourceId.String() + "#" + v.Property +} + +func (v IaCValue) MarshalText() ([]byte, error) { + return []byte(v.String()), nil +} + +func (v *IaCValue) UnmarshalText(b []byte) error { + parts := bytes.SplitN(b, []byte("#"), 2) + if len(parts) != 2 { + return fmt.Errorf("invalid IaCValue format: %s", string(b)) + } + err := v.ResourceId.UnmarshalText(parts[0]) + if err != nil { + return err + } + v.Property = string(parts[1]) + return nil +} diff --git a/pkg/engine/classification/base_classifications.go b/pkg/engine/classification/base_classifications.go index 839870f52..2a4862206 100644 --- a/pkg/engine/classification/base_classifications.go +++ b/pkg/engine/classification/base_classifications.go @@ -2,30 +2,30 @@ package classification var BaseClassificationDocument = &ClassificationDocument{ Classifications: map[string]Classification{ - "aws:app_runner_service:": {Gives: []Gives{}, Is: []string{"compute", "serverless"}}, - "aws:cloudfront_distribution:": {Gives: []Gives{}, Is: []string{"network", "cdn"}}, - "aws:ec2_instance:": {Gives: []Gives{}, Is: []string{"compute", "instance"}}, - "aws:ecr_image:": {Gives: []Gives{}, Is: []string{"container_image"}}, - "aws:ecs_cluster:": {Gives: []Gives{}, Is: []string{"cluster"}}, - "aws:ecs_service:": {Gives: []Gives{}, Is: []string{"compute"}}, - "aws:dynamodb_table:": {Gives: []Gives{}, Is: []string{"storage", "kv", "nosql"}}, - "aws:efs_file_system:": {Gives: []Gives{}, Is: []string{"storage", "filesystem"}}, - "aws:eks_cluster:": {Gives: []Gives{}, Is: []string{"cluster", "kubernetes"}}, - "aws:elasticache_cluster:": {Gives: []Gives{}, Is: []string{"storage", "redis", "cache"}}, - "aws:lambda_function:": {Gives: []Gives{}, Is: []string{"compute", "serverless"}}, - "aws:load_balancer:": {Gives: []Gives{}, Is: []string{"network", "loadbalancer"}}, - "aws:rds_instance:": {Gives: []Gives{}, Is: []string{"storage", "relational"}}, - "aws:rds_proxy:": {Gives: []Gives{}, Is: []string{"proxy"}}, - "aws:rest_api:": {Gives: []Gives{}, Is: []string{"api"}}, - "aws:route53_hosted_zone:": {Gives: []Gives{}, Is: []string{"network", "dns"}}, - "aws:s3_bucket:": {Gives: []Gives{}, Is: []string{"storage", "blob"}}, - "aws:sns_topic:": {Gives: []Gives{}, Is: []string{"messaging", "pubsub"}}, - "aws:sqs_queue:": {Gives: []Gives{}, Is: []string{"messaging", "queue"}}, - "aws:secret:": {Gives: []Gives{}, Is: []string{"storage", "secret"}}, - "aws:vpc:": {Gives: []Gives{}, Is: []string{"network"}}, - "docker:image:": {Gives: []Gives{}, Is: []string{"container_image"}}, - "kubernetes:deployment:": {Gives: []Gives{}, Is: []string{"compute", "kubernetes"}}, - "kubernetes:helm_chart:": {Gives: []Gives{}, Is: []string{"kubernetes"}}, - "kubernetes:pod:": {Gives: []Gives{}, Is: []string{"compute", "kubernetes"}}, + "aws:app_runner_service": {Gives: []Gives{}, Is: []string{"compute", "serverless"}}, + "aws:cloudfront_distribution": {Gives: []Gives{}, Is: []string{"network", "cdn"}}, + "aws:ec2_instance": {Gives: []Gives{}, Is: []string{"compute", "instance"}}, + "aws:ecr_image": {Gives: []Gives{}, Is: []string{"container_image"}}, + "aws:ecs_cluster": {Gives: []Gives{}, Is: []string{"cluster"}}, + "aws:ecs_service": {Gives: []Gives{}, Is: []string{"compute"}}, + "aws:dynamodb_table": {Gives: []Gives{}, Is: []string{"storage", "kv", "nosql"}}, + "aws:efs_file_system": {Gives: []Gives{}, Is: []string{"storage", "filesystem"}}, + "aws:eks_cluster": {Gives: []Gives{}, Is: []string{"cluster", "kubernetes"}}, + "aws:elasticache_cluster": {Gives: []Gives{}, Is: []string{"storage", "redis", "cache"}}, + "aws:lambda_function": {Gives: []Gives{}, Is: []string{"compute", "serverless"}}, + "aws:load_balancer": {Gives: []Gives{}, Is: []string{"network", "loadbalancer"}}, + "aws:rds_instance": {Gives: []Gives{}, Is: []string{"storage", "relational"}}, + "aws:rds_proxy": {Gives: []Gives{}, Is: []string{"proxy"}}, + "aws:rest_api": {Gives: []Gives{}, Is: []string{"api"}}, + "aws:route53_hosted_zone": {Gives: []Gives{}, Is: []string{"network", "dns"}}, + "aws:s3_bucket": {Gives: []Gives{}, Is: []string{"storage", "blob"}}, + "aws:sns_topic": {Gives: []Gives{}, Is: []string{"messaging", "pubsub"}}, + "aws:sqs_queue": {Gives: []Gives{}, Is: []string{"messaging", "queue"}}, + "aws:secret": {Gives: []Gives{}, Is: []string{"storage", "secret"}}, + "aws:vpc": {Gives: []Gives{}, Is: []string{"network"}}, + "docker:image": {Gives: []Gives{}, Is: []string{"container_image"}}, + "kubernetes:deployment": {Gives: []Gives{}, Is: []string{"compute", "kubernetes"}}, + "kubernetes:helm_chart": {Gives: []Gives{}, Is: []string{"kubernetes"}}, + "kubernetes:pod": {Gives: []Gives{}, Is: []string{"compute", "kubernetes"}}, }, } diff --git a/pkg/engine/classification/classification.go b/pkg/engine/classification/classification.go index eb15f32ba..243ec8064 100644 --- a/pkg/engine/classification/classification.go +++ b/pkg/engine/classification/classification.go @@ -57,8 +57,7 @@ func (c *ClassificationDocument) GivesAttributeForFunctionality(resource constru } func (c *ClassificationDocument) GetClassification(resource construct.Resource) Classification { - bareRes := reflect.New(reflect.TypeOf(resource).Elem()).Interface().(construct.Resource) - return c.Classifications[bareRes.Id().String()] + return c.Classifications[resource.Id().QualifiedTypeName()] } func (c *ClassificationDocument) GetFunctionality(resource construct.Resource) construct.Functionality { @@ -119,5 +118,13 @@ func ReadClassificationDoc(path string, fs embed.FS) (*ClassificationDocument, e if err != nil { return nil, err } + // fixup any ids that still have the trailing ':' + // TODO remove once all classification documents are fixed + for k, v := range classificationDoc.Classifications { + if strings.HasSuffix(k, ":") { + delete(classificationDoc.Classifications, k) + classificationDoc.Classifications[strings.TrimSuffix(k, ":")] = v + } + } return classificationDoc, nil } diff --git a/pkg/engine/cli.go b/pkg/engine/cli.go index b3febbc7e..be4aaf3ff 100644 --- a/pkg/engine/cli.go +++ b/pkg/engine/cli.go @@ -36,6 +36,7 @@ type EngineMain struct { var engineCfg struct { provider string guardrails string + jsonLog bool } var listResourceFieldsConfig struct { @@ -75,6 +76,11 @@ func setupLogger(analyticsClient *analytics.Client) (*zap.Logger, error) { } else { zapCfg = zap.NewProductionConfig() } + if engineCfg.jsonLog { + zapCfg.Encoding = "json" + } else { + zapCfg.Encoding = consoleEncoderName + } return zapCfg.Build(zap.WrapCore(func(core zapcore.Core) zapcore.Core { trackingCore := analyticsClient.NewFieldListener(zapcore.WarnLevel) @@ -135,6 +141,7 @@ func (em *EngineMain) AddEngineCli(root *cobra.Command) error { flags.StringVarP(&architectureEngineCfg.constraints, "constraints", "c", "", "Constraints file") flags.StringVarP(&architectureEngineCfg.outputDir, "output-dir", "o", "", "Output directory") flags.BoolVarP(&architectureEngineCfg.verbose, "verbose", "v", false, "Verbose flag") + flags.BoolVar(&engineCfg.jsonLog, "json-log", false, "Output logs in JSON format.") root.AddGroup(engineGroup) root.AddCommand(listResourceTypesCmd) diff --git a/pkg/engine/constraints/constraint.go b/pkg/engine/constraints/constraint.go index 6bd185c46..07bcd6116 100644 --- a/pkg/engine/constraints/constraint.go +++ b/pkg/engine/constraints/constraint.go @@ -166,7 +166,7 @@ func ParseConstraintsFromFile(bytes []byte) (map[ConstraintScope][]Constraint, e joinedErr = errors.Join(joinedErr, err) continue } - validOperators := []ConstraintOperator{AddConstraintOperator} + validOperators := []ConstraintOperator{EqualsConstraintOperator, AddConstraintOperator} if !collectionutil.Contains(validOperators, constraint.Operator) { joinedErr = errors.Join(joinedErr, fmt.Errorf("invalid operator %s for resource constraint", constraint.Operator)) continue diff --git a/pkg/engine/constraints/resource_constraint.go b/pkg/engine/constraints/resource_constraint.go index b18c99992..38cef5642 100644 --- a/pkg/engine/constraints/resource_constraint.go +++ b/pkg/engine/constraints/resource_constraint.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "reflect" + "strings" "github.com/klothoplatform/klotho/pkg/construct" "github.com/klothoplatform/klotho/pkg/engine/classification" @@ -44,8 +45,58 @@ func (constraint *ResourceConstraint) IsSatisfied(dag *construct.ResourceGraph, if res == nil { return false } - val := reflect.ValueOf(res).Elem().FieldByName(constraint.Property) - return val.IsValid() && val.Interface() == constraint.Value + strct := reflect.ValueOf(res) + for strct.Kind() == reflect.Ptr { + strct = strct.Elem() + } + val := strct.FieldByName(constraint.Property) + if !val.IsValid() { + // Try to find the field by its json or yaml tag (especially to handle case [upper/lower] [Pascal/snake]) + // Replicated from resource_configuration.go#parseFieldName so there's no dependency + for i := 0; i < strct.NumField(); i++ { + field := strct.Type().Field(i) + if constraint.Property == strings.ToLower(field.Name) { + // When YAML marshalling fields that don't have a tag, they're just lower cased + // so this condition should catch those. + val = strct.Field(i) + break + } + tag := strings.Split(field.Tag.Get("json"), ",")[0] + if constraint.Property == tag { + val = strct.Field(i) + break + } + tag = strings.Split(field.Tag.Get("yaml"), ",")[0] + if constraint.Property == tag { + val = strct.Field(i) + break + } + } + if !val.IsValid() { + return false + } + } + return val.Interface() == constraint.Value + + case AddConstraintOperator: + res := dag.GetResource(constraint.Target) + if res == nil { + return false + } + parent := reflect.ValueOf(res).Elem() + val := parent.FieldByName(constraint.Property) + if !val.IsValid() { + return false + } + switch val.Kind() { + case reflect.Slice, reflect.Array: + for i := 0; i < val.Len(); i++ { + if val.Index(i).Interface() == constraint.Value { + return true + } + } + return false + } } return true } @@ -61,5 +112,5 @@ func (constraint *ResourceConstraint) Validate() error { } func (constraint *ResourceConstraint) String() string { - return fmt.Sprintf("ResourceConstraint: %s %s %s %s", constraint.Target, constraint.Property, constraint.Operator, constraint.Value) + return fmt.Sprintf("ResourceConstraint: %s %s %s %v", constraint.Target, constraint.Property, constraint.Operator, constraint.Value) } diff --git a/pkg/engine/construct_expansion.go b/pkg/engine/construct_expansion.go index d8913d212..13ea686f2 100644 --- a/pkg/engine/construct_expansion.go +++ b/pkg/engine/construct_expansion.go @@ -32,74 +32,75 @@ type ( // If a dependency in the working state included a construct, the engine copies the dependency to all directly linked resources func (e *Engine) ExpandConstructs() { for _, res := range e.Context.WorkingState.ListConstructs() { - if res.Id().Provider == construct.AbstractConstructProvider { - zap.S().Debugf("Expanding construct %s", res.Id()) - construct, ok := res.(construct.Construct) + if res.Id().Provider != construct.AbstractConstructProvider { + continue + } + zap.S().Debugf("Expanding construct %s", res.Id()) + construct, ok := res.(construct.Construct) + if !ok { + e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ + Construct: res, + Cause: fmt.Errorf("unable to cast base construct %s to construct while expanding construct", res.Id()), + }) + continue + } + + constructType := "" + attributes := make(map[string]any) + for _, constraint := range e.Context.Constraints[constraints.ConstructConstraintScope] { + constructConstraint, ok := constraint.(*constraints.ConstructConstraint) if !ok { e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ Construct: res, - Cause: fmt.Errorf("unable to cast base construct %s to construct while expanding construct", res.Id()), + Cause: fmt.Errorf("constraint %s is incorrect type. Expected to be a construct constraint while expanding construct", constraint), }) continue } - constructType := "" - attributes := make(map[string]any) - for _, constraint := range e.Context.Constraints[constraints.ConstructConstraintScope] { - constructConstraint, ok := constraint.(*constraints.ConstructConstraint) - if !ok { + if constructConstraint.Target == construct.Id() { + if constructType != "" && constructType != constructConstraint.Type { e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ Construct: res, - Cause: fmt.Errorf("constraint %s is incorrect type. Expected to be a construct constraint while expanding construct", constraint), + Cause: fmt.Errorf("unable to expand construct %s, conflicting types in constraints", res.Id()), }) - continue + break } - - if constructConstraint.Target == construct.Id() { - if constructType != "" && constructType != constructConstraint.Type { - e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ - Construct: res, - Cause: fmt.Errorf("unable to expand construct %s, conflicting types in constraints", res.Id()), - }) - break - } - for k, v := range constructConstraint.Attributes { - if val, ok := attributes[k]; ok { - if v != val { - e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ - Construct: res, - Cause: fmt.Errorf("unable to expand construct %s, attribute %s has conflicting values", res.Id(), k), - }) - break - } + for k, v := range constructConstraint.Attributes { + if val, ok := attributes[k]; ok { + if v != val { + e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ + Construct: res, + Cause: fmt.Errorf("unable to expand construct %s, attribute %s has conflicting values", res.Id(), k), + }) + break } - attributes[k] = v } + attributes[k] = v } } + } - for k, v := range construct.Attributes() { - if val, ok := attributes[k]; ok { - if v != val { - e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ - Construct: res, - Cause: fmt.Errorf("unable to expand construct %s, attribute %s has conflicting values", res.Id(), k), - }) - break - } + for k, v := range construct.Attributes() { + if val, ok := attributes[k]; ok { + if v != val { + e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ + Construct: res, + Cause: fmt.Errorf("unable to expand construct %s, attribute %s has conflicting values", res.Id(), k), + }) + break } - attributes[k] = v - } - solutions, err := e.expandConstruct(constructType, attributes, construct) - if err != nil { - e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ - Construct: res, - Cause: err, - }) - continue } - e.Context.constructExpansionSolutions[res.Id()] = solutions + attributes[k] = v + } + solutions, err := e.expandConstruct(constructType, attributes, construct) + if err != nil { + e.Context.Errors = append(e.Context.Errors, &ConstructExpansionError{ + Construct: res, + Cause: err, + }) + continue } + e.Context.constructExpansionSolutions[res.Id()] = solutions } } diff --git a/pkg/engine/dataflow.go b/pkg/engine/dataflow.go index 5ff839e5b..01f89f4fd 100644 --- a/pkg/engine/dataflow.go +++ b/pkg/engine/dataflow.go @@ -7,17 +7,6 @@ import ( "github.com/klothoplatform/klotho/pkg/construct" ) -type nodeSettings struct { - // AllowIncoming determines whether the node's incoming edges should be added to the dataflow DAG - AllowIncoming bool - // AllowOutgoing determines whether the node's outgoing edges should be added to the dataflow DAG - AllowOutgoing bool -} - -// resourcePostFilter is a function that determines whether a resource should remain in the final dataflow DAG -// based on its own properties and the state of the dataflow DAG. -type resourcePostFilter func(resource construct.Resource, dag *construct.ResourceGraph) bool - func (e *Engine) RenderConnection(path []construct.Resource) bool { for _, res := range path[1 : len(path)-1] { tag := e.GetResourceVizTag(string(DataflowView), res) diff --git a/pkg/engine/decisions.go b/pkg/engine/decisions.go index 667084071..9f6aadfe5 100644 --- a/pkg/engine/decisions.go +++ b/pkg/engine/decisions.go @@ -140,6 +140,16 @@ func (e *Engine) handleDecisions(context *SolveContext, decisions []Decision) { } func (e *Engine) handleDecision(context *SolveContext, decision Decision) { + addResource := func(r construct.Resource, configure bool) bool { + if context.ResourceGraph.GetResource(r.Id()) != nil { + return false + } + context.ResourceGraph.AddResource(r) + if configure { + e.configureResource(context, r) + } + return true + } switch decision.Action { case ActionConfigure: if decision.Result.Resource != nil && decision.Result.Config != nil { @@ -182,23 +192,28 @@ func (e *Engine) handleDecision(context *SolveContext, decision Decision) { } case ActionCreate: if decision.Result.Resource != nil { - if context.ResourceGraph.GetResource(decision.Result.Resource.Id()) == nil { - context.ResourceGraph.AddResource(decision.Result.Resource) + if addResource(decision.Result.Resource, true) { context.recordDecision(decision) } } case ActionConnect: if decision.Result.Edge != nil { - if context.ResourceGraph.GetResource(decision.Result.Edge.Source.Id()) == nil { - e.handleDecision(context, Decision{Level: LevelInfo, Action: ActionCreate, Result: &DecisionResult{Resource: decision.Result.Edge.Source}, Cause: decision.Cause}) - } - if context.ResourceGraph.GetResource(decision.Result.Edge.Destination.Id()) == nil { - e.handleDecision(context, Decision{Level: LevelInfo, Action: ActionCreate, Result: &DecisionResult{Resource: decision.Result.Edge.Destination}, Cause: decision.Cause}) - } + addedSource := addResource(decision.Result.Edge.Source, false) + addedDestination := addResource(decision.Result.Edge.Destination, false) + if context.ResourceGraph.GetDependency(decision.Result.Edge.Source.Id(), decision.Result.Edge.Destination.Id()) == nil { context.ResourceGraph.AddDependencyWithData(decision.Result.Edge.Source, decision.Result.Edge.Destination, decision.Result.Edge.Properties.Data) context.recordDecision(decision) } + // Configure the resources after the dependency was added + if addedSource { + e.configureResource(context, decision.Result.Edge.Source) + context.recordDecision(Decision{Level: LevelInfo, Action: ActionCreate, Result: &DecisionResult{Resource: decision.Result.Edge.Source}, Cause: decision.Cause}) + } + if addedDestination { + e.configureResource(context, decision.Result.Edge.Destination) + context.recordDecision(Decision{Level: LevelInfo, Action: ActionCreate, Result: &DecisionResult{Resource: decision.Result.Edge.Destination}, Cause: decision.Cause}) + } } case ActionDisconnect: if decision.Result.Edge != nil { diff --git a/pkg/engine/edge_configuration.go b/pkg/engine/edge_configuration.go index 8f93cc7e1..22e2bd32c 100644 --- a/pkg/engine/edge_configuration.go +++ b/pkg/engine/edge_configuration.go @@ -2,7 +2,7 @@ package engine import ( "fmt" - "strings" + "regexp" "github.com/klothoplatform/klotho/pkg/construct" "github.com/klothoplatform/klotho/pkg/graph" @@ -11,7 +11,7 @@ import ( ) func (e *Engine) configureEdge(dep graph.Edge[construct.Resource], context *SolveContext) []EngineError { - templateKey := fmt.Sprintf("%s:%s:-%s:%s:", dep.Source.Id().Provider, dep.Source.Id().Type, dep.Destination.Id().Provider, dep.Destination.Id().Type) + templateKey := fmt.Sprintf("%s-%s", dep.Source.Id().QualifiedTypeName(), dep.Destination.Id().QualifiedTypeName()) _, found := e.KnowledgeBase.GetResourceEdge(dep.Source, dep.Destination) if e.EdgeTemplates[templateKey] == nil && !found { return []EngineError{&InternalError{Child: &EdgeConfigurationError{Edge: dep}, Cause: fmt.Errorf("no edge template found for %s", templateKey)}} @@ -36,6 +36,10 @@ func (e *Engine) configureEdge(dep graph.Edge[construct.Resource], context *Solv if engineErrors != nil { return engineErrors } + + // Re-run make operational in case the configuration changed the requirements + e.MakeResourceOperational(context, dep.Source) + e.MakeResourceOperational(context, dep.Destination) } err := e.KnowledgeBase.ConfigureEdge(&dep, context.ResourceGraph) @@ -126,11 +130,12 @@ func (e *Engine) EdgeTemplateExpand(template knowledgebase.EdgeTemplate, resourc return } -func EdgeTemplateConfigure(template knowledgebase.EdgeTemplate, graph *construct.ResourceGraph, edge *graph.Edge[construct.Resource], resourceMap map[construct.ResourceId]construct.Resource) (decisions []Decision, engineErrors []EngineError) { +// EdgeTemplateConfigure isn't based on user configuration, but is the configuration of resources based on the templates' rules. +func EdgeTemplateConfigure(template knowledgebase.EdgeTemplate, dag *construct.ResourceGraph, edge *graph.Edge[construct.Resource], resourceMap map[construct.ResourceId]construct.Resource) (decisions []Decision, engineErrors []EngineError) { for _, config := range template.Configuration { id, fields := getIdAndFields(config.Resource) res := resourceMap[id] - res, err := getResourceFromIdString(res, fields, graph) + res, err := getResourceFromIdString(res, fields, dag) if err != nil { engineErrors = append(engineErrors, &InternalError{ Child: &EdgeConfigurationError{Edge: *edge}, @@ -145,6 +150,45 @@ func EdgeTemplateConfigure(template knowledgebase.EdgeTemplate, graph *construct }) continue } + + if config.Config.ValueTemplate != "" { + ctx := knowledgebase.ConfigTemplateContext{DAG: dag} + data := knowledgebase.ConfigTemplateData{ + Resource: res.Id(), + Edge: graph.Edge[construct.ResourceId]{ + Source: edge.Source.Id(), + Destination: edge.Destination.Id(), + }, + } + newConfig, err := ctx.ResolveConfig(config.Config, data) + if err != nil { + engineErrors = append(engineErrors, &ResourceConfigurationError{ + Resource: res, + Cause: err, + Config: config.Config, + }) + } else { + decisions = append(decisions, Decision{ + Action: ActionConfigure, + Level: LevelInfo, + Result: &DecisionResult{ + Resource: res, + Config: &knowledgebase.ConfigurationRule{ + Resource: res.Id(), + Config: newConfig, + }, + }, + Cause: &Cause{ + EdgeExpansion: edge, + }, + }) + } + continue + } + + // TODO convert all the resource references in `value` to use `value_template` then the replacement + // won't be needed anymore. The primary use-case is for IaCValues, which can be accomplished with the + // `fieldRef` template function. newConfig := knowledgebase.Configuration{} valBytes, err := yaml.Marshal(config.Config) if err != nil { @@ -156,7 +200,8 @@ func EdgeTemplateConfigure(template knowledgebase.EdgeTemplate, graph *construct } valStr := string(valBytes) for id, resource := range resourceMap { - valStr = strings.ReplaceAll(valStr, id.String(), resource.Id().String()) + re := regexp.MustCompile(fmt.Sprintf(`\b%s:?\b`, id.String())) + valStr = re.ReplaceAllString(valStr, resource.Id().String()) } err = yaml.Unmarshal([]byte(valStr), &newConfig) if err != nil { @@ -167,8 +212,13 @@ func EdgeTemplateConfigure(template knowledgebase.EdgeTemplate, graph *construct continue } decisions = append(decisions, Decision{ - Level: LevelInfo, - Result: &DecisionResult{Resource: res, Config: &knowledgebase.ConfigurationRule{Config: newConfig, Resource: res.Id()}}, + Level: Level(valStr), + Result: &DecisionResult{ + Resource: res, + Config: &knowledgebase.ConfigurationRule{ + Resource: res.Id(), + Config: newConfig, + }}, Action: ActionConfigure, Cause: &Cause{ EdgeExpansion: edge, @@ -178,6 +228,7 @@ func EdgeTemplateConfigure(template knowledgebase.EdgeTemplate, graph *construct return } +// EdgeTemplateMakeOperational is responsible for executing the templates' OperationalRules for managing the edge's dependencies. func (e *Engine) EdgeTemplateMakeOperational(template knowledgebase.EdgeTemplate, graph *construct.ResourceGraph, edge *graph.Edge[construct.Resource], resourceMap map[construct.ResourceId]construct.Resource) (decisions []Decision, engineErrors []EngineError) { for _, rule := range template.OperationalRules { id, fields := getIdAndFields(rule.Resource) diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 85e3aed9b..70565ac6c 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -362,18 +362,15 @@ func (e *Engine) SolveGraph(context *SolveContext) { graph := context.ResourceGraph configuredEdges := make(map[construct.ResourceId]map[construct.ResourceId]bool) operationalResources := make(map[construct.ResourceId]bool) + for i := 0; i < NUM_LOOPS; i++ { context.Errors = []EngineError{} - for _, rc := range e.Context.Constraints[constraints.ResourceConstraintScope] { - rc := rc.(*constraints.ResourceConstraint) - config := knowledgebase.Configuration{Field: rc.Property, Value: rc.Value} - configRule := knowledgebase.ConfigurationRule{Config: config, Resource: rc.Target} - e.handleDecision(context, Decision{Level: LevelInfo, Result: &DecisionResult{Config: &configRule, Resource: context.ResourceGraph.GetResource(rc.Target)}, Action: ActionConfigure, Cause: &Cause{Constraint: rc}}) + for _, r := range graph.ListResources() { + e.configureResource(context, r) } for _, dep := range graph.ListDependencies() { - if configuredEdges[dep.Source.Id()] == nil { configuredEdges[dep.Source.Id()] = make(map[construct.ResourceId]bool) } @@ -452,7 +449,7 @@ func (e *Engine) ApplyApplicationConstraint(constraint *constraints.ApplicationC if resource == nil { return fmt.Errorf("construct, %s, does not exist", constraint.Node) } - if !e.deleteConstruct(resource, true, true) { + if !e.deleteConstruct(e.Context.WorkingState, resource, true, true) { return fmt.Errorf("cannot remove construct %s, failed", constraint.Node) } return nil @@ -474,7 +471,7 @@ func (e *Engine) ApplyApplicationConstraint(constraint *constraints.ApplicationC } var reconnectToUpstream []construct.BaseConstruct for _, up := range upstream { - deleted := e.deleteConstruct(up, false, false) + deleted := e.deleteConstruct(e.Context.WorkingState, up, false, false) if deleted { reconnectToUpstream = append(reconnectToUpstream, e.Context.WorkingState.GetUpstreamConstructs(up)...) } else { @@ -483,7 +480,7 @@ func (e *Engine) ApplyApplicationConstraint(constraint *constraints.ApplicationC } var reconnectToDownstream []construct.BaseConstruct for _, down := range downstream { - deleted := e.deleteConstruct(down, false, false) + deleted := e.deleteConstruct(e.Context.WorkingState, down, false, false) if deleted { reconnectToDownstream = append(reconnectToDownstream, e.Context.WorkingState.GetDownstreamConstructs(down)...) } else { @@ -561,7 +558,7 @@ func (e *Engine) ApplyEdgeConstraint(constraint *constraints.EdgeConstraint) err for _, res := range path { resource := e.Context.WorkingState.GetConstruct(res.Id()) if resource != nil { - e.deleteConstruct(resource, false, false) + e.deleteConstruct(e.Context.WorkingState, resource, false, false) } } } @@ -630,29 +627,6 @@ func (e *Engine) handleEdgeConstainConstraint(constraint *constraints.EdgeConstr return nil } -func (e *Engine) ApplyResourceConstraint(graph *construct.ResourceGraph, constraint *constraints.ResourceConstraint) EngineError { - resource := graph.GetResource(constraint.Target) - if resource == nil { - return &ResourceConfigurationError{ - Constraint: constraint, - Cause: fmt.Errorf("resource %s does not exist", constraint.Target), - } - } - err := ConfigureField(resource, constraint.Property, constraint.Value, true, graph) - if err != nil { - return &ResourceConfigurationError{ - Resource: resource, - Cause: err, - Config: knowledgebase.Configuration{ - Field: constraint.Property, - Value: constraint.Value, - }, - Constraint: constraint, - } - } - return nil -} - // ValidateConstraints validates all constraints against the end state resource graph // It returns any constraints which were not satisfied by resource graphs current state func (e *Engine) ValidateConstraints(context *SolveContext) []constraints.Constraint { diff --git a/pkg/engine/enginetesting/classification_mock.go b/pkg/engine/enginetesting/classification_mock.go index e01aa0582..edff58146 100644 --- a/pkg/engine/enginetesting/classification_mock.go +++ b/pkg/engine/enginetesting/classification_mock.go @@ -6,9 +6,9 @@ import ( var BaseClassificationDocument = &classification.ClassificationDocument{ Classifications: map[string]classification.Classification{ - "mock:mock1:": {Gives: []classification.Gives{}, Is: []string{"compute", "kv", "nosql"}}, - "mock:mock2:": {Gives: []classification.Gives{}, Is: []string{"compute", "instance", "storage"}}, - "mock:mock3:": {Gives: []classification.Gives{{Attribute: "serverless", Functionality: []string{"compute"}}}, Is: []string{"relational", "storage"}}, - "mock:mock4:": {Gives: []classification.Gives{{Attribute: "highly_available", Functionality: []string{"compute"}}}, Is: []string{}}, + "mock:mock1": {Gives: []classification.Gives{}, Is: []string{"compute", "kv", "nosql"}}, + "mock:mock2": {Gives: []classification.Gives{}, Is: []string{"compute", "instance", "storage"}}, + "mock:mock3": {Gives: []classification.Gives{{Attribute: "serverless", Functionality: []string{"compute"}}}, Is: []string{"relational", "storage"}}, + "mock:mock4": {Gives: []classification.Gives{{Attribute: "highly_available", Functionality: []string{"compute"}}}, Is: []string{}}, }, } diff --git a/pkg/engine/errors.go b/pkg/engine/errors.go index 5817f9d90..8bc1271da 100644 --- a/pkg/engine/errors.go +++ b/pkg/engine/errors.go @@ -13,18 +13,16 @@ import ( type ( EngineError interface { error - Type() string json.Marshaler } OperationalResourceError struct { - Needs []string - Count int - Direction knowledgebase.Direction - Resource construct.Resource - Parent construct.Resource - MustCreate bool - Cause error + Rule knowledgebase.OperationalRule + ToCreate construct.ResourceId + Count int + Resource construct.Resource + Parent construct.Resource + Cause error } EdgeExpansionError struct { @@ -66,15 +64,6 @@ type ( } ) -func NewOperationalResourceError(resource construct.Resource, needs []string, cause error) *OperationalResourceError { - return &OperationalResourceError{ - Resource: resource, - Needs: needs, - Cause: cause, - Count: 1, - } -} - func (err *OperationalResourceError) Error() string { return fmt.Sprintf("error in making resource %s operational: %v", err.Resource.Id(), err.Cause) } @@ -115,94 +104,76 @@ func (err *InternalError) Error() string { return fmt.Sprintf("internal error: %v", err.Cause) } -func (err *OperationalResourceError) Type() string { - return "OperationalResourceError" -} - -func (err *EdgeExpansionError) Type() string { - return "EdgeExpansionError" -} - -func (err *EdgeConfigurationError) Type() string { - return "EdgeConfigurationError" -} - -func (err *ResourceNotOperationalError) Type() string { - return "ResourceNotOperationalError" -} - -func (err *ResourceConfigurationError) Type() string { - return "ResourceConfigurationError" -} - -func (err *ConstructExpansionError) Type() string { - return "ConstructExpansionError" -} - -func (err *InternalError) Type() string { - return "InternalError" +func causeString(cause error) string { + if cause == nil { + return "" + } + return cause.Error() } func (err *OperationalResourceError) MarshalJSON() ([]byte, error) { + var parentId construct.ResourceId + if err.Parent != nil { + parentId = err.Parent.Id() + } return json.Marshal(map[string]interface{}{ - "type": err.Type(), - "needs": err.Needs, - "count": err.Count, - "cause": err.Cause.Error(), - "parent": err.Parent.Id().String(), - "created": err.MustCreate, + "type": fmt.Sprintf("%T", err), + "rule": err.Rule, + "toCreate": err.ToCreate, + "cause": causeString(err.Cause), + "parent": parentId.String(), }) } func (err *EdgeExpansionError) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ - "type": err.Type(), + "type": fmt.Sprintf("%T", err), "constraint": err.Constraint, - "cause": err.Cause.Error(), + "cause": causeString(err.Cause), "edge": fmt.Sprintf("%s,%s", err.Edge.Source.Id(), err.Edge.Destination.Id()), }) } func (err *EdgeConfigurationError) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ - "type": err.Type(), + "type": fmt.Sprintf("%T", err), "constraint": err.Constraint, - "cause": err.Cause.Error(), + "cause": causeString(err.Cause), "edge": fmt.Sprintf("%s,%s", err.Edge.Source.Id(), err.Edge.Destination.Id()), }) } func (err *ResourceNotOperationalError) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ - "type": err.Type(), + "type": fmt.Sprintf("%T", err), "constraint": err.Constraint, - "cause": err.Cause.Error(), + "cause": causeString(err.Cause), "resource": err.Resource.Id().String(), }) } func (err *ResourceConfigurationError) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ - "type": err.Type(), + "type": fmt.Sprintf("%T", err), "constraint": err.Constraint, - "cause": err.Cause.Error(), + "cause": causeString(err.Cause), "resource": err.Resource.Id().String(), }) } func (err *ConstructExpansionError) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ - "type": err.Type(), + "type": fmt.Sprintf("%T", err), "constraint": err.Constraint, - "cause": err.Cause.Error(), + "cause": causeString(err.Cause), "construct": err.Construct.Id().String(), }) } func (err *InternalError) MarshalJSON() ([]byte, error) { return json.Marshal(map[string]interface{}{ - "type": err.Type(), - "cause": err.Cause.Error(), + "type": fmt.Sprintf("%T", err), + "cause": causeString(err.Cause), "child": err.Child, }) } diff --git a/pkg/engine/operational_resources.go b/pkg/engine/operational_resources.go index ff710c745..f5795fc3c 100644 --- a/pkg/engine/operational_resources.go +++ b/pkg/engine/operational_resources.go @@ -9,6 +9,7 @@ import ( "github.com/klothoplatform/klotho/pkg/construct" "github.com/klothoplatform/klotho/pkg/engine/classification" "github.com/klothoplatform/klotho/pkg/graph" + "go.uber.org/zap" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base" ) @@ -103,16 +104,21 @@ func callMakeOperational(rg *construct.ResourceGraph, resource construct.Resourc func (e *Engine) handleOperationalRule(resource construct.Resource, rule knowledgebase.OperationalRule, dag *construct.ResourceGraph, downstreamParent construct.Resource) ([]Decision, []EngineError) { resourcesOfType := []construct.Resource{} - // if we are supposed to set a field and the field is already set and has the number of resources needed, we dont need to run this function - // Also make sure theres no sub rules so we dont short circuit - if rule.SetField != "" && len(rule.Rules) == 0 { - field := reflect.ValueOf(resource).Elem().FieldByName(rule.SetField) - if field.IsValid() { - if (field.Kind() == reflect.Slice || field.Kind() == reflect.Array) && field.Len() > rule.NumNeeded { - return nil, nil - } else if field.Kind() == reflect.Ptr && !field.IsNil() { - return nil, nil - } + if rule.If != "" { + ctx := knowledgebase.ConfigTemplateContext{DAG: dag} + data := knowledgebase.ConfigTemplateData{Resource: resource.Id()} + result := false + err := ctx.ExecuteDecode(rule.If, data, &result) + if err != nil { + return nil, []EngineError{&OperationalResourceError{ + Rule: rule, + Resource: resource, + Cause: err, + }} + } + if !result { + zap.S().Debugf("rule %s for resource %s did not match if condition, skippingw", rule.String(), resource.Id()) + return nil, nil } } @@ -128,20 +134,22 @@ func (e *Engine) handleOperationalRule(resource construct.Resource, rule knowled dependentResources = dag.GetAllDownstreamResources(resource) } } - if rule.ResourceTypes != nil && rule.Classifications != nil { + if rule.ResourceTypes != nil && rule.Classifications != nil && rule.Resources != nil { return nil, []EngineError{ &InternalError{ Child: &ResourceNotOperationalError{Resource: resource}, Cause: fmt.Errorf("rule cannot have both resource types and classifications defined %s for resource %s", rule.String(), resource.Id()), }, } - } else if rule.ResourceTypes != nil { + } else if len(rule.ResourceTypes) > 0 { for _, res := range dependentResources { if collectionutil.Contains(rule.ResourceTypes, res.Id().Type) && res.Id().Provider == resource.Id().Provider { resourcesOfType = append(resourcesOfType, res) } } - } else if rule.Classifications != nil { + } else if len(rule.Resources) > 0 { + return e.handleExactResourceEnforcement(resource, rule, dag) + } else if len(rule.Classifications) > 0 { for _, res := range dependentResources { if e.ClassificationDocument.ResourceContainsClassifications(res, rule.Classifications) { resourcesOfType = append(resourcesOfType, res) @@ -172,41 +180,100 @@ func (e *Engine) handleOperationalRule(resource construct.Resource, rule knowled } } +func (e *Engine) handleExactResourceEnforcement(resource construct.Resource, rule knowledgebase.OperationalRule, dag *construct.ResourceGraph) (decisions []Decision, errs []EngineError) { + ctx := knowledgebase.ConfigTemplateContext{DAG: dag} + data := knowledgebase.ConfigTemplateData{Resource: resource.Id()} + + addDep := func(dep construct.Resource) { + var result DecisionResult + if rule.Direction == knowledgebase.Upstream { + result.Edge = &graph.Edge[construct.Resource]{ + Source: dep, + Destination: resource, + } + } else { + result.Edge = &graph.Edge[construct.Resource]{ + Source: resource, + Destination: dep, + } + } + decisions = append(decisions, Decision{ + Action: ActionConnect, + Result: &result, + Cause: &Cause{OperationalResource: resource}, + }) + } + +requiredLoop: + for _, resStr := range rule.Resources { + var selector construct.ResourceId + err := ctx.ExecuteDecode(resStr, data, &selector) + if err != nil { + errs = append(errs, &InternalError{ + Child: &ResourceNotOperationalError{Resource: resource, Cause: err}, + Cause: err, + }) + continue + } + if selector.IsZero() { + // ? Should this error instead? + // Make sure we don't just add arbitrary dependencies, since all resources match the zero value + continue + } + + if selector.Name != "" { + if r := dag.GetResource(selector); r != nil { + addDep(r) + continue + } + } else { + for _, r := range dag.ListResources() { + if selector.Matches(r.Id()) { + addDep(r) + continue requiredLoop + } + } + } + + errs = append(errs, &OperationalResourceError{ + Rule: rule, + Resource: resource, + ToCreate: selector, + Count: 1, + }) + } + return +} + func (e *Engine) handleExactlyOneEnforcement(resource construct.Resource, rule knowledgebase.OperationalRule, resourcesOfType []construct.Resource, downstreamParent construct.Resource, dag *construct.ResourceGraph) ([]Decision, []EngineError) { var decisions []Decision if len(resourcesOfType) > 1 { + ids := make([]string, len(resourcesOfType)) + for i, res := range resourcesOfType { + ids[i] = res.Id().String() + } + sort.Strings(ids) return decisions, []EngineError{ &ResourceNotOperationalError{ Resource: resource, - Cause: fmt.Errorf("rule with enforcement only_one has more than one resource for rule %s for resource %s", rule.String(), resource.Id()), + Cause: fmt.Errorf("rule with enforcement exactly one has more than one resource for rule %s for resource %s (%v)", rule.String(), resource.Id(), ids), }, } } else if len(resourcesOfType) == 0 { switch rule.UnsatisfiedAction.Operation { case knowledgebase.CreateUnsatisfiedResource: - var needs []string - if rule.UnsatisfiedAction.DefaultType != "" { - needs = []string{rule.UnsatisfiedAction.DefaultType} - } else { - if rule.Classifications != nil { - needs = rule.Classifications - } else { - needs = []string{rule.ResourceTypes[0]} - } - } var oreParent construct.Resource if !rule.NoParentDependency { oreParent = downstreamParent } return decisions, []EngineError{&OperationalResourceError{ - Resource: resource, - Parent: oreParent, - Direction: rule.Direction, - Count: 1, - Needs: needs, - MustCreate: rule.UnsatisfiedAction.Unique, - Cause: fmt.Errorf("rule with enforcement exactly one has less than the required number of resources of type %s or classifications %s, %d for resource %s", rule.ResourceTypes, rule.Classifications, len(resourcesOfType), resource.Id()), + Rule: rule, + Resource: resource, + Count: 1, + Parent: oreParent, + Cause: fmt.Errorf("rule with enforcement exactly one has less than the required number of resources of type %s or classifications %s, %d for resource %s", rule.ResourceTypes, rule.Classifications, len(resourcesOfType), resource.Id()), }} + case knowledgebase.ErrorUnsatisfiedResource: return decisions, []EngineError{ &ResourceNotOperationalError{ @@ -220,7 +287,7 @@ func (e *Engine) handleExactlyOneEnforcement(resource construct.Resource, rule k if !rule.RemoveDirectDependency { decisions = append(decisions, addDependencyDecisionForDirection(rule.Direction, resource, res)) } - err := setField(dag, resource, rule, res) + err := e.setField(dag, resource, rule, res) if err != nil { return decisions, []EngineError{ &ResourceNotOperationalError{ @@ -250,7 +317,6 @@ func (e *Engine) handleExactlyOneEnforcement(resource construct.Resource, rule k if subRuleErrors != nil { return decisions, subRuleErrors } - return decisions, nil } @@ -267,7 +333,7 @@ func (e *Engine) handleConditionalEnforcement(resource construct.Resource, rule } return decisions, nil } else if len(resourcesOfType) == 1 { - err := setField(dag, resource, rule, resourcesOfType[0]) + err := e.setField(dag, resource, rule, resourcesOfType[0]) if err != nil { return decisions, []EngineError{ &ResourceNotOperationalError{ @@ -284,7 +350,7 @@ func (e *Engine) handleConditionalEnforcement(resource construct.Resource, rule } else { setFieldErrors := []EngineError{} for _, res := range resourcesOfType { - err := setField(dag, resource, rule, res) + err := e.setField(dag, resource, rule, res) if err != nil { setFieldErrors = append(setFieldErrors, &ResourceNotOperationalError{ Resource: resource, @@ -313,7 +379,7 @@ func (e *Engine) handleConditionalEnforcement(resource construct.Resource, rule func (e *Engine) handleAnyAvailableEnforcement(resource construct.Resource, rule knowledgebase.OperationalRule, resourcesOfType []construct.Resource, downstreamParent construct.Resource, dag *construct.ResourceGraph) ([]Decision, []EngineError) { var decisions []Decision for _, res := range resourcesOfType { - err := setField(dag, resource, rule, res) + err := e.setField(dag, resource, rule, res) if err != nil { return decisions, []EngineError{ &ResourceNotOperationalError{ @@ -326,39 +392,17 @@ func (e *Engine) handleAnyAvailableEnforcement(resource construct.Resource, rule if rule.NumNeeded > len(resourcesOfType) { switch rule.UnsatisfiedAction.Operation { case knowledgebase.CreateUnsatisfiedResource: - var needs []string - if len(resourcesOfType) > 0 { - var existingTypes []string - for _, res := range resourcesOfType { - existingTypes = append(existingTypes, res.Id().Type) - } - if len(existingTypes) == 1 { - needs = existingTypes - } - } else if rule.UnsatisfiedAction.DefaultType != "" { - needs = []string{rule.UnsatisfiedAction.DefaultType} - } else { - if rule.Classifications != nil { - needs = rule.Classifications - } else { - needs = rule.ResourceTypes - } - } var oreParent construct.Resource if !rule.NoParentDependency { oreParent = downstreamParent } - return decisions, []EngineError{ - &OperationalResourceError{ - Resource: resource, - Parent: oreParent, - Direction: rule.Direction, - Count: rule.NumNeeded - len(resourcesOfType), - MustCreate: rule.UnsatisfiedAction.Unique, - Needs: needs, - Cause: fmt.Errorf("rule with enforcement any has less than the required number of resources of type %s or classifications %s, %d for resource %s", rule.ResourceTypes, rule.Classifications, len(resourcesOfType), resource.Id()), - }, - } + return decisions, []EngineError{&OperationalResourceError{ + Rule: rule, + Resource: resource, + Count: rule.NumNeeded - len(resourcesOfType), + Parent: oreParent, + Cause: fmt.Errorf("rule with enforcement any has less than the required number of resources of type %s or classifications %s, %d for resource %s", rule.ResourceTypes, rule.Classifications, len(resourcesOfType), resource.Id()), + }} case knowledgebase.ErrorUnsatisfiedResource: return decisions, []EngineError{ &ResourceNotOperationalError{ @@ -382,23 +426,46 @@ func (e *Engine) handleAnyAvailableEnforcement(resource construct.Resource, rule return decisions, nil } -func setField(dag *construct.ResourceGraph, resource construct.Resource, rule knowledgebase.OperationalRule, res construct.Resource) error { - copyResource := cloneResource(resource) +func (e *Engine) setField(dag *construct.ResourceGraph, resource construct.Resource, rule knowledgebase.OperationalRule, fieldResource construct.Resource) error { if rule.SetField == "" { return nil } - if reflect.ValueOf(resource).Elem().FieldByName(rule.SetField).Kind() == reflect.Slice || reflect.ValueOf(resource).Elem().FieldByName(rule.SetField).Kind() == reflect.Array { - reflect.ValueOf(resource).Elem().FieldByName(rule.SetField).Set(reflect.Append(reflect.ValueOf(resource).Elem().FieldByName(rule.SetField), reflect.ValueOf(res))) - } else if reflect.TypeOf(construct.ResourceId{}) == reflect.ValueOf(resource).Elem().FieldByName(rule.SetField).Type() { - reflect.ValueOf(resource).Elem().FieldByName(rule.SetField).Set(reflect.ValueOf(res.Id())) + // snapshot the ID from before any field changes + oldId := resource.Id() + + resVal := reflect.ValueOf(resource) + fieldValue := reflect.ValueOf(fieldResource) + + field := resVal.Elem().FieldByName(rule.SetField) + + if field.Kind() == reflect.Slice || field.Kind() == reflect.Array { + field.Set(reflect.Append(field, fieldValue)) } else { - reflect.ValueOf(resource).Elem().FieldByName(rule.SetField).Set(reflect.ValueOf(res)) - } - if copyResource.Id() != resource.Id() { - if dag.GetResource(resource.Id()) != nil { - return fmt.Errorf("resource %s was replaced with %s, but the original resource still exists in the graph", copyResource.Id(), resource.Id()) + if field.Kind() == reflect.Ptr && !field.IsNil() { + oldFieldValue := field.Interface() + if oldRes, ok := oldFieldValue.(construct.Resource); ok && fieldResource.Id() != oldRes.Id() { + err := dag.RemoveDependency(resource.Id(), oldRes.Id()) + if err != nil { + return err + } + zap.S().Infof("Removing old field value for '%s' (%s) for %s", rule.SetField, oldRes.Id(), fieldResource.Id()) + // Remove the old field value if it's unused + e.deleteResource(dag, oldRes, false, false) + } } - err := dag.ReplaceConstruct(copyResource, resource) + + if resourceIdType.AssignableTo(field.Type()) { + field.Set(reflect.ValueOf(fieldResource.Id())) + } else { + field.Set(fieldValue) + } + zap.S().Infof("configured %s#%s to %s", resource.Id(), rule.SetField, fieldResource.Id()) + } + // If this sets the field driving the namespace, for example, + // then the Id could change, so replace the resource in the graph + // to update all the edges to the new Id. + if oldId != resource.Id() { + err := dag.ReplaceConstructId(oldId, resource) if err != nil { return err } @@ -410,27 +477,70 @@ func setField(dag *construct.ResourceGraph, resource construct.Resource, rule kn // If the error cannot be fixed, it will return an error. func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, dag *construct.ResourceGraph) ([]Decision, error) { var decisions []Decision + if !err.ToCreate.IsZero() && err.ToCreate.Name != "" { + if err.Count > 1 { + return nil, fmt.Errorf("cannot create multiple resources for a specific resource id %s", err.ToCreate) + } + r, createErr := e.CreateResourceFromId(err.ToCreate) + if createErr != nil { + return nil, createErr + } + var edge *graph.Edge[construct.Resource] + if err.Rule.Direction == knowledgebase.Downstream { + edge = &graph.Edge[construct.Resource]{ + Source: err.Resource, + Destination: r, + } + } else { + edge = &graph.Edge[construct.Resource]{ + Source: r, + Destination: err.Resource, + } + } + return []Decision{{ + Level: LevelInfo, + Action: ActionConnect, + Cause: &Cause{}, + Result: &DecisionResult{ + Edge: edge, + }, + }}, nil + } + resources := e.ListResources() + var needs []string + switch { + case len(err.Rule.Classifications) > 0: + needs = err.Rule.Classifications + + case len(err.Rule.ResourceTypes) > 0: + // Pick the first one, assume the template writer prioritized which one should be created + needs = []string{err.Rule.ResourceTypes[0]} + + case err.ToCreate.Type != "": + needs = []string{err.ToCreate.Type} + + case err.Rule.UnsatisfiedAction.DefaultType != "": + needs = []string{err.Rule.UnsatisfiedAction.DefaultType} + } // determine the type of resource necessary to satisfy the operational resource error var neededResource construct.Resource for _, res := range resources { - - if e.ClassificationDocument.ResourceContainsClassifications(res, err.Needs) { - var hasPath bool - if err.Direction == knowledgebase.Downstream { - hasPath = e.KnowledgeBase.HasPath(err.Resource, res) - } else { - hasPath = e.KnowledgeBase.HasPath(res, err.Resource) - } - // if a type is explicilty stated as needed, we will consider it even if there isnt a direct p - if !hasPath { - continue - } - if neededResource != nil { - return nil, fmt.Errorf("multiple resources found that can satisfy the operational resource error") - } - neededResource = res + if !e.ClassificationDocument.ResourceContainsClassifications(res, needs) { + continue + } + var hasPath bool + if err.Rule.Direction == knowledgebase.Downstream { + hasPath = e.KnowledgeBase.HasPath(err.Resource, res) + } else { + hasPath = e.KnowledgeBase.HasPath(res, err.Resource) + } + // if a type is explicilty stated as needed, we will consider it even if there isnt a direct p + if !hasPath { + continue } + neededResource = res + break } if neededResource == nil { return nil, fmt.Errorf("no resources found that can satisfy the operational resource error") @@ -441,14 +551,14 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d if err.Parent != nil { var resources []construct.Resource // The direction here is flipped since we are looking at the resources relative to the parent, not relative to the resource used in the error - if err.Direction == knowledgebase.Upstream { + if err.Rule.Direction == knowledgebase.Upstream { resources = dag.GetAllDownstreamResources(err.Parent) } else { resources = dag.GetAllUpstreamResources(err.Parent) } for _, res := range resources { if res.Id().Type == neededResource.Id().Type && res.Id().Provider == neededResource.Id().Provider && dag.GetDependency(err.Resource.Id(), res.Id()) == nil { - decisions = append(decisions, addDependencyDecisionForDirection(err.Direction, err.Resource, res)) + decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, err.Resource, res)) numSatisfied++ } } @@ -461,7 +571,7 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d var availableResources []construct.Resource // we only want to look at available resources if we dont have a parent they need to be scoped to. // This prevents us from saying that resource_a is available if it is a child of resource_b when the error has a parent of resource_c - if err.Parent == nil && !err.MustCreate { + if err.Parent == nil && !err.Rule.UnsatisfiedAction.Unique { //Todo: Get nearest resource. we should look one resource upstream until we find available resources so that we have a higher chance of choosing the right one for _, res := range dag.ListResources() { if res.Id().Type == neededResource.Id().Type { @@ -479,7 +589,7 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d for i := 0; i < err.Count-currNumSatisfied; i++ { for _, res := range availableResources { if len(resourceIds) > i && res.Id().Name == resourceIds[i] { - decisions = append(decisions, addDependencyDecisionForDirection(err.Direction, err.Resource, res)) + decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, err.Resource, res)) numSatisfied++ break } @@ -497,13 +607,12 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d } for i := numSatisfied; i < err.Count; i++ { newRes := cloneResource(neededResource) - nameResource(numResources, newRes, err.Resource, err.MustCreate) + nameResource(numResources, newRes, err.Resource, err.Rule.UnsatisfiedAction.Unique) - decisions = append(decisions, addDependencyDecisionForDirection(err.Direction, err.Resource, newRes)) + decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, err.Resource, newRes)) if err.Parent != nil { - decisions = append(decisions, addDependencyDecisionForDirection(err.Direction, newRes, err.Parent)) + decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, newRes, err.Parent)) } - numSatisfied++ numResources++ } } diff --git a/pkg/engine/operational_resources_test.go b/pkg/engine/operational_resources_test.go index c57cf9c33..d873fe78d 100644 --- a/pkg/engine/operational_resources_test.go +++ b/pkg/engine/operational_resources_test.go @@ -36,11 +36,9 @@ func Test_handleOperationalRule(t *testing.T) { }, resource: &enginetesting.MockResource5{Name: "this"}, wantErr: []error{&OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Needs: []string{"mock1"}, - Direction: knowledgebase.Downstream, - Count: 1, - Cause: fmt.Errorf("rule with enforcement exactly one has less than the required number of resources of type [mock1] or classifications [], 0 for resource mock:mock5:this"), + Count: 1, + Resource: &enginetesting.MockResource5{Name: "this"}, + Cause: fmt.Errorf("rule with enforcement exactly one has less than the required number of resources of type [mock1] or classifications [], 0 for resource mock:mock5:this"), }}, }, { @@ -79,7 +77,7 @@ func Test_handleOperationalRule(t *testing.T) { }, wantErr: []error{&ResourceNotOperationalError{ Resource: &enginetesting.MockResource5{Name: "this"}, - Cause: fmt.Errorf("rule with enforcement only_one has more than one resource for rule exactly_one [mock1] for resource mock:mock5:this"), + Cause: fmt.Errorf("rule with enforcement exactly one has more than one resource for rule exactly_one [mock1] for resource mock:mock5:this ([mock:mock1:that mock:mock1:that2])"), }, }, }, @@ -131,18 +129,25 @@ func Test_handleOperationalRule(t *testing.T) { {Source: &enginetesting.MockResource5{Name: "this"}, Destination: &enginetesting.MockResource3{Name: "that"}}, }, wantErr: []error{&OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Count: 2, - Direction: knowledgebase.Downstream, - Needs: []string{"mock2"}, - Parent: &enginetesting.MockResource3{Name: "that"}, - Cause: fmt.Errorf("rule with enforcement any has less than the required number of resources of type [mock2] or classifications [], 0 for resource mock:mock5:this"), + Rule: knowledgebase.OperationalRule{ // the subrule + Enforcement: knowledgebase.AnyAvailable, + Direction: knowledgebase.Downstream, + ResourceTypes: []string{"mock2"}, + SetField: "Mock2s", + NumNeeded: 2, + UnsatisfiedAction: knowledgebase.UnsatisfiedAction{ + Operation: knowledgebase.CreateUnsatisfiedResource, + }, + }, + Resource: &enginetesting.MockResource5{Name: "this"}, + Count: 2, + Parent: &enginetesting.MockResource3{Name: "that"}, + Cause: fmt.Errorf("rule with enforcement any has less than the required number of resources of type [mock2] or classifications [], 0 for resource mock:mock5:this"), }}, }, { name: "if one multiple exist error", rule: knowledgebase.OperationalRule{ - Enforcement: knowledgebase.Conditional, Direction: knowledgebase.Downstream, ResourceTypes: []string{"mock1"}, @@ -171,7 +176,16 @@ func Test_handleOperationalRule(t *testing.T) { decisions, errs := engine.handleOperationalRule(tt.resource, tt.rule, dag, tt.parent) if tt.wantErr != nil { - assert.Greater(len(errs), 0) + for _, e := range tt.wantErr { + if opErr, ok := e.(*OperationalResourceError); ok { + if opErr.Rule.Direction == "" { + opErr.Rule = tt.rule + } + if opErr.Resource == nil { + opErr.Resource = tt.resource + } + } + } assert.ElementsMatch(errs, tt.wantErr) return } @@ -194,11 +208,10 @@ func Test_handleOperationalResourceError(t *testing.T) { { name: "needs one downstream", ore: &OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Direction: knowledgebase.Downstream, - Needs: []string{"mock1"}, - Count: 1, - Cause: fmt.Errorf("0"), + Resource: &enginetesting.MockResource5{Name: "this"}, + Rule: knowledgebase.OperationalRule{ResourceTypes: []string{"mock1"}, Direction: knowledgebase.Downstream}, + Count: 1, + Cause: fmt.Errorf("0"), }, want: []Decision{ { @@ -212,11 +225,10 @@ func Test_handleOperationalResourceError(t *testing.T) { { name: "needs multiple downstream", ore: &OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Direction: knowledgebase.Downstream, - Needs: []string{"mock2"}, - Count: 2, - Cause: fmt.Errorf("0"), + Resource: &enginetesting.MockResource5{Name: "this"}, + Rule: knowledgebase.OperationalRule{ResourceTypes: []string{"mock2"}, Direction: knowledgebase.Downstream}, + Count: 2, + Cause: fmt.Errorf("0"), }, want: []Decision{ { @@ -236,12 +248,11 @@ func Test_handleOperationalResourceError(t *testing.T) { { name: "needs parents resource", ore: &OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Direction: knowledgebase.Downstream, - Needs: []string{"mock1"}, - Count: 1, - Parent: &enginetesting.MockResource3{Name: "parent"}, - Cause: fmt.Errorf("0"), + Resource: &enginetesting.MockResource5{Name: "this"}, + Rule: knowledgebase.OperationalRule{ResourceTypes: []string{"mock1"}, Direction: knowledgebase.Downstream}, + Count: 1, + Parent: &enginetesting.MockResource3{Name: "parent"}, + Cause: fmt.Errorf("0"), }, existingDependencies: []graph.Edge[construct.Resource]{ {Source: &enginetesting.MockResource1{Name: "child"}, Destination: &enginetesting.MockResource3{Name: "parent"}}, @@ -258,12 +269,11 @@ func Test_handleOperationalResourceError(t *testing.T) { { name: "needs 2 but parent only has 1 resource", ore: &OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Direction: knowledgebase.Downstream, - Needs: []string{"mock1"}, - Count: 2, - Parent: &enginetesting.MockResource3{Name: "parent"}, - Cause: fmt.Errorf("0"), + Resource: &enginetesting.MockResource5{Name: "this"}, + Rule: knowledgebase.OperationalRule{ResourceTypes: []string{"mock1"}, Direction: knowledgebase.Downstream}, + Count: 2, + Parent: &enginetesting.MockResource3{Name: "parent"}, + Cause: fmt.Errorf("0"), }, existingDependencies: []graph.Edge[construct.Resource]{ {Source: &enginetesting.MockResource1{Name: "child"}, Destination: &enginetesting.MockResource3{Name: "parent"}}, @@ -293,11 +303,10 @@ func Test_handleOperationalResourceError(t *testing.T) { { name: "chooses existing resource to satisfy needs", ore: &OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Direction: knowledgebase.Downstream, - Needs: []string{"mock1"}, - Count: 2, - Cause: fmt.Errorf("0"), + Resource: &enginetesting.MockResource5{Name: "this"}, + Rule: knowledgebase.OperationalRule{ResourceTypes: []string{"mock1"}, Direction: knowledgebase.Downstream}, + Count: 2, + Cause: fmt.Errorf("0"), }, existingDependencies: []graph.Edge[construct.Resource]{ {Source: &enginetesting.MockResource1{Name: "child"}, Destination: &enginetesting.MockResource3{Name: "parent"}}, @@ -321,12 +330,14 @@ func Test_handleOperationalResourceError(t *testing.T) { { name: "must create new resource to satisfy needs", ore: &OperationalResourceError{ - Resource: &enginetesting.MockResource5{Name: "this"}, - Direction: knowledgebase.Downstream, - Needs: []string{"mock1"}, - Count: 2, - MustCreate: true, - Cause: fmt.Errorf("0"), + Resource: &enginetesting.MockResource5{Name: "this"}, + Rule: knowledgebase.OperationalRule{ + ResourceTypes: []string{"mock1"}, + UnsatisfiedAction: knowledgebase.UnsatisfiedAction{Unique: true}, + Direction: knowledgebase.Downstream, + }, + Count: 2, + Cause: fmt.Errorf("0"), }, existingDependencies: []graph.Edge[construct.Resource]{ {Source: &enginetesting.MockResource1{Name: "child"}, Destination: &enginetesting.MockResource3{Name: "parent"}}, @@ -375,227 +386,6 @@ func Test_handleOperationalResourceError(t *testing.T) { } } -func Test_TemplateConfigure(t *testing.T) { - tests := []struct { - name string - resource *enginetesting.MockResource6 - template knowledgebase.ResourceTemplate - want *enginetesting.MockResource6 - }{ - { - name: "simple values", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Field1", Value: 1}, - {Field: "Field2", Value: "two"}, - {Field: "Field3", Value: true}, - }, - }, - want: &enginetesting.MockResource6{ - Field1: 1, - Field2: "two", - Field3: true, - }, - }, - { - name: "simple array", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Arr1", Value: []string{"1", "2", "3"}}, - }, - }, - want: &enginetesting.MockResource6{ - Arr1: []string{"1", "2", "3"}, - }, - }, - { - name: "struct array", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Arr2", Value: []map[string]interface{}{ - { - "Field1": 1, - "Field2": "two", - "Field3": true, - }, - { - "Field1": 2, - "Field2": "three", - "Field3": false, - }, - }}, - }, - }, - want: &enginetesting.MockResource6{ - Arr2: []enginetesting.TestRes1{ - { - Field1: 1, - Field2: "two", - Field3: true, - }, - { - Field1: 2, - Field2: "three", - Field3: false, - }, - }, - }, - }, - { - name: "pointer array", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Arr3", Value: []map[string]interface{}{ - { - "Field1": 1, - "Field2": "two", - "Field3": true, - }, - { - "Field1": 2, - "Field2": "three", - "Field3": false, - }, - }}, - }, - }, - want: &enginetesting.MockResource6{ - Arr3: []*enginetesting.TestRes1{ - { - Field1: 1, - Field2: "two", - Field3: true, - }, - { - Field1: 2, - Field2: "three", - Field3: false, - }, - }, - }, - }, - { - name: "struct", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Struct1", Value: map[string]interface{}{ - "Field1": 1, - "Field2": "two", - "Field3": true, - "Arr1": []string{"1", "2", "3"}, - }}, - }, - }, - want: &enginetesting.MockResource6{ - Struct1: enginetesting.TestRes1{ - Field1: 1, - Field2: "two", - Field3: true, - Arr1: []string{"1", "2", "3"}, - }, - }, - }, - { - name: "pointer", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Struct2", Value: map[string]interface{}{ - "Field1": 1, - "Field2": "two", - "Field3": true, - "Arr1": []string{"1", "2", "3"}, - }}, - }, - }, - want: &enginetesting.MockResource6{ - Struct2: &enginetesting.TestRes1{ - Field1: 1, - Field2: "two", - Field3: true, - Arr1: []string{"1", "2", "3"}, - }, - }, - }, - { - name: "pointer sub field", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Struct2.Field1", Value: 1}, - {Field: "Struct2.Arr1", Value: []string{"1", "2", "3"}}, - }, - }, - want: &enginetesting.MockResource6{ - Struct2: &enginetesting.TestRes1{ - Field1: 1, - Arr1: []string{"1", "2", "3"}, - }, - }, - }, - { - name: "struct sub field", - resource: &enginetesting.MockResource6{}, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Struct1.Field1", Value: 1}, - {Field: "Struct1.Arr1", Value: []string{"1", "2", "3"}}, - }, - }, - want: &enginetesting.MockResource6{ - Struct1: enginetesting.TestRes1{ - Field1: 1, - Arr1: []string{"1", "2", "3"}, - }, - }, - }, - { - name: "doesnt overwrite field", - resource: &enginetesting.MockResource6{ - Field1: 1, - }, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Field1", Value: 5}, - }, - }, - want: &enginetesting.MockResource6{ - Field1: 1, - }, - }, - { - name: "doesnt append to array", - resource: &enginetesting.MockResource6{ - Arr1: []string{"1", "2", "3"}, - }, - template: knowledgebase.ResourceTemplate{ - Configuration: []knowledgebase.Configuration{ - {Field: "Arr1", Value: []string{"4"}}, - }, - }, - want: &enginetesting.MockResource6{ - Arr1: []string{"1", "2", "3"}, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert := assert.New(t) - - err := TemplateConfigure(tt.resource, tt.template, nil) - if !assert.NoError(err) { - return - } - assert.Equal(tt.want, tt.resource) - }) - } -} - func CompareDecisions(t *testing.T, expected, actual []Decision) { if !assert.Equal(t, len(expected), len(actual), "expected %d decisions, got %d: %v", len(expected), len(actual), actual) { return diff --git a/pkg/engine/reconciler.go b/pkg/engine/reconciler.go index 65be7a1a6..2cfad5607 100644 --- a/pkg/engine/reconciler.go +++ b/pkg/engine/reconciler.go @@ -8,43 +8,42 @@ import ( // ignoreCriteria determines if we can delete a resource because the knowledge base in use by the engine, shows that the initial resource is dependent on the sub resource for deletion. // If the sub resource is deletion dependent on any of the dependent resources passed in then we will determine weather we can delete the dependent resource first. -func (e *Engine) ignoreCriteria(resource construct.Resource, dependentResources []construct.BaseConstruct) bool { +func (e *Engine) ignoreCriteria(graph *construct.ConstructGraph, resource construct.Resource, dependentResources []construct.BaseConstruct) bool { DEP: for _, dep := range dependentResources { if _, ok := dep.(construct.Construct); ok { continue - } else if dep, ok := dep.(construct.Resource); ok { - found := false - for _, res := range e.Context.WorkingState.GetDownstreamConstructs(resource) { - if _, ok := res.(construct.Construct); ok { - continue - } - if dep == res { - found = true - det, _ := e.KnowledgeBase.GetResourceEdge(resource, dep) - if !det.DeletetionDependent { - return false - } - continue DEP - } + } + dep, ok := dep.(construct.Resource) + if !ok { + continue + } + + for _, res := range graph.GetDownstreamConstructs(resource) { + if _, ok := res.(construct.Construct); ok { + continue } - for _, res := range e.Context.WorkingState.GetUpstreamConstructs(resource) { - if _, ok := res.(construct.Construct); ok { - continue - } - if dep == res { - found = true - det, _ := e.KnowledgeBase.GetResourceEdge(dep, resource) - if !det.DeletetionDependent { - return false - } - continue DEP + if dep == res { + det, _ := e.KnowledgeBase.GetResourceEdge(resource, dep) + if !det.DeletetionDependent { + return false } + continue DEP + } + } + for _, res := range graph.GetUpstreamConstructs(resource) { + if _, ok := res.(construct.Construct); ok { + continue } - if !found { - return false + if dep == res { + det, _ := e.KnowledgeBase.GetResourceEdge(dep, resource) + if !det.DeletetionDependent { + return false + } + continue DEP } } + return false } return true } @@ -53,17 +52,16 @@ DEP: // // if explicit is set it is meant to show that a user has explicitly requested for the resource to be deleted or that the resource requested is being deleted by its parent resource // if overrideExplicit is set, it means that the explicit delete request still has to satisfy the resources delete criteria. If it is set to false, then the explicit deletion request is always performed -func (e *Engine) deleteConstruct(c construct.BaseConstruct, explicit bool, overrideExplicit bool) bool { +func (e *Engine) deleteConstruct(graph *construct.ConstructGraph, c construct.BaseConstruct, explicit bool, overrideExplicit bool) bool { log := zap.S().With(zap.String("id", c.Id().String())) log.Debug("Deleting resource") - graph := e.Context.WorkingState - upstreamNodes := e.Context.WorkingState.GetUpstreamConstructs(c) - downstreamNodes := e.Context.WorkingState.GetDownstreamConstructs(c) + upstreamNodes := graph.GetUpstreamConstructs(c) + downstreamNodes := graph.GetDownstreamConstructs(c) var reflectResources []construct.Resource if resource, ok := c.(construct.Resource); ok { reflectResources = construct.GetResourcesReflectively(graph, resource) - if !e.canDeleteResource(resource, explicit, overrideExplicit, upstreamNodes, downstreamNodes) { + if !e.canDeleteResource(graph, resource, explicit, overrideExplicit, upstreamNodes, downstreamNodes) { return false } } else if _, ok := c.(construct.Construct); ok { @@ -87,17 +85,17 @@ func (e *Engine) deleteConstruct(c construct.BaseConstruct, explicit bool, overr if resource.DeleteContext().RequiresExplicitDelete { explicitUpstreams = append(explicitUpstreams, resource) } else { - explicitUpstreams = append(explicitUpstreams, e.getExplicitUpstreams(resource)...) + explicitUpstreams = append(explicitUpstreams, getExplicitDeleteUpstreams(graph, resource)...) } } var explicitDownStreams []construct.BaseConstruct if c, ok := downstreamNode.(construct.Construct); ok { - explicitUpstreams = append(explicitUpstreams, c) + explicitDownStreams = append(explicitUpstreams, c) } else if resource, ok := downstreamNode.(construct.Resource); ok { if resource.DeleteContext().RequiresExplicitDelete { explicitDownStreams = append(explicitDownStreams, downstreamNode) } else { - explicitDownStreams = append(explicitDownStreams, e.getExplicitDownStreams(downstreamNode)...) + explicitDownStreams = append(explicitDownStreams, getExplicitDeleteDownstreams(graph, downstreamNode)...) } } @@ -114,7 +112,7 @@ func (e *Engine) deleteConstruct(c construct.BaseConstruct, explicit bool, overr continue UP } } - paths, err := e.Context.WorkingState.AllPaths(u.Id(), d.Id()) + paths, err := graph.AllPaths(u.Id(), d.Id()) if err != nil { zap.S().Debugf("Error getting paths between %s and %s", u.Id(), d.Id()) continue @@ -146,7 +144,7 @@ func (e *Engine) deleteConstruct(c construct.BaseConstruct, explicit bool, overr continue } } - e.deleteConstruct(res, explicit, false) + e.deleteConstruct(graph, res, explicit, false) } for _, res := range downstreamNodes { explicit := false @@ -156,12 +154,12 @@ func (e *Engine) deleteConstruct(c construct.BaseConstruct, explicit bool, overr continue } } - e.deleteConstruct(res, explicit, false) + e.deleteConstruct(graph, res, explicit, false) } return true } -func (e *Engine) canDeleteResource(resource construct.Resource, explicit bool, overrideExplicit bool, upstreamNodes []construct.BaseConstruct, downstreamNodes []construct.BaseConstruct) bool { +func (e *Engine) canDeleteResource(graph *construct.ConstructGraph, resource construct.Resource, explicit bool, overrideExplicit bool, upstreamNodes []construct.BaseConstruct, downstreamNodes []construct.BaseConstruct) bool { log := zap.S().With(zap.String("id", resource.Id().String())) deletionCriteria := resource.DeleteContext() if deletionCriteria.RequiresExplicitDelete && !explicit { @@ -174,42 +172,42 @@ func (e *Engine) canDeleteResource(resource construct.Resource, explicit bool, o // If upstream nodes exist, attempt to delete the resources upstream of the resource before deciding that the deletion process cannot continue if deletionCriteria.RequiresNoUpstream && !explicit && len(upstreamNodes) > 0 { log.Debugf("Cannot delete resource %s as it still has upstream dependencies", resource.Id()) - if !e.ignoreCriteria(resource, upstreamNodes) { + if !e.ignoreCriteria(graph, resource, upstreamNodes) { return false } for _, up := range upstreamNodes { - e.deleteConstruct(up, false, false) + e.deleteConstruct(graph, up, false, false) } - if len(e.Context.WorkingState.GetUpstreamConstructs(resource)) > 0 { + if len(graph.GetUpstreamConstructs(resource)) > 0 { log.Debugf("Cannot delete resource %s as it still has upstream dependencies", resource.Id()) return false } } if deletionCriteria.RequiresNoDownstream && !explicit && len(downstreamNodes) > 0 { log.Debugf("Cannot delete resource %s as it still has downstream dependencies", resource.Id()) - if !e.ignoreCriteria(resource, downstreamNodes) { + if !e.ignoreCriteria(graph, resource, downstreamNodes) { return false } for _, down := range downstreamNodes { - e.deleteConstruct(down, false, false) + e.deleteConstruct(graph, down, false, false) } - if len(e.Context.WorkingState.GetDownstreamConstructs(resource)) > 0 { + if len(graph.GetDownstreamConstructs(resource)) > 0 { log.Debugf("Cannot delete resource %s as it still has downstream dependencies", resource.Id()) return false } } if deletionCriteria.RequiresNoUpstreamOrDownstream && !explicit && len(downstreamNodes) > 0 && len(upstreamNodes) > 0 { log.Debugf("Cannot delete resource %s as it still has downstream dependencies", resource.Id()) - if !e.ignoreCriteria(resource, upstreamNodes) && !e.ignoreCriteria(resource, downstreamNodes) { + if !e.ignoreCriteria(graph, resource, upstreamNodes) && !e.ignoreCriteria(graph, resource, downstreamNodes) { return false } for _, down := range downstreamNodes { - e.deleteConstruct(down, false, false) + e.deleteConstruct(graph, down, false, false) } for _, up := range upstreamNodes { - e.deleteConstruct(up, false, false) + e.deleteConstruct(graph, up, false, false) } - if len(e.Context.WorkingState.GetDownstreamConstructs(resource)) > 0 && len(e.Context.WorkingState.GetUpstreamConstructs(resource)) > 0 { + if len(graph.GetDownstreamConstructs(resource)) > 0 && len(graph.GetUpstreamConstructs(resource)) > 0 { log.Debugf("Cannot delete resource %s as it still has upstream and downstream dependencies", resource.Id()) return false } @@ -217,9 +215,9 @@ func (e *Engine) canDeleteResource(resource construct.Resource, explicit bool, o return true } -func (e *Engine) getExplicitUpstreams(res construct.BaseConstruct) []construct.BaseConstruct { +func getExplicitDeleteUpstreams(graph *construct.ConstructGraph, res construct.BaseConstruct) []construct.BaseConstruct { var resources []construct.BaseConstruct - upstreams := e.Context.WorkingState.GetUpstreamConstructs(res) + upstreams := graph.GetUpstreamConstructs(res) if len(upstreams) == 0 { return resources } @@ -234,15 +232,15 @@ func (e *Engine) getExplicitUpstreams(res construct.BaseConstruct) []construct.B } if len(resources) == 0 { for _, up := range upstreams { - resources = append(resources, e.getExplicitUpstreams(up)...) + resources = append(resources, getExplicitDeleteUpstreams(graph, up)...) } } return resources } -func (e *Engine) getExplicitDownStreams(res construct.BaseConstruct) []construct.BaseConstruct { +func getExplicitDeleteDownstreams(graph *construct.ConstructGraph, res construct.BaseConstruct) []construct.BaseConstruct { var resources []construct.BaseConstruct - downstreams := e.Context.WorkingState.GetDownstreamConstructs(res) + downstreams := graph.GetDownstreamConstructs(res) if len(downstreams) == 0 { return resources } @@ -257,7 +255,7 @@ func (e *Engine) getExplicitDownStreams(res construct.BaseConstruct) []construct } if len(resources) == 0 { for _, down := range downstreams { - resources = append(resources, e.getExplicitDownStreams(down)...) + resources = append(resources, getExplicitDeleteDownstreams(graph, down)...) } } return resources diff --git a/pkg/engine/reconciler_resources.go b/pkg/engine/reconciler_resources.go new file mode 100644 index 000000000..f73f67b8c --- /dev/null +++ b/pkg/engine/reconciler_resources.go @@ -0,0 +1,213 @@ +package engine + +import ( + "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/engine/constraints" + "go.uber.org/zap" +) + +// ignoreCriteriaR like ignoreCriteria but for resources and their graphs +func (e *Engine) ignoreCriteriaR(graph *construct.ResourceGraph, resource construct.Resource, dependentResources []construct.Resource) bool { +DEP: + for _, dep := range dependentResources { + for _, res := range graph.GetDownstreamResources(resource) { + if dep == res { + det, _ := e.KnowledgeBase.GetResourceEdge(resource, dep) + if !det.DeletetionDependent { + return false + } + continue DEP + } + } + for _, res := range graph.GetUpstreamResources(resource) { + if dep == res { + det, _ := e.KnowledgeBase.GetResourceEdge(dep, resource) + if !det.DeletetionDependent { + return false + } + continue DEP + } + } + return false + } + return true +} + +// delete resource is used by the engine to remove resources from the resource graph in its context. +// +// if explicit is set it is meant to show that a user has explicitly requested for the resource to be deleted or that the resource requested is being deleted by its parent resource +// if overrideExplicit is set, it means that the explicit delete request still has to satisfy the resources delete criteria. If it is set to false, then the explicit deletion request is always performed +func (e *Engine) deleteResource(graph *construct.ResourceGraph, resource construct.Resource, explicit bool, overrideExplicit bool) bool { + log := zap.S().With(zap.String("id", resource.Id().String())) + log.Debug("Deleting resource") + upstreamNodes := graph.GetUpstreamResources(resource) + downstreamNodes := graph.GetDownstreamResources(resource) + + reflectResources := make(map[construct.ResourceId]construct.Resource) + if !e.canDeleteResourceR(graph, resource, explicit, overrideExplicit, upstreamNodes, downstreamNodes) { + return false + } + + for _, reflectResource := range construct.GetResourcesReflectively(graph, resource) { + reflectResources[reflectResource.Id()] = reflectResource + } + + err := graph.RemoveResourceAndEdges(resource) + if err != nil { + return false + } + + for _, upstreamNode := range upstreamNodes { + if _, ok := reflectResources[upstreamNode.Id()]; ok { + continue + } + + var explicitUpstreams []construct.Resource + if upstreamNode.DeleteContext().RequiresExplicitDelete { + explicitUpstreams = append(explicitUpstreams, upstreamNode) + } else { + explicitUpstreams = append(explicitUpstreams, getExplicitDeleteUpstreamsR(graph, upstreamNode)...) + } + + for _, downstreamNode := range downstreamNodes { + if _, ok := reflectResources[downstreamNode.Id()]; ok { + continue + } + + var explicitDownStreams []construct.Resource + if downstreamNode.DeleteContext().RequiresExplicitDelete { + explicitDownStreams = append(explicitDownStreams, downstreamNode) + } else { + explicitDownStreams = append(explicitDownStreams, getExplicitDeleteDownstreamsR(graph, downstreamNode)...) + } + + for _, u := range explicitUpstreams { + for _, d := range explicitDownStreams { + paths, err := graph.AllPaths(u.Id(), d.Id()) + if err != nil { + zap.S().Debugf("Error getting paths between %s and %s", u.Id(), d.Id()) + continue + } + if len(paths) == 0 { + log.Debugf("Adding dependency between %s and %s resources to reconnect path", u.Id(), d.Id()) + graph.AddDependencyById(u.Id(), d.Id(), nil) + e.Context.Constraints[constraints.EdgeConstraintScope] = append(e.Context.Constraints[constraints.EdgeConstraintScope], + &constraints.EdgeConstraint{ + Operator: constraints.MustNotContainConstraintOperator, + Target: constraints.Edge{ + Source: u.Id(), + Target: d.Id(), + }, + Node: resource.Id(), + }, + ) + } + } + } + } + } + + for _, res := range upstreamNodes { + _, explicit := reflectResources[res.Id()] + e.deleteResource(graph, res, explicit, false) + } + for _, res := range downstreamNodes { + _, explicit := reflectResources[res.Id()] + e.deleteResource(graph, res, explicit, false) + } + return true +} + +func (e *Engine) canDeleteResourceR(graph *construct.ResourceGraph, resource construct.Resource, explicit bool, overrideExplicit bool, upstreamNodes []construct.Resource, downstreamNodes []construct.Resource) bool { + log := zap.S().With(zap.String("id", resource.Id().String())) + deletionCriteria := resource.DeleteContext() + if deletionCriteria.RequiresExplicitDelete && !explicit { + return false + } + if !overrideExplicit { + explicit = false + } + // Check to see if there are upstream nodes for the resource trying to be deleted + // If upstream nodes exist, attempt to delete the resources upstream of the resource before deciding that the deletion process cannot continue + if deletionCriteria.RequiresNoUpstream && !explicit && len(upstreamNodes) > 0 { + log.Debugf("Cannot delete resource %s as it still has upstream dependencies", resource.Id()) + if !e.ignoreCriteriaR(graph, resource, upstreamNodes) { + return false + } + for _, up := range upstreamNodes { + e.deleteResource(graph, up, false, false) + } + if len(graph.GetUpstreamResources(resource)) > 0 { + log.Debugf("Cannot delete resource %s as it still has upstream dependencies", resource.Id()) + return false + } + } + if deletionCriteria.RequiresNoDownstream && !explicit && len(downstreamNodes) > 0 { + log.Debugf("Cannot delete resource %s as it still has downstream dependencies", resource.Id()) + if !e.ignoreCriteriaR(graph, resource, downstreamNodes) { + return false + } + for _, down := range downstreamNodes { + e.deleteResource(graph, down, false, false) + } + if len(graph.GetDownstreamResources(resource)) > 0 { + log.Debugf("Cannot delete resource %s as it still has downstream dependencies", resource.Id()) + return false + } + } + if deletionCriteria.RequiresNoUpstreamOrDownstream && !explicit && len(downstreamNodes) > 0 && len(upstreamNodes) > 0 { + log.Debugf("Cannot delete resource %s as it still has downstream dependencies", resource.Id()) + if !e.ignoreCriteriaR(graph, resource, upstreamNodes) && !e.ignoreCriteriaR(graph, resource, downstreamNodes) { + return false + } + for _, down := range downstreamNodes { + e.deleteResource(graph, down, false, false) + } + for _, up := range upstreamNodes { + e.deleteResource(graph, up, false, false) + } + if len(graph.GetDownstreamResources(resource)) > 0 && len(graph.GetUpstreamResources(resource)) > 0 { + log.Debugf("Cannot delete resource %s as it still has upstream and downstream dependencies", resource.Id()) + return false + } + } + return true +} + +func getExplicitDeleteUpstreamsR(graph *construct.ResourceGraph, res construct.Resource) []construct.Resource { + var resources []construct.Resource + upstreams := graph.GetUpstreamResources(res) + if len(upstreams) == 0 { + return resources + } + for _, up := range upstreams { + if up.DeleteContext().RequiresExplicitDelete { + resources = append(resources, up) + } + } + if len(resources) == 0 { + for _, up := range upstreams { + resources = append(resources, getExplicitDeleteUpstreamsR(graph, up)...) + } + } + return resources +} + +func getExplicitDeleteDownstreamsR(graph *construct.ResourceGraph, res construct.Resource) []construct.Resource { + var resources []construct.Resource + downstreams := graph.GetDownstreamResources(res) + if len(downstreams) == 0 { + return resources + } + for _, d := range downstreams { + if d.DeleteContext().RequiresExplicitDelete { + resources = append(resources, d) + } + } + if len(resources) == 0 { + for _, down := range downstreams { + resources = append(resources, getExplicitDeleteDownstreamsR(graph, down)...) + } + } + return resources +} diff --git a/pkg/engine/resource_configuration.go b/pkg/engine/resource_configuration.go index e83f48bcb..7de4af511 100644 --- a/pkg/engine/resource_configuration.go +++ b/pkg/engine/resource_configuration.go @@ -7,7 +7,9 @@ import ( "strings" "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/engine/constraints" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base" + "go.uber.org/zap" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -61,6 +63,85 @@ type SetMapKey struct { Value reflect.Value } +func (e *Engine) configureResource(context *SolveContext, r construct.Resource) { + configureComplete := make(map[string]struct{}) + for _, rc := range e.Context.Constraints[constraints.ResourceConstraintScope] { + rc := rc.(*constraints.ResourceConstraint) + if rc.Target != r.Id() { + continue + } + config := knowledgebase.Configuration{Field: rc.Property, Value: rc.Value} + configRule := knowledgebase.ConfigurationRule{Config: config, Resource: rc.Target} + + field, err := GetFieldByName(reflect.ValueOf(r), rc.Property) + if err != nil { + context.Errors = append(context.Errors, &ResourceConfigurationError{ + Resource: r, + Cause: err, + Config: config, + Constraint: rc, + }) + continue + } + configureComplete[field.Name] = struct{}{} + e.handleDecision( + context, + Decision{ + Level: LevelInfo, + Result: &DecisionResult{ + Config: &configRule, + Resource: r, + }, + Action: ActionConfigure, + Cause: &Cause{Constraint: rc}, + }, + ) + } + + tmpl := e.GetTemplateForResource(r) + if tmpl == nil { + return + } + for _, cfg := range tmpl.Configuration { + if _, done := configureComplete[cfg.Field]; done { + continue + } + if cfg.ValueTemplate != "" { + ctx := knowledgebase.ConfigTemplateContext{DAG: context.ResourceGraph} + data := knowledgebase.ConfigTemplateData{ + Resource: r.Id(), + } + var err error + cfg, err = ctx.ResolveConfig(cfg, data) + if err != nil { + context.Errors = append(context.Errors, &ResourceConfigurationError{ + Resource: r, + Cause: err, + Config: cfg, + }) + continue + } + } + configRule := &knowledgebase.ConfigurationRule{Config: cfg, Resource: r.Id()} + e.handleDecision( + context, + Decision{ + Level: LevelInfo, + Result: &DecisionResult{ + Config: configRule, + Resource: r, + }, + Action: ActionConfigure, + Cause: &Cause{ResourceConfiguration: r}, + }, + ) + } + // Re-run make operational in case the configuration changed the requirements + e.MakeResourceOperational(context, r) +} + +var resourceIdType = reflect.TypeOf(construct.ResourceId{}) + // ConfigureField is a function that takes a resource, a field name, and a value and sets the field on the resource to the value // It also takes a graph so that it can resolve references // It returns an error if the field cannot be set @@ -85,7 +166,7 @@ func ConfigureField(resource construct.Resource, fieldName string, value interfa case reflect.Pointer, reflect.Struct: // Since there can be pointers to primitive types and others, we will ensure that those still work if field.Kind() == reflect.Pointer && field.Elem().Kind() != reflect.Struct { - if reflect.TypeOf(value) != field.Type() && reflect.TypeOf(value).String() == "construct.ResourceId" { + if reflect.TypeOf(value) != field.Type() && reflect.TypeOf(value) == resourceIdType { return fmt.Errorf("config template is not the correct type for field %s and resource %s. expected it to be %s, but got %s", fieldName, resource.Id(), field.Type(), reflect.TypeOf(value)) } } else if reflect.ValueOf(value).Kind() != reflect.Map && !field.Type().Implements(reflect.TypeOf((*construct.Resource)(nil)).Elem()) && field.Type() != reflect.TypeOf(construct.ResourceId{}) { @@ -96,7 +177,7 @@ func ConfigureField(resource construct.Resource, fieldName string, value interfa return err } default: - if reflect.TypeOf(value) != field.Type() && reflect.TypeOf(value).String() == "construct.ResourceId" { + if reflect.TypeOf(value) != field.Type() && reflect.TypeOf(value) == resourceIdType { return fmt.Errorf("config template is not the correct type for field %s and resource %s. expected it to be %s, but got %s", fieldName, resource.Id(), field.Type(), reflect.TypeOf(value)) } err := configureField(value, field, graph, zeroValueAllowed) @@ -107,6 +188,7 @@ func ConfigureField(resource construct.Resource, fieldName string, value interfa if setMapKey != nil { setMapKey.Map.SetMapIndex(setMapKey.Key, setMapKey.Value) } + zap.S().Infof("configured %s#%s to value '%v'", resource.Id(), fieldName, value) return nil } @@ -136,7 +218,7 @@ func configureField(val interface{}, field reflect.Value, dag *construct.Resourc } field.Elem().Set(reflect.ValueOf(res).Elem()) return nil - } else if field.Type().Implements(reflect.TypeOf((*construct.Resource)(nil)).Elem()) && reflect.ValueOf(val).Type().String() == "construct.ResourceId" { + } else if field.Type().Implements(reflect.TypeOf((*construct.Resource)(nil)).Elem()) && reflect.ValueOf(val).Type() == resourceIdType { id := val.(construct.ResourceId) res := getFieldFromIdString(id.String(), dag) // if the return type is a resource id we need to get the correlating resource object @@ -157,11 +239,11 @@ func configureField(val interface{}, field reflect.Value, dag *construct.Resourc if reflect.TypeOf(val).Kind() == reflect.String { fieldFromString := getFieldFromIdString(val.(string), dag) if fieldFromString != nil { - field.Set(reflect.ValueOf(fieldFromString)) - if reflect.TypeOf(fieldFromString) != field.Type() { - return fmt.Errorf("the type represented by %s not the correct type for field %s. expected it to be %s, but got %s", val, field, field.Type(), reflect.TypeOf(fieldFromString)) + fval := reflect.ValueOf(fieldFromString) + if fval.Type().AssignableTo(field.Type()) { + field.Set(reflect.ValueOf(fieldFromString)) + return nil } - return nil } } @@ -274,7 +356,7 @@ func configureField(val interface{}, field reflect.Value, dag *construct.Resourc field.Set(reflect.ValueOf(val)) } default: - if field.Kind() == reflect.String && reflect.TypeOf(val).Kind() != reflect.String && reflect.TypeOf(val).Elem().String() == "construct.ResourceId" { + if field.Kind() == reflect.String && reflect.TypeOf(val).Kind() != reflect.String && reflect.TypeOf(val).Elem() == resourceIdType { id := val.(*construct.ResourceId) strVal := getFieldFromIdString(id.String(), dag) if strVal != nil { @@ -303,18 +385,18 @@ func getIdAndFields(id construct.ResourceId) (construct.ResourceId, string) { func getFieldFromIdString(id string, dag *construct.ResourceGraph) any { arr := strings.Split(id, "#") - resId := &construct.ResourceId{} + var resId construct.ResourceId err := resId.UnmarshalText([]byte(arr[0])) if err != nil { return nil } - if len(arr) == 1 { - return *resId - } - res := dag.GetResource(*resId) + res := dag.GetResource(resId) if res == nil { return nil } + if len(arr) == 1 { + return resId + } field, _, err := parseFieldName(res, arr[1], dag, true) if err != nil { @@ -323,6 +405,37 @@ func getFieldFromIdString(id string, dag *construct.ResourceGraph) any { return field.Interface() } +func GetFieldByName(s reflect.Value, fieldName string) (reflect.StructField, error) { + for s.Kind() == reflect.Ptr { + s = s.Elem() + } + t := s.Type() + field, ok := t.FieldByName(fieldName) + if ok { + return field, nil + } + + // Try to find the field by its json or yaml tag (especially to handle case [upper/lower] [Pascal/snake]) + for i := 0; i < t.NumField(); i++ { + f := t.Field(i) + if fieldName == strings.ToLower(f.Name) { + // When YAML marshalling fields that don't have a tag, they're just lower cased + // so this condition should catch those. + return f, nil + } + tagName, _, _ := strings.Cut(f.Tag.Get("json"), ",") + if fieldName == tagName { + return f, nil + } + tagName, _, _ = strings.Cut(f.Tag.Get("yaml"), ",") + if fieldName == tagName { + return f, nil + } + } + + return reflect.StructField{}, fmt.Errorf("unable to find field %s on resource %s", fieldName, s) +} + // ParseFieldName parses a field name and returns the value of the field // Example: "spec.template.spec.containers[0].image" will return the value of the image field of the first container in the template // @@ -340,18 +453,22 @@ func parseFieldName(resource construct.Resource, fieldName string, dag *construc key = strings.TrimPrefix(key, "\"") key = strings.TrimSuffix(key, "\"") } + parent := field if i == 0 { - field = reflect.ValueOf(resource).Elem().FieldByName(currFieldName) - } else { - if field.Kind() == reflect.Ptr { - field = field.Elem().FieldByName(currFieldName) - } else { - field = field.FieldByName(currFieldName) - } + parent = reflect.ValueOf(resource).Elem() + } + if parent.Kind() == reflect.Ptr { + parent = field.Elem() } + field = parent.FieldByName(currFieldName) if !field.IsValid() { - return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, field is not valid", fields[i], resource.Id()) - } else if field.IsZero() && field.Kind() == reflect.Ptr { + fieldType, err := GetFieldByName(parent, currFieldName) + if err != nil { + return reflect.Value{}, nil, err + } + field = parent.FieldByIndex(fieldType.Index) + } + if field.IsZero() && field.Kind() == reflect.Ptr { if !configure { return reflect.Value{}, nil, nil } @@ -363,7 +480,7 @@ func parseFieldName(resource construct.Resource, fieldName string, dag *construc if field.Kind() == reflect.Map { // Right now we only support string keys on maps, so error if we see a mismatch if field.Type().Key().Kind() != reflect.String { - return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, field is not a map[string]", fields[i], resource.Id()) + return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, field is not a map[string]", strings.Join(fields[:i+1], "."), resource.Id()) } // create the map if it is currently nil if field.IsNil() { @@ -375,10 +492,9 @@ func parseFieldName(resource construct.Resource, fieldName string, dag *construc if err == nil { // if the key is a resource id, then we need to get the field from the resource field := getFieldFromIdString(resId.String(), dag) - if field == nil { - return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s when getting field from id string", key, resId.String()) + if field != nil { + key = fmt.Sprintf("%v", field) } - key = fmt.Sprintf("%v", field) } // create a copy of the value and clone the existing one. We do this because map values are not addressable newField := reflect.New(field.Type().Elem()).Elem() @@ -391,15 +507,15 @@ func parseFieldName(resource construct.Resource, fieldName string, dag *construc } else if field.Kind() == reflect.Slice || field.Kind() == reflect.Array { index, err := strconv.Atoi(key) if err != nil { - return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, could not convert index to int", fields[i], resource.Id()) + return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, could not convert index to int", strings.Join(fields[:i+1], "."), resource.Id()) } if index >= field.Len() { - return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, length of array is less than index", fields[i], resource.Id()) + return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, length of array is less than index", strings.Join(fields[:i+1], "."), resource.Id()) } field = field.Index(index) } else { - return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, field type does not support key or index", fields[i], resource.Id()) + return reflect.Value{}, nil, fmt.Errorf("unable to find field %s on resource %s, field type does not support key or index", strings.Join(fields[:i+1], "."), resource.Id()) } } } diff --git a/pkg/engine/resource_configuration_test.go b/pkg/engine/resource_configuration_test.go index fb9255811..e75b06da2 100644 --- a/pkg/engine/resource_configuration_test.go +++ b/pkg/engine/resource_configuration_test.go @@ -290,7 +290,7 @@ func Test_ConfigureField(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - err := ConfigureField(tt.resource, tt.config.Field, tt.config.Value, tt.config.ZeroValueAllowed, nil) + err := ConfigureField(tt.resource, tt.config.Field, tt.config.Value, tt.config.ZeroValueAllowed, construct.NewResourceGraph()) if !assert.NoError(err) { return } @@ -464,9 +464,9 @@ func Test_parseFieldName(t *testing.T) { if !assert.NotNil(setMapKey) { return } - assert.Equal(tt.mapKey.Map.Interface(), setMapKey.Map.Interface()) - assert.Equal(tt.mapKey.Key.Interface(), setMapKey.Key.Interface()) - assert.Equal(tt.mapKey.Value.Interface(), setMapKey.Value.Interface()) + assert.Equal(tt.mapKey.Map.Interface(), setMapKey.Map.Interface(), "map") + assert.Equal(tt.mapKey.Key.Interface(), setMapKey.Key.Interface(), "key") + assert.Equal(tt.mapKey.Value.Interface(), setMapKey.Value.Interface(), "value") } }) } diff --git a/pkg/graph/graph.go b/pkg/graph/graph.go index e82f90573..3f798e211 100644 --- a/pkg/graph/graph.go +++ b/pkg/graph/graph.go @@ -1,6 +1,7 @@ package graph import ( + "fmt" "sort" "github.com/dominikbraun/graph" @@ -101,32 +102,43 @@ func (d *Directed[V]) AllPaths(source, target string) ([][]string, error) { } func (d *Directed[V]) OutgoingEdges(from V) []Edge[V] { + return d.OutgoingEdgesById(d.hasher(from)) +} + +func (d *Directed[V]) OutgoingEdgesById(from string) []Edge[V] { + source := d.GetVertex(from) return handleOutgoingEdges(d, from, func(destination V) Edge[V] { return Edge[V]{ - Source: from, + Source: source, Destination: destination, } }) } func (d *Directed[V]) IncomingEdges(to V) []Edge[V] { + return d.IncomingEdgesById(d.hasher(to)) +} + +func (d *Directed[V]) IncomingEdgesById(to string) []Edge[V] { + dest := d.GetVertex(to) return handleIncomingEdges(d, to, func(destination V) Edge[V] { return Edge[V]{ Source: destination, - Destination: to, + Destination: dest, } }) } func (d *Directed[V]) RemoveVertex(v string) error { err := d.underlying.RemoveVertex(v) - if err != nil && !errors.Is(err, graph.ErrVertexNotFound) { - zap.S().With(zap.Error(err)).Errorf(`Unexpected error while removing %s. %s`, v, ourFault) - return err - } else if errors.Is(err, graph.ErrVertexNotFound) { + if err == nil { + return nil + } + if errors.Is(err, graph.ErrVertexNotFound) { zap.S().With(zap.Error(err)).Debugf(`Ignoring error while removing %s because it does not exist`, v) + return nil } - return nil + return fmt.Errorf("could not remove %s: %w", v, err) } func (d *Directed[V]) AddVertex(v V) { @@ -190,10 +202,18 @@ func (d *Directed[V]) GetVertexWithProperties(source string) (V, graph.VertexPro } func (d *Directed[V]) OutgoingVertices(from V) []V { + return d.OutgoingVerticesById(d.hasher(from)) +} + +func (d *Directed[V]) OutgoingVerticesById(from string) []V { return handleOutgoingEdges(d, from, func(destination V) V { return destination }) } func (d *Directed[V]) IncomingVertices(to V) []V { + return d.IncomingVerticesById(d.hasher(to)) +} + +func (d *Directed[V]) IncomingVerticesById(to string) []V { return handleIncomingEdges(d, to, func(destination V) V { return destination }) } @@ -304,7 +324,7 @@ func (d *Directed[V]) CreatesCycle(source string, dest string) (bool, error) { return graph.CreatesCycle(d.underlying, source, dest) } -func handleOutgoingEdges[V any, O any](d *Directed[V], from V, generate func(destination V) O) []O { +func handleOutgoingEdges[V any, O any](d *Directed[V], from string, generate func(destination V) O) []O { // Note: this is very inefficient. The graph library we use doesn't let us get just the roots, so we pull in // the full predecessor map, get all the ids with no outgoing edges, and then look up the vertex for each one // of those. @@ -318,12 +338,12 @@ func handleOutgoingEdges[V any, O any](d *Directed[V], from V, generate func(des panic(err) } var results []O - vertexAdjacency, ok := fullAdjacency[d.hasher(from)] + vertexAdjacency, ok := fullAdjacency[from] if !ok { return results } for _, edge := range vertexAdjacency { - if edge.Source != d.hasher(from) { + if edge.Source != from { continue } if toV, err := d.underlying.Vertex(edge.Target); err == nil { @@ -337,7 +357,7 @@ func handleOutgoingEdges[V any, O any](d *Directed[V], from V, generate func(des return results } -func handleIncomingEdges[V any, O any](d *Directed[V], to V, generate func(destination V) O) []O { +func handleIncomingEdges[V any, O any](d *Directed[V], to string, generate func(destination V) O) []O { // Note: this is very inefficient. The graph library we use doesn't let us get just the roots, so we pull in // the full predecessor map, get all the ids with no outgoing edges, and then look up the vertex for each one // of those. @@ -353,7 +373,7 @@ func handleIncomingEdges[V any, O any](d *Directed[V], to V, generate func(desti var results []O for _, v := range fullAdjacency { for _, edge := range v { - if edge.Target != d.hasher(to) { + if edge.Target != to { continue } if toV, err := d.underlying.Vertex(edge.Source); err == nil { diff --git a/pkg/infra/iac2/templates/.gitignore b/pkg/infra/iac2/templates/.gitignore index 1d93e4fbd..3814b7ee4 100644 --- a/pkg/infra/iac2/templates/.gitignore +++ b/pkg/infra/iac2/templates/.gitignore @@ -1,2 +1,2 @@ factory.js -package-lock.json \ No newline at end of file +package-lock.json diff --git a/pkg/knowledge_base/edge_kb_test.go b/pkg/knowledge_base/edge_kb_test.go index 1b222ede8..6c25556c9 100644 --- a/pkg/knowledge_base/edge_kb_test.go +++ b/pkg/knowledge_base/edge_kb_test.go @@ -159,3 +159,35 @@ func (f *E) DeleteContext() construct.DeleteContext { RequiresNoUpstream: true, } } + +type ( + TestResource[T any] struct { + Provider string + Type string + Name string + Field T + } + SimpleResource = TestResource[struct{}] +) + +func (t *TestResource[T]) Id() construct.ResourceId { + id := construct.ResourceId{ + Provider: t.Provider, + Type: t.Type, + Name: t.Name, + } + if id.Provider == "" { + id.Provider = "test" + } + if id.Type == "" { + id.Type = "resource" + } + return id +} + +func (t *TestResource[T]) BaseConstructRefs() construct.BaseConstructSet { + return nil +} +func (t *TestResource[T]) DeleteContext() construct.DeleteContext { + return construct.DeleteContext{} +} diff --git a/pkg/knowledge_base/operational_funcs.go b/pkg/knowledge_base/operational_funcs.go new file mode 100644 index 000000000..f3cbb0699 --- /dev/null +++ b/pkg/knowledge_base/operational_funcs.go @@ -0,0 +1,433 @@ +package knowledgebase + +import ( + "bytes" + "encoding" + "encoding/json" + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "text/template" + + "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/graph" + "go.uber.org/zap" +) + +type ( + // ConfigTemplateContext is used to scope the DAG into the template functions + ConfigTemplateContext struct { + DAG *construct.ResourceGraph + + resultJson bool + } + + // ConfigTemplateData provides the resource or edge to the templates as + // `{{ .Self }}` for resources + // `{{ .Source }}` and `{{ .Destination }}` for edges + ConfigTemplateData struct { + Resource construct.ResourceId + Edge graph.Edge[construct.ResourceId] + } +) + +func (ctx *ConfigTemplateContext) Parse(tmpl string) (*template.Template, error) { + t, err := template.New("config").Funcs(template.FuncMap{ + "upstream": ctx.Upstream, + "allUpstream": ctx.AllUpstream, + "downstream": ctx.Downstream, + "allDownstream": ctx.AllDownstream, + "shortestPath": ctx.ShortestPath, + "fieldValue": ctx.FieldValue, + "fieldRef": ctx.FieldRef, + + "toJson": ctx.toJson, + + "split": strings.Split, + "join": strings.Join, + "filterIds": filterIds, + "filterMatch": filterMatch, + "mapString": mapString, + "zipToMap": zipToMap, + "keysToMapWithDefault": keysToMapWithDefault, + "replace": replaceRegex, + + "add": add, + "sub": sub, + }).Parse(tmpl) + return t, err +} + +// ExecuteDecode executes the template `tmpl` using `data` and decodes the value into `value` +func (ctx ConfigTemplateContext) ExecuteDecode(tmpl string, data ConfigTemplateData, value interface{}) error { + t, err := ctx.Parse(tmpl) + if err != nil { + return err + } + buf := new(bytes.Buffer) + if err := t.Execute(buf, data); err != nil { + return err + } + + if ctx.resultJson { + dec := json.NewDecoder(buf) + return dec.Decode(value) + } + + // trim the spaces so you don't have to sprinkle the templates with `{{-` and `-}}` (the `-` trims spaces) + bstr := strings.TrimSpace(buf.String()) + + switch value := value.(type) { + case *string: + *value = bstr + return nil + + case *[]byte: + *value = []byte(bstr) + return nil + + case *bool: + b, err := strconv.ParseBool(bstr) + if err != nil { + return err + } + *value = b + return nil + + case encoding.TextUnmarshaler: + // notably, this handles `construct.ResourceId` and `construct.IaCValue` + return value.UnmarshalText([]byte(bstr)) + } + + resultStr := reflect.ValueOf(buf.String()) + valueRefl := reflect.ValueOf(value).Elem() + if resultStr.Type().AssignableTo(valueRefl.Type()) { + // this covers alias types like `type MyString string` + valueRefl.Set(resultStr) + return nil + } + + return fmt.Errorf("cannot decode template result '%s' into %T", buf, value) +} + +func (ctx *ConfigTemplateContext) ResolveConfig(config Configuration, data ConfigTemplateData) (Configuration, error) { + res := ctx.DAG.GetResource(data.Resource) + + field := reflect.ValueOf(res).Elem().FieldByName(config.Field) + if !field.IsValid() { + return config, fmt.Errorf("field %s not found on resource %s", config.Field, data.Resource) + } + + valueRefl := reflect.New(field.Type()) + value := valueRefl.Interface() + err := ctx.ExecuteDecode(config.ValueTemplate, data, value) + if err != nil { + return config, err + } + + config.Value = valueRefl.Elem().Interface() + config.ValueTemplate = "" + return config, nil +} + +func (data ConfigTemplateData) Self() (construct.ResourceId, error) { + if data.Resource.IsZero() { + return construct.ResourceId{}, fmt.Errorf("no .Self is set") + } + return data.Resource, nil +} + +func (data ConfigTemplateData) Source() (construct.ResourceId, error) { + if data.Edge.Source.IsZero() { + return construct.ResourceId{}, fmt.Errorf("no .Source is set") + } + return data.Edge.Source, nil +} + +func (data ConfigTemplateData) Destination() (construct.ResourceId, error) { + if data.Edge.Destination.IsZero() { + return construct.ResourceId{}, fmt.Errorf("no .Destination is set") + } + return data.Edge.Destination, nil +} + +// Log is primarily used for debugging templates and shouldn't actually appear in any. +// Allows for outputting any intermediate values (such as `$integration := downstream "aws:api_integration" .Self`) +func (data ConfigTemplateData) Log(level string, message string, args ...interface{}) string { + l := zap.L() + if !data.Resource.IsZero() { + l = l.With(zap.String("resource", data.Resource.String())) + } + if !data.Edge.Source.IsZero() { + l = l.With(zap.String("edge", data.Edge.Source.String()+" -> "+data.Edge.Destination.String())) + } + switch strings.ToLower(level) { + case "debug": + l.Sugar().Debugf(message, args...) + case "info": + l.Sugar().Infof(message, args...) + case "warn": + l.Sugar().Warnf(message, args...) + case "error": + l.Sugar().Errorf(message, args...) + default: + l.Sugar().Warnf(message, args...) + } + return "" +} + +func argToRID(arg any) (construct.ResourceId, error) { + switch arg := arg.(type) { + case construct.ResourceId: + return arg, nil + + case construct.Resource: + return arg.Id(), nil + + case string: + var resId construct.ResourceId + err := resId.UnmarshalText([]byte(arg)) + return resId, err + } + + return construct.ResourceId{}, fmt.Errorf("invalid argument type %T", arg) +} + +// Upstream returns the first resource that matches `selector` which is upstream of `resource` +func (ctx *ConfigTemplateContext) Upstream(selector any, resource construct.ResourceId) (construct.ResourceId, error) { + selId, err := argToRID(selector) + if err != nil { + return construct.ResourceId{}, err + } + + upstream := ctx.DAG.GetAllUpstreamResources(ctx.DAG.GetResource(resource)) + for _, up := range upstream { + if selId.Matches(up.Id()) { + return up.Id(), nil + } + } + return construct.ResourceId{}, + fmt.Errorf("no upstream resource of '%s' found matching selector '%s'", resource, selId) +} + +// AllUpstream is like Upstream but returns all transitive upstream resources. +// nolint: lll +func (ctx *ConfigTemplateContext) AllUpstream(selector any, resource construct.ResourceId) ([]construct.ResourceId, error) { + selId, err := argToRID(selector) + if err != nil { + return nil, err + } + + var matches []construct.ResourceId + upstream := ctx.DAG.GetAllUpstreamResources(ctx.DAG.GetResource(resource)) + for _, up := range upstream { + if selId.Matches(up.Id()) { + matches = append(matches, up.Id()) + } + } + return matches, nil +} + +// Downstream returns the first resource that matches `selector` which is downstream of `resource` +// nolint: lll +func (ctx *ConfigTemplateContext) Downstream(selector any, resource construct.ResourceId) (construct.ResourceId, error) { + selId, err := argToRID(selector) + if err != nil { + return construct.ResourceId{}, err + } + + downstream := ctx.DAG.GetAllDownstreamResources(ctx.DAG.GetResource(resource)) + for _, down := range downstream { + if selId.Matches(down.Id()) { + return down.Id(), nil + } + } + return construct.ResourceId{}, + fmt.Errorf("no downstream resource of '%s' found matching selector '%s'", resource, selId) +} + +// AllDownstream is like Downstream but returns all transitive downstream resources. +// nolint: lll +func (ctx *ConfigTemplateContext) AllDownstream(selector any, resource construct.ResourceId) ([]construct.ResourceId, error) { + selId, err := argToRID(selector) + if err != nil { + return nil, err + } + + var matches []construct.ResourceId + downstream := ctx.DAG.GetAllDownstreamResources(ctx.DAG.GetResource(resource)) + for _, down := range downstream { + if selId.Matches(down.Id()) { + matches = append(matches, down.Id()) + } + } + return matches, nil +} + +// ShortestPath returns all the resource IDs on the shortest path from source to destination +func (ctx *ConfigTemplateContext) ShortestPath(source, destination any) ([]construct.ResourceId, error) { + srcId, err := argToRID(source) + if err != nil { + return nil, err + } + dstId, err := argToRID(destination) + if err != nil { + return nil, err + } + path, err := ctx.DAG.ShortestPath(srcId, dstId) + if err != nil { + return nil, err + } + pathIds := make([]construct.ResourceId, len(path)) + for i, p := range path { + pathIds[i] = p.Id() + } + return pathIds, nil +} + +// FieldValue returns the value of `field` on `resource` in json +func (ctx *ConfigTemplateContext) FieldValue(field string, resource any) (any, error) { + resId, err := argToRID(resource) + if err != nil { + return "", err + } + + r := ctx.DAG.GetResource(resId) + if r == nil { + return nil, fmt.Errorf("resource '%s' not found", resId) + } + + fieldValue := reflect.ValueOf(r).Elem().FieldByName(field) + if !fieldValue.IsValid() { + return nil, fmt.Errorf("field '%s' not found on resource '%s'", field, resId) + } + return fieldValue.Interface(), nil +} + +// FieldRef returns a reference to `field` on `resource` (as an IaCValue) +func (ctx *ConfigTemplateContext) FieldRef(field string, resource any) (construct.IaCValue, error) { + resId, err := argToRID(resource) + if err != nil { + return construct.IaCValue{}, err + } + + return construct.IaCValue{ + ResourceId: resId, + Property: field, + }, nil +} + +// toJson is used to return complex values that do not have TextUnmarshaler implemented +func (ctx *ConfigTemplateContext) toJson(value any) (string, error) { + j, err := json.Marshal(value) + if err != nil { + return "", err + } + ctx.resultJson = true + return string(j), nil +} + +// filterMatch returns a json array by filtering the values array with the regex pattern +func filterMatch(pattern string, values []string) ([]string, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + + matches := make([]string, 0, len(values)) + for _, v := range values { + if ok := re.MatchString(v); ok { + matches = append(matches, v) + } + } + return matches, nil +} + +func filterIds(selector any, ids []construct.ResourceId) ([]construct.ResourceId, error) { + selId, err := argToRID(selector) + if err != nil { + return nil, err + } + matches := make([]construct.ResourceId, 0, len(ids)) + for _, r := range ids { + if selId.Matches(r) { + matches = append(matches, r) + } + } + return matches, nil +} + +// mapstring takes in a regex pattern and replacement as well as a json array of strings +// roughly `unmarshal value | sed s/pattern/replace/g | marshal` +func mapString(pattern, replace string, values []string) ([]string, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + + nv := make([]string, len(values)) + for i, v := range values { + nv[i] = re.ReplaceAllString(v, replace) + } + return nv, nil +} + +// zipToMap returns a json map by zipping the keys and values arrays +// Example: zipToMap(['a', 'b'], [1, 2]) => {"a": 1, "b": 2} +func zipToMap(keys []string, valuesArg any) (map[string]any, error) { + // Have to use reflection here because technically, []string is not assignable to []any + // thanks Go. + valuesRefl := reflect.ValueOf(valuesArg) + if valuesRefl.Kind() != reflect.Slice && valuesRefl.Kind() != reflect.Array { + return nil, fmt.Errorf("values is not a slice or array") + } + if len(keys) != valuesRefl.Len() { + return nil, fmt.Errorf("key length (%d) != value length (%d)", len(keys), valuesRefl.Len()) + } + + m := make(map[string]any) + for i, k := range keys { + m[k] = valuesRefl.Index(i).Interface() + } + return m, nil +} + +// keysToMapWithDefault returns a json map by mapping the keys array to the static defaultValue +// Example keysToMapWithDefault(0, ['a', 'b']) => {"a": 0, "b": 0} +func keysToMapWithDefault(defaultValue any, keys []string) (map[string]any, error) { + m := make(map[string]any) + for _, k := range keys { + m[k] = defaultValue + } + return m, nil +} + +func add(args ...int) int { + total := 0 + for _, a := range args { + total += a + } + return total +} + +func sub(args ...int) int { + if len(args) == 0 { + return 0 + } + total := args[0] + for _, a := range args[1:] { + total -= a + } + return total +} + +func replaceRegex(pattern, replace, value string) (string, error) { + re, err := regexp.Compile(pattern) + if err != nil { + return "", err + } + s := re.ReplaceAllString(value, replace) + return s, nil +} diff --git a/pkg/knowledge_base/operational_funcs_test.go b/pkg/knowledge_base/operational_funcs_test.go new file mode 100644 index 000000000..63e0b351a --- /dev/null +++ b/pkg/knowledge_base/operational_funcs_test.go @@ -0,0 +1,142 @@ +package knowledgebase + +import ( + "reflect" + "testing" + + "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/graph" + "github.com/stretchr/testify/assert" +) + +func TestConfigTemplateContext_ExecuteDecode(t *testing.T) { + ctx := &ConfigTemplateContext{DAG: construct.NewResourceGraph()} + + // Set up graph (using test:resource qualified type unless otherwise specified): + // a -> b -> test:x:y -> c -> d + // b -> m -> c + a := &SimpleResource{Name: "a"} + b := &SimpleResource{Name: "b"} + ctx.DAG.AddDependency(a, b) + xy := &TestResource[string]{Type: "x", Name: "y", Field: "my value"} + ctx.DAG.AddDependency(b, xy) + c := &SimpleResource{Name: "c"} + ctx.DAG.AddDependency(xy, c) + d := &SimpleResource{Name: "d"} + ctx.DAG.AddDependency(c, d) + m := &SimpleResource{Name: "m"} + ctx.DAG.AddDependency(b, m) + ctx.DAG.AddDependency(m, c) + + // Simple data is a convenience for specify the data similar to an edge configuration data + // of xy -> c setting a property on xy. + simpleData := ConfigTemplateData{ + Resource: xy.Id(), + Edge: graph.Edge[construct.ResourceId]{Source: xy.Id(), Destination: c.Id()}, + } + + tests := []struct { + name string + tmpl string + data ConfigTemplateData + want any + wantErr bool + }{ + { + name: "string literal", + tmpl: "value", + want: "value", + }, + { + name: "bool literal", + tmpl: "true", + want: true, + }, + { + name: "ResourceId literal", + tmpl: "test:resource:a", + want: a.Id(), + }, + { + name: "IaCValue literal", + tmpl: "test:resource:a#Property", + want: construct.IaCValue{ResourceId: a.Id(), Property: "Property"}, + }, + + // Data "field" access + { + name: "ResourceId Self", + tmpl: `{{ .Self }}`, + data: simpleData, + want: xy.Id(), + }, + { + name: "ResourceId Source", + tmpl: `{{ .Source }}`, + data: simpleData, + want: xy.Id(), + }, + { + name: "ResourceId Destination", + tmpl: `{{ .Destination }}`, + data: simpleData, + want: c.Id(), + }, + + // DAG access + { + name: "upstream", + tmpl: `{{ upstream "test:resource" .Self }}`, + data: simpleData, + want: b.Id(), + }, + { + name: "all upstream", + tmpl: `{{ allUpstream "test:resource" .Self | toJson }}`, + data: simpleData, + want: []construct.ResourceId{b.Id(), a.Id()}, + }, + { + name: "downstream", + tmpl: `{{ downstream "test:resource" .Self }}`, + data: simpleData, + want: c.Id(), + }, + { + name: "all downstream", + tmpl: `{{ allDownstream "test:resource" .Self | toJson }}`, + data: simpleData, + want: []construct.ResourceId{c.Id(), d.Id()}, + }, + + // Resource access + { + name: "fieldValue", + tmpl: `{{ fieldValue "Field" .Self }}`, + data: simpleData, + want: "my value", + }, + { + name: "fieldValue", + tmpl: `{{ fieldRef "Field" .Self }}`, + data: simpleData, + want: construct.IaCValue{ResourceId: xy.Id(), Property: "Field"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + + gotV := reflect.New(reflect.TypeOf(tt.want)) + err := ctx.ExecuteDecode(tt.tmpl, tt.data, gotV.Interface()) + if tt.wantErr { + assert.Error(err) + return + } + if !assert.NoError(err) { + return + } + assert.Equal(tt.want, gotV.Elem().Interface()) + }) + } +} diff --git a/pkg/knowledge_base/resource.go b/pkg/knowledge_base/resource.go index 0e2e94523..48d8d6104 100644 --- a/pkg/knowledge_base/resource.go +++ b/pkg/knowledge_base/resource.go @@ -33,6 +33,7 @@ type ( Direction Direction `json:"direction" yaml:"direction"` // ResourceTypes defines the resource types that the rule should be enforced on. Resource types must be specified if classifications is not specified ResourceTypes []string `json:"resource_types" yaml:"resource_types"` + Resources []string `json:"resources" yaml:"resources"` // Classifications defines the classifications that the rule should be enforced on. Classifications must be specified if resource types is not specified Classifications []string `json:"classifications" yaml:"classifications"` // SetField defines the field on the resource that should be set to the resource that satisfies the rule @@ -46,6 +47,8 @@ type ( Rules []OperationalRule `json:"rules" yaml:"rules"` // UnsatisfiedAction defines what action should be taken if the rule is not satisfied UnsatisfiedAction UnsatisfiedAction `json:"unsatisfied_action" yaml:"unsatisfied_action"` + // If is a template condition used if Enforcement is conditional + If string `json:"if" yaml:"if"` // NoParentDependency is a flag to signal if a sub rule is not supposed to add a dependency on the resource that satisfies the parent rule NoParentDependency bool `json:"no_parent_dependency" yaml:"no_parent_dependency"` @@ -66,7 +69,8 @@ type ( // Fields defines a field that should be set on the resource Field string `json:"field" yaml:"field"` // Value defines the value that should be set on the resource - Value any `json:"value" yaml:"value"` + Value any `json:"value" yaml:"value"` + ValueTemplate string `json:"value_template" yaml:"value_template"` // ZeroValueAllowed defines if the value can be set to the zero value of the field ZeroValueAllowed bool `json:"zero_value_allowed" yaml:"zero_value_allowed"` } diff --git a/pkg/provider/aws/edges/api_deployment-api_integration.yaml b/pkg/provider/aws/edges/api_deployment-api_integration.yaml index a823cb507..45afaa9df 100644 --- a/pkg/provider/aws/edges/api_deployment-api_integration.yaml +++ b/pkg/provider/aws/edges/api_deployment-api_integration.yaml @@ -1,11 +1,11 @@ -source: 'aws:api_deployment:' -destination: 'aws:api_integration:' +source: aws:api_deployment +destination: aws:api_integration direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:api_deployment:' + - resource: aws:api_deployment config: field: Triggers[aws:api_integration:#Name] value: aws:api_integration:#Name diff --git a/pkg/provider/aws/edges/api_deployment-api_method.yaml b/pkg/provider/aws/edges/api_deployment-api_method.yaml index 7c0e65e7d..0ec97cd5e 100644 --- a/pkg/provider/aws/edges/api_deployment-api_method.yaml +++ b/pkg/provider/aws/edges/api_deployment-api_method.yaml @@ -1,11 +1,11 @@ -source: 'aws:api_deployment:' -destination: 'aws:api_method:' +source: aws:api_deployment +destination: aws:api_method direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:api_deployment:' + - resource: aws:api_deployment config: field: Triggers[aws:api_method:#Name] value: aws:api_method:#Name diff --git a/pkg/provider/aws/edges/api_deployment-rest_api.yaml b/pkg/provider/aws/edges/api_deployment-rest_api.yaml index 7cb238141..2745cef0a 100644 --- a/pkg/provider/aws/edges/api_deployment-rest_api.yaml +++ b/pkg/provider/aws/edges/api_deployment-rest_api.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_deployment:' -destination: 'aws:rest_api:' +source: aws:api_deployment +destination: aws:rest_api direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/api_integration-lambda_function.yaml b/pkg/provider/aws/edges/api_integration-lambda_function.yaml index 3e51f081c..cdae8690b 100644 --- a/pkg/provider/aws/edges/api_integration-lambda_function.yaml +++ b/pkg/provider/aws/edges/api_integration-lambda_function.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_integration:' -destination: 'aws:lambda_function:' +source: aws:api_integration +destination: aws:lambda_function direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -9,21 +9,21 @@ expansion: - aws:lambda_permission:-lambdapermission dependencies: - source: aws:lambda_permission:-lambdapermission - destination: 'aws:lambda_function:' + destination: aws:lambda_function - source: aws:api_integration:#RestApi destination: aws:lambda_permission:-lambdapermission configuration: - - resource: 'aws:api_integration:' + - resource: aws:api_integration config: field: Uri value: - ResourceId: 'aws:lambda_function:' + ResourceId: aws:lambda_function Property: lambda_integration_uri - - resource: 'aws:api_integration:' + - resource: aws:api_integration config: field: IntegrationHttpMethod value: POST #lambda integration only invokes with POST - - resource: 'aws:api_integration:' + - resource: aws:api_integration config: field: Type value: AWS_PROXY diff --git a/pkg/provider/aws/edges/api_integration-load_balancer.yaml b/pkg/provider/aws/edges/api_integration-load_balancer.yaml index 941e76a21..ee216e8b9 100644 --- a/pkg/provider/aws/edges/api_integration-load_balancer.yaml +++ b/pkg/provider/aws/edges/api_integration-load_balancer.yaml @@ -1,21 +1,23 @@ -source: 'aws:api_integration:' -destination: 'aws:load_balancer:' +source: aws:api_integration +destination: aws:load_balancer direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: expansion: resources: - - 'aws:vpc_link:' + - aws:vpc_link dependencies: - - source: 'aws:vpc_link:' - destination: 'aws:load_balancer:' - - source: 'aws:api_integration:' - destination: 'aws:vpc_link:' + - source: aws:vpc_link + destination: aws:load_balancer + - source: aws:api_integration + destination: aws:vpc_link configuration: - - resource: 'aws:api_integration:' + - resource: aws:api_integration config: field: Uri - value: - ResourceId: 'aws:load_balancer:' - Property: nlb_uri + value_template: '{{ fieldRef "nlb_uri" .Destination}}' + - resource: aws:api_integration + config: + field: IntegrationHttpMethod + value_template: '{{ fieldRef "HttpMethod" (upstream "aws:api_method" .Source) }}' diff --git a/pkg/provider/aws/edges/api_integration-vpc_link.yaml b/pkg/provider/aws/edges/api_integration-vpc_link.yaml index 26939977b..f4beb0b17 100644 --- a/pkg/provider/aws/edges/api_integration-vpc_link.yaml +++ b/pkg/provider/aws/edges/api_integration-vpc_link.yaml @@ -1,19 +1,19 @@ -source: 'aws:api_integration:' -destination: 'aws:vpc_link:' +source: aws:api_integration +destination: aws:vpc_link direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:api_integration:' + - resource: aws:api_integration config: field: VpcLink - value: 'aws:vpc_link:' - - resource: 'aws:api_integration:' + value: aws:vpc_link + - resource: aws:api_integration config: field: ConnectionType value: VPC_LINK - - resource: 'aws:api_integration:' + - resource: aws:api_integration config: field: Type value: HTTP_PROXY diff --git a/pkg/provider/aws/edges/api_method-api_integration.yaml b/pkg/provider/aws/edges/api_method-api_integration.yaml index e317e28f7..d23602a14 100644 --- a/pkg/provider/aws/edges/api_method-api_integration.yaml +++ b/pkg/provider/aws/edges/api_method-api_integration.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_method:' -destination: 'aws:api_integration:' +source: aws:api_method +destination: aws:api_integration direct_edge_only: false deployment_order_reversed: true deletion_dependent: false @@ -7,4 +7,16 @@ reuse: expansion: dependencies: - source: aws:api_integration:#Resource - destination: 'aws:api_method:' + destination: aws:api_method +configuration: + - resource: aws:api_method + config: + field: RequestParameters + value_template: | + {{ + split (fieldValue "Route" .Destination) "/" | + filterMatch "^:\\w+$" | + mapString ":(.*)" "method.request.path.$1" | + keysToMapWithDefault true | + toJson + }} diff --git a/pkg/provider/aws/edges/api_resource-api_integration.yaml b/pkg/provider/aws/edges/api_resource-api_integration.yaml index f4a869d7f..7c49dbed2 100644 --- a/pkg/provider/aws/edges/api_resource-api_integration.yaml +++ b/pkg/provider/aws/edges/api_resource-api_integration.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_resource:' -destination: 'aws:api_integration:' +source: aws:api_resource +destination: aws:api_integration direct_edge_only: false deployment_order_reversed: true deletion_dependent: false diff --git a/pkg/provider/aws/edges/api_resource-api_method.yaml b/pkg/provider/aws/edges/api_resource-api_method.yaml index 9f7da6134..d0439ea78 100644 --- a/pkg/provider/aws/edges/api_resource-api_method.yaml +++ b/pkg/provider/aws/edges/api_resource-api_method.yaml @@ -1,11 +1,11 @@ -source: 'aws:api_resource:' -destination: 'aws:api_method:' +source: aws:api_resource +destination: aws:api_method direct_edge_only: false deployment_order_reversed: true deletion_dependent: false reuse: configuration: - - resource: 'aws:api_method:' + - resource: aws:api_method config: field: Resource - value: 'aws:api_resource:' + value: aws:api_resource diff --git a/pkg/provider/aws/edges/api_resource-api_resource.yaml b/pkg/provider/aws/edges/api_resource-api_resource.yaml index 634aa539f..f7ea175a9 100644 --- a/pkg/provider/aws/edges/api_resource-api_resource.yaml +++ b/pkg/provider/aws/edges/api_resource-api_resource.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_resource:' -destination: 'aws:api_resource:' +source: aws:api_resource +destination: aws:api_resource direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/api_stage-api_deployment.yaml b/pkg/provider/aws/edges/api_stage-api_deployment.yaml index 7fe926459..6c17d7d33 100644 --- a/pkg/provider/aws/edges/api_stage-api_deployment.yaml +++ b/pkg/provider/aws/edges/api_stage-api_deployment.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_stage:' -destination: 'aws:api_deployment:' +source: aws:api_stage +destination: aws:api_deployment direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/api_stage-rest_api.yaml b/pkg/provider/aws/edges/api_stage-rest_api.yaml index aefbbc26b..578f862dd 100644 --- a/pkg/provider/aws/edges/api_stage-rest_api.yaml +++ b/pkg/provider/aws/edges/api_stage-rest_api.yaml @@ -1,5 +1,5 @@ -source: 'aws:api_stage:' -destination: 'aws:rest_api:' +source: aws:api_stage +destination: aws:rest_api direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/app_runner_service-dynamodb_table.yaml b/pkg/provider/aws/edges/app_runner_service-dynamodb_table.yaml index 0a813bc74..80d0c97cd 100644 --- a/pkg/provider/aws/edges/app_runner_service-dynamodb_table.yaml +++ b/pkg/provider/aws/edges/app_runner_service-dynamodb_table.yaml @@ -1,5 +1,5 @@ -source: 'aws:app_runner_service:' -destination: 'aws:dynamodb_table:' +source: aws:app_runner_service +destination: aws:dynamodb_table direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:app_runner_service:#InstanceRole - destination: 'aws:dynamodb_table:' + destination: aws:dynamodb_table diff --git a/pkg/provider/aws/edges/app_runner_service-ecr_image.yaml b/pkg/provider/aws/edges/app_runner_service-ecr_image.yaml index c30de92dd..01eb55d75 100644 --- a/pkg/provider/aws/edges/app_runner_service-ecr_image.yaml +++ b/pkg/provider/aws/edges/app_runner_service-ecr_image.yaml @@ -1,5 +1,5 @@ -source: 'aws:app_runner_service:' -destination: 'aws:ecr_image:' +source: aws:app_runner_service +destination: aws:ecr_image direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/app_runner_service-s3_bucket.yaml b/pkg/provider/aws/edges/app_runner_service-s3_bucket.yaml index 6c231bb9c..dbe0e4bb7 100644 --- a/pkg/provider/aws/edges/app_runner_service-s3_bucket.yaml +++ b/pkg/provider/aws/edges/app_runner_service-s3_bucket.yaml @@ -1,5 +1,5 @@ -source: 'aws:app_runner_service:' -destination: 'aws:s3_bucket:' +source: aws:app_runner_service +destination: aws:s3_bucket direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:app_runner_service:#InstanceRole - destination: 'aws:s3_bucket:' + destination: aws:s3_bucket diff --git a/pkg/provider/aws/edges/app_runner_service-secret.yaml b/pkg/provider/aws/edges/app_runner_service-secret.yaml index 992b7d2dc..b6bdb6f1e 100644 --- a/pkg/provider/aws/edges/app_runner_service-secret.yaml +++ b/pkg/provider/aws/edges/app_runner_service-secret.yaml @@ -1,5 +1,5 @@ -source: 'aws:app_runner_service:' -destination: 'aws:secret:' +source: aws:app_runner_service +destination: aws:secret direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:app_runner_service:#InstanceRole - destination: 'aws:secret:' + destination: aws:secret diff --git a/pkg/provider/aws/edges/app_runner_service-ses_email_identity.yaml b/pkg/provider/aws/edges/app_runner_service-ses_email_identity.yaml index 4b3707359..d70d91b93 100644 --- a/pkg/provider/aws/edges/app_runner_service-ses_email_identity.yaml +++ b/pkg/provider/aws/edges/app_runner_service-ses_email_identity.yaml @@ -1,5 +1,5 @@ -source: 'aws:app_runner_service:' -destination: 'aws:ses_email_identity:' +source: aws:app_runner_service +destination: aws:ses_email_identity direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:app_runner_service:#InstanceRole - destination: 'aws:ses_email_identity:' + destination: aws:ses_email_identity diff --git a/pkg/provider/aws/edges/ec2_instance-ami.yaml b/pkg/provider/aws/edges/ec2_instance-ami.yaml index b77f19fae..7c7eae363 100644 --- a/pkg/provider/aws/edges/ec2_instance-ami.yaml +++ b/pkg/provider/aws/edges/ec2_instance-ami.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:ami:' +source: aws:ec2_instance +destination: aws:ami direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-dynamodb_table.yaml b/pkg/provider/aws/edges/ec2_instance-dynamodb_table.yaml index 0f5efeb8a..d3bb1bd01 100644 --- a/pkg/provider/aws/edges/ec2_instance-dynamodb_table.yaml +++ b/pkg/provider/aws/edges/ec2_instance-dynamodb_table.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:dynamodb_table:' +source: aws:ec2_instance +destination: aws:dynamodb_table direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: 'aws:ec2_instance:#InstanceProfile.Role' - destination: 'aws:dynamodb_table:' + destination: aws:dynamodb_table diff --git a/pkg/provider/aws/edges/ec2_instance-efs_mount_target.yaml b/pkg/provider/aws/edges/ec2_instance-efs_mount_target.yaml index eb455e622..ddba63ec0 100644 --- a/pkg/provider/aws/edges/ec2_instance-efs_mount_target.yaml +++ b/pkg/provider/aws/edges/ec2_instance-efs_mount_target.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:efs_mount_target:' +source: aws:ec2_instance +destination: aws:efs_mount_target direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: 'aws:ec2_instance:#InstanceProfile.Role' - destination: 'aws:efs_mount_target:' + destination: aws:efs_mount_target diff --git a/pkg/provider/aws/edges/ec2_instance-elasticache_cluster.yaml b/pkg/provider/aws/edges/ec2_instance-elasticache_cluster.yaml index 01799b111..3f0ef18a6 100644 --- a/pkg/provider/aws/edges/ec2_instance-elasticache_cluster.yaml +++ b/pkg/provider/aws/edges/ec2_instance-elasticache_cluster.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:elasticache_cluster:' +source: aws:ec2_instance +destination: aws:elasticache_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-instance_profile.yaml b/pkg/provider/aws/edges/ec2_instance-instance_profile.yaml index c342c8976..1a429b66e 100644 --- a/pkg/provider/aws/edges/ec2_instance-instance_profile.yaml +++ b/pkg/provider/aws/edges/ec2_instance-instance_profile.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:iam_instance_profile:' +source: aws:ec2_instance +destination: aws:iam_instance_profile direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-rds_instance.yaml b/pkg/provider/aws/edges/ec2_instance-rds_instance.yaml index 59b385fc4..fbd32e752 100644 --- a/pkg/provider/aws/edges/ec2_instance-rds_instance.yaml +++ b/pkg/provider/aws/edges/ec2_instance-rds_instance.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:rds_instance:' +source: aws:ec2_instance +destination: aws:rds_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-rds_proxy.yaml b/pkg/provider/aws/edges/ec2_instance-rds_proxy.yaml index ae8b73208..bec3af06a 100644 --- a/pkg/provider/aws/edges/ec2_instance-rds_proxy.yaml +++ b/pkg/provider/aws/edges/ec2_instance-rds_proxy.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:rds_proxy:' +source: aws:ec2_instance +destination: aws:rds_proxy direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-s3_bucket.yaml b/pkg/provider/aws/edges/ec2_instance-s3_bucket.yaml index 2a4c82672..3e26a1594 100644 --- a/pkg/provider/aws/edges/ec2_instance-s3_bucket.yaml +++ b/pkg/provider/aws/edges/ec2_instance-s3_bucket.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:s3_bucket:' +source: aws:ec2_instance +destination: aws:s3_bucket direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: 'aws:ec2_instance:#InstanceProfile.Role' - destination: 'aws:s3_bucket:' + destination: aws:s3_bucket diff --git a/pkg/provider/aws/edges/ec2_instance-secret.yaml b/pkg/provider/aws/edges/ec2_instance-secret.yaml index 04d1922d2..9b84aeb6c 100644 --- a/pkg/provider/aws/edges/ec2_instance-secret.yaml +++ b/pkg/provider/aws/edges/ec2_instance-secret.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:secret:' +source: aws:ec2_instance +destination: aws:secret direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: 'aws:ec2_instance:#InstanceProfile.Role' - destination: 'aws:secret:' + destination: aws:secret diff --git a/pkg/provider/aws/edges/ec2_instance-security_group.yaml b/pkg/provider/aws/edges/ec2_instance-security_group.yaml index d59805fbd..a263d89f7 100644 --- a/pkg/provider/aws/edges/ec2_instance-security_group.yaml +++ b/pkg/provider/aws/edges/ec2_instance-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:security_group:' +source: aws:ec2_instance +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-sns_topic.yaml b/pkg/provider/aws/edges/ec2_instance-sns_topic.yaml index d07290494..c0bafe13a 100644 --- a/pkg/provider/aws/edges/ec2_instance-sns_topic.yaml +++ b/pkg/provider/aws/edges/ec2_instance-sns_topic.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:sns_topic:' +source: aws:ec2_instance +destination: aws:sns_topic direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-sqs_queue.yaml b/pkg/provider/aws/edges/ec2_instance-sqs_queue.yaml index 5c519d078..1e9afda4f 100644 --- a/pkg/provider/aws/edges/ec2_instance-sqs_queue.yaml +++ b/pkg/provider/aws/edges/ec2_instance-sqs_queue.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:sqs_queue:' +source: aws:ec2_instance +destination: aws:sqs_queue direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-subnet_private.yaml b/pkg/provider/aws/edges/ec2_instance-subnet_private.yaml index e24b4d83e..2a3c4b1c2 100644 --- a/pkg/provider/aws/edges/ec2_instance-subnet_private.yaml +++ b/pkg/provider/aws/edges/ec2_instance-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:subnet_private:' +source: aws:ec2_instance +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ec2_instance-subnet_public.yaml b/pkg/provider/aws/edges/ec2_instance-subnet_public.yaml index 556d6c750..d6c9ddbb0 100644 --- a/pkg/provider/aws/edges/ec2_instance-subnet_public.yaml +++ b/pkg/provider/aws/edges/ec2_instance-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:ec2_instance:' -destination: 'aws:subnet_public:' +source: aws:ec2_instance +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecr_image-docker_image.yaml b/pkg/provider/aws/edges/ecr_image-docker_image.yaml index 73ce80ba8..5b0ad2129 100644 --- a/pkg/provider/aws/edges/ecr_image-docker_image.yaml +++ b/pkg/provider/aws/edges/ecr_image-docker_image.yaml @@ -1,15 +1,15 @@ -source: 'aws:ecr_image:' -destination: 'docker:image:' +source: aws:ecr_image +destination: docker:image direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'docker:image:' + - resource: docker:image config: field: CreatesDockerfile value: true - - resource: 'aws:ecr_image:' + - resource: aws:ecr_image config: field: BaseImage value: docker:image:#Name diff --git a/pkg/provider/aws/edges/ecr_image-ecr_repo.yaml b/pkg/provider/aws/edges/ecr_image-ecr_repo.yaml index ccb2816c7..1f5186c9c 100644 --- a/pkg/provider/aws/edges/ecr_image-ecr_repo.yaml +++ b/pkg/provider/aws/edges/ecr_image-ecr_repo.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecr_image:' -destination: 'aws:ecr_repo:' +source: aws:ecr_image +destination: aws:ecr_repo direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-dynamodb_table.yaml b/pkg/provider/aws/edges/ecs_service-dynamodb_table.yaml index 020497b50..23f4f7bad 100644 --- a/pkg/provider/aws/edges/ecs_service-dynamodb_table.yaml +++ b/pkg/provider/aws/edges/ecs_service-dynamodb_table.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:dynamodb_table:' +source: aws:ecs_service +destination: aws:dynamodb_table direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:ecs_service:#TaskDefinition.ExecutionRole - destination: 'aws:dynamodb_table:' + destination: aws:dynamodb_table diff --git a/pkg/provider/aws/edges/ecs_service-ecs_cluster.yaml b/pkg/provider/aws/edges/ecs_service-ecs_cluster.yaml index 3b42e3261..818118444 100644 --- a/pkg/provider/aws/edges/ecs_service-ecs_cluster.yaml +++ b/pkg/provider/aws/edges/ecs_service-ecs_cluster.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:ecs_cluster:' +source: aws:ecs_service +destination: aws:ecs_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-ecs_task_definition.yaml b/pkg/provider/aws/edges/ecs_service-ecs_task_definition.yaml index cd56a9b31..56e0b1f7f 100644 --- a/pkg/provider/aws/edges/ecs_service-ecs_task_definition.yaml +++ b/pkg/provider/aws/edges/ecs_service-ecs_task_definition.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:ecs_task_definition:' +source: aws:ecs_service +destination: aws:ecs_task_definition direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-elasticache_cluster.yaml b/pkg/provider/aws/edges/ecs_service-elasticache_cluster.yaml index 56c9d2ef8..167cdc4e0 100644 --- a/pkg/provider/aws/edges/ecs_service-elasticache_cluster.yaml +++ b/pkg/provider/aws/edges/ecs_service-elasticache_cluster.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:elasticache_cluster:' +source: aws:ecs_service +destination: aws:elasticache_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-rds_instance.yaml b/pkg/provider/aws/edges/ecs_service-rds_instance.yaml index 88fa3c600..fc8f5d082 100644 --- a/pkg/provider/aws/edges/ecs_service-rds_instance.yaml +++ b/pkg/provider/aws/edges/ecs_service-rds_instance.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:rds_instance:' +source: aws:ecs_service +destination: aws:rds_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-rds_proxy.yaml b/pkg/provider/aws/edges/ecs_service-rds_proxy.yaml index 59e8d1e4e..6472421f7 100644 --- a/pkg/provider/aws/edges/ecs_service-rds_proxy.yaml +++ b/pkg/provider/aws/edges/ecs_service-rds_proxy.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:rds_proxy:' +source: aws:ecs_service +destination: aws:rds_proxy direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-s3_bucket.yaml b/pkg/provider/aws/edges/ecs_service-s3_bucket.yaml index d8a25e90c..b4af36782 100644 --- a/pkg/provider/aws/edges/ecs_service-s3_bucket.yaml +++ b/pkg/provider/aws/edges/ecs_service-s3_bucket.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:s3_bucket:' +source: aws:ecs_service +destination: aws:s3_bucket direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:ecs_service:#TaskDefinition.ExecutionRole - destination: 'aws:s3_bucket:' + destination: aws:s3_bucket diff --git a/pkg/provider/aws/edges/ecs_service-secret.yaml b/pkg/provider/aws/edges/ecs_service-secret.yaml index 90dbd8e6a..8fd019037 100644 --- a/pkg/provider/aws/edges/ecs_service-secret.yaml +++ b/pkg/provider/aws/edges/ecs_service-secret.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:secret:' +source: aws:ecs_service +destination: aws:secret direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:ecs_service:#TaskDefinition.ExecutionRole - destination: 'aws:secret:' + destination: aws:secret diff --git a/pkg/provider/aws/edges/ecs_service-security_group.yaml b/pkg/provider/aws/edges/ecs_service-security_group.yaml index deaddc969..cf19133ad 100644 --- a/pkg/provider/aws/edges/ecs_service-security_group.yaml +++ b/pkg/provider/aws/edges/ecs_service-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:security_group:' +source: aws:ecs_service +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-sns_topic.yaml b/pkg/provider/aws/edges/ecs_service-sns_topic.yaml index 7f5197283..f02c35129 100644 --- a/pkg/provider/aws/edges/ecs_service-sns_topic.yaml +++ b/pkg/provider/aws/edges/ecs_service-sns_topic.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:sns_topic:' +source: aws:ecs_service +destination: aws:sns_topic direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-sqs_queue.yaml b/pkg/provider/aws/edges/ecs_service-sqs_queue.yaml index 5c68b999a..0c45cfa52 100644 --- a/pkg/provider/aws/edges/ecs_service-sqs_queue.yaml +++ b/pkg/provider/aws/edges/ecs_service-sqs_queue.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:sqs_queue:' +source: aws:ecs_service +destination: aws:sqs_queue direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-subnet_private.yaml b/pkg/provider/aws/edges/ecs_service-subnet_private.yaml index 661bf8c2c..d7892789b 100644 --- a/pkg/provider/aws/edges/ecs_service-subnet_private.yaml +++ b/pkg/provider/aws/edges/ecs_service-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:subnet_private:' +source: aws:ecs_service +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-subnet_public.yaml b/pkg/provider/aws/edges/ecs_service-subnet_public.yaml index 0ec6c38d2..961c20772 100644 --- a/pkg/provider/aws/edges/ecs_service-subnet_public.yaml +++ b/pkg/provider/aws/edges/ecs_service-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:subnet_public:' +source: aws:ecs_service +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_service-vpc.yaml b/pkg/provider/aws/edges/ecs_service-vpc.yaml index d5c3f6cbb..2fbfea5d4 100644 --- a/pkg/provider/aws/edges/ecs_service-vpc.yaml +++ b/pkg/provider/aws/edges/ecs_service-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_service:' -destination: 'aws:vpc:' +source: aws:ecs_service +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_task_definition-docker_image.yaml b/pkg/provider/aws/edges/ecs_task_definition-docker_image.yaml index d838eb148..f11236ac3 100644 --- a/pkg/provider/aws/edges/ecs_task_definition-docker_image.yaml +++ b/pkg/provider/aws/edges/ecs_task_definition-docker_image.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_task_definition:' -destination: 'docker:image:' +source: aws:ecs_task_definition +destination: docker:image direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_task_definition-ecr_image.yaml b/pkg/provider/aws/edges/ecs_task_definition-ecr_image.yaml index 07d32fee9..128a16817 100644 --- a/pkg/provider/aws/edges/ecs_task_definition-ecr_image.yaml +++ b/pkg/provider/aws/edges/ecs_task_definition-ecr_image.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_task_definition:' -destination: 'aws:ecr_image:' +source: aws:ecs_task_definition +destination: aws:ecr_image direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_task_definition-log_group.yaml b/pkg/provider/aws/edges/ecs_task_definition-log_group.yaml index dbf79a2e3..dbeda6c0c 100644 --- a/pkg/provider/aws/edges/ecs_task_definition-log_group.yaml +++ b/pkg/provider/aws/edges/ecs_task_definition-log_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_task_definition:' -destination: 'aws:log_group:' +source: aws:ecs_task_definition +destination: aws:log_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/ecs_task_definition-region.yaml b/pkg/provider/aws/edges/ecs_task_definition-region.yaml index 00bdd4eb4..a6b4d739f 100644 --- a/pkg/provider/aws/edges/ecs_task_definition-region.yaml +++ b/pkg/provider/aws/edges/ecs_task_definition-region.yaml @@ -1,5 +1,5 @@ -source: 'aws:ecs_task_definition:' -destination: 'aws:region:' +source: aws:ecs_task_definition +destination: aws:region direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_access_point-efs_file_system.yaml b/pkg/provider/aws/edges/efs_access_point-efs_file_system.yaml index 6ea9364a7..487ee5931 100644 --- a/pkg/provider/aws/edges/efs_access_point-efs_file_system.yaml +++ b/pkg/provider/aws/edges/efs_access_point-efs_file_system.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_access_point:' -destination: 'aws:efs_file_system:' +source: aws:efs_access_point +destination: aws:efs_file_system direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_access_point-efs_mount_target.yaml b/pkg/provider/aws/edges/efs_access_point-efs_mount_target.yaml index a6f771811..0de3be301 100644 --- a/pkg/provider/aws/edges/efs_access_point-efs_mount_target.yaml +++ b/pkg/provider/aws/edges/efs_access_point-efs_mount_target.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_access_point:' -destination: 'aws:efs_mount_target:' +source: aws:efs_access_point +destination: aws:efs_mount_target direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_file_system-availability_zones.yaml b/pkg/provider/aws/edges/efs_file_system-availability_zones.yaml index db50b1a46..121b1d8bf 100644 --- a/pkg/provider/aws/edges/efs_file_system-availability_zones.yaml +++ b/pkg/provider/aws/edges/efs_file_system-availability_zones.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_file_system:' -destination: 'aws:availability_zones:' +source: aws:efs_file_system +destination: aws:availability_zones direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_file_system-kms_key.yaml b/pkg/provider/aws/edges/efs_file_system-kms_key.yaml index a0be12f42..18a5be345 100644 --- a/pkg/provider/aws/edges/efs_file_system-kms_key.yaml +++ b/pkg/provider/aws/edges/efs_file_system-kms_key.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_file_system:' -destination: 'aws:kms_key:' +source: aws:efs_file_system +destination: aws:kms_key direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_mount_target-efs_file_system.yaml b/pkg/provider/aws/edges/efs_mount_target-efs_file_system.yaml index 0d0982b4a..372565497 100644 --- a/pkg/provider/aws/edges/efs_mount_target-efs_file_system.yaml +++ b/pkg/provider/aws/edges/efs_mount_target-efs_file_system.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_mount_target:' -destination: 'aws:efs_file_system:' +source: aws:efs_mount_target +destination: aws:efs_file_system direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_mount_target-security_group.yaml b/pkg/provider/aws/edges/efs_mount_target-security_group.yaml index 827114aa2..991308898 100644 --- a/pkg/provider/aws/edges/efs_mount_target-security_group.yaml +++ b/pkg/provider/aws/edges/efs_mount_target-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_mount_target:' -destination: 'aws:security_group:' +source: aws:efs_mount_target +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_mount_target-subnet_private.yaml b/pkg/provider/aws/edges/efs_mount_target-subnet_private.yaml index e815a51a2..99cdd4ddc 100644 --- a/pkg/provider/aws/edges/efs_mount_target-subnet_private.yaml +++ b/pkg/provider/aws/edges/efs_mount_target-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_mount_target:' -destination: 'aws:subnet_private:' +source: aws:efs_mount_target +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/efs_mount_target-subnet_public.yaml b/pkg/provider/aws/edges/efs_mount_target-subnet_public.yaml index a11d64cb0..14e25ff32 100644 --- a/pkg/provider/aws/edges/efs_mount_target-subnet_public.yaml +++ b/pkg/provider/aws/edges/efs_mount_target-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:efs_mount_target:' -destination: 'aws:subnet_public:' +source: aws:efs_mount_target +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_add_on-eks_cluster.yaml b/pkg/provider/aws/edges/eks_add_on-eks_cluster.yaml index 4f6d70fc0..06343d715 100644 --- a/pkg/provider/aws/edges/eks_add_on-eks_cluster.yaml +++ b/pkg/provider/aws/edges/eks_add_on-eks_cluster.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_addon:' -destination: 'aws:eks_cluster:' +source: aws:eks_addon +destination: aws:eks_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_cluster-security_group.yaml b/pkg/provider/aws/edges/eks_cluster-security_group.yaml index 14d093323..a33fbabd7 100644 --- a/pkg/provider/aws/edges/eks_cluster-security_group.yaml +++ b/pkg/provider/aws/edges/eks_cluster-security_group.yaml @@ -1,11 +1,11 @@ -source: 'aws:eks_cluster:' -destination: 'aws:security_group:' +source: aws:eks_cluster +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:security_group:' + - resource: aws:security_group config: field: IngressRules value: diff --git a/pkg/provider/aws/edges/eks_cluster-subnet_private.yaml b/pkg/provider/aws/edges/eks_cluster-subnet_private.yaml index 445bba1b2..1281c017e 100644 --- a/pkg/provider/aws/edges/eks_cluster-subnet_private.yaml +++ b/pkg/provider/aws/edges/eks_cluster-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_cluster:' -destination: 'aws:subnet_private:' +source: aws:eks_cluster +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_cluster-subnet_public.yaml b/pkg/provider/aws/edges/eks_cluster-subnet_public.yaml index 180123011..51be4fcf5 100644 --- a/pkg/provider/aws/edges/eks_cluster-subnet_public.yaml +++ b/pkg/provider/aws/edges/eks_cluster-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_cluster:' -destination: 'aws:subnet_public:' +source: aws:eks_cluster +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_cluster-vpc.yaml b/pkg/provider/aws/edges/eks_cluster-vpc.yaml index 9002421b6..befa2c4ef 100644 --- a/pkg/provider/aws/edges/eks_cluster-vpc.yaml +++ b/pkg/provider/aws/edges/eks_cluster-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_cluster:' -destination: 'aws:vpc:' +source: aws:eks_cluster +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_fargate_profile-subnet_private.yaml b/pkg/provider/aws/edges/eks_fargate_profile-subnet_private.yaml index 71ed71909..7f0a23b3a 100644 --- a/pkg/provider/aws/edges/eks_fargate_profile-subnet_private.yaml +++ b/pkg/provider/aws/edges/eks_fargate_profile-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_fargate_profile:' -destination: 'aws:subnet_private:' +source: aws:eks_fargate_profile +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_fargate_profile-subnet_public.yaml b/pkg/provider/aws/edges/eks_fargate_profile-subnet_public.yaml index 069d43857..1de8ad86a 100644 --- a/pkg/provider/aws/edges/eks_fargate_profile-subnet_public.yaml +++ b/pkg/provider/aws/edges/eks_fargate_profile-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_fargate_profile:' -destination: 'aws:subnet_public:' +source: aws:eks_fargate_profile +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_node_group-subnet_private.yaml b/pkg/provider/aws/edges/eks_node_group-subnet_private.yaml index a7fb2589e..4c7ecf73a 100644 --- a/pkg/provider/aws/edges/eks_node_group-subnet_private.yaml +++ b/pkg/provider/aws/edges/eks_node_group-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_node_group:' -destination: 'aws:subnet_private:' +source: aws:eks_node_group +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/eks_node_group-subnet_public.yaml b/pkg/provider/aws/edges/eks_node_group-subnet_public.yaml index 83066610b..ec5095b0c 100644 --- a/pkg/provider/aws/edges/eks_node_group-subnet_public.yaml +++ b/pkg/provider/aws/edges/eks_node_group-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:eks_node_group:' -destination: 'aws:subnet_public:' +source: aws:eks_node_group +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/elasticache_cluster-elasticache_subnetgroup.yaml b/pkg/provider/aws/edges/elasticache_cluster-elasticache_subnetgroup.yaml index 8286049a0..4be6056c3 100644 --- a/pkg/provider/aws/edges/elasticache_cluster-elasticache_subnetgroup.yaml +++ b/pkg/provider/aws/edges/elasticache_cluster-elasticache_subnetgroup.yaml @@ -1,5 +1,5 @@ -source: 'aws:elasticache_cluster:' -destination: 'aws:elasticache_subnetgroup:' +source: aws:elasticache_cluster +destination: aws:elasticache_subnetgroup direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/elasticache_cluster-log_group.yaml b/pkg/provider/aws/edges/elasticache_cluster-log_group.yaml index 504ea2ddb..cda6d445c 100644 --- a/pkg/provider/aws/edges/elasticache_cluster-log_group.yaml +++ b/pkg/provider/aws/edges/elasticache_cluster-log_group.yaml @@ -1,11 +1,11 @@ -source: 'aws:elasticache_cluster:' -destination: 'aws:log_group:' +source: aws:elasticache_cluster +destination: aws:log_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:log_group:' + - resource: aws:log_group config: field: RetentionInDays value: 5 diff --git a/pkg/provider/aws/edges/elasticache_cluster-security_group.yaml b/pkg/provider/aws/edges/elasticache_cluster-security_group.yaml index 8ac524bb7..dc907d2ac 100644 --- a/pkg/provider/aws/edges/elasticache_cluster-security_group.yaml +++ b/pkg/provider/aws/edges/elasticache_cluster-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:elasticache_cluster:' -destination: 'aws:security_group:' +source: aws:elasticache_cluster +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/elasticache_subnetgroup-subnet_private.yaml b/pkg/provider/aws/edges/elasticache_subnetgroup-subnet_private.yaml index 4f3c7da63..f976e33db 100644 --- a/pkg/provider/aws/edges/elasticache_subnetgroup-subnet_private.yaml +++ b/pkg/provider/aws/edges/elasticache_subnetgroup-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:elasticache_subnetgroup:' -destination: 'aws:subnet_private:' +source: aws:elasticache_subnetgroup +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/iam_oidc_provider-eks_cluster.yaml b/pkg/provider/aws/edges/iam_oidc_provider-eks_cluster.yaml index c43db54e3..2c3233bba 100644 --- a/pkg/provider/aws/edges/iam_oidc_provider-eks_cluster.yaml +++ b/pkg/provider/aws/edges/iam_oidc_provider-eks_cluster.yaml @@ -1,11 +1,11 @@ -source: 'aws:iam_oidc_provider:' -destination: 'aws:eks_cluster:' +source: aws:iam_oidc_provider +destination: aws:eks_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:iam_oidc_provider:' + - resource: aws:iam_oidc_provider config: field: ClientIdLists value: diff --git a/pkg/provider/aws/edges/iam_oidc_provider-region.yaml b/pkg/provider/aws/edges/iam_oidc_provider-region.yaml index c3ba008b0..8c9578bdb 100644 --- a/pkg/provider/aws/edges/iam_oidc_provider-region.yaml +++ b/pkg/provider/aws/edges/iam_oidc_provider-region.yaml @@ -1,5 +1,5 @@ -source: 'aws:iam_oidc_provider:' -destination: 'aws:region:' +source: aws:iam_oidc_provider +destination: aws:region direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/internet_gateway-vpc.yaml b/pkg/provider/aws/edges/internet_gateway-vpc.yaml index d117dff1e..2f2e7e1ba 100644 --- a/pkg/provider/aws/edges/internet_gateway-vpc.yaml +++ b/pkg/provider/aws/edges/internet_gateway-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:internet_gateway:' -destination: 'aws:vpc:' +source: aws:internet_gateway +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_deployment-lambda_function.yaml b/pkg/provider/aws/edges/kubernetes_deployment-lambda_function.yaml index 5d47f31bf..329add2a2 100644 --- a/pkg/provider/aws/edges/kubernetes_deployment-lambda_function.yaml +++ b/pkg/provider/aws/edges/kubernetes_deployment-lambda_function.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:deployment:' -destination: 'aws:lambda_function:' +source: kubernetes:deployment +destination: aws:lambda_function direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_deployment-sns_topic.yaml b/pkg/provider/aws/edges/kubernetes_deployment-sns_topic.yaml index 6a142e775..a23529daa 100644 --- a/pkg/provider/aws/edges/kubernetes_deployment-sns_topic.yaml +++ b/pkg/provider/aws/edges/kubernetes_deployment-sns_topic.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:deployment:' -destination: 'aws:sns_topic:' +source: kubernetes:deployment +destination: aws:sns_topic direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_deployment-sqs_queue.yaml b/pkg/provider/aws/edges/kubernetes_deployment-sqs_queue.yaml index aa377957b..b19db66ce 100644 --- a/pkg/provider/aws/edges/kubernetes_deployment-sqs_queue.yaml +++ b/pkg/provider/aws/edges/kubernetes_deployment-sqs_queue.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:deployment:' -destination: 'aws:sqs_queue:' +source: kubernetes:deployment +destination: aws:sqs_queue direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_kubeconfig-eks_cluster.yaml b/pkg/provider/aws/edges/kubernetes_kubeconfig-eks_cluster.yaml index 244f50252..8026b4bb8 100644 --- a/pkg/provider/aws/edges/kubernetes_kubeconfig-eks_cluster.yaml +++ b/pkg/provider/aws/edges/kubernetes_kubeconfig-eks_cluster.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:kubeconfig:' -destination: 'aws:eks_cluster:' +source: kubernetes:kubeconfig +destination: aws:eks_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_pod-lambda_function.yaml b/pkg/provider/aws/edges/kubernetes_pod-lambda_function.yaml index 2cb48a362..91897a9ba 100644 --- a/pkg/provider/aws/edges/kubernetes_pod-lambda_function.yaml +++ b/pkg/provider/aws/edges/kubernetes_pod-lambda_function.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:pod:' -destination: 'aws:lambda_function:' +source: kubernetes:pod +destination: aws:lambda_function direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_pod-sns_topic.yaml b/pkg/provider/aws/edges/kubernetes_pod-sns_topic.yaml index 8ab0ed7c6..0bb51a54e 100644 --- a/pkg/provider/aws/edges/kubernetes_pod-sns_topic.yaml +++ b/pkg/provider/aws/edges/kubernetes_pod-sns_topic.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:pod:' -destination: 'aws:sns_topic:' +source: kubernetes:pod +destination: aws:sns_topic direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/kubernetes_pod-sqs_queue.yaml b/pkg/provider/aws/edges/kubernetes_pod-sqs_queue.yaml index 1952b5b17..84cbf8b6c 100644 --- a/pkg/provider/aws/edges/kubernetes_pod-sqs_queue.yaml +++ b/pkg/provider/aws/edges/kubernetes_pod-sqs_queue.yaml @@ -1,5 +1,5 @@ -source: 'kubernetes:pod:' -destination: 'aws:sqs_queue:' +source: kubernetes:pod +destination: aws:sqs_queue direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_function-dynamodb_table.yaml b/pkg/provider/aws/edges/lambda_function-dynamodb_table.yaml index b74a332c3..80f189b65 100644 --- a/pkg/provider/aws/edges/lambda_function-dynamodb_table.yaml +++ b/pkg/provider/aws/edges/lambda_function-dynamodb_table.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:dynamodb_table:' +source: aws:lambda_function +destination: aws:dynamodb_table direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:lambda_function:#Role - destination: 'aws:dynamodb_table:' + destination: aws:dynamodb_table diff --git a/pkg/provider/aws/edges/lambda_function-ecr_image.yaml b/pkg/provider/aws/edges/lambda_function-ecr_image.yaml index 203c73e37..f845526e2 100644 --- a/pkg/provider/aws/edges/lambda_function-ecr_image.yaml +++ b/pkg/provider/aws/edges/lambda_function-ecr_image.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:ecr_image:' +source: aws:lambda_function +destination: aws:ecr_image direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_function-elasticache_cluster.yaml b/pkg/provider/aws/edges/lambda_function-elasticache_cluster.yaml index 8c96a6799..b83cf3ba0 100644 --- a/pkg/provider/aws/edges/lambda_function-elasticache_cluster.yaml +++ b/pkg/provider/aws/edges/lambda_function-elasticache_cluster.yaml @@ -1,11 +1,11 @@ -source: 'aws:lambda_function:' -destination: 'aws:elasticache_cluster:' +source: aws:lambda_function +destination: aws:elasticache_cluster direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: operational_rules: - - resource: 'aws:lambda_function:' + - resource: aws:lambda_function rule: enforcement: exactly_one direction: downstream diff --git a/pkg/provider/aws/edges/lambda_function-lambda_function.yaml b/pkg/provider/aws/edges/lambda_function-lambda_function.yaml index 3aff51a36..6c594e286 100644 --- a/pkg/provider/aws/edges/lambda_function-lambda_function.yaml +++ b/pkg/provider/aws/edges/lambda_function-lambda_function.yaml @@ -7,11 +7,11 @@ reuse: expansion: resources: - aws:iam_policy:-InvocationPolicy - - 'aws:role_policy_attachment:' + - aws:role_policy_attachment dependencies: - source: aws:iam_policy:-InvocationPolicy destination: aws:lambda_function:dst - - source: 'aws:role_policy_attachment:' + - source: aws:role_policy_attachment destination: aws:iam_policy:-InvocationPolicy - - source: 'aws:role_policy_attachment:' + - source: aws:role_policy_attachment destination: aws:lambda_function:src#Role diff --git a/pkg/provider/aws/edges/lambda_function-log_group.yaml b/pkg/provider/aws/edges/lambda_function-log_group.yaml index 6bf31a60a..dfa898e3e 100644 --- a/pkg/provider/aws/edges/lambda_function-log_group.yaml +++ b/pkg/provider/aws/edges/lambda_function-log_group.yaml @@ -1,11 +1,11 @@ -source: 'aws:lambda_function:' -destination: 'aws:log_group:' +source: aws:lambda_function +destination: aws:log_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:log_group:' + - resource: aws:log_group config: field: RetentionInDays value: 5 diff --git a/pkg/provider/aws/edges/lambda_function-rds_instance.yaml b/pkg/provider/aws/edges/lambda_function-rds_instance.yaml index d7b6a3d9d..6c5aaade5 100644 --- a/pkg/provider/aws/edges/lambda_function-rds_instance.yaml +++ b/pkg/provider/aws/edges/lambda_function-rds_instance.yaml @@ -1,11 +1,11 @@ -source: 'aws:lambda_function:' -destination: 'aws:rds_instance:' +source: aws:lambda_function +destination: aws:rds_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: operational_rules: - - resource: 'aws:lambda_function:' + - resource: aws:lambda_function rule: enforcement: exactly_one direction: downstream diff --git a/pkg/provider/aws/edges/lambda_function-rds_proxy.yaml b/pkg/provider/aws/edges/lambda_function-rds_proxy.yaml index 4098e3a05..b42ec2e6e 100644 --- a/pkg/provider/aws/edges/lambda_function-rds_proxy.yaml +++ b/pkg/provider/aws/edges/lambda_function-rds_proxy.yaml @@ -1,11 +1,11 @@ -source: 'aws:lambda_function:' -destination: 'aws:rds_proxy:' +source: aws:lambda_function +destination: aws:rds_proxy direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: operational_rules: - - resource: 'aws:lambda_function:' + - resource: aws:lambda_function rule: enforcement: exactly_one direction: downstream diff --git a/pkg/provider/aws/edges/lambda_function-s3_bucket.yaml b/pkg/provider/aws/edges/lambda_function-s3_bucket.yaml index 0572d6106..352770044 100644 --- a/pkg/provider/aws/edges/lambda_function-s3_bucket.yaml +++ b/pkg/provider/aws/edges/lambda_function-s3_bucket.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:s3_bucket:' +source: aws:lambda_function +destination: aws:s3_bucket direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:lambda_function:#Role - destination: 'aws:s3_bucket:' + destination: aws:s3_bucket diff --git a/pkg/provider/aws/edges/lambda_function-secret.yaml b/pkg/provider/aws/edges/lambda_function-secret.yaml index 24c5e0706..4d293cb94 100644 --- a/pkg/provider/aws/edges/lambda_function-secret.yaml +++ b/pkg/provider/aws/edges/lambda_function-secret.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:secret:' +source: aws:lambda_function +destination: aws:secret direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:lambda_function:#Role - destination: 'aws:secret:' + destination: aws:secret diff --git a/pkg/provider/aws/edges/lambda_function-security_group.yaml b/pkg/provider/aws/edges/lambda_function-security_group.yaml index daa3f2e63..3816b20ec 100644 --- a/pkg/provider/aws/edges/lambda_function-security_group.yaml +++ b/pkg/provider/aws/edges/lambda_function-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:security_group:' +source: aws:lambda_function +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_function-ses_email_identity.yaml b/pkg/provider/aws/edges/lambda_function-ses_email_identity.yaml index d03ab33f6..22dbe815c 100644 --- a/pkg/provider/aws/edges/lambda_function-ses_email_identity.yaml +++ b/pkg/provider/aws/edges/lambda_function-ses_email_identity.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:ses_email_identity:' +source: aws:lambda_function +destination: aws:ses_email_identity direct_edge_only: false deployment_order_reversed: false deletion_dependent: false @@ -7,4 +7,4 @@ reuse: expansion: dependencies: - source: aws:lambda_function:#Role - destination: 'aws:ses_email_identity:' + destination: aws:ses_email_identity diff --git a/pkg/provider/aws/edges/lambda_function-sns_topic.yaml b/pkg/provider/aws/edges/lambda_function-sns_topic.yaml index e3f5647b3..870838a35 100644 --- a/pkg/provider/aws/edges/lambda_function-sns_topic.yaml +++ b/pkg/provider/aws/edges/lambda_function-sns_topic.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:sns_topic:' +source: aws:lambda_function +destination: aws:sns_topic direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_function-sqs_queue.yaml b/pkg/provider/aws/edges/lambda_function-sqs_queue.yaml index 181a9a5e5..a198f184d 100644 --- a/pkg/provider/aws/edges/lambda_function-sqs_queue.yaml +++ b/pkg/provider/aws/edges/lambda_function-sqs_queue.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:sqs_queue:' +source: aws:lambda_function +destination: aws:sqs_queue direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_function-subnet_private.yaml b/pkg/provider/aws/edges/lambda_function-subnet_private.yaml index dfc660f4b..0772b513f 100644 --- a/pkg/provider/aws/edges/lambda_function-subnet_private.yaml +++ b/pkg/provider/aws/edges/lambda_function-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:subnet_private:' +source: aws:lambda_function +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_function-subnet_public.yaml b/pkg/provider/aws/edges/lambda_function-subnet_public.yaml index 15c94f787..720bebbdd 100644 --- a/pkg/provider/aws/edges/lambda_function-subnet_public.yaml +++ b/pkg/provider/aws/edges/lambda_function-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_function:' -destination: 'aws:subnet_public:' +source: aws:lambda_function +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/lambda_permission-lambda_function.yaml b/pkg/provider/aws/edges/lambda_permission-lambda_function.yaml index 8daf1a910..608804885 100644 --- a/pkg/provider/aws/edges/lambda_permission-lambda_function.yaml +++ b/pkg/provider/aws/edges/lambda_permission-lambda_function.yaml @@ -1,5 +1,5 @@ -source: 'aws:lambda_permission:' -destination: 'aws:lambda_function:' +source: aws:lambda_permission +destination: aws:lambda_function direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/load_balancer-load_balancer_listener.yaml b/pkg/provider/aws/edges/load_balancer-load_balancer_listener.yaml index 07e7e80f8..d11f92e95 100644 --- a/pkg/provider/aws/edges/load_balancer-load_balancer_listener.yaml +++ b/pkg/provider/aws/edges/load_balancer-load_balancer_listener.yaml @@ -1,23 +1,23 @@ -source: 'aws:load_balancer:' -destination: 'aws:load_balancer_listener:' +source: aws:load_balancer +destination: aws:load_balancer_listener direct_edge_only: false deployment_order_reversed: true deletion_dependent: false reuse: configuration: - - resource: 'aws:load_balancer_listener:' + - resource: aws:load_balancer_listener config: field: LoadBalancer - value: 'aws:load_balancer:' - - resource: 'aws:load_balancer:' + value: aws:load_balancer + - resource: aws:load_balancer config: field: Type value: network - - resource: 'aws:load_balancer:' + - resource: aws:load_balancer config: field: Scheme value: internal - - resource: 'aws:load_balancer_listener:' + - resource: aws:load_balancer_listener config: field: Port value: 80 diff --git a/pkg/provider/aws/edges/load_balancer-security_group.yaml b/pkg/provider/aws/edges/load_balancer-security_group.yaml index d8f98b734..8c6c1acad 100644 --- a/pkg/provider/aws/edges/load_balancer-security_group.yaml +++ b/pkg/provider/aws/edges/load_balancer-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:load_balancer:' -destination: 'aws:security_group:' +source: aws:load_balancer +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/load_balancer-subnet_private.yaml b/pkg/provider/aws/edges/load_balancer-subnet_private.yaml index 2c5794e38..f1af04dc2 100644 --- a/pkg/provider/aws/edges/load_balancer-subnet_private.yaml +++ b/pkg/provider/aws/edges/load_balancer-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:load_balancer:' -destination: 'aws:subnet_private:' +source: aws:load_balancer +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/load_balancer-subnet_public.yaml b/pkg/provider/aws/edges/load_balancer-subnet_public.yaml index e08ec09a5..a1212acbb 100644 --- a/pkg/provider/aws/edges/load_balancer-subnet_public.yaml +++ b/pkg/provider/aws/edges/load_balancer-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:load_balancer:' -destination: 'aws:subnet_public:' +source: aws:load_balancer +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/load_balancer_listener-target_group.yaml b/pkg/provider/aws/edges/load_balancer_listener-target_group.yaml index 8f440f242..56ecc93b6 100644 --- a/pkg/provider/aws/edges/load_balancer_listener-target_group.yaml +++ b/pkg/provider/aws/edges/load_balancer_listener-target_group.yaml @@ -1,27 +1,27 @@ -source: 'aws:load_balancer_listener:' -destination: 'aws:target_group:' +source: aws:load_balancer_listener +destination: aws:target_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: expansion: dependencies: - - source: 'aws:target_group:' + - source: aws:target_group destination: aws:load_balancer_listener:#LoadBalancer.Subnets[0].Vpc configuration: - - resource: 'aws:load_balancer_listener:' + - resource: aws:load_balancer_listener config: field: Protocol value: aws:target_group:#Protocol - - resource: 'aws:load_balancer_listener:' + - resource: aws:load_balancer_listener config: field: DefaultActions value: - TargetGroupArn: - ResourceId: 'aws:target_group:' + ResourceId: aws:target_group Property: arn Type: forward - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: Vpc value: aws:load_balancer_listener:#LoadBalancer.Subnets[0].Vpc diff --git a/pkg/provider/aws/edges/nat_gateway-elastic_ip.yaml b/pkg/provider/aws/edges/nat_gateway-elastic_ip.yaml index d7a724041..88042c8de 100644 --- a/pkg/provider/aws/edges/nat_gateway-elastic_ip.yaml +++ b/pkg/provider/aws/edges/nat_gateway-elastic_ip.yaml @@ -1,5 +1,5 @@ -source: 'aws:nat_gateway:' -destination: 'aws:elastic_ip:' +source: aws:nat_gateway +destination: aws:elastic_ip direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/nat_gateway-subnet_public.yaml b/pkg/provider/aws/edges/nat_gateway-subnet_public.yaml index 56762190b..b3e87d107 100644 --- a/pkg/provider/aws/edges/nat_gateway-subnet_public.yaml +++ b/pkg/provider/aws/edges/nat_gateway-subnet_public.yaml @@ -1,5 +1,5 @@ -source: 'aws:nat_gateway:' -destination: 'aws:subnet_public:' +source: aws:nat_gateway +destination: aws:subnet_public direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/private_dns_namespace-vpc.yaml b/pkg/provider/aws/edges/private_dns_namespace-vpc.yaml index 56ebcd994..e388372bd 100644 --- a/pkg/provider/aws/edges/private_dns_namespace-vpc.yaml +++ b/pkg/provider/aws/edges/private_dns_namespace-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:private_dns_namespace:' -destination: 'aws:vpc:' +source: aws:private_dns_namespace +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/rds_instance-rds_subnet_group.yaml b/pkg/provider/aws/edges/rds_instance-rds_subnet_group.yaml index 34cfde754..4e6284ac0 100644 --- a/pkg/provider/aws/edges/rds_instance-rds_subnet_group.yaml +++ b/pkg/provider/aws/edges/rds_instance-rds_subnet_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:rds_instance:' -destination: 'aws:rds_subnet_group:' +source: aws:rds_instance +destination: aws:rds_subnet_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/rds_instance-security_group.yaml b/pkg/provider/aws/edges/rds_instance-security_group.yaml index 4d5a5c06c..0bdf09fa4 100644 --- a/pkg/provider/aws/edges/rds_instance-security_group.yaml +++ b/pkg/provider/aws/edges/rds_instance-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:rds_instance:' -destination: 'aws:security_group:' +source: aws:rds_instance +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/rds_proxy-rds_proxy_target_group.yaml b/pkg/provider/aws/edges/rds_proxy-rds_proxy_target_group.yaml index d4f1ee335..9b6fa5d1e 100644 --- a/pkg/provider/aws/edges/rds_proxy-rds_proxy_target_group.yaml +++ b/pkg/provider/aws/edges/rds_proxy-rds_proxy_target_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:rds_proxy:' -destination: 'aws:rds_proxy_target_group:' +source: aws:rds_proxy +destination: aws:rds_proxy_target_group direct_edge_only: false deployment_order_reversed: true deletion_dependent: false @@ -11,7 +11,7 @@ expansion: dependencies: - source: aws:iam_policy:-ormsecretpolicy destination: aws:secret_version:-credentials#Secret - - source: 'aws:rds_proxy:' + - source: aws:rds_proxy destination: aws:secret_version:-credentials#Secret - source: aws:rds_proxy:#Role destination: aws:iam_policy:-ormsecretpolicy @@ -20,7 +20,7 @@ expansion: - source: aws:rds_proxy:#Role destination: aws:rds_proxy_target_group:#RdsInstance configuration: - - resource: 'aws:rds_proxy_target_group:' + - resource: aws:rds_proxy_target_group config: field: RdsProxy - value: 'aws:rds_proxy:' + value: aws:rds_proxy diff --git a/pkg/provider/aws/edges/rds_proxy-secret.yaml b/pkg/provider/aws/edges/rds_proxy-secret.yaml index 3bea77206..7479fbf04 100644 --- a/pkg/provider/aws/edges/rds_proxy-secret.yaml +++ b/pkg/provider/aws/edges/rds_proxy-secret.yaml @@ -1,16 +1,16 @@ -source: 'aws:rds_proxy:' -destination: 'aws:secret:' +source: aws:rds_proxy +destination: aws:secret direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:rds_proxy:' + - resource: aws:rds_proxy config: field: Auths value: - AuthScheme: 'SECRETS' IamAuth: 'DISABLED' SecretArn: - ResourceId: 'aws:secret:' + ResourceId: aws:secret Property: arn diff --git a/pkg/provider/aws/edges/rds_proxy-security_group.yaml b/pkg/provider/aws/edges/rds_proxy-security_group.yaml index 13c1721ad..42f807919 100644 --- a/pkg/provider/aws/edges/rds_proxy-security_group.yaml +++ b/pkg/provider/aws/edges/rds_proxy-security_group.yaml @@ -1,5 +1,5 @@ -source: 'aws:rds_proxy:' -destination: 'aws:security_group:' +source: aws:rds_proxy +destination: aws:security_group direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/rds_proxy_target_group-rds_instance.yaml b/pkg/provider/aws/edges/rds_proxy_target_group-rds_instance.yaml index bf2cb0d07..ad965f8ea 100644 --- a/pkg/provider/aws/edges/rds_proxy_target_group-rds_instance.yaml +++ b/pkg/provider/aws/edges/rds_proxy_target_group-rds_instance.yaml @@ -1,11 +1,11 @@ -source: 'aws:rds_proxy_target_group:' -destination: 'aws:rds_instance:' +source: aws:rds_proxy_target_group +destination: aws:rds_instance direct_edge_only: false deployment_order_reversed: true deletion_dependent: false reuse: downstream configuration: - - resource: 'aws:rds_proxy_target_group:' + - resource: aws:rds_proxy_target_group config: field: RdsInstance - value: 'aws:rds_instance:' + value: aws:rds_instance diff --git a/pkg/provider/aws/edges/rds_subnet_group-group-subnet_private.yaml b/pkg/provider/aws/edges/rds_subnet_group-group-subnet_private.yaml index 12cb9e488..3667b90a0 100644 --- a/pkg/provider/aws/edges/rds_subnet_group-group-subnet_private.yaml +++ b/pkg/provider/aws/edges/rds_subnet_group-group-subnet_private.yaml @@ -1,5 +1,5 @@ -source: 'aws:rds_subnet_group:' -destination: 'aws:subnet_private:' +source: aws:rds_subnet_group +destination: aws:subnet_private direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/rest_api-api_integration.yaml b/pkg/provider/aws/edges/rest_api-api_integration.yaml index 2e1858508..89c2f6f42 100644 --- a/pkg/provider/aws/edges/rest_api-api_integration.yaml +++ b/pkg/provider/aws/edges/rest_api-api_integration.yaml @@ -1,5 +1,5 @@ -source: 'aws:rest_api:' -destination: 'aws:api_integration:' +source: aws:rest_api +destination: aws:api_integration direct_edge_only: false deployment_order_reversed: true deletion_dependent: false diff --git a/pkg/provider/aws/edges/rest_api-api_method.yaml b/pkg/provider/aws/edges/rest_api-api_method.yaml index a83c42182..22138fbb5 100644 --- a/pkg/provider/aws/edges/rest_api-api_method.yaml +++ b/pkg/provider/aws/edges/rest_api-api_method.yaml @@ -1,5 +1,5 @@ -source: 'aws:rest_api:' -destination: 'aws:api_method:' +source: aws:rest_api +destination: aws:api_method direct_edge_only: false deployment_order_reversed: true deletion_dependent: false diff --git a/pkg/provider/aws/edges/rest_api-api_resource.yaml b/pkg/provider/aws/edges/rest_api-api_resource.yaml index f4d238981..5cc4d0908 100644 --- a/pkg/provider/aws/edges/rest_api-api_resource.yaml +++ b/pkg/provider/aws/edges/rest_api-api_resource.yaml @@ -1,5 +1,5 @@ -source: 'aws:rest_api:' -destination: 'aws:api_resource:' +source: aws:rest_api +destination: aws:api_resource direct_edge_only: false deployment_order_reversed: true deletion_dependent: false diff --git a/pkg/provider/aws/edges/rest_api-lambda_permission.yaml b/pkg/provider/aws/edges/rest_api-lambda_permission.yaml index 17c12f701..f827c6ad8 100644 --- a/pkg/provider/aws/edges/rest_api-lambda_permission.yaml +++ b/pkg/provider/aws/edges/rest_api-lambda_permission.yaml @@ -1,21 +1,21 @@ -source: 'aws:rest_api:' -destination: 'aws:lambda_permission:' +source: aws:rest_api +destination: aws:lambda_permission direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:lambda_permission:' + - resource: aws:lambda_permission config: field: Source value: - ResourceId: 'aws:rest_api:' + ResourceId: aws:rest_api Property: child_resources - - resource: 'aws:lambda_permission:' + - resource: aws:lambda_permission config: field: Principal value: apigateway.amazonaws.com - - resource: 'aws:lambda_permission:' + - resource: aws:lambda_permission config: field: Action value: lambda:InvokeFunction diff --git a/pkg/provider/aws/edges/route53_hosted_zone-vpc.yaml b/pkg/provider/aws/edges/route53_hosted_zone-vpc.yaml index 4554b604b..b48c306dd 100644 --- a/pkg/provider/aws/edges/route53_hosted_zone-vpc.yaml +++ b/pkg/provider/aws/edges/route53_hosted_zone-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:route53_hosted_zone:' -destination: 'aws:vpc:' +source: aws:route53_hosted_zone +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/route_table-internet_gateway.yaml b/pkg/provider/aws/edges/route_table-internet_gateway.yaml index 1ee8948dc..53a64bfab 100644 --- a/pkg/provider/aws/edges/route_table-internet_gateway.yaml +++ b/pkg/provider/aws/edges/route_table-internet_gateway.yaml @@ -1,15 +1,15 @@ -source: 'aws:route_table:' -destination: 'aws:internet_gateway:' +source: aws:route_table +destination: aws:internet_gateway direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:route_table:' + - resource: aws:route_table config: field: Routes value: - CidrBlock: 0.0.0.0/0 GatewayId: - ResourceId: 'aws:internet_gateway:' + ResourceId: aws:internet_gateway Property: id diff --git a/pkg/provider/aws/edges/route_table-nat_gateway.yaml b/pkg/provider/aws/edges/route_table-nat_gateway.yaml index 1b0520a5d..82dec739c 100644 --- a/pkg/provider/aws/edges/route_table-nat_gateway.yaml +++ b/pkg/provider/aws/edges/route_table-nat_gateway.yaml @@ -1,15 +1,15 @@ -source: 'aws:route_table:' -destination: 'aws:nat_gateway:' +source: aws:route_table +destination: aws:nat_gateway direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:route_table:' + - resource: aws:route_table config: field: Routes value: - CidrBlock: 0.0.0.0/0 NatGatewayId: - ResourceId: 'aws:nat_gateway:' + ResourceId: aws:nat_gateway Property: id diff --git a/pkg/provider/aws/edges/route_table-vpc.yaml b/pkg/provider/aws/edges/route_table-vpc.yaml index 1001af130..a3a9e226b 100644 --- a/pkg/provider/aws/edges/route_table-vpc.yaml +++ b/pkg/provider/aws/edges/route_table-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:route_table:' -destination: 'aws:vpc:' +source: aws:route_table +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/s3_bucket_policy-cloudfront_origin_access_identity.yaml b/pkg/provider/aws/edges/s3_bucket_policy-cloudfront_origin_access_identity.yaml index e6054723a..f0d4fe402 100644 --- a/pkg/provider/aws/edges/s3_bucket_policy-cloudfront_origin_access_identity.yaml +++ b/pkg/provider/aws/edges/s3_bucket_policy-cloudfront_origin_access_identity.yaml @@ -1,11 +1,11 @@ -source: 'aws:s3_bucket_policy:' -destination: 'aws:cloudfront_origin_access_identity:' +source: aws:s3_bucket_policy +destination: aws:cloudfront_origin_access_identity direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:cloudfront_origin_access_identity:' + - resource: aws:cloudfront_origin_access_identity config: field: Comment value: this is needed to set up S3 polices so that the S3 bucket is not public diff --git a/pkg/provider/aws/edges/s3_bucket_policy-s3_bucket.yaml b/pkg/provider/aws/edges/s3_bucket_policy-s3_bucket.yaml index 29ff8d07b..0bdee0782 100644 --- a/pkg/provider/aws/edges/s3_bucket_policy-s3_bucket.yaml +++ b/pkg/provider/aws/edges/s3_bucket_policy-s3_bucket.yaml @@ -1,11 +1,11 @@ -source: 'aws:s3_bucket_policy:' -destination: 'aws:s3_bucket:' +source: aws:s3_bucket_policy +destination: aws:s3_bucket direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:s3_bucket_policy:' + - resource: aws:s3_bucket_policy config: field: Bucket - value: 'aws:s3_bucket:' + value: aws:s3_bucket diff --git a/pkg/provider/aws/edges/s3_object-s3_bucket.yaml b/pkg/provider/aws/edges/s3_object-s3_bucket.yaml index 29c23cd7e..9becf14ea 100644 --- a/pkg/provider/aws/edges/s3_object-s3_bucket.yaml +++ b/pkg/provider/aws/edges/s3_object-s3_bucket.yaml @@ -1,5 +1,5 @@ -source: 'aws:s3_object:' -destination: 'aws:s3_bucket:' +source: aws:s3_object +destination: aws:s3_bucket direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/secret-secret_version.yaml b/pkg/provider/aws/edges/secret-secret_version.yaml index 978047496..99ce3be8f 100644 --- a/pkg/provider/aws/edges/secret-secret_version.yaml +++ b/pkg/provider/aws/edges/secret-secret_version.yaml @@ -1,5 +1,5 @@ -source: 'aws:secret:' -destination: 'aws:secret_version:' +source: aws:secret +destination: aws:secret_version direct_edge_only: false deployment_order_reversed: true deletion_dependent: true diff --git a/pkg/provider/aws/edges/secret_version-rds_instance.yaml b/pkg/provider/aws/edges/secret_version-rds_instance.yaml index ff2a16461..258272803 100644 --- a/pkg/provider/aws/edges/secret_version-rds_instance.yaml +++ b/pkg/provider/aws/edges/secret_version-rds_instance.yaml @@ -1,15 +1,15 @@ -source: 'aws:secret_version:' -destination: 'aws:rds_instance:' +source: aws:secret_version +destination: aws:rds_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:secret_version:' + - resource: aws:secret_version config: field: Path value: aws:rds_instance:#CredentialsPath - - resource: 'aws:secret_version:' + - resource: aws:secret_version config: field: Type value: string diff --git a/pkg/provider/aws/edges/security_group-vpc.yaml b/pkg/provider/aws/edges/security_group-vpc.yaml index ca4d66957..8a79edeba 100644 --- a/pkg/provider/aws/edges/security_group-vpc.yaml +++ b/pkg/provider/aws/edges/security_group-vpc.yaml @@ -1,17 +1,17 @@ -source: 'aws:security_group:' -destination: 'aws:vpc:' +source: aws:security_group +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:security_group:' + - resource: aws:security_group config: field: IngressRules value: - Description: Allow ingress traffic from ip addresses within the vpc CidrBlocks: - - ResourceId: 'aws:vpc:' + - ResourceId: aws:vpc Property: cidr_block FromPort: 0 Protocol: '-1' @@ -21,7 +21,7 @@ configuration: FromPort: 0 Protocol: '-1' ToPort: 0 - - resource: 'aws:security_group:' + - resource: aws:security_group config: field: EgressRules value: diff --git a/pkg/provider/aws/edges/sns_subscription-ec2_instance.yaml b/pkg/provider/aws/edges/sns_subscription-ec2_instance.yaml index 7010e770f..8cfec6c60 100644 --- a/pkg/provider/aws/edges/sns_subscription-ec2_instance.yaml +++ b/pkg/provider/aws/edges/sns_subscription-ec2_instance.yaml @@ -1,5 +1,5 @@ -source: 'aws:sns_subscription:' -destination: 'aws:ec2_instance:' +source: aws:sns_subscription +destination: aws:ec2_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sns_subscription-ecs_service.yaml b/pkg/provider/aws/edges/sns_subscription-ecs_service.yaml index 89ae586db..783a090c8 100644 --- a/pkg/provider/aws/edges/sns_subscription-ecs_service.yaml +++ b/pkg/provider/aws/edges/sns_subscription-ecs_service.yaml @@ -1,5 +1,5 @@ -source: 'aws:sns_subscription:' -destination: 'aws:ecs_service:' +source: aws:sns_subscription +destination: aws:ecs_service direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sns_subscription-kubernetes_deployment.yaml b/pkg/provider/aws/edges/sns_subscription-kubernetes_deployment.yaml index 320d20d5f..81adf4a62 100644 --- a/pkg/provider/aws/edges/sns_subscription-kubernetes_deployment.yaml +++ b/pkg/provider/aws/edges/sns_subscription-kubernetes_deployment.yaml @@ -1,5 +1,5 @@ -source: 'aws:sns_subscription:' -destination: 'kubernetes:deployment:' +source: aws:sns_subscription +destination: kubernetes:deployment direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sns_subscription-kubernetes_pod.yaml b/pkg/provider/aws/edges/sns_subscription-kubernetes_pod.yaml index 7a56340ba..b1cf63eb8 100644 --- a/pkg/provider/aws/edges/sns_subscription-kubernetes_pod.yaml +++ b/pkg/provider/aws/edges/sns_subscription-kubernetes_pod.yaml @@ -1,5 +1,5 @@ -source: 'aws:sns_subscription:' -destination: 'kubernetes:pod:' +source: aws:sns_subscription +destination: kubernetes:pod direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sns_subscription-lambda_function.yaml b/pkg/provider/aws/edges/sns_subscription-lambda_function.yaml index 8445bb1a0..96e627047 100644 --- a/pkg/provider/aws/edges/sns_subscription-lambda_function.yaml +++ b/pkg/provider/aws/edges/sns_subscription-lambda_function.yaml @@ -1,5 +1,5 @@ -source: 'aws:sns_subscription:' -destination: 'aws:lambda_function:' +source: aws:sns_subscription +destination: aws:lambda_function direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sns_topic-sns_subscription.yaml b/pkg/provider/aws/edges/sns_topic-sns_subscription.yaml index 46ff9d204..bb51d129b 100644 --- a/pkg/provider/aws/edges/sns_topic-sns_subscription.yaml +++ b/pkg/provider/aws/edges/sns_topic-sns_subscription.yaml @@ -1,5 +1,5 @@ -source: 'aws:sns_topic:' -destination: 'aws:sns_subscription:' +source: aws:sns_topic +destination: aws:sns_subscription direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sqs_queue-ec2_instance.yaml b/pkg/provider/aws/edges/sqs_queue-ec2_instance.yaml index 848dac349..16e742099 100644 --- a/pkg/provider/aws/edges/sqs_queue-ec2_instance.yaml +++ b/pkg/provider/aws/edges/sqs_queue-ec2_instance.yaml @@ -1,5 +1,5 @@ -source: 'aws:sqs_queue:' -destination: 'aws:ec2_instance:' +source: aws:sqs_queue +destination: aws:ec2_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sqs_queue-ecs_service.yaml b/pkg/provider/aws/edges/sqs_queue-ecs_service.yaml index 91a9da9f2..25bb80f98 100644 --- a/pkg/provider/aws/edges/sqs_queue-ecs_service.yaml +++ b/pkg/provider/aws/edges/sqs_queue-ecs_service.yaml @@ -1,5 +1,5 @@ -source: 'aws:sqs_queue:' -destination: 'aws:ecs_service:' +source: aws:sqs_queue +destination: aws:ecs_service direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sqs_queue-kubernetes_deployment.yaml b/pkg/provider/aws/edges/sqs_queue-kubernetes_deployment.yaml index 4a97aa3e5..63f1b8c96 100644 --- a/pkg/provider/aws/edges/sqs_queue-kubernetes_deployment.yaml +++ b/pkg/provider/aws/edges/sqs_queue-kubernetes_deployment.yaml @@ -1,5 +1,5 @@ -source: 'aws:sqs_queue:' -destination: 'kubernetes:deployment:' +source: aws:sqs_queue +destination: kubernetes:deployment direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sqs_queue-kubernetes_pod.yaml b/pkg/provider/aws/edges/sqs_queue-kubernetes_pod.yaml index 5963b3cf4..0e5e6cdbc 100644 --- a/pkg/provider/aws/edges/sqs_queue-kubernetes_pod.yaml +++ b/pkg/provider/aws/edges/sqs_queue-kubernetes_pod.yaml @@ -1,5 +1,5 @@ -source: 'aws:sqs_queue:' -destination: 'kubernetes:pod:' +source: aws:sqs_queue +destination: kubernetes:pod direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sqs_queue-lambda_function.yaml b/pkg/provider/aws/edges/sqs_queue-lambda_function.yaml index f28bd327c..a92e169e1 100644 --- a/pkg/provider/aws/edges/sqs_queue-lambda_function.yaml +++ b/pkg/provider/aws/edges/sqs_queue-lambda_function.yaml @@ -1,5 +1,5 @@ -source: 'aws:sqs_queue:' -destination: 'aws:lambda_function:' +source: aws:sqs_queue +destination: aws:lambda_function direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/sqs_queue-sqs_queue_policy.yaml b/pkg/provider/aws/edges/sqs_queue-sqs_queue_policy.yaml index 906d2617f..ec6825487 100644 --- a/pkg/provider/aws/edges/sqs_queue-sqs_queue_policy.yaml +++ b/pkg/provider/aws/edges/sqs_queue-sqs_queue_policy.yaml @@ -1,5 +1,5 @@ -source: 'aws:sqs_queue:' -destination: 'aws:sqs_queue_policy:' +source: aws:sqs_queue +destination: aws:sqs_queue_policy direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/subnet_private-availability_zones.yaml b/pkg/provider/aws/edges/subnet_private-availability_zones.yaml index e08a301a3..7667b69a5 100644 --- a/pkg/provider/aws/edges/subnet_private-availability_zones.yaml +++ b/pkg/provider/aws/edges/subnet_private-availability_zones.yaml @@ -1,5 +1,5 @@ -source: 'aws:subnet_private:' -destination: 'aws:availability_zones:' +source: aws:subnet_private +destination: aws:availability_zones direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/subnet_private-route_table.yaml b/pkg/provider/aws/edges/subnet_private-route_table.yaml index b4159cfa8..fec6cb54b 100644 --- a/pkg/provider/aws/edges/subnet_private-route_table.yaml +++ b/pkg/provider/aws/edges/subnet_private-route_table.yaml @@ -1,5 +1,5 @@ -source: 'aws:subnet_private:' -destination: 'aws:route_table:' +source: aws:subnet_private +destination: aws:route_table direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/subnet_private-vpc.yaml b/pkg/provider/aws/edges/subnet_private-vpc.yaml index 7f94cdf38..8f063c4d2 100644 --- a/pkg/provider/aws/edges/subnet_private-vpc.yaml +++ b/pkg/provider/aws/edges/subnet_private-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:subnet_private:' -destination: 'aws:vpc:' +source: aws:subnet_private +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/subnet_public-availability_zones.yaml b/pkg/provider/aws/edges/subnet_public-availability_zones.yaml index 541b711cc..ce2b3a28a 100644 --- a/pkg/provider/aws/edges/subnet_public-availability_zones.yaml +++ b/pkg/provider/aws/edges/subnet_public-availability_zones.yaml @@ -1,5 +1,5 @@ -source: 'aws:subnet_public:' -destination: 'aws:availability_zones:' +source: aws:subnet_public +destination: aws:availability_zones direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/subnet_public-route_table.yaml b/pkg/provider/aws/edges/subnet_public-route_table.yaml index fe6310440..3117e0987 100644 --- a/pkg/provider/aws/edges/subnet_public-route_table.yaml +++ b/pkg/provider/aws/edges/subnet_public-route_table.yaml @@ -1,5 +1,5 @@ -source: 'aws:subnet_public:' -destination: 'aws:route_table:' +source: aws:subnet_public +destination: aws:route_table direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/subnet_public-vpc.yaml b/pkg/provider/aws/edges/subnet_public-vpc.yaml index 8b9712357..b68e37f41 100644 --- a/pkg/provider/aws/edges/subnet_public-vpc.yaml +++ b/pkg/provider/aws/edges/subnet_public-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:subnet_public:' -destination: 'aws:vpc:' +source: aws:subnet_public +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/target_group-ec2_instance.yaml b/pkg/provider/aws/edges/target_group-ec2_instance.yaml index f5e2bc5e3..2c971b1a4 100644 --- a/pkg/provider/aws/edges/target_group-ec2_instance.yaml +++ b/pkg/provider/aws/edges/target_group-ec2_instance.yaml @@ -1,27 +1,27 @@ -source: 'aws:target_group:' -destination: 'aws:ec2_instance:' +source: aws:target_group +destination: aws:ec2_instance direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: Port value: 3000 - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: Protocol value: HTTPS - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: TargetType value: instance - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: Targets value: Id: - ResourceId: 'aws:ec2_instance:' + ResourceId: aws:ec2_instance Property: id Port: 3000 diff --git a/pkg/provider/aws/edges/target_group-ecs_service.yaml b/pkg/provider/aws/edges/target_group-ecs_service.yaml index 61d583b1b..c5876fdae 100644 --- a/pkg/provider/aws/edges/target_group-ecs_service.yaml +++ b/pkg/provider/aws/edges/target_group-ecs_service.yaml @@ -1,19 +1,19 @@ -source: 'aws:target_group:' -destination: 'aws:ecs_service:' +source: aws:target_group +destination: aws:ecs_service direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: Port value: 3000 - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: Protocol value: TCP - - resource: 'aws:target_group:' + - resource: aws:target_group config: field: TargetType value: ip diff --git a/pkg/provider/aws/edges/target_group-vpc.yaml b/pkg/provider/aws/edges/target_group-vpc.yaml index 94953671e..48ab5884d 100644 --- a/pkg/provider/aws/edges/target_group-vpc.yaml +++ b/pkg/provider/aws/edges/target_group-vpc.yaml @@ -1,5 +1,5 @@ -source: 'aws:target_group:' -destination: 'aws:vpc:' +source: aws:target_group +destination: aws:vpc direct_edge_only: false deployment_order_reversed: false deletion_dependent: false diff --git a/pkg/provider/aws/edges/vpc_link-load_balancer.yaml b/pkg/provider/aws/edges/vpc_link-load_balancer.yaml index 7e1a9640e..0552d3d18 100644 --- a/pkg/provider/aws/edges/vpc_link-load_balancer.yaml +++ b/pkg/provider/aws/edges/vpc_link-load_balancer.yaml @@ -1,11 +1,11 @@ -source: 'aws:vpc_link:' -destination: 'aws:load_balancer:' +source: aws:vpc_link +destination: aws:load_balancer direct_edge_only: false deployment_order_reversed: false deletion_dependent: false reuse: configuration: - - resource: 'aws:vpc_link:' + - resource: aws:vpc_link config: field: Target - value: 'aws:load_balancer:' + value: aws:load_balancer diff --git a/pkg/provider/aws/knowledgebase/api_gateway.go b/pkg/provider/aws/knowledgebase/api_gateway.go index b57fc3968..2d7cad1bf 100644 --- a/pkg/provider/aws/knowledgebase/api_gateway.go +++ b/pkg/provider/aws/knowledgebase/api_gateway.go @@ -10,14 +10,6 @@ import ( ) var ApiGatewayKB = knowledgebase.Build( - knowledgebase.EdgeBuilder[*resources.ApiIntegration, *resources.LambdaFunction]{ - Configure: func(integration *resources.ApiIntegration, function *resources.LambdaFunction, dag *construct.ResourceGraph, data knowledgebase.EdgeData) error { - if integration.RestApi == nil { - return fmt.Errorf("cannot configure integration %s, missing rest api or method", integration.Id()) - } - return configureIntegration(integration, dag) - }, - }, knowledgebase.EdgeBuilder[*resources.ApiIntegration, *resources.LoadBalancer]{ Reuse: knowledgebase.ReuseDownstream, Configure: func(integration *resources.ApiIntegration, loadBalancer *resources.LoadBalancer, dag *construct.ResourceGraph, data knowledgebase.EdgeData) error { @@ -25,39 +17,7 @@ var ApiGatewayKB = knowledgebase.Build( return fmt.Errorf("cannot configure integration %s, missing rest api or method", integration.Id()) } integration.IntegrationHttpMethod = strings.ToUpper(integration.Method.HttpMethod) - return configureIntegration(integration, dag) + return nil }, }, ) - -func configureIntegration(integration *resources.ApiIntegration, dag *construct.ResourceGraph) error { - - if integration.RestApi == nil || integration.Method == nil { - return fmt.Errorf("cannot configure integration %s, missing rest api or method", integration.Id()) - } - - segments := strings.Split(integration.Route, "/") - methodRequestParams := map[string]bool{} - integrationRequestParams := map[string]string{} - for _, segment := range segments { - if strings.Contains(segment, ":") { - // We strip the pathParam of the : and * characters (which signal path parameters or wildcard routes) to be able to inject them into our method and integration request parameters - pathParam := fmt.Sprintf("request.path.%s", segment) - pathParam = strings.ReplaceAll(pathParam, ":", "") - pathParam = strings.ReplaceAll(pathParam, "*", "") - methodRequestParams[fmt.Sprintf("method.%s", pathParam)] = true - integrationRequestParams[fmt.Sprintf("integration.%s", pathParam)] = fmt.Sprintf("method.%s", pathParam) - } - } - integration.RequestParameters = integrationRequestParams - integration.Method.RequestParameters = methodRequestParams - - for _, res := range dag.GetUpstreamResources(integration.RestApi) { - switch resource := res.(type) { - case *resources.ApiDeployment: - dag.AddDependency(resource, integration.Method) - dag.AddDependency(resource, integration) - } - } - return nil -} diff --git a/pkg/provider/aws/provider.go b/pkg/provider/aws/provider.go index aaa9a9e11..f5da234d2 100644 --- a/pkg/provider/aws/provider.go +++ b/pkg/provider/aws/provider.go @@ -2,6 +2,7 @@ package aws import ( "embed" + "errors" "fmt" "io/fs" "reflect" @@ -108,27 +109,28 @@ var awsEdgeTempaltes embed.FS func (a *AWS) GetEdgeTemplates() map[string]*knowledgebase.EdgeTemplate { templates := map[string]*knowledgebase.EdgeTemplate{} - if err := fs.WalkDir(awsEdgeTempaltes, ".", func(path string, d fs.DirEntry, nerr error) error { + err := fs.WalkDir(awsEdgeTempaltes, ".", func(path string, d fs.DirEntry, nerr error) error { if d.IsDir() { return nil } content, err := awsEdgeTempaltes.ReadFile(fmt.Sprintf("edges/%s", d.Name())) if err != nil { - panic(err) + return errors.Join(nerr, err) } resTemplate := &knowledgebase.EdgeTemplate{} err = yaml.Unmarshal(content, resTemplate) if err != nil { - panic(err) + return errors.Join(nerr, fmt.Errorf("unable to unmarshal edge template %s: %w", d.Name(), err)) } templateKey := resTemplate.Key() if templates[templateKey] != nil { - panic(fmt.Errorf("duplicate template for type %s", templateKey)) + return errors.Join(nerr, fmt.Errorf("duplicate template for type %s", templateKey)) } templates[templateKey] = resTemplate return nil - }); err != nil { - return templates + }) + if err != nil { + panic(err) } return templates } diff --git a/pkg/provider/aws/resources/api_gateway.go b/pkg/provider/aws/resources/api_gateway.go index 1942da207..8413d2a96 100644 --- a/pkg/provider/aws/resources/api_gateway.go +++ b/pkg/provider/aws/resources/api_gateway.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/klothoplatform/klotho/pkg/construct" - "github.com/klothoplatform/klotho/pkg/engine/classification" "github.com/klothoplatform/klotho/pkg/sanitization/aws" ) @@ -182,7 +181,8 @@ type ApiIntegrationCreateParams struct { HttpMethod string } -// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi is correlated to the constructs which required its creation. +// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi +// is correlated to the constructs which required its creation. func (integration *ApiIntegration) Create(dag *construct.ResourceGraph, params ApiIntegrationCreateParams) error { name := apiResourceSanitizer.Apply(fmt.Sprintf("%s-%s-%s", params.AppName, params.Path, params.HttpMethod)) @@ -200,37 +200,6 @@ func (integration *ApiIntegration) Create(dag *construct.ResourceGraph, params A return nil } -func (integration *ApiIntegration) MakeOperational(dag *construct.ResourceGraph, appName string, classifier classification.Classifier) error { - if integration.RestApi == nil { - return fmt.Errorf("rest api is not set on integration %s", integration.Name) - } - - isOnlyIntegration := false - integrations := construct.GetDownstreamResourcesOfType[*ApiIntegration](dag, integration.RestApi) - if len(integrations) == 1 { - isOnlyIntegration = true - } - - if integration.Route == "" && isOnlyIntegration { - integration.Route = "/:proxy*" - } - - if integration.Route != "" && integration.Route != "/" { - resource, err := construct.CreateResource[*ApiResource](dag, ApiResourceCreateParams{ - AppName: appName, - Refs: construct.BaseConstructSetOf(integration), - Path: integration.Route, - ApiName: integration.RestApi.Name, - }) - if err != nil { - return err - } - integration.Resource = resource - dag.AddDependency(resource, integration) - } - return nil -} - type ApiMethodCreateParams struct { AppName string Refs construct.BaseConstructSet @@ -239,7 +208,8 @@ type ApiMethodCreateParams struct { HttpMethod string } -// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi is correlated to the constructs which required its creation. +// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi +// is correlated to the constructs which required its creation. func (method *ApiMethod) Create(dag *construct.ResourceGraph, params ApiMethodCreateParams) error { name := apiResourceSanitizer.Apply(fmt.Sprintf("%s-%s-%s", params.AppName, params.Path, params.HttpMethod)) @@ -277,7 +247,8 @@ type ApiDeploymentCreateParams struct { Name string } -// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi is correlated to the constructs which required its creation. +// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi +// is correlated to the constructs which required its creation. func (deployment *ApiDeployment) Create(dag *construct.ResourceGraph, params ApiDeploymentCreateParams) error { name := apiResourceSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name)) @@ -302,7 +273,8 @@ type ApiStageCreateParams struct { Name string } -// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi is correlated to the constructs which required its creation. +// Create takes in an all necessary parameters to generate the RestApi name and ensure that the RestApi +// is correlated to the constructs which required its creation. func (stage *ApiStage) Create(dag *construct.ResourceGraph, params ApiStageCreateParams) error { name := apiResourceSanitizer.Apply(fmt.Sprintf("%s-%s", params.AppName, params.Name)) diff --git a/pkg/provider/aws/resources/templates/api_deployment.yaml b/pkg/provider/aws/resources/templates/api_deployment.yaml index b4aaef045..48b1f9f54 100644 --- a/pkg/provider/aws/resources/templates/api_deployment.yaml +++ b/pkg/provider/aws/resources/templates/api_deployment.yaml @@ -3,8 +3,8 @@ type: api_deployment rules: - enforcement: exactly_one direction: downstream - resource_types: - - rest_api + resources: + - '{{ downstream "aws:rest_api" (upstream "aws:api_stage" .Self) }}' set_field: RestApi unsatisfied_action: operation: create @@ -17,4 +17,4 @@ rules: delete_context: requires_no_upstream: true views: - dataflow: small \ No newline at end of file + dataflow: small diff --git a/pkg/provider/aws/resources/templates/api_integration.yaml b/pkg/provider/aws/resources/templates/api_integration.yaml index dbbe74479..c6b67f06e 100644 --- a/pkg/provider/aws/resources/templates/api_integration.yaml +++ b/pkg/provider/aws/resources/templates/api_integration.yaml @@ -16,10 +16,27 @@ rules: set_field: RestApi unsatisfied_action: operation: create + - direction: upstream + resources: + # TODO move the name sanitization into api resource name sanitization + - aws:api_resource:{{ fieldValue "Route" .Self | replace "\\{([^}+]+)\\+?\\}" "$1" | replace "^/" "" | replace "/" "-" }} + set_field: Resource + unsatisfied_action: + operation: create configuration: - field: IntegrationHttpMethod value: ANY + - field: Route + value: /{proxy+} + - field: RequestParameters + value_template: | + {{ $params := split (fieldValue "Route" .Self) "/" | filterMatch "^:\\w+$" }} + {{ zipToMap + ($params | mapString ":(.*)" "integration.request.path.$1") + ($params | mapString ":(.*)" "method.request.path.$1") + | toJson + }} delete_context: requires_no_upstream_or_downstream: true views: - dataflow: small \ No newline at end of file + dataflow: small diff --git a/pkg/provider/aws/resources/templates/api_method.yaml b/pkg/provider/aws/resources/templates/api_method.yaml index fe5f7f7f0..964af224e 100644 --- a/pkg/provider/aws/resources/templates/api_method.yaml +++ b/pkg/provider/aws/resources/templates/api_method.yaml @@ -3,8 +3,8 @@ type: api_method rules: - enforcement: exactly_one direction: upstream - resource_types: - - rest_api + resources: + - '{{ upstream "aws:rest_api" (downstream "aws:api_integration" .Self) }}' set_field: RestApi unsatisfied_action: operation: create @@ -16,4 +16,4 @@ configuration: delete_context: requires_no_upstream_or_downstream: true views: - dataflow: small \ No newline at end of file + dataflow: small diff --git a/pkg/provider/aws/resources/templates/api_resource.yaml b/pkg/provider/aws/resources/templates/api_resource.yaml index 69c0fd372..529890176 100644 --- a/pkg/provider/aws/resources/templates/api_resource.yaml +++ b/pkg/provider/aws/resources/templates/api_resource.yaml @@ -3,12 +3,41 @@ type: api_resource rules: - enforcement: exactly_one direction: upstream - resource_types: - - rest_api + resources: + - '{{ upstream "aws:rest_api" (downstream "aws:api_integration" .Self) }}' set_field: RestApi unsatisfied_action: operation: create + - enforcement: exactly_one + direction: upstream + if: | # Only need a parent resource if this one isn't the root + {{ $integration := downstream "aws:api_integration" .Self }} + {{ $parts := slice (split (fieldValue "Route" $integration) "/") 1 }} + {{ $paths := shortestPath .Self $integration | filterIds "aws:api_resource" }} + {{ .Log "info" "api_resource:if integration=%v ; parts=%v ; paths=%v" $integration $parts $paths }} + {{ lt (len $paths) (len $parts) }} + resources: + - | + {{ $integration := downstream "aws:api_integration" .Self }} + {{ $parts := slice (split (fieldValue "Route" $integration) "/") 1 }} + {{ $paths := shortestPath .Self $integration | filterIds "aws:api_resource" }} + {{ $parents := slice $parts 0 (sub (len $parts) (len $paths)) }} + {{ $name := join $parents "-" | replace "\\{([^}+]+)\\+?\\}" "$1" | replace "^/" "" | replace "/" "-" }} + {{ .Log "info" "api_resource:parent integration=%v ; parts=%v ; paths=%v ; parents=%v ; name=%v" $integration $parts $paths $parents $name }} + aws:api_resource:{{ $name }} + set_field: ParentResource + unsatisfied_action: + operation: create +configuration: + - field: PathPart + value_template: + | # Use the integration's route and this resource's position (how many children it has) to determine the path part + {{ $integration := downstream "aws:api_integration" .Self }} + {{ $parts := slice (split (fieldValue "Route" $integration) "/") 1 }} + {{ $paths := shortestPath .Self $integration | filterIds "aws:api_resource" }} + {{ .Log "info" "api_resource:PathPart integration=%v ; parts=%v ; paths=%v" $integration $parts $paths }} + {{ index $parts (sub (len $parts) (len $paths)) }} delete_context: requires_no_upstream_or_downstream: true views: - dataflow: small \ No newline at end of file + dataflow: small From 7e8a1de53d42037bed3dade19470e5d5b31c392e Mon Sep 17 00:00:00 2001 From: gordon-klotho Date: Tue, 19 Sep 2023 10:17:36 -0400 Subject: [PATCH 2/4] Remove logging from api_resource template --- pkg/provider/aws/resources/templates/api_resource.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/provider/aws/resources/templates/api_resource.yaml b/pkg/provider/aws/resources/templates/api_resource.yaml index 529890176..4eb9ed165 100644 --- a/pkg/provider/aws/resources/templates/api_resource.yaml +++ b/pkg/provider/aws/resources/templates/api_resource.yaml @@ -14,7 +14,6 @@ rules: {{ $integration := downstream "aws:api_integration" .Self }} {{ $parts := slice (split (fieldValue "Route" $integration) "/") 1 }} {{ $paths := shortestPath .Self $integration | filterIds "aws:api_resource" }} - {{ .Log "info" "api_resource:if integration=%v ; parts=%v ; paths=%v" $integration $parts $paths }} {{ lt (len $paths) (len $parts) }} resources: - | @@ -23,7 +22,6 @@ rules: {{ $paths := shortestPath .Self $integration | filterIds "aws:api_resource" }} {{ $parents := slice $parts 0 (sub (len $parts) (len $paths)) }} {{ $name := join $parents "-" | replace "\\{([^}+]+)\\+?\\}" "$1" | replace "^/" "" | replace "/" "-" }} - {{ .Log "info" "api_resource:parent integration=%v ; parts=%v ; paths=%v ; parents=%v ; name=%v" $integration $parts $paths $parents $name }} aws:api_resource:{{ $name }} set_field: ParentResource unsatisfied_action: @@ -35,7 +33,6 @@ configuration: {{ $integration := downstream "aws:api_integration" .Self }} {{ $parts := slice (split (fieldValue "Route" $integration) "/") 1 }} {{ $paths := shortestPath .Self $integration | filterIds "aws:api_resource" }} - {{ .Log "info" "api_resource:PathPart integration=%v ; parts=%v ; paths=%v" $integration $parts $paths }} {{ index $parts (sub (len $parts) (len $paths)) }} delete_context: requires_no_upstream_or_downstream: true From 6f0b596ba4806271a2399f76aa821bc66e40c273 Mon Sep 17 00:00:00 2001 From: gordon-klotho Date: Tue, 19 Sep 2023 10:26:58 -0400 Subject: [PATCH 3/4] Make sure setfield is always run --- pkg/engine/operational_resources.go | 96 +++++++++++++++++------------ 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/pkg/engine/operational_resources.go b/pkg/engine/operational_resources.go index f5795fc3c..9a99076a2 100644 --- a/pkg/engine/operational_resources.go +++ b/pkg/engine/operational_resources.go @@ -1,6 +1,7 @@ package engine import ( + "errors" "fmt" "reflect" "sort" @@ -459,8 +460,8 @@ func (e *Engine) setField(dag *construct.ResourceGraph, resource construct.Resou } else { field.Set(fieldValue) } - zap.S().Infof("configured %s#%s to %s", resource.Id(), rule.SetField, fieldResource.Id()) } + zap.S().Infof("set field %s#%s to %s", resource.Id(), rule.SetField, fieldResource.Id()) // If this sets the field driving the namespace, for example, // then the Id could change, so replace the resource in the graph // to update all the edges to the new Id. @@ -475,26 +476,30 @@ func (e *Engine) setField(dag *construct.ResourceGraph, resource construct.Resou // handleOperationalResourceError tries to determine how to fix OperatioanlResourceErrors by adding dependencies to the resource graph where needed. // If the error cannot be fixed, it will return an error. -func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, dag *construct.ResourceGraph) ([]Decision, error) { +func (e *Engine) handleOperationalResourceError(operation *OperationalResourceError, dag *construct.ResourceGraph) ([]Decision, error) { var decisions []Decision - if !err.ToCreate.IsZero() && err.ToCreate.Name != "" { - if err.Count > 1 { - return nil, fmt.Errorf("cannot create multiple resources for a specific resource id %s", err.ToCreate) + if !operation.ToCreate.IsZero() && operation.ToCreate.Name != "" { + if operation.Count > 1 { + return nil, fmt.Errorf("cannot create multiple resources for a specific resource id %s", operation.ToCreate) } - r, createErr := e.CreateResourceFromId(err.ToCreate) - if createErr != nil { - return nil, createErr + r, err := e.CreateResourceFromId(operation.ToCreate) + if err != nil { + return nil, err + } + err = e.setField(dag, operation.Resource, operation.Rule, r) + if err != nil { + return nil, err } var edge *graph.Edge[construct.Resource] - if err.Rule.Direction == knowledgebase.Downstream { + if operation.Rule.Direction == knowledgebase.Downstream { edge = &graph.Edge[construct.Resource]{ - Source: err.Resource, + Source: operation.Resource, Destination: r, } } else { edge = &graph.Edge[construct.Resource]{ Source: r, - Destination: err.Resource, + Destination: operation.Resource, } } return []Decision{{ @@ -510,18 +515,18 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d resources := e.ListResources() var needs []string switch { - case len(err.Rule.Classifications) > 0: - needs = err.Rule.Classifications + case len(operation.Rule.Classifications) > 0: + needs = operation.Rule.Classifications - case len(err.Rule.ResourceTypes) > 0: + case len(operation.Rule.ResourceTypes) > 0: // Pick the first one, assume the template writer prioritized which one should be created - needs = []string{err.Rule.ResourceTypes[0]} + needs = []string{operation.Rule.ResourceTypes[0]} - case err.ToCreate.Type != "": - needs = []string{err.ToCreate.Type} + case operation.ToCreate.Type != "": + needs = []string{operation.ToCreate.Type} - case err.Rule.UnsatisfiedAction.DefaultType != "": - needs = []string{err.Rule.UnsatisfiedAction.DefaultType} + case operation.Rule.UnsatisfiedAction.DefaultType != "": + needs = []string{operation.Rule.UnsatisfiedAction.DefaultType} } // determine the type of resource necessary to satisfy the operational resource error var neededResource construct.Resource @@ -530,10 +535,10 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d continue } var hasPath bool - if err.Rule.Direction == knowledgebase.Downstream { - hasPath = e.KnowledgeBase.HasPath(err.Resource, res) + if operation.Rule.Direction == knowledgebase.Downstream { + hasPath = e.KnowledgeBase.HasPath(operation.Resource, res) } else { - hasPath = e.KnowledgeBase.HasPath(res, err.Resource) + hasPath = e.KnowledgeBase.HasPath(res, operation.Resource) } // if a type is explicilty stated as needed, we will consider it even if there isnt a direct p if !hasPath { @@ -548,22 +553,27 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d // first check if the parent resource passed into the error has any upstream resources we can reuse numSatisfied := 0 - if err.Parent != nil { + if operation.Parent != nil { var resources []construct.Resource // The direction here is flipped since we are looking at the resources relative to the parent, not relative to the resource used in the error - if err.Rule.Direction == knowledgebase.Upstream { - resources = dag.GetAllDownstreamResources(err.Parent) + if operation.Rule.Direction == knowledgebase.Upstream { + resources = dag.GetAllDownstreamResources(operation.Parent) } else { - resources = dag.GetAllUpstreamResources(err.Parent) + resources = dag.GetAllUpstreamResources(operation.Parent) } + var errs error for _, res := range resources { - if res.Id().Type == neededResource.Id().Type && res.Id().Provider == neededResource.Id().Provider && dag.GetDependency(err.Resource.Id(), res.Id()) == nil { - decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, err.Resource, res)) + if res.Id().Type == neededResource.Id().Type && res.Id().Provider == neededResource.Id().Provider && dag.GetDependency(operation.Resource.Id(), res.Id()) == nil { + decisions = append(decisions, addDependencyDecisionForDirection(operation.Rule.Direction, operation.Resource, res)) + errs = errors.Join(errs, e.setField(dag, operation.Resource, operation.Rule, res)) numSatisfied++ } } + if errs != nil { + return decisions, errs + } } - if numSatisfied == err.Count { + if numSatisfied == operation.Count { return decisions, nil } @@ -571,7 +581,7 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d var availableResources []construct.Resource // we only want to look at available resources if we dont have a parent they need to be scoped to. // This prevents us from saying that resource_a is available if it is a child of resource_b when the error has a parent of resource_c - if err.Parent == nil && !err.Rule.UnsatisfiedAction.Unique { + if operation.Parent == nil && !operation.Rule.UnsatisfiedAction.Unique { //Todo: Get nearest resource. we should look one resource upstream until we find available resources so that we have a higher chance of choosing the right one for _, res := range dag.ListResources() { if res.Id().Type == neededResource.Id().Type { @@ -586,18 +596,23 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d sort.Strings(resourceIds) currNumSatisfied := numSatisfied - for i := 0; i < err.Count-currNumSatisfied; i++ { + var errs error + for i := 0; i < operation.Count-currNumSatisfied; i++ { for _, res := range availableResources { if len(resourceIds) > i && res.Id().Name == resourceIds[i] { - decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, err.Resource, res)) + decisions = append(decisions, addDependencyDecisionForDirection(operation.Rule.Direction, operation.Resource, res)) + errs = errors.Join(errs, e.setField(dag, operation.Resource, operation.Rule, res)) numSatisfied++ break } } } + if errs != nil { + return decisions, errs + } // if theres no available resources from us to choose from, we must create new resources - if len(availableResources) < err.Count-numSatisfied { + if len(availableResources) < operation.Count-numSatisfied { // We track the number of resources of the same type here for naming purposes, since we dont actually create new resources in this method we need to increment when we detect our decision will create a new resource numResources := 0 for _, res := range dag.ListResources() { @@ -605,16 +620,21 @@ func (e *Engine) handleOperationalResourceError(err *OperationalResourceError, d numResources++ } } - for i := numSatisfied; i < err.Count; i++ { + var errs error + for i := numSatisfied; i < operation.Count; i++ { newRes := cloneResource(neededResource) - nameResource(numResources, newRes, err.Resource, err.Rule.UnsatisfiedAction.Unique) + nameResource(numResources, newRes, operation.Resource, operation.Rule.UnsatisfiedAction.Unique) - decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, err.Resource, newRes)) - if err.Parent != nil { - decisions = append(decisions, addDependencyDecisionForDirection(err.Rule.Direction, newRes, err.Parent)) + decisions = append(decisions, addDependencyDecisionForDirection(operation.Rule.Direction, operation.Resource, newRes)) + if operation.Parent != nil { + decisions = append(decisions, addDependencyDecisionForDirection(operation.Rule.Direction, newRes, operation.Parent)) } + errs = errors.Join(errs, e.setField(dag, operation.Resource, operation.Rule, newRes)) numResources++ } + if errs != nil { + return decisions, errs + } } return decisions, nil From 6bb4bafac9dbca3d64f18c349b7071d2169d9405 Mon Sep 17 00:00:00 2001 From: gordon-klotho Date: Tue, 19 Sep 2023 10:56:27 -0400 Subject: [PATCH 4/4] Use elements match for arrays in op func tests --- pkg/knowledge_base/operational_funcs_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/knowledge_base/operational_funcs_test.go b/pkg/knowledge_base/operational_funcs_test.go index 63e0b351a..aa0625ae0 100644 --- a/pkg/knowledge_base/operational_funcs_test.go +++ b/pkg/knowledge_base/operational_funcs_test.go @@ -136,7 +136,12 @@ func TestConfigTemplateContext_ExecuteDecode(t *testing.T) { if !assert.NoError(err) { return } - assert.Equal(tt.want, gotV.Elem().Interface()) + switch want := tt.want.(type) { + case []construct.ResourceId: + assert.ElementsMatch(want, gotV.Elem().Interface()) + default: + assert.Equal(want, gotV.Elem().Interface()) + } }) } }