diff --git a/pkg/engine2/constraints.go b/pkg/engine2/constraints.go index bb964360d..30dd30423 100644 --- a/pkg/engine2/constraints.go +++ b/pkg/engine2/constraints.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - "github.com/dominikbraun/graph" construct "github.com/klothoplatform/klotho/pkg/construct2" "github.com/klothoplatform/klotho/pkg/engine2/constraints" "github.com/klothoplatform/klotho/pkg/engine2/reconciler" @@ -120,46 +119,12 @@ func applyEdgeConstraint(ctx solution_context.SolutionContext, constraint constr } } - removePath := func() error { - paths, err := graph.AllPathsBetween(ctx.DataflowGraph(), constraint.Target.Source, constraint.Target.Target) - switch { - case errors.Is(err, graph.ErrTargetNotReachable): - return nil - case err != nil: - return err - } - - var errs error - - // first we will remove all dependencies that make up the paths from the constraints source to target - for _, path := range paths { - for i, res := range path { - if i == 0 { - continue - } - errs = errors.Join(errs, ctx.OperationalView().RemoveEdge(path[i-1], res)) - } - } - if errs != nil { - return errs - } - - // Next we will try to delete any node in those paths in case they no longer are required for the architecture - // We will pass the explicit field as false so that explicitly added resources do not get deleted - for _, path := range paths { - for _, resource := range path { - errs = errors.Join(errs, reconciler.RemoveResource(ctx, resource, false)) - } - } - return errs - } - switch constraint.Operator { case constraints.MustExistConstraintOperator: return ctx.OperationalView().AddEdge(constraint.Target.Source, constraint.Target.Target) case constraints.MustNotExistConstraintOperator: - return removePath() + return reconciler.RemovePath(constraint.Target.Source, constraint.Target.Target, ctx) } return nil } diff --git a/pkg/engine2/operational_eval/dot.go b/pkg/engine2/operational_eval/dot.go index fd5e7f8af..ed998000c 100644 --- a/pkg/engine2/operational_eval/dot.go +++ b/pkg/engine2/operational_eval/dot.go @@ -39,10 +39,10 @@ func keyAttributes(eval *Evaluator, key Key) map[string]string { if key.PathSatisfication.Classification != "" { extra = append(extra, fmt.Sprintf("<%s>", key.PathSatisfication.Classification)) } - if propertyReferenceChangesBoundary(key.PathSatisfication.Target) { + if key.PathSatisfication.Target.PropertyReferenceChangesBoundary() { extra = append(extra, fmt.Sprintf("target#%s", key.PathSatisfication.Target.PropertyReference)) } - if propertyReferenceChangesBoundary(key.PathSatisfication.Source) { + if key.PathSatisfication.Source.PropertyReferenceChangesBoundary() { extra = append(extra, fmt.Sprintf("source#%s", key.PathSatisfication.Target.PropertyReference)) } if len(extra) > 0 { diff --git a/pkg/engine2/operational_eval/evaluator.go b/pkg/engine2/operational_eval/evaluator.go index 8804ccae6..f84e0ea90 100644 --- a/pkg/engine2/operational_eval/evaluator.go +++ b/pkg/engine2/operational_eval/evaluator.go @@ -125,10 +125,10 @@ func (key Key) String() string { if key.PathSatisfication.Classification != "" { args = append(args, fmt.Sprintf("<%s>", key.PathSatisfication.Classification)) } - if propertyReferenceChangesBoundary(key.PathSatisfication.Target) { + if key.PathSatisfication.Target.PropertyReferenceChangesBoundary() { args = append(args, fmt.Sprintf("target#%s", key.PathSatisfication.Target.PropertyReference)) } - if propertyReferenceChangesBoundary(key.PathSatisfication.Source) { + if key.PathSatisfication.Source.PropertyReferenceChangesBoundary() { args = append(args, fmt.Sprintf("source#%s", key.PathSatisfication.Target.PropertyReference)) } return fmt.Sprintf("Expand(%s)", strings.Join(args, ", ")) diff --git a/pkg/engine2/operational_eval/graph.go b/pkg/engine2/operational_eval/graph.go index 87e24d0d4..66798ef53 100644 --- a/pkg/engine2/operational_eval/graph.go +++ b/pkg/engine2/operational_eval/graph.go @@ -94,7 +94,7 @@ func (eval *Evaluator) pathVertices(source, target construct.ResourceId) (graphC // We are checking to see if either of the source or target nodes will change due to property references, // if there are property references we want to ensure the correct dependency ordering is in place so // we cannot yet split the expansion vertex up or build the temp graph - if propertyReferenceChangesBoundary(satisfication.Source) || propertyReferenceChangesBoundary(satisfication.Target) { + if satisfication.Source.PropertyReferenceChangesBoundary() || satisfication.Target.PropertyReferenceChangesBoundary() { buildTempGraph = false } diff --git a/pkg/engine2/operational_eval/vertex_path_expand.go b/pkg/engine2/operational_eval/vertex_path_expand.go index 545704a20..4a9356421 100644 --- a/pkg/engine2/operational_eval/vertex_path_expand.go +++ b/pkg/engine2/operational_eval/vertex_path_expand.go @@ -310,7 +310,7 @@ func (v *pathExpandVertex) getExpansionsToRun(eval *Evaluator) ([]path_selection return nil, fmt.Errorf("could not find target resource %s: %w", v.Edge.Target, err) } edge := construct.ResourceEdge{Source: sourceRes, Target: targetRes} - expansions, err := DeterminePathSatisfactionInputs(eval.Solution, v.Satisfication, edge) + expansions, err := path_selection.DeterminePathSatisfactionInputs(eval.Solution, v.Satisfication, edge) if err != nil { errs = errors.Join(errs, err) } @@ -510,68 +510,6 @@ func (v *pathExpandVertex) Dependencies(eval *Evaluator) (graphChanges, error) { return changes, errs } -func DeterminePathSatisfactionInputs( - sol solution_context.SolutionContext, - satisfaction knowledgebase.EdgePathSatisfaction, - edge construct.ResourceEdge, -) (expansions []path_selection.ExpansionInput, errs error) { - srcIds := []construct.ResourceId{edge.Source.ID} - targetIds := []construct.ResourceId{edge.Target.ID} - var err error - if propertyReferenceChangesBoundary(satisfaction.Source) { - srcIds, err = solution_context.GetResourcesFromPropertyReference(sol, edge.Source.ID, satisfaction.Source.PropertyReference) - if err != nil { - errs = errors.Join(errs, fmt.Errorf( - "failed to determine path satisfaction inputs. could not find resource %s: %w", - edge.Source.ID, err, - )) - } - } - if propertyReferenceChangesBoundary(satisfaction.Target) { - targetIds, err = solution_context.GetResourcesFromPropertyReference(sol, edge.Target.ID, satisfaction.Target.PropertyReference) - if err != nil { - errs = errors.Join(errs, fmt.Errorf( - "failed to determine path satisfaction inputs. could not find resource %s: %w", - edge.Target.ID, err, - )) - - } - } - - for _, srcId := range srcIds { - for _, targetId := range targetIds { - if srcId == targetId { - continue - } - src, err := sol.RawView().Vertex(srcId) - if err != nil { - errs = errors.Join(errs, fmt.Errorf( - "failed to determine path satisfaction inputs. could not find resource %s: %w", - srcId, err, - )) - continue - } - - target, err := sol.RawView().Vertex(targetId) - if err != nil { - errs = errors.Join(errs, fmt.Errorf( - "failed to determine path satisfaction inputs. could not find resource %s: %w", - targetId, err, - )) - continue - } - - e := construct.ResourceEdge{Source: src, Target: target} - exp := path_selection.ExpansionInput{ - Dep: e, - Classification: satisfaction.Classification, - } - expansions = append(expansions, exp) - } - } - return -} - // getDepsForPropertyRef takes a property reference and recurses down until the property is not filled in on the resource // When we reach resources with missing property references, we know they are the property vertex keys we must depend on func getDepsForPropertyRef( @@ -605,12 +543,3 @@ func getDepsForPropertyRef( } return keys } - -// propertyReferenceChangesBoundary returns whether the [PathSatisfactionRoute] changes the boundary of the path -// via satisfaction rules. Note: validity checks do not change the boundary, so those return false. -func propertyReferenceChangesBoundary(v knowledgebase.PathSatisfactionRoute) bool { - if v.Validity != "" { - return false - } - return v.PropertyReference != "" -} diff --git a/pkg/engine2/path_selection/candidate_weight.go b/pkg/engine2/path_selection/candidate_weight.go index 3357cc33f..89b5dc013 100644 --- a/pkg/engine2/path_selection/candidate_weight.go +++ b/pkg/engine2/path_selection/candidate_weight.go @@ -3,10 +3,11 @@ package path_selection import ( "errors" + "github.com/dominikbraun/graph" "github.com/klothoplatform/klotho/pkg/collectionutil" construct "github.com/klothoplatform/klotho/pkg/construct2" - "github.com/klothoplatform/klotho/pkg/engine2/operational_rule" "github.com/klothoplatform/klotho/pkg/engine2/solution_context" + "github.com/klothoplatform/klotho/pkg/graph_addons" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2" ) @@ -52,7 +53,7 @@ func determineCandidateWeight( weight += 9 } - undirected, err := operational_rule.BuildUndirectedGraph(ctx) + undirected, err := BuildUndirectedGraph(ctx) if err != nil { errs = errors.Join(errs, err) return @@ -97,3 +98,32 @@ func determineCandidateWeight( weight += availableWeight return } + +func BuildUndirectedGraph(ctx solution_context.SolutionContext) (construct.Graph, error) { + undirected := graph.NewWithStore( + construct.ResourceHasher, + graph_addons.NewMemoryStore[construct.ResourceId, *construct.Resource](), + ) + err := undirected.AddVerticesFrom(ctx.RawView()) + if err != nil { + return nil, err + } + edges, err := ctx.RawView().Edges() + if err != nil { + return nil, err + } + for _, e := range edges { + weight := 1 + // increase weights for edges that are connected to a functional resource + if knowledgebase.GetFunctionality(ctx.KnowledgeBase(), e.Source) != knowledgebase.Unknown { + weight = 1000 + } else if knowledgebase.GetFunctionality(ctx.KnowledgeBase(), e.Target) != knowledgebase.Unknown { + weight = 1000 + } + err := undirected.AddEdge(e.Source, e.Target, graph.EdgeWeight(weight)) + if err != nil { + return nil, err + } + } + return undirected, nil +} diff --git a/pkg/engine2/path_selection/paths.go b/pkg/engine2/path_selection/paths.go new file mode 100644 index 000000000..205968171 --- /dev/null +++ b/pkg/engine2/path_selection/paths.go @@ -0,0 +1,159 @@ +package path_selection + +import ( + "errors" + "fmt" + + "github.com/dominikbraun/graph" + construct "github.com/klothoplatform/klotho/pkg/construct2" + "github.com/klothoplatform/klotho/pkg/engine2/solution_context" + knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2" +) + +func GetPaths( + sol solution_context.SolutionContext, + source, target construct.ResourceId, + pathValidityChecks func(source, target construct.ResourceId, path []construct.ResourceId) bool, + hasPathCheck bool, +) ([]construct.Path, error) { + var errs error + var result []construct.Path + pathsCache := map[construct.SimpleEdge][][]construct.ResourceId{} + pathSatisfactions, err := sol.KnowledgeBase().GetPathSatisfactionsFromEdge(source, target) + if err != nil { + return result, err + } + sourceRes, err := sol.RawView().Vertex(source) + if err != nil { + return result, fmt.Errorf("has path could not find source resource %s: %w", source, err) + } + targetRes, err := sol.RawView().Vertex(target) + if err != nil { + return result, fmt.Errorf("has path could not find target resource %s: %w", target, err) + } + edge := construct.ResourceEdge{Source: sourceRes, Target: targetRes} + for _, satisfaction := range pathSatisfactions { + expansions, err := DeterminePathSatisfactionInputs(sol, satisfaction, edge) + if err != nil { + return result, err + } + for _, expansion := range expansions { + simple := construct.SimpleEdge{Source: expansion.Dep.Source.ID, Target: expansion.Dep.Target.ID} + paths, found := pathsCache[simple] + if !found { + var err error + paths, err = graph.AllPathsBetween(sol.RawView(), expansion.Dep.Source.ID, expansion.Dep.Target.ID) + if err != nil { + errs = errors.Join(errs, err) + continue + } + pathsCache[simple] = paths + } + if len(paths) == 0 { + return result, nil + } + // we have to track the result of each expansion because if we cant find a path for a single expansion + // we denote that we dont have an actual path from src -> target + var expansionResult []construct.ResourceId + if expansion.Classification != "" { + PATHS: + for _, path := range paths { + for i, res := range path { + if i == 0 { + continue + } + if et := sol.KnowledgeBase().GetEdgeTemplate(path[i-1], res); et != nil && et.DirectEdgeOnly { + continue PATHS + } + + } + if !PathSatisfiesClassification(sol.KnowledgeBase(), path, expansion.Classification) { + continue PATHS + } + if !pathValidityChecks(source, target, path) { + continue PATHS + } + result = append(result, path) + expansionResult = path + if hasPathCheck { + break + } + } + } else { + expansionResult = paths[0] + for _, path := range paths { + result = append(result, path) + } + if hasPathCheck { + break + } + } + if expansionResult == nil { + return nil, nil + } + } + } + return result, nil +} + +func DeterminePathSatisfactionInputs( + sol solution_context.SolutionContext, + satisfaction knowledgebase.EdgePathSatisfaction, + edge construct.ResourceEdge, +) (expansions []ExpansionInput, errs error) { + srcIds := []construct.ResourceId{edge.Source.ID} + targetIds := []construct.ResourceId{edge.Target.ID} + var err error + if satisfaction.Source.PropertyReferenceChangesBoundary() { + srcIds, err = solution_context.GetResourcesFromPropertyReference(sol, edge.Source.ID, satisfaction.Source.PropertyReference) + if err != nil { + errs = errors.Join(errs, fmt.Errorf( + "failed to determine path satisfaction inputs. could not find resource %s: %w", + edge.Source.ID, err, + )) + } + } + if satisfaction.Target.PropertyReferenceChangesBoundary() { + targetIds, err = solution_context.GetResourcesFromPropertyReference(sol, edge.Target.ID, satisfaction.Target.PropertyReference) + if err != nil { + errs = errors.Join(errs, fmt.Errorf( + "failed to determine path satisfaction inputs. could not find resource %s: %w", + edge.Target.ID, err, + )) + + } + } + + for _, srcId := range srcIds { + for _, targetId := range targetIds { + if srcId == targetId { + continue + } + src, err := sol.RawView().Vertex(srcId) + if err != nil { + errs = errors.Join(errs, fmt.Errorf( + "failed to determine path satisfaction inputs. could not find resource %s: %w", + srcId, err, + )) + continue + } + + target, err := sol.RawView().Vertex(targetId) + if err != nil { + errs = errors.Join(errs, fmt.Errorf( + "failed to determine path satisfaction inputs. could not find resource %s: %w", + targetId, err, + )) + continue + } + + e := construct.ResourceEdge{Source: src, Target: target} + exp := ExpansionInput{ + Dep: e, + Classification: satisfaction.Classification, + } + expansions = append(expansions, exp) + } + } + return +} diff --git a/pkg/engine2/reconciler/remove_edge.go b/pkg/engine2/reconciler/remove_edge.go new file mode 100644 index 000000000..1609fdb2b --- /dev/null +++ b/pkg/engine2/reconciler/remove_edge.go @@ -0,0 +1,210 @@ +package reconciler + +import ( + "errors" + "fmt" + + "github.com/dominikbraun/graph" + construct "github.com/klothoplatform/klotho/pkg/construct2" + "github.com/klothoplatform/klotho/pkg/engine2/path_selection" + "github.com/klothoplatform/klotho/pkg/engine2/solution_context" + "github.com/klothoplatform/klotho/pkg/set" + "go.uber.org/zap" +) + +// RemovePath removes all paths between the source and target node. +// +// It will determine when edges within those paths are used for contexts outside of the source and target paths and not remove them. +func RemovePath( + source, target construct.ResourceId, + ctx solution_context.SolutionContext, +) error { + zap.S().Infof("Removing path %s -> %s", source, target) + paths, err := graph.AllPathsBetween(ctx.DataflowGraph(), source, target) + switch { + case errors.Is(err, graph.ErrTargetNotReachable): + return nil + case err != nil: + return err + } + + nodes := nodesInPaths(paths) + used, err := nodesUsedOutsideOfContext(nodes, ctx) + if err != nil { + return err + } + edges, err := findEdgesUsedInOtherPathSelection(source, target, nodes, ctx) + if err != nil { + return err + } + + var errs error + for _, path := range paths { + errs = errors.Join(errs, removeSinglePath(source, target, path, used, edges, ctx)) + } + + // Next we will try to delete any node in those paths in case they no longer are required for the architecture + // We will pass the explicit field as false so that explicitly added resources do not get deleted + for _, path := range paths { + for _, resource := range path { + errs = errors.Join(errs, RemoveResource(ctx, resource, false)) + } + } + return errs +} + +// removeSinglePath removes all edges in a single path between the source and target node, if they are allowed to be removed. +// +// in order for an edge to be removed: +// The source of the edge must not have other upstream dependencies (signaling there are other paths its connecting) +// The edge must not be used in path solving another connection from the source or to the target +func removeSinglePath( + source, target construct.ResourceId, + path []construct.ResourceId, + used set.Set[construct.ResourceId], + edges set.Set[construct.SimpleEdge], + ctx solution_context.SolutionContext, +) error { + var errs error + // first we will remove all dependencies that make up the paths from the constraints source to target + for i, res := range path { + if i == 0 { + continue + } + // check if the previous resource is used outside of its context. + // Since we are deleting the edge downstream we have to make sure the source is not used, + // resulting in this edge being a part of another path + if used.Contains(path[i-1]) && !target.Matches(path[i-1]) && !source.Matches(path[i-1]) { + continue + } + if edges.Contains(construct.SimpleEdge{Source: path[i-1], Target: res}) { + continue + } + errs = errors.Join(errs, ctx.OperationalView().RemoveEdge(path[i-1], res)) + if i > 1 { + errs = errors.Join(errs, RemoveResource(ctx, path[i-1], false)) + } + } + return errs +} + +func nodesInPaths( + paths [][]construct.ResourceId, +) set.Set[construct.ResourceId] { + nodes := make(set.Set[construct.ResourceId]) + for _, path := range paths { + for _, res := range path { + nodes.Add(res) + } + } + return nodes +} + +// nodesUsedOutsideOfContext returns all nodes that are used outside of the context. +// Being used outside of the context entails that there are upstream connections in the dataflow graph +// outside of the set of nodes used in the all paths between the source and target node. +// +// We only care about upstream because any extra connections downstream will stay intact and wont result in other +// paths being affected +func nodesUsedOutsideOfContext( + nodes set.Set[construct.ResourceId], + ctx solution_context.SolutionContext, +) (set.Set[construct.ResourceId], error) { + var errs error + used := make(set.Set[construct.ResourceId]) + pred, err := ctx.RawView().PredecessorMap() + if err != nil { + return nil, err + } + for node := range nodes { + upstreams := pred[node] + for upstream := range upstreams { + if !nodes.Contains(upstream) { + used.Add(node) + } + } + } + return used, errs +} + +// findEdgesUsedInOtherPathSelection returns all edges that are used in other path selections to the target or from the source. +func findEdgesUsedInOtherPathSelection( + source, target construct.ResourceId, + nodes set.Set[construct.ResourceId], + ctx solution_context.SolutionContext, +) (set.Set[construct.SimpleEdge], error) { + edges := make(set.Set[construct.SimpleEdge]) + var errs error + upstreams, err := construct.AllUpstreamDependencies(ctx.DataflowGraph(), target) + if err != nil { + errs = errors.Join(errs, err) + } + for _, upstream := range upstreams { + if nodes.Contains(upstream) { + continue + } + upstreamRT, err := ctx.KnowledgeBase().GetResourceTemplate(upstream) + if err != nil { + errs = errors.Join(errs, err) + continue + } else if upstreamRT == nil { + errs = errors.Join(errs, fmt.Errorf("resource template %s not found", upstream)) + continue + } + if len(upstreamRT.PathSatisfaction.AsSource) == 0 { + continue + } + paths, err := path_selection.GetPaths(ctx, upstream, target, + func(source, target construct.ResourceId, path []construct.ResourceId) bool { return true }, false) + if err != nil { + errs = errors.Join(errs, err) + continue + } + for _, path := range paths { + for i, res := range path { + if i == 0 { + continue + } + edges.Add(construct.SimpleEdge{Source: path[i-1], Target: res}) + } + } + } + downstreams, err := construct.AllDownstreamDependencies(ctx.DataflowGraph(), source) + if err != nil { + errs = errors.Join(errs, err) + } + for _, downstream := range downstreams { + if nodes.Contains(downstream) { + continue + } + downstreamRT, err := ctx.KnowledgeBase().GetResourceTemplate(downstream) + if err != nil { + errs = errors.Join(errs, err) + continue + } else if downstreamRT == nil { + errs = errors.Join(errs, fmt.Errorf("resource template %s not found", downstream)) + continue + } + if len(downstreamRT.PathSatisfaction.AsTarget) == 0 { + continue + } + + paths, err := path_selection.GetPaths(ctx, source, downstream, + func(source, target construct.ResourceId, path []construct.ResourceId) bool { return true }, false) + if err != nil { + errs = errors.Join(errs, err) + continue + } + + for _, path := range paths { + for i, res := range path { + if i == 0 { + continue + } + edges.Add(construct.SimpleEdge{Source: path[i-1], Target: res}) + + } + } + } + return edges, errs +} diff --git a/pkg/engine2/reconciler/remove_edge_test.go b/pkg/engine2/reconciler/remove_edge_test.go new file mode 100644 index 000000000..25de518af --- /dev/null +++ b/pkg/engine2/reconciler/remove_edge_test.go @@ -0,0 +1,162 @@ +package reconciler + +import ( + "testing" + + "github.com/dominikbraun/graph" + construct "github.com/klothoplatform/klotho/pkg/construct2" + "github.com/klothoplatform/klotho/pkg/construct2/graphtest" + "github.com/klothoplatform/klotho/pkg/engine2/enginetesting" + knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func Test_RemovePath(t *testing.T) { + tests := []struct { + name string + source construct.ResourceId + target construct.ResourceId + mockKB []mock.Call + initialState []any + want []any + }{ + { + name: "remove path simple", + source: construct.ResourceId{Provider: "p", Type: "t", Name: "s"}, + target: construct.ResourceId{Provider: "p", Type: "t", Name: "t"}, + mockKB: []mock.Call{ + { + Method: "GetResourceTemplate", + Arguments: []any{mock.Anything}, + ReturnArguments: []any{&knowledgebase.ResourceTemplate{ + Classification: knowledgebase.Classification{Is: []string{string(knowledgebase.Compute)}}, + }, nil}, + }, + { + Method: "GetEdgeTemplate", + Arguments: []any{mock.Anything, mock.Anything}, + ReturnArguments: []any{&knowledgebase.EdgeTemplate{}}, + }, + }, + initialState: []any{"p:t:s", "p:t:t", "p:t:s -> p:t:t"}, + want: []any{"p:t:s", "p:t:t"}, + }, + { + name: "remove shared path", + source: construct.ResourceId{Provider: "p", Type: "t", Name: "s"}, + target: construct.ResourceId{Provider: "p", Type: "t", Name: "b"}, + mockKB: []mock.Call{ + { + Method: "GetResourceTemplate", + Arguments: []any{mock.Anything}, + ReturnArguments: []any{&knowledgebase.ResourceTemplate{ + Classification: knowledgebase.Classification{Is: []string{string(knowledgebase.Compute)}}, + }, nil}, + }, + { + Method: "GetEdgeTemplate", + Arguments: []any{mock.Anything, mock.Anything}, + ReturnArguments: []any{&knowledgebase.EdgeTemplate{}}, + }, + { + Method: "GetPathSatisfactionsFromEdge", + Arguments: []any{mock.Anything, mock.Anything}, + ReturnArguments: []any{ + []knowledgebase.EdgePathSatisfaction{}, nil, + }, + }, + }, + initialState: []any{"p:t:s", "p:t:t", "p:t:s -> p:t:t", "p:t:a", "p:t:b", "p:t:a -> p:t:t", "p:t:t -> p:t:b"}, + want: []any{"p:t:s", "p:t:t", "p:t:a", "p:t:b", "p:t:a -> p:t:t", "p:t:t -> p:t:b"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + testSolution := enginetesting.NewTestSolution() + for _, call := range tt.mockKB { + testSolution.KB.On(call.Method, call.Arguments...).Return(call.ReturnArguments...) + } + testSolution.LoadState(t, tt.initialState...) + err := RemovePath(tt.source, tt.target, testSolution) + if !assert.NoError(err) { + return + } + expect := graphtest.MakeGraph(t, construct.NewGraph(), tt.want...) + graphtest.AssertGraphEqual(t, expect, testSolution.DataflowGraph(), "graph not expected") + }) + } +} + +func Test_findEdgesUsedInOtherPathSelection(t *testing.T) { + tests := []struct { + name string + source construct.ResourceId + target construct.ResourceId + mockKB []mock.Call + initialState []any + want []construct.SimpleEdge + }{ + { + name: "find shared edges", + source: construct.ResourceId{Provider: "p", Type: "t", Name: "s"}, + target: construct.ResourceId{Provider: "p", Type: "t", Name: "b"}, + mockKB: []mock.Call{ + { + Method: "GetResourceTemplate", + Arguments: []any{mock.Anything}, + ReturnArguments: []any{&knowledgebase.ResourceTemplate{ + Classification: knowledgebase.Classification{Is: []string{string(knowledgebase.Compute)}}, + PathSatisfaction: knowledgebase.PathSatisfaction{ + AsSource: []knowledgebase.PathSatisfactionRoute{ + {Classification: ""}, + }, + AsTarget: []knowledgebase.PathSatisfactionRoute{ + {Classification: ""}, + }, + }, + }, nil}, + }, + { + Method: "GetEdgeTemplate", + Arguments: []any{mock.Anything, mock.Anything}, + ReturnArguments: []any{&knowledgebase.EdgeTemplate{}}, + }, + { + Method: "GetPathSatisfactionsFromEdge", + Arguments: []any{mock.Anything, mock.Anything}, + ReturnArguments: []any{ + []knowledgebase.EdgePathSatisfaction{ + {Classification: ""}, + }, nil, + }, + }, + }, + initialState: []any{"p:t:s", "p:t:t", "p:t:s -> p:t:t", "p:t:a", "p:t:b", "p:t:a -> p:t:t", "p:t:t -> p:t:b"}, + want: []construct.SimpleEdge{ + {Source: construct.ResourceId{Provider: "p", Type: "t", Name: "a"}, Target: construct.ResourceId{Provider: "p", Type: "t", Name: "t"}}, + {Source: construct.ResourceId{Provider: "p", Type: "t", Name: "t"}, Target: construct.ResourceId{Provider: "p", Type: "t", Name: "b"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + testSolution := enginetesting.NewTestSolution() + for _, call := range tt.mockKB { + testSolution.KB.On(call.Method, call.Arguments...).Return(call.ReturnArguments...) + } + testSolution.LoadState(t, tt.initialState...) + paths, err := graph.AllPathsBetween(testSolution.DataflowGraph(), tt.source, tt.target) + if !assert.NoError(err) { + return + } + edges, err := findEdgesUsedInOtherPathSelection(tt.source, tt.target, nodesInPaths(paths), testSolution) + if !assert.NoError(err) { + return + } + assert.ElementsMatch(tt.want, edges.ToSlice()) + }) + } +} diff --git a/pkg/engine2/testdata/remove_path.dataflow-viz.yaml b/pkg/engine2/testdata/remove_path.dataflow-viz.yaml new file mode 100644 index 000000000..e0356003f --- /dev/null +++ b/pkg/engine2/testdata/remove_path.dataflow-viz.yaml @@ -0,0 +1,22 @@ +provider: aws +resources: + rds_instance/rds-instance-1: + children: aws:rds_subnet_group:rds_subnet_group-0,aws:security_group:vpc-0:rds-instance-1-security_group + parent: vpc/vpc-0 + + + vpc/vpc-0: + + lambda_function/lambda_function_3: + children: aws:ecr_image:lambda_function_3-image,aws:iam_role:lambda_function_3-ExecutionRole,aws:log_group:lambda_function_3-log-group + parent: vpc/vpc-0 + + + lambda_function/lambda_function_0: + children: aws:ecr_image:lambda_function_0-image,aws:iam_role:lambda_function_0-ExecutionRole,aws:log_group:lambda_function_0-log-group + parent: vpc/vpc-0 + + lambda_function/lambda_function_0 -> rds_instance/rds-instance-1: + path: aws:iam_role:lambda_function_0-ExecutionRole + + diff --git a/pkg/engine2/testdata/remove_path.expect.yaml b/pkg/engine2/testdata/remove_path.expect.yaml new file mode 100644 index 000000000..4c1f1ab59 --- /dev/null +++ b/pkg/engine2/testdata/remove_path.expect.yaml @@ -0,0 +1,311 @@ +resources: + aws:security_group:vpc-0:lambda_function_0-security_group: + EgressRules: + - CidrBlocks: + - 0.0.0.0/0 + Description: Allows all outbound IPv4 traffic + FromPort: 0 + Protocol: "-1" + ToPort: 0 + IngressRules: + - Description: Allow ingress traffic from within the same security group + FromPort: 0 + Protocol: "-1" + Self: true + ToPort: 0 + Vpc: aws:vpc:vpc-0 + aws:security_group:vpc-0:lambda_function_3-security_group: + EgressRules: + - CidrBlocks: + - 0.0.0.0/0 + Description: Allows all outbound IPv4 traffic + FromPort: 0 + Protocol: "-1" + ToPort: 0 + IngressRules: + - Description: Allow ingress traffic from within the same security group + FromPort: 0 + Protocol: "-1" + Self: true + ToPort: 0 + Vpc: aws:vpc:vpc-0 + aws:lambda_function:lambda_function_0: + EnvironmentVariables: + RDS_INSTANCE_1_RDS_CONNECTION_ARN: aws:rds_instance:rds-instance-1#RdsConnectionArn + RDS_INSTANCE_1_RDS_ENDPOINT: aws:rds_instance:rds-instance-1#Endpoint + RDS_INSTANCE_1_RDS_PASSWORD: aws:rds_instance:rds-instance-1#Password + RDS_INSTANCE_1_RDS_USERNAME: aws:rds_instance:rds-instance-1#Username + ExecutionRole: aws:iam_role:lambda_function_0-ExecutionRole + Image: aws:ecr_image:lambda_function_0-image + LogGroup: aws:log_group:lambda_function_0-log-group + MemorySize: 512 + SecurityGroups: + - aws:security_group:vpc-0:lambda_function_0-security_group + Subnets: + - aws:subnet:vpc-0:subnet-0 + - aws:subnet:vpc-0:subnet-1 + Timeout: 180 + aws:lambda_function:lambda_function_3: + EnvironmentVariables: + RDS_INSTANCE_1_RDS_CONNECTION_ARN: aws:rds_instance:rds-instance-1#RdsConnectionArn + RDS_INSTANCE_1_RDS_ENDPOINT: aws:rds_instance:rds-instance-1#Endpoint + RDS_INSTANCE_1_RDS_PASSWORD: aws:rds_instance:rds-instance-1#Password + RDS_INSTANCE_1_RDS_USERNAME: aws:rds_instance:rds-instance-1#Username + ExecutionRole: aws:iam_role:lambda_function_3-ExecutionRole + Image: aws:ecr_image:lambda_function_3-image + LogGroup: aws:log_group:lambda_function_3-log-group + MemorySize: 512 + SecurityGroups: + - aws:security_group:vpc-0:lambda_function_3-security_group + Subnets: + - aws:subnet:vpc-0:subnet-0 + - aws:subnet:vpc-0:subnet-1 + Timeout: 180 + aws:ecr_image:lambda_function_0-image: + Context: . + Dockerfile: lambda_function_0-image.Dockerfile + Repo: aws:ecr_repo:ecr_repo-0 + aws:iam_role:lambda_function_0-ExecutionRole: + AssumeRolePolicyDoc: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Version: "2012-10-17" + InlinePolicies: + - Name: rds-instance-1-policy + Policy: + Statement: + - Action: + - rds-db:connect + Effect: Allow + Resource: + - aws:rds_instance:rds-instance-1#RdsConnectionArn + Version: "2012-10-17" + ManagedPolicies: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole + aws:ecr_image:lambda_function_3-image: + Context: . + Dockerfile: lambda_function_3-image.Dockerfile + Repo: aws:ecr_repo:ecr_repo-0 + aws:iam_role:lambda_function_3-ExecutionRole: + AssumeRolePolicyDoc: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Version: "2012-10-17" + InlinePolicies: + - Name: rds-instance-1-policy + Policy: + Statement: + - Action: + - rds-db:connect + Effect: Allow + Resource: + - aws:rds_instance:rds-instance-1#RdsConnectionArn + Version: "2012-10-17" + ManagedPolicies: + - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + aws:ecr_repo:ecr_repo-0: + ForceDelete: true + aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip: + aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip: + aws:log_group:lambda_function_0-log-group: + LogGroupName: /aws/lambda/lambda_function_0 + RetentionInDays: 5 + aws:log_group:lambda_function_3-log-group: + LogGroupName: /aws/lambda/lambda_function_3 + RetentionInDays: 5 + aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway: + ElasticIp: aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip + Subnet: aws:subnet:vpc-0:subnet-2 + aws:subnet:vpc-0:subnet-2: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-0 + CidrBlock: 10.0.0.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-2-route_table + Type: public + Vpc: aws:vpc:vpc-0 + aws:route_table_association:subnet-2-subnet-2-route_table: + RouteTable: aws:route_table:vpc-0:subnet-2-route_table + Subnet: aws:subnet:vpc-0:subnet-2 + aws:route_table:vpc-0:subnet-2-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + Gateway: aws:internet_gateway:vpc-0:internet_gateway-0 + Vpc: aws:vpc:vpc-0 + aws:availability_zone:region-0:availability_zone-0: + Index: 0 + Region: aws:region:region-0 + aws:internet_gateway:vpc-0:internet_gateway-0: + Vpc: aws:vpc:vpc-0 + aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway: + ElasticIp: aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip + Subnet: aws:subnet:vpc-0:subnet-3 + aws:subnet:vpc-0:subnet-3: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-1 + CidrBlock: 10.0.64.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-3-route_table + Type: public + Vpc: aws:vpc:vpc-0 + aws:route_table_association:subnet-3-subnet-3-route_table: + RouteTable: aws:route_table:vpc-0:subnet-3-route_table + Subnet: aws:subnet:vpc-0:subnet-3 + aws:route_table:vpc-0:subnet-3-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + Gateway: aws:internet_gateway:vpc-0:internet_gateway-0 + Vpc: aws:vpc:vpc-0 + aws:availability_zone:region-0:availability_zone-1: + Index: 1 + Region: aws:region:region-0 + aws:region:region-0: + aws:rds_instance:rds-instance-1: + AllocatedStorage: 20 + DatabaseName: main + Engine: postgres + EngineVersion: "13.7" + IamDatabaseAuthenticationEnabled: true + InstanceClass: db.t3.micro + SecurityGroups: + - aws:security_group:vpc-0:rds-instance-1-security_group + SkipFinalSnapshot: true + SubnetGroup: aws:rds_subnet_group:rds_subnet_group-0 + aws:rds_subnet_group:rds_subnet_group-0: + Subnets: + - aws:subnet:vpc-0:subnet-0 + - aws:subnet:vpc-0:subnet-1 + aws:subnet:vpc-0:subnet-0: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-0 + CidrBlock: 10.0.128.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-0-route_table + Type: private + Vpc: aws:vpc:vpc-0 + aws:subnet:vpc-0:subnet-1: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-1 + CidrBlock: 10.0.192.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-1-route_table + Type: private + Vpc: aws:vpc:vpc-0 + aws:route_table_association:subnet-0-subnet-0-route_table: + RouteTable: aws:route_table:vpc-0:subnet-0-route_table + Subnet: aws:subnet:vpc-0:subnet-0 + aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:route_table_association:subnet-1-subnet-1-route_table: + RouteTable: aws:route_table:vpc-0:subnet-1-route_table + Subnet: aws:subnet:vpc-0:subnet-1 + aws:security_group:vpc-0:rds-instance-1-security_group: + EgressRules: + - CidrBlocks: + - 0.0.0.0/0 + Description: Allows all outbound IPv4 traffic + FromPort: 0 + Protocol: "-1" + ToPort: 0 + IngressRules: + - CidrBlocks: + - 10.0.192.0/18 + Description: Allow ingress traffic from ip addresses within the subnet subnet-1 + FromPort: 0 + Protocol: "-1" + ToPort: 0 + - Description: Allow ingress traffic from within the same security group + FromPort: 0 + Protocol: "-1" + Self: true + ToPort: 0 + - CidrBlocks: + - 10.0.128.0/18 + Description: Allow ingress traffic from ip addresses within the subnet subnet-0 + FromPort: 0 + Protocol: "-1" + ToPort: 0 + Vpc: aws:vpc:vpc-0 + aws:route_table:vpc-0:subnet-0-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + NatGateway: aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway + Vpc: aws:vpc:vpc-0 + aws:route_table:vpc-0:subnet-1-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + NatGateway: aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway + Vpc: aws:vpc:vpc-0 + aws:vpc:vpc-0: + CidrBlock: 10.0.0.0/16 + EnableDnsHostnames: true + EnableDnsSupport: true +edges: + aws:security_group:vpc-0:lambda_function_0-security_group -> aws:lambda_function:lambda_function_0: + aws:security_group:vpc-0:lambda_function_0-security_group -> aws:vpc:vpc-0: + aws:security_group:vpc-0:lambda_function_3-security_group -> aws:lambda_function:lambda_function_3: + aws:security_group:vpc-0:lambda_function_3-security_group -> aws:vpc:vpc-0: + aws:lambda_function:lambda_function_0 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:lambda_function:lambda_function_0 -> aws:ecr_image:lambda_function_0-image: + aws:lambda_function:lambda_function_0 -> aws:iam_role:lambda_function_0-ExecutionRole: + aws:lambda_function:lambda_function_0 -> aws:subnet:vpc-0:subnet-0: + aws:lambda_function:lambda_function_0 -> aws:subnet:vpc-0:subnet-1: + aws:lambda_function:lambda_function_3 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:lambda_function:lambda_function_3 -> aws:ecr_image:lambda_function_3-image: + aws:lambda_function:lambda_function_3 -> aws:iam_role:lambda_function_3-ExecutionRole: + aws:lambda_function:lambda_function_3 -> aws:subnet:vpc-0:subnet-0: + aws:lambda_function:lambda_function_3 -> aws:subnet:vpc-0:subnet-1: + aws:ecr_image:lambda_function_0-image -> aws:ecr_repo:ecr_repo-0: + aws:iam_role:lambda_function_0-ExecutionRole -> aws:log_group:lambda_function_0-log-group: + aws:iam_role:lambda_function_0-ExecutionRole -> aws:rds_instance:rds-instance-1: + aws:ecr_image:lambda_function_3-image -> aws:ecr_repo:ecr_repo-0: + aws:iam_role:lambda_function_3-ExecutionRole -> aws:log_group:lambda_function_3-log-group: + aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway -> aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip: + aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway -> aws:subnet:vpc-0:subnet-2: + aws:subnet:vpc-0:subnet-2 -> aws:availability_zone:region-0:availability_zone-0: + aws:subnet:vpc-0:subnet-2 -> aws:route_table_association:subnet-2-subnet-2-route_table: + aws:subnet:vpc-0:subnet-2 -> aws:vpc:vpc-0: + aws:route_table_association:subnet-2-subnet-2-route_table -> aws:route_table:vpc-0:subnet-2-route_table: + aws:route_table:vpc-0:subnet-2-route_table -> aws:internet_gateway:vpc-0:internet_gateway-0: + aws:route_table:vpc-0:subnet-2-route_table -> aws:vpc:vpc-0: + aws:availability_zone:region-0:availability_zone-0 -> aws:region:region-0: + aws:internet_gateway:vpc-0:internet_gateway-0 -> aws:vpc:vpc-0: + aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip: + aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:subnet:vpc-0:subnet-3: + aws:subnet:vpc-0:subnet-3 -> aws:availability_zone:region-0:availability_zone-1: + aws:subnet:vpc-0:subnet-3 -> aws:route_table_association:subnet-3-subnet-3-route_table: + aws:subnet:vpc-0:subnet-3 -> aws:vpc:vpc-0: + aws:route_table_association:subnet-3-subnet-3-route_table -> aws:route_table:vpc-0:subnet-3-route_table: + aws:route_table:vpc-0:subnet-3-route_table -> aws:internet_gateway:vpc-0:internet_gateway-0: + aws:route_table:vpc-0:subnet-3-route_table -> aws:vpc:vpc-0: + aws:availability_zone:region-0:availability_zone-1 -> aws:region:region-0: + aws:rds_instance:rds-instance-1 -> aws:rds_subnet_group:rds_subnet_group-0: + aws:rds_subnet_group:rds_subnet_group-0 -> aws:subnet:vpc-0:subnet-0: + aws:rds_subnet_group:rds_subnet_group-0 -> aws:subnet:vpc-0:subnet-1: + aws:subnet:vpc-0:subnet-0 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:subnet:vpc-0:subnet-0 -> aws:availability_zone:region-0:availability_zone-0: + aws:subnet:vpc-0:subnet-0 -> aws:route_table_association:subnet-0-subnet-0-route_table: + aws:subnet:vpc-0:subnet-0 -> aws:security_group:vpc-0:rds-instance-1-security_group: + aws:subnet:vpc-0:subnet-0 -> aws:vpc:vpc-0: + aws:subnet:vpc-0:subnet-1 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:subnet:vpc-0:subnet-1 -> aws:availability_zone:region-0:availability_zone-1: + aws:subnet:vpc-0:subnet-1 -> aws:route_table_association:subnet-1-subnet-1-route_table: + aws:subnet:vpc-0:subnet-1 -> aws:security_group:vpc-0:rds-instance-1-security_group: + aws:subnet:vpc-0:subnet-1 -> aws:vpc:vpc-0: + aws:route_table_association:subnet-0-subnet-0-route_table -> aws:route_table:vpc-0:subnet-0-route_table: + aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group -> aws:log_group:lambda_function_0-log-group: + aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group -> aws:log_group:lambda_function_3-log-group: + aws:route_table_association:subnet-1-subnet-1-route_table -> aws:route_table:vpc-0:subnet-1-route_table: + aws:security_group:vpc-0:rds-instance-1-security_group -> aws:rds_instance:rds-instance-1: + aws:security_group:vpc-0:rds-instance-1-security_group -> aws:vpc:vpc-0: + aws:route_table:vpc-0:subnet-0-route_table -> aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway: + aws:route_table:vpc-0:subnet-0-route_table -> aws:vpc:vpc-0: + aws:route_table:vpc-0:subnet-1-route_table -> aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway: + aws:route_table:vpc-0:subnet-1-route_table -> aws:vpc:vpc-0: diff --git a/pkg/engine2/testdata/remove_path.iac-viz.yaml b/pkg/engine2/testdata/remove_path.iac-viz.yaml new file mode 100644 index 000000000..23072b096 --- /dev/null +++ b/pkg/engine2/testdata/remove_path.iac-viz.yaml @@ -0,0 +1,135 @@ +provider: aws +resources: + vpc/vpc-0: + + region/region-0: + + aws:internet_gateway:vpc-0/internet_gateway-0: + aws:internet_gateway:vpc-0/internet_gateway-0 -> vpc/vpc-0: + + aws:availability_zone:region-0/availability_zone-0: + aws:availability_zone:region-0/availability_zone-0 -> region/region-0: + + aws:route_table:vpc-0/subnet-2-route_table: + aws:route_table:vpc-0/subnet-2-route_table -> aws:internet_gateway:vpc-0/internet_gateway-0: + aws:route_table:vpc-0/subnet-2-route_table -> vpc/vpc-0: + + aws:availability_zone:region-0/availability_zone-1: + aws:availability_zone:region-0/availability_zone-1 -> region/region-0: + + aws:route_table:vpc-0/subnet-3-route_table: + aws:route_table:vpc-0/subnet-3-route_table -> aws:internet_gateway:vpc-0/internet_gateway-0: + aws:route_table:vpc-0/subnet-3-route_table -> vpc/vpc-0: + + elastic_ip/subnet-0-route_table-nat_gateway-elastic_ip: + + aws:subnet:vpc-0/subnet-2: + aws:subnet:vpc-0/subnet-2 -> aws:availability_zone:region-0/availability_zone-0: + aws:subnet:vpc-0/subnet-2 -> aws:route_table:vpc-0/subnet-2-route_table: + aws:subnet:vpc-0/subnet-2 -> vpc/vpc-0: + + elastic_ip/subnet-1-route_table-nat_gateway-elastic_ip: + + aws:subnet:vpc-0/subnet-3: + aws:subnet:vpc-0/subnet-3 -> aws:availability_zone:region-0/availability_zone-1: + aws:subnet:vpc-0/subnet-3 -> aws:route_table:vpc-0/subnet-3-route_table: + aws:subnet:vpc-0/subnet-3 -> vpc/vpc-0: + + aws:nat_gateway:subnet-2/subnet-0-route_table-nat_gateway: + aws:nat_gateway:subnet-2/subnet-0-route_table-nat_gateway -> elastic_ip/subnet-0-route_table-nat_gateway-elastic_ip: + aws:nat_gateway:subnet-2/subnet-0-route_table-nat_gateway -> aws:subnet:vpc-0/subnet-2: + + aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway: + aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway -> elastic_ip/subnet-1-route_table-nat_gateway-elastic_ip: + aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway -> aws:subnet:vpc-0/subnet-3: + + aws:route_table:vpc-0/subnet-0-route_table: + aws:route_table:vpc-0/subnet-0-route_table -> aws:nat_gateway:subnet-2/subnet-0-route_table-nat_gateway: + aws:route_table:vpc-0/subnet-0-route_table -> vpc/vpc-0: + + aws:security_group:vpc-0/rds-instance-1-security_group: + aws:security_group:vpc-0/rds-instance-1-security_group -> vpc/vpc-0: + + aws:route_table:vpc-0/subnet-1-route_table: + aws:route_table:vpc-0/subnet-1-route_table -> aws:nat_gateway:subnet-3/subnet-1-route_table-nat_gateway: + aws:route_table:vpc-0/subnet-1-route_table -> vpc/vpc-0: + + aws:subnet:vpc-0/subnet-0: + aws:subnet:vpc-0/subnet-0 -> aws:availability_zone:region-0/availability_zone-0: + aws:subnet:vpc-0/subnet-0 -> aws:route_table:vpc-0/subnet-0-route_table: + aws:subnet:vpc-0/subnet-0 -> aws:security_group:vpc-0/rds-instance-1-security_group: + aws:subnet:vpc-0/subnet-0 -> vpc/vpc-0: + + aws:subnet:vpc-0/subnet-1: + aws:subnet:vpc-0/subnet-1 -> aws:availability_zone:region-0/availability_zone-1: + aws:subnet:vpc-0/subnet-1 -> aws:route_table:vpc-0/subnet-1-route_table: + aws:subnet:vpc-0/subnet-1 -> aws:security_group:vpc-0/rds-instance-1-security_group: + aws:subnet:vpc-0/subnet-1 -> vpc/vpc-0: + + rds_subnet_group/rds_subnet_group-0: + rds_subnet_group/rds_subnet_group-0 -> aws:subnet:vpc-0/subnet-0: + rds_subnet_group/rds_subnet_group-0 -> aws:subnet:vpc-0/subnet-1: + + ecr_repo/ecr_repo-0: + + log_group/lambda_function_3-log-group: + + log_group/lambda_function_0-log-group: + + rds_instance/rds-instance-1: + rds_instance/rds-instance-1 -> rds_subnet_group/rds_subnet_group-0: + rds_instance/rds-instance-1 -> aws:security_group:vpc-0/rds-instance-1-security_group: + + ecr_image/lambda_function_3-image: + ecr_image/lambda_function_3-image -> ecr_repo/ecr_repo-0: + + iam_role/lambda_function_3-executionrole: + iam_role/lambda_function_3-executionrole -> log_group/lambda_function_3-log-group: + + aws:security_group:vpc-0/lambda_function_3-security_group: + aws:security_group:vpc-0/lambda_function_3-security_group -> vpc/vpc-0: + + ecr_image/lambda_function_0-image: + ecr_image/lambda_function_0-image -> ecr_repo/ecr_repo-0: + + iam_role/lambda_function_0-executionrole: + iam_role/lambda_function_0-executionrole -> log_group/lambda_function_0-log-group: + iam_role/lambda_function_0-executionrole -> rds_instance/rds-instance-1: + + aws:security_group:vpc-0/lambda_function_0-security_group: + aws:security_group:vpc-0/lambda_function_0-security_group -> vpc/vpc-0: + + route_table_association/subnet-3-subnet-3-route_table: + route_table_association/subnet-3-subnet-3-route_table -> aws:route_table:vpc-0/subnet-3-route_table: + route_table_association/subnet-3-subnet-3-route_table -> aws:subnet:vpc-0/subnet-3: + + route_table_association/subnet-2-subnet-2-route_table: + route_table_association/subnet-2-subnet-2-route_table -> aws:route_table:vpc-0/subnet-2-route_table: + route_table_association/subnet-2-subnet-2-route_table -> aws:subnet:vpc-0/subnet-2: + + route_table_association/subnet-1-subnet-1-route_table: + route_table_association/subnet-1-subnet-1-route_table -> aws:route_table:vpc-0/subnet-1-route_table: + route_table_association/subnet-1-subnet-1-route_table -> aws:subnet:vpc-0/subnet-1: + + route_table_association/subnet-0-subnet-0-route_table: + route_table_association/subnet-0-subnet-0-route_table -> aws:route_table:vpc-0/subnet-0-route_table: + route_table_association/subnet-0-subnet-0-route_table -> aws:subnet:vpc-0/subnet-0: + + lambda_function/lambda_function_3: + lambda_function/lambda_function_3 -> ecr_image/lambda_function_3-image: + lambda_function/lambda_function_3 -> iam_role/lambda_function_3-executionrole: + lambda_function/lambda_function_3 -> log_group/lambda_function_3-log-group: + lambda_function/lambda_function_3 -> rds_instance/rds-instance-1: + lambda_function/lambda_function_3 -> aws:security_group:vpc-0/lambda_function_3-security_group: + lambda_function/lambda_function_3 -> aws:subnet:vpc-0/subnet-0: + lambda_function/lambda_function_3 -> aws:subnet:vpc-0/subnet-1: + + lambda_function/lambda_function_0: + lambda_function/lambda_function_0 -> ecr_image/lambda_function_0-image: + lambda_function/lambda_function_0 -> iam_role/lambda_function_0-executionrole: + lambda_function/lambda_function_0 -> log_group/lambda_function_0-log-group: + lambda_function/lambda_function_0 -> rds_instance/rds-instance-1: + lambda_function/lambda_function_0 -> aws:security_group:vpc-0/lambda_function_0-security_group: + lambda_function/lambda_function_0 -> aws:subnet:vpc-0/subnet-0: + lambda_function/lambda_function_0 -> aws:subnet:vpc-0/subnet-1: + diff --git a/pkg/engine2/testdata/remove_path.input.yaml b/pkg/engine2/testdata/remove_path.input.yaml new file mode 100644 index 000000000..9eb66ba2f --- /dev/null +++ b/pkg/engine2/testdata/remove_path.input.yaml @@ -0,0 +1,318 @@ +constraints: +- operator: must_not_exist + scope: edge + target: + source: aws:lambda_function:lambda_function_3 + target: aws:rds_instance:rds-instance-1 +resources: + aws:security_group:vpc-0:lambda_function_0-security_group: + EgressRules: + - CidrBlocks: + - 0.0.0.0/0 + Description: Allows all outbound IPv4 traffic + FromPort: 0 + Protocol: "-1" + ToPort: 0 + IngressRules: + - Description: Allow ingress traffic from within the same security group + FromPort: 0 + Protocol: "-1" + Self: true + ToPort: 0 + Vpc: aws:vpc:vpc-0 + aws:security_group:vpc-0:lambda_function_3-security_group: + EgressRules: + - CidrBlocks: + - 0.0.0.0/0 + Description: Allows all outbound IPv4 traffic + FromPort: 0 + Protocol: "-1" + ToPort: 0 + IngressRules: + - Description: Allow ingress traffic from within the same security group + FromPort: 0 + Protocol: "-1" + Self: true + ToPort: 0 + Vpc: aws:vpc:vpc-0 + aws:lambda_function:lambda_function_0: + EnvironmentVariables: + RDS_INSTANCE_1_RDS_CONNECTION_ARN: aws:rds_instance:rds-instance-1#RdsConnectionArn + RDS_INSTANCE_1_RDS_ENDPOINT: aws:rds_instance:rds-instance-1#Endpoint + RDS_INSTANCE_1_RDS_PASSWORD: aws:rds_instance:rds-instance-1#Password + RDS_INSTANCE_1_RDS_USERNAME: aws:rds_instance:rds-instance-1#Username + ExecutionRole: aws:iam_role:lambda_function_0-ExecutionRole + Image: aws:ecr_image:lambda_function_0-image + LogGroup: aws:log_group:lambda_function_0-log-group + MemorySize: 512 + SecurityGroups: + - aws:security_group:vpc-0:lambda_function_0-security_group + Subnets: + - aws:subnet:vpc-0:subnet-0 + - aws:subnet:vpc-0:subnet-1 + Timeout: 180 + aws:lambda_function:lambda_function_3: + EnvironmentVariables: + RDS_INSTANCE_1_RDS_CONNECTION_ARN: aws:rds_instance:rds-instance-1#RdsConnectionArn + RDS_INSTANCE_1_RDS_ENDPOINT: aws:rds_instance:rds-instance-1#Endpoint + RDS_INSTANCE_1_RDS_PASSWORD: aws:rds_instance:rds-instance-1#Password + RDS_INSTANCE_1_RDS_USERNAME: aws:rds_instance:rds-instance-1#Username + ExecutionRole: aws:iam_role:lambda_function_3-ExecutionRole + Image: aws:ecr_image:lambda_function_3-image + LogGroup: aws:log_group:lambda_function_3-log-group + MemorySize: 512 + SecurityGroups: + - aws:security_group:vpc-0:lambda_function_3-security_group + Subnets: + - aws:subnet:vpc-0:subnet-0 + - aws:subnet:vpc-0:subnet-1 + Timeout: 180 + aws:ecr_image:lambda_function_0-image: + Context: . + Dockerfile: lambda_function_0-image.Dockerfile + Repo: aws:ecr_repo:ecr_repo-0 + aws:iam_role:lambda_function_0-ExecutionRole: + AssumeRolePolicyDoc: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Version: "2012-10-17" + InlinePolicies: + - Name: rds-instance-1-policy + Policy: + Statement: + - Action: + - rds-db:connect + Effect: Allow + Resource: + - aws:rds_instance:rds-instance-1#RdsConnectionArn + Version: "2012-10-17" + ManagedPolicies: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole + aws:ecr_image:lambda_function_3-image: + Context: . + Dockerfile: lambda_function_3-image.Dockerfile + Repo: aws:ecr_repo:ecr_repo-0 + aws:iam_role:lambda_function_3-ExecutionRole: + AssumeRolePolicyDoc: + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Version: "2012-10-17" + InlinePolicies: + - Name: rds-instance-1-policy + Policy: + Statement: + - Action: + - rds-db:connect + Effect: Allow + Resource: + - aws:rds_instance:rds-instance-1#RdsConnectionArn + Version: "2012-10-17" + ManagedPolicies: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole + aws:ecr_repo:ecr_repo-0: + ForceDelete: true + aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip: + aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip: + aws:log_group:lambda_function_0-log-group: + LogGroupName: /aws/lambda/lambda_function_0 + RetentionInDays: 5 + aws:log_group:lambda_function_3-log-group: + LogGroupName: /aws/lambda/lambda_function_3 + RetentionInDays: 5 + aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway: + ElasticIp: aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip + Subnet: aws:subnet:vpc-0:subnet-2 + aws:subnet:vpc-0:subnet-2: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-0 + CidrBlock: 10.0.0.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-2-route_table + Type: public + Vpc: aws:vpc:vpc-0 + aws:route_table_association:subnet-2-subnet-2-route_table: + RouteTable: aws:route_table:vpc-0:subnet-2-route_table + Subnet: aws:subnet:vpc-0:subnet-2 + aws:route_table:vpc-0:subnet-2-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + Gateway: aws:internet_gateway:vpc-0:internet_gateway-0 + Vpc: aws:vpc:vpc-0 + aws:availability_zone:region-0:availability_zone-0: + Index: 0 + Region: aws:region:region-0 + aws:internet_gateway:vpc-0:internet_gateway-0: + Vpc: aws:vpc:vpc-0 + aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway: + ElasticIp: aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip + Subnet: aws:subnet:vpc-0:subnet-3 + aws:subnet:vpc-0:subnet-3: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-1 + CidrBlock: 10.0.64.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-3-route_table + Type: public + Vpc: aws:vpc:vpc-0 + aws:route_table_association:subnet-3-subnet-3-route_table: + RouteTable: aws:route_table:vpc-0:subnet-3-route_table + Subnet: aws:subnet:vpc-0:subnet-3 + aws:route_table:vpc-0:subnet-3-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + Gateway: aws:internet_gateway:vpc-0:internet_gateway-0 + Vpc: aws:vpc:vpc-0 + aws:availability_zone:region-0:availability_zone-1: + Index: 1 + Region: aws:region:region-0 + aws:region:region-0: + aws:rds_instance:rds-instance-1: + AllocatedStorage: 20 + DatabaseName: main + Engine: postgres + EngineVersion: "13.7" + IamDatabaseAuthenticationEnabled: true + InstanceClass: db.t3.micro + SecurityGroups: + - aws:security_group:vpc-0:rds-instance-1-security_group + SkipFinalSnapshot: true + SubnetGroup: aws:rds_subnet_group:rds_subnet_group-0 + aws:rds_subnet_group:rds_subnet_group-0: + Subnets: + - aws:subnet:vpc-0:subnet-0 + - aws:subnet:vpc-0:subnet-1 + aws:subnet:vpc-0:subnet-0: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-0 + CidrBlock: 10.0.128.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-0-route_table + Type: private + Vpc: aws:vpc:vpc-0 + aws:subnet:vpc-0:subnet-1: + AvailabilityZone: aws:availability_zone:region-0:availability_zone-1 + CidrBlock: 10.0.192.0/18 + MapPublicIpOnLaunch: false + RouteTable: aws:route_table:vpc-0:subnet-1-route_table + Type: private + Vpc: aws:vpc:vpc-0 + aws:route_table_association:subnet-0-subnet-0-route_table: + RouteTable: aws:route_table:vpc-0:subnet-0-route_table + Subnet: aws:subnet:vpc-0:subnet-0 + aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:route_table_association:subnet-1-subnet-1-route_table: + RouteTable: aws:route_table:vpc-0:subnet-1-route_table + Subnet: aws:subnet:vpc-0:subnet-1 + aws:security_group:vpc-0:rds-instance-1-security_group: + EgressRules: + - CidrBlocks: + - 0.0.0.0/0 + Description: Allows all outbound IPv4 traffic + FromPort: 0 + Protocol: "-1" + ToPort: 0 + IngressRules: + - Description: Allow ingress traffic from within the same security group + FromPort: 0 + Protocol: "-1" + Self: true + ToPort: 0 + - CidrBlocks: + - 10.0.128.0/18 + Description: Allow ingress traffic from ip addresses within the subnet subnet-0 + FromPort: 0 + Protocol: "-1" + ToPort: 0 + - CidrBlocks: + - 10.0.192.0/18 + Description: Allow ingress traffic from ip addresses within the subnet subnet-1 + FromPort: 0 + Protocol: "-1" + ToPort: 0 + Vpc: aws:vpc:vpc-0 + aws:route_table:vpc-0:subnet-0-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + NatGateway: aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway + Vpc: aws:vpc:vpc-0 + aws:route_table:vpc-0:subnet-1-route_table: + Routes: + - CidrBlock: 0.0.0.0/0 + NatGateway: aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway + Vpc: aws:vpc:vpc-0 + aws:vpc:vpc-0: + CidrBlock: 10.0.0.0/16 + EnableDnsHostnames: true + EnableDnsSupport: true +edges: + aws:security_group:vpc-0:lambda_function_0-security_group -> aws:lambda_function:lambda_function_0: + aws:security_group:vpc-0:lambda_function_0-security_group -> aws:vpc:vpc-0: + aws:security_group:vpc-0:lambda_function_3-security_group -> aws:lambda_function:lambda_function_3: + aws:security_group:vpc-0:lambda_function_3-security_group -> aws:vpc:vpc-0: + aws:lambda_function:lambda_function_0 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:lambda_function:lambda_function_0 -> aws:ecr_image:lambda_function_0-image: + aws:lambda_function:lambda_function_0 -> aws:iam_role:lambda_function_0-ExecutionRole: + aws:lambda_function:lambda_function_0 -> aws:subnet:vpc-0:subnet-0: + aws:lambda_function:lambda_function_0 -> aws:subnet:vpc-0:subnet-1: + aws:lambda_function:lambda_function_3 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:lambda_function:lambda_function_3 -> aws:ecr_image:lambda_function_3-image: + aws:lambda_function:lambda_function_3 -> aws:iam_role:lambda_function_3-ExecutionRole: + aws:lambda_function:lambda_function_3 -> aws:subnet:vpc-0:subnet-0: + aws:lambda_function:lambda_function_3 -> aws:subnet:vpc-0:subnet-1: + aws:ecr_image:lambda_function_0-image -> aws:ecr_repo:ecr_repo-0: + aws:iam_role:lambda_function_0-ExecutionRole -> aws:log_group:lambda_function_0-log-group: + aws:iam_role:lambda_function_0-ExecutionRole -> aws:rds_instance:rds-instance-1: + aws:ecr_image:lambda_function_3-image -> aws:ecr_repo:ecr_repo-0: + aws:iam_role:lambda_function_3-ExecutionRole -> aws:log_group:lambda_function_3-log-group: + aws:iam_role:lambda_function_3-ExecutionRole -> aws:rds_instance:rds-instance-1: + aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway -> aws:elastic_ip:subnet-0-route_table-nat_gateway-elastic_ip: + aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway -> aws:subnet:vpc-0:subnet-2: + aws:subnet:vpc-0:subnet-2 -> aws:availability_zone:region-0:availability_zone-0: + aws:subnet:vpc-0:subnet-2 -> aws:route_table_association:subnet-2-subnet-2-route_table: + aws:subnet:vpc-0:subnet-2 -> aws:vpc:vpc-0: + aws:route_table_association:subnet-2-subnet-2-route_table -> aws:route_table:vpc-0:subnet-2-route_table: + aws:route_table:vpc-0:subnet-2-route_table -> aws:internet_gateway:vpc-0:internet_gateway-0: + aws:route_table:vpc-0:subnet-2-route_table -> aws:vpc:vpc-0: + aws:availability_zone:region-0:availability_zone-0 -> aws:region:region-0: + aws:internet_gateway:vpc-0:internet_gateway-0 -> aws:vpc:vpc-0: + aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip: + aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:subnet:vpc-0:subnet-3: + aws:subnet:vpc-0:subnet-3 -> aws:availability_zone:region-0:availability_zone-1: + aws:subnet:vpc-0:subnet-3 -> aws:route_table_association:subnet-3-subnet-3-route_table: + aws:subnet:vpc-0:subnet-3 -> aws:vpc:vpc-0: + aws:route_table_association:subnet-3-subnet-3-route_table -> aws:route_table:vpc-0:subnet-3-route_table: + aws:route_table:vpc-0:subnet-3-route_table -> aws:internet_gateway:vpc-0:internet_gateway-0: + aws:route_table:vpc-0:subnet-3-route_table -> aws:vpc:vpc-0: + aws:availability_zone:region-0:availability_zone-1 -> aws:region:region-0: + aws:rds_instance:rds-instance-1 -> aws:rds_subnet_group:rds_subnet_group-0: + aws:rds_subnet_group:rds_subnet_group-0 -> aws:subnet:vpc-0:subnet-0: + aws:rds_subnet_group:rds_subnet_group-0 -> aws:subnet:vpc-0:subnet-1: + aws:subnet:vpc-0:subnet-0 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:subnet:vpc-0:subnet-0 -> aws:availability_zone:region-0:availability_zone-0: + aws:subnet:vpc-0:subnet-0 -> aws:route_table_association:subnet-0-subnet-0-route_table: + aws:subnet:vpc-0:subnet-0 -> aws:security_group:vpc-0:rds-instance-1-security_group: + aws:subnet:vpc-0:subnet-0 -> aws:vpc:vpc-0: + aws:subnet:vpc-0:subnet-1 -> aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group: + aws:subnet:vpc-0:subnet-1 -> aws:availability_zone:region-0:availability_zone-1: + aws:subnet:vpc-0:subnet-1 -> aws:route_table_association:subnet-1-subnet-1-route_table: + aws:subnet:vpc-0:subnet-1 -> aws:security_group:vpc-0:rds-instance-1-security_group: + aws:subnet:vpc-0:subnet-1 -> aws:vpc:vpc-0: + aws:route_table_association:subnet-0-subnet-0-route_table -> aws:route_table:vpc-0:subnet-0-route_table: + aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group -> aws:log_group:lambda_function_0-log-group: + aws:SERVICE_API:lambda_function_0-lambda_function_0-log-group -> aws:log_group:lambda_function_3-log-group: + aws:route_table_association:subnet-1-subnet-1-route_table -> aws:route_table:vpc-0:subnet-1-route_table: + aws:security_group:vpc-0:rds-instance-1-security_group -> aws:rds_instance:rds-instance-1: + aws:security_group:vpc-0:rds-instance-1-security_group -> aws:vpc:vpc-0: + aws:route_table:vpc-0:subnet-0-route_table -> aws:nat_gateway:subnet-2:subnet-0-route_table-nat_gateway: + aws:route_table:vpc-0:subnet-0-route_table -> aws:vpc:vpc-0: + aws:route_table:vpc-0:subnet-1-route_table -> aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway: + aws:route_table:vpc-0:subnet-1-route_table -> aws:vpc:vpc-0: diff --git a/pkg/engine2/visualizer.go b/pkg/engine2/visualizer.go index 2a98a8400..6b077fe3f 100644 --- a/pkg/engine2/visualizer.go +++ b/pkg/engine2/visualizer.go @@ -9,7 +9,6 @@ import ( "github.com/dominikbraun/graph" "github.com/klothoplatform/klotho/pkg/collectionutil" construct "github.com/klothoplatform/klotho/pkg/construct2" - "github.com/klothoplatform/klotho/pkg/engine2/operational_eval" "github.com/klothoplatform/klotho/pkg/engine2/path_selection" "github.com/klothoplatform/klotho/pkg/engine2/solution_context" klotho_io "github.com/klothoplatform/klotho/pkg/io" @@ -310,72 +309,16 @@ func HasPath(topo Topology, sol solution_context.SolutionContext, source, target return false, nil } return checkPaths(topo, sol, source, target) - } func checkPaths(topo Topology, sol solution_context.SolutionContext, source, target construct.ResourceId) (bool, error) { - var errs error - pathsCache := map[construct.SimpleEdge][][]construct.ResourceId{} - pathSatisfactions, err := sol.KnowledgeBase().GetPathSatisfactionsFromEdge(source, target) - if err != nil { - return false, err - } - 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) - } - edge := construct.ResourceEdge{Source: sourceRes, Target: targetRes} - for _, satisfaction := range pathSatisfactions { - expansions, err := operational_eval.DeterminePathSatisfactionInputs(sol, satisfaction, edge) - if err != nil { - return false, err - } - for _, expansion := range expansions { - simple := construct.SimpleEdge{Source: expansion.Dep.Source.ID, Target: expansion.Dep.Target.ID} - paths, found := pathsCache[simple] - if !found { - var err error - paths, err = graph.AllPathsBetween(sol.RawView(), expansion.Dep.Source.ID, expansion.Dep.Target.ID) - if err != nil { - errs = errors.Join(errs, err) - continue - } - pathsCache[simple] = paths - } - if len(paths) == 0 { - return false, nil - } - containedClassification := false - if expansion.Classification != "" { - PATHS: - for _, path := range paths { - for i, res := range path { - if i == 0 { - continue - } - if et := sol.KnowledgeBase().GetEdgeTemplate(path[i-1], res); et != nil && et.DirectEdgeOnly { - continue PATHS - } - if i < len(path)-1 && (topo.Nodes[res.String()] != nil && res != source && res != target) { - continue PATHS - } - } - if path_selection.PathSatisfiesClassification(sol.KnowledgeBase(), path, expansion.Classification) { - containedClassification = true - break - } - } - } else { - containedClassification = true - } - if !containedClassification { - return false, nil + paths, err := path_selection.GetPaths(sol, source, target, func(source, target construct.ResourceId, path []construct.ResourceId) bool { + for i, res := range path { + if i < len(path)-1 && (topo.Nodes[res.String()] != nil && res != source && res != target) { + return false } } - } - return true, nil + return true + }, true) + return len(paths) > 0, err } diff --git a/pkg/knowledge_base2/path_satisfaction.go b/pkg/knowledge_base2/path_satisfaction.go index 25129d359..8fa38e7e7 100644 --- a/pkg/knowledge_base2/path_satisfaction.go +++ b/pkg/knowledge_base2/path_satisfaction.go @@ -94,3 +94,10 @@ func (kb *KnowledgeBase) GetPathSatisfactionsFromEdge(source, target construct.R } return pathSatisfications, nil } + +func (v PathSatisfactionRoute) PropertyReferenceChangesBoundary() bool { + if v.Validity != "" { + return false + } + return v.PropertyReference != "" +} diff --git a/pkg/templates/aws/resources/elasticache_subnet_group.yaml b/pkg/templates/aws/resources/elasticache_subnet_group.yaml index 596c32f71..d17203ea0 100644 --- a/pkg/templates/aws/resources/elasticache_subnet_group.yaml +++ b/pkg/templates/aws/resources/elasticache_subnet_group.yaml @@ -1,6 +1,19 @@ qualified_type_name: aws:elasticache_subnet_group display_name: ElastiCache Subnet Group - +sanitize_name: + # Identifiers have these naming constraints: + # - Must contain 1–63 alphanumeric characters or hyphens. + # - First character must be a letter. + # - Can't end with a hyphen or contain two consecutive hyphens. + | + {{ . + | replace `^[^[:alpha:]]+` "" + | replace `--+` "-" + | replace `-$` "" + | replace `[^[:alnum:]-]+` "-" + | length 1 63 + }} + properties: Subnets: type: list(resource(aws:subnet))