From f6f6b0289f79a1596935e7d30e2f6961f5ffb5ef Mon Sep 17 00:00:00 2001 From: Jordan Singer Date: Tue, 21 Nov 2023 10:04:18 -0500 Subject: [PATCH] use consumption to influence topology --- pkg/engine2/visualizer.go | 17 +++ pkg/knowledge_base2/emitter.go | 132 +++++++++++++----- pkg/knowledge_base2/property_types.go | 100 +++++++++++++ .../private_dns_namespace-ecs_service.yaml | 5 + pkg/templates/aws/resources/ecs_service.yaml | 5 + 5 files changed, 225 insertions(+), 34 deletions(-) create mode 100644 pkg/templates/aws/edges/private_dns_namespace-ecs_service.yaml diff --git a/pkg/engine2/visualizer.go b/pkg/engine2/visualizer.go index 8b5c437f0..c2492596d 100644 --- a/pkg/engine2/visualizer.go +++ b/pkg/engine2/visualizer.go @@ -280,6 +280,23 @@ func HasPath(topo Topology, sol solution_context.SolutionContext, source, target if len(targetTemplate.PathSatisfaction.AsTarget) == 0 || len(srcTemplate.PathSatisfaction.AsSource) == 0 { return false, nil } + sourceRes, err := sol.RawView().Vertex(source) + if err != nil { + return false, fmt.Errorf("has path could not find source resource %s: %w", source, err) + } + targetRes, err := sol.RawView().Vertex(target) + if err != nil { + return false, fmt.Errorf("has path could not find target resource %s: %w", target, err) + } + + consumed, err := knowledgebase.HasConsumedFromResource(sourceRes, targetRes, + solution_context.DynamicCtx(sol)) + if err != nil { + return false, err + } + if !consumed { + return false, nil + } return checkPaths(topo, sol, source, target) } diff --git a/pkg/knowledge_base2/emitter.go b/pkg/knowledge_base2/emitter.go index ffbdce5b8..a5e80faa4 100644 --- a/pkg/knowledge_base2/emitter.go +++ b/pkg/knowledge_base2/emitter.go @@ -50,18 +50,22 @@ func ConsumeFromResource(consumer, emitter *construct.Resource, ctx DynamicValue errs = errors.Join(errs, err) continue } - - pval, _ := consumer.GetProperty(consume.PropertyPath) - if pval == nil { - id := consumer.ID - if consume.Resource != "" { - data := DynamicValueData{Resource: consumer.ID} - err = ctx.ExecuteDecode(consume.Resource, data, &id) - if err != nil { - errs = errors.Join(errs, err) - continue - } + id := consumer.ID + if consume.Resource != "" { + data := DynamicValueData{Resource: consumer.ID} + err = ctx.ExecuteDecode(consume.Resource, data, &id) + if err != nil { + errs = errors.Join(errs, err) + continue } + } + resource, err := ctx.Graph.Vertex(id) + if err != nil { + errs = errors.Join(errs, err) + continue + } + pval, _ := resource.GetProperty(consume.PropertyPath) + if pval == nil { if consume.Converter != "" { val, err = consume.Convert(val, id, ctx) if err != nil { @@ -77,7 +81,7 @@ func ConsumeFromResource(consumer, emitter *construct.Resource, ctx DynamicValue continue } - err = consume.Consume(val, ctx, consumer) + err = consume.Consume(val, ctx, resource) if err != nil { errs = errors.Join(errs, err) continue @@ -88,6 +92,78 @@ func ConsumeFromResource(consumer, emitter *construct.Resource, ctx DynamicValue return delays, errs } +// HasConsumedFromResource returns true if the consumer has consumed from the emitter +// In order to return true, only one of the emitted values has to be set correctly +func HasConsumedFromResource(consumer, emitter *construct.Resource, ctx DynamicValueContext) (bool, error) { + consumerTemplate, err := ctx.KnowledgeBase.GetResourceTemplate(consumer.ID) + if err != nil { + return false, err + } + emitterTemplate, err := ctx.KnowledgeBase.GetResourceTemplate(emitter.ID) + if err != nil { + return false, err + } + noEmittedMatches := true + var errs error + for _, consume := range consumerTemplate.Consumption.Consumed { + for _, emit := range emitterTemplate.Consumption.Emitted { + if consume.Model == emit.Model { + noEmittedMatches = false + val, err := emit.Emit(ctx, emitter.ID) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + id := consumer.ID + if consume.Resource != "" { + data := DynamicValueData{Resource: consumer.ID} + err = ctx.ExecuteDecode(consume.Resource, data, &id) + if err != nil { + errs = errors.Join(errs, err) + continue + } + } + resource, err := ctx.Graph.Vertex(id) + if err != nil { + errs = errors.Join(errs, err) + continue + } + pval, _ := resource.GetProperty(consume.PropertyPath) + if pval == nil { + continue + } + if consume.Converter != "" { + val, err = consume.Convert(val, id, ctx) + if err != nil { + errs = errors.Join(errs, err) + continue + } + } + rt, err := ctx.KnowledgeBase.GetResourceTemplate(resource.ID) + if err != nil { + errs = errors.Join(errs, err) + continue + } + prop := rt.GetProperty(consume.PropertyPath) + if prop == nil { + errs = errors.Join(errs, fmt.Errorf("property %s not found", consume.PropertyPath)) + continue + } + ptype, err := prop.PropertyType() + if err != nil { + errs = errors.Join(errs, err) + continue + } + if ptype.Contains(pval, val) { + return true, nil + } + } + } + } + return noEmittedMatches, nil +} + func (c *ConsumptionObject) Convert(value any, res construct.ResourceId, ctx DynamicValueContext) (any, error) { if c.Converter == "" { return value, fmt.Errorf("no converter specified") @@ -109,9 +185,16 @@ func (c *ConsumptionObject) Convert(value any, res construct.ResourceId, ctx Dyn return value, err } bstr := strings.TrimSpace(buf.String()) + // We convert here just to make sure it gets translated to the right type of input + // We will convert again when consuming to ensure strings/etc are converted to their respective struct + // if they match a property ref/id/etc val, err := TransformToPropertyValue(res, c.PropertyPath, bstr, ctx, DynamicValueData{Resource: res}) - if err == nil { - return val, nil + if err != nil { + return val, err + } + val, err = TransformToPropertyValue(res, c.PropertyPath, val, ctx, DynamicValueData{Resource: res}) + if err != nil { + return val, err } return val, nil } @@ -143,26 +226,7 @@ func (c *ConsumptionObject) Emit(ctx DynamicValueContext, resource construct.Res } func (c *ConsumptionObject) Consume(val any, ctx DynamicValueContext, resource *construct.Resource) error { - var err error - if c.Resource != "" { - newId := construct.ResourceId{} - data := DynamicValueData{Resource: resource.ID} - err = ctx.ExecuteDecode(c.Resource, data, &newId) - if err != nil { - return err - } - resource, err = ctx.Graph.Vertex(newId) - if err != nil { - return err - } - } - if c.Converter != "" { - val, err = c.Convert(val, resource.ID, ctx) - if err != nil { - return err - } - } - err = resource.SetProperty(c.PropertyPath, val) + err := resource.SetProperty(c.PropertyPath, val) if err != nil { return err } diff --git a/pkg/knowledge_base2/property_types.go b/pkg/knowledge_base2/property_types.go index 8937db789..28bde51d7 100644 --- a/pkg/knowledge_base2/property_types.go +++ b/pkg/knowledge_base2/property_types.go @@ -3,6 +3,7 @@ package knowledgebase2 import ( "errors" "fmt" + "reflect" "strings" "github.com/klothoplatform/klotho/pkg/collectionutil" @@ -15,6 +16,7 @@ type ( Parse(value any, ctx DynamicContext, data DynamicValueData) (any, error) SetProperty(property *Property) ZeroValue() any + Contains(value any, contains any) bool } MapPropertyType struct { @@ -545,3 +547,101 @@ func (r *ResourcePropertyType) ZeroValue() any { func (p *PropertyRefPropertyType) ZeroValue() any { return construct.PropertyRef{} } + +func (m *MapPropertyType) Contains(value any, contains any) bool { + mapVal, ok := value.(map[string]any) + if !ok { + return false + } + containsMap, ok := contains.(map[string]any) + if !ok { + return false + } + for k, v := range containsMap { + if val, found := mapVal[k]; found || reflect.DeepEqual(val, v) { + return true + } + } + for _, v := range mapVal { + for _, cv := range containsMap { + if reflect.DeepEqual(v, cv) { + return true + } + } + } + return false +} + +func (l *ListPropertyType) Contains(value any, contains any) bool { + list, ok := value.([]any) + if !ok { + return false + } + containsList, ok := contains.([]any) + if !ok { + return false + } + for _, v := range list { + for _, cv := range containsList { + if reflect.DeepEqual(v, cv) { + return true + } + } + } + return false +} + +func (s *SetPropertyType) Contains(value any, contains any) bool { + valSet, ok := value.(set.HashedSet[string, any]) + if !ok { + return false + } + containsSet, ok := contains.(set.HashedSet[string, any]) + if !ok { + return false + } + for _, v := range containsSet.M { + for _, val := range valSet.M { + if reflect.DeepEqual(v, val) { + return true + } + } + } + return false +} + +func (s *StringPropertyType) Contains(value any, contains any) bool { + vString, ok := value.(string) + if !ok { + return false + } + cString, ok := contains.(string) + if !ok { + return false + } + return strings.Contains(vString, cString) +} + +func (i *IntPropertyType) Contains(value any, contains any) bool { + return value == contains +} + +func (f *FloatPropertyType) Contains(value any, contains any) bool { + return value == contains +} + +func (b *BoolPropertyType) Contains(value any, contains any) bool { + return value == contains +} + +func (b *AnyPropertyType) Contains(value any, contains any) bool { + return value == contains +} + +func (r *ResourcePropertyType) Contains(value any, contains any) bool { + return value == contains +} + +func (p *PropertyRefPropertyType) Contains(value any, contains any) bool { + return value == contains +} diff --git a/pkg/templates/aws/edges/private_dns_namespace-ecs_service.yaml b/pkg/templates/aws/edges/private_dns_namespace-ecs_service.yaml new file mode 100644 index 000000000..a446bf708 --- /dev/null +++ b/pkg/templates/aws/edges/private_dns_namespace-ecs_service.yaml @@ -0,0 +1,5 @@ +source: aws:private_dns_namespace +target: aws:ecs_service + +classification: + - service_endpoint diff --git a/pkg/templates/aws/resources/ecs_service.yaml b/pkg/templates/aws/resources/ecs_service.yaml index 59a97dfa6..a241e1bee 100644 --- a/pkg/templates/aws/resources/ecs_service.yaml +++ b/pkg/templates/aws/resources/ecs_service.yaml @@ -70,10 +70,15 @@ consumption: - model: EnvironmentVariables property_path: EnvironmentVariables resource: '{{ fieldValue "TaskDefinition" .Self }}' + emitted: + - model: EnvironmentVariables + value: + '{{ .Self.Name }}_ECS_SERVICE_NAME': '{{ .Self.Name }}' path_satisfaction: as_target: - network + - service_endpoint as_source: - network#Subnets