-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Jordan Singer
committed
Nov 17, 2023
1 parent
e12f14c
commit 883119f
Showing
24 changed files
with
430 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
package path_selection | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"reflect" | ||
|
||
"github.com/dominikbraun/graph" | ||
"github.com/klothoplatform/klotho/pkg/collectionutil" | ||
construct "github.com/klothoplatform/klotho/pkg/construct2" | ||
"github.com/klothoplatform/klotho/pkg/engine2/solution_context" | ||
knowledgebase "github.com/klothoplatform/klotho/pkg/knowledge_base2" | ||
) | ||
|
||
// checkUniquenessValidity checks if the candidate is valid based on if it is intended to be created as unique resource | ||
// for another resource. If the resource was created by an operational rule with the unique flag, we wont consider it as valid | ||
func checkUniquenessValidity( | ||
ctx solution_context.SolutionContext, | ||
src, trgt construct.ResourceId, | ||
) (bool, error) { | ||
// check if the node is a phantom node | ||
source, err := ctx.RawView().Vertex(src) | ||
switch { | ||
case errors.Is(err, graph.ErrVertexNotFound): | ||
source = &construct.Resource{ID: src} | ||
case err != nil: | ||
return false, err | ||
} | ||
|
||
target, err := ctx.RawView().Vertex(trgt) | ||
switch { | ||
case errors.Is(err, graph.ErrVertexNotFound): | ||
target = &construct.Resource{ID: trgt} | ||
case err != nil: | ||
return false, err | ||
} | ||
|
||
// check if the upstream resource has a unique rule for the matched resource type | ||
valid, err := checkProperties(ctx, target, source, knowledgebase.DirectionUpstream) | ||
if err != nil { | ||
return false, err | ||
} | ||
if !valid { | ||
return false, nil | ||
} | ||
|
||
// check if the downstream resource has a unique rule for the matched resource type | ||
valid, err = checkProperties(ctx, source, target, knowledgebase.DirectionDownstream) | ||
if err != nil { | ||
return false, err | ||
} | ||
if !valid { | ||
return false, nil | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
// check properties checks the resource's properties to make sure its not supposed to have a unique toCheck type | ||
// if it is, it makes sure that the toCheck is not used elsewhere or already being used as its unique type | ||
func checkProperties(ctx solution_context.SolutionContext, resource, toCheck *construct.Resource, direction knowledgebase.Direction) (bool, error) { | ||
//check if the upstream resource has a unique rule for the matched resource type | ||
template, err := ctx.KnowledgeBase().GetResourceTemplate(resource.ID) | ||
if err != nil || template == nil { | ||
return false, fmt.Errorf("error getting resource template for resource %s: %w", resource.ID, err) | ||
} | ||
explicitlyNotValid := false | ||
explicitlyValid := false | ||
err = template.LoopProperties(resource, func(prop *knowledgebase.Property) error { | ||
if prop.OperationalRule == nil { | ||
return nil | ||
} | ||
for _, step := range prop.OperationalRule.Steps { | ||
if !step.Unique || step.Direction != direction { | ||
continue | ||
} | ||
//check if the upstream resource is the same type as the matched resource type | ||
for _, selector := range step.Resources { | ||
match, err := selector.CanUse(solution_context.DynamicCtx(ctx), knowledgebase.DynamicValueData{Resource: resource.ID}, | ||
toCheck) | ||
if err != nil { | ||
return fmt.Errorf("error checking if resource %s matches selector %s: %w", toCheck, selector, err) | ||
} | ||
// if its a match for the selectors, lets ensure that it has a dependency and exists in the properties of the rul | ||
if !match { | ||
continue | ||
} | ||
property, err := resource.GetProperty(prop.Path) | ||
if err != nil { | ||
return fmt.Errorf("error getting property %s for resource %s: %w", prop.Path, toCheck, err) | ||
} | ||
if property != nil { | ||
if checkIfPropertyContainsResource(property, toCheck.ID) { | ||
explicitlyValid = true | ||
return knowledgebase.ErrStopWalk | ||
} | ||
} else { | ||
loneDep, err := checkIfLoneDependency(ctx, resource.ID, toCheck.ID, direction) | ||
if err != nil { | ||
return err | ||
} | ||
if loneDep { | ||
explicitlyValid = true | ||
return knowledgebase.ErrStopWalk | ||
} | ||
} | ||
explicitlyNotValid = true | ||
return knowledgebase.ErrStopWalk | ||
} | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return false, err | ||
} | ||
if explicitlyValid { | ||
return true, nil | ||
} else if explicitlyNotValid { | ||
return false, nil | ||
} | ||
|
||
// if we cant validate uniqueness off of properties we then need to see if the resource was created to be unique | ||
// check if the upstream resource was created as a unique resource by any of its direct dependents | ||
valid, err := checkIfCreatedAsUniqueValidity(ctx, resource, toCheck, direction) | ||
if err != nil { | ||
return false, err | ||
} | ||
if !valid { | ||
return false, nil | ||
} | ||
return true, nil | ||
} | ||
|
||
// checkIfPropertyContainsResource checks if the property contains the resource id passed in | ||
func checkIfPropertyContainsResource(property interface{}, resource construct.ResourceId) bool { | ||
switch reflect.ValueOf(property).Kind() { | ||
case reflect.Slice, reflect.Array: | ||
for _, p := range property.([]construct.ResourceId) { | ||
if p.Matches(resource) { | ||
return true | ||
} | ||
} | ||
case reflect.Struct: | ||
if id, ok := property.(construct.ResourceId); ok && id.Matches(resource) { | ||
return true | ||
} | ||
if pref, ok := property.(construct.PropertyRef); ok && pref.Resource.Matches(resource) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func checkIfLoneDependency(ctx solution_context.SolutionContext, resource, toCheck construct.ResourceId, direction knowledgebase.Direction) (bool, error) { | ||
var resources []construct.ResourceId | ||
var err error | ||
// we are going to check if the resource was created as a unique resource by any of its direct dependents. if it was and that | ||
// dependent is not the other id, its not a valid candidate for this edge | ||
|
||
// here the direction matches because we are checking the resource for being used by another resource similar to other | ||
if direction == knowledgebase.DirectionDownstream { | ||
resources, err = solution_context.Upstream(ctx, resource, knowledgebase.ResourceDirectLayer) | ||
if err != nil { | ||
return false, err | ||
} | ||
} else { | ||
resources, err = solution_context.Downstream(ctx, resource, knowledgebase.ResourceDirectLayer) | ||
if err != nil { | ||
return false, err | ||
} | ||
} | ||
if len(resources) == 0 { | ||
return true, nil | ||
} else if len(resources) == 1 && resources[0].Matches(toCheck) { | ||
return true, nil | ||
} | ||
return false, nil | ||
} | ||
|
||
// checkIfCreatedAsUnique checks if the resource was created as a unique resource by any of its direct dependents. if it was and that | ||
// dependent is not the other id, its not a valid candidate for this edge | ||
func checkIfCreatedAsUniqueValidity(ctx solution_context.SolutionContext, resource, other *construct.Resource, direction knowledgebase.Direction) (bool, error) { | ||
var resources []construct.ResourceId | ||
var foundMatch bool | ||
var err error | ||
// we are going to check if the resource was created as a unique resource by any of its direct dependents. if it was and that | ||
// dependent is not the other id, its not a valid candidate for this edge | ||
|
||
// here the direction matches because we are checking the resource for being used by another resource similar to other | ||
if direction == knowledgebase.DirectionUpstream { | ||
resources, err = solution_context.Upstream(ctx, resource.ID, knowledgebase.ResourceDirectLayer) | ||
if err != nil { | ||
return false, err | ||
} | ||
} else { | ||
resources, err = solution_context.Downstream(ctx, resource.ID, knowledgebase.ResourceDirectLayer) | ||
if err != nil { | ||
return false, err | ||
} | ||
} | ||
// if the dependencies contains the other resource, dont run any checks as we assume its valid | ||
if collectionutil.Contains(resources, other.ID) { | ||
return true, nil | ||
} | ||
|
||
for _, res := range resources { | ||
|
||
// check if the upstream resource has a unique rule for the matched resource type | ||
template, err := ctx.KnowledgeBase().GetResourceTemplate(res) | ||
if err != nil || template == nil { | ||
return false, fmt.Errorf("error getting resource template for resource %s: %w", res, err) | ||
} | ||
currRes, err := ctx.RawView().Vertex(res) | ||
if err != nil { | ||
return false, err | ||
} | ||
err = template.LoopProperties(currRes, func(prop *knowledgebase.Property) error { | ||
if prop.OperationalRule == nil { | ||
return nil | ||
} | ||
for _, step := range prop.OperationalRule.Steps { | ||
// we want the step to be the opposite of the direction passed in so we know its creating the resource in the direction of the resource | ||
// since we are looking at the resources dependencies | ||
if !step.Unique || step.Direction == direction { | ||
continue | ||
} | ||
//check if the upstream resource is the same type as the matched resource type | ||
for _, selector := range step.Resources { | ||
match, err := selector.CanUse(solution_context.DynamicCtx(ctx), knowledgebase.DynamicValueData{Resource: currRes.ID}, | ||
resource) | ||
if err != nil { | ||
return fmt.Errorf("error checking if resource %s matches selector %s: %w", other, selector, err) | ||
} | ||
// if its a match for the selectors, lets ensure that it has a dependency and exists in the properties of the rul | ||
if !match { | ||
continue | ||
} | ||
|
||
foundMatch = true | ||
return knowledgebase.ErrStopWalk | ||
} | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return false, err | ||
} | ||
if foundMatch { | ||
return false, nil | ||
} | ||
} | ||
return true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.