Skip to content

Commit

Permalink
k8s fixes for network access
Browse files Browse the repository at this point in the history
  • Loading branch information
Jordan Singer committed Nov 17, 2023
1 parent e12f14c commit 883119f
Show file tree
Hide file tree
Showing 24 changed files with 430 additions and 18 deletions.
5 changes: 3 additions & 2 deletions pkg/engine2/operational_eval/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,17 @@ func (eval *Evaluator) resourceVertices(
changes := newChanges()
var errs error

addProp := func(prop *knowledgebase.Property) {
addProp := func(prop *knowledgebase.Property) error {
vertex := &propertyVertex{
Ref: construct.PropertyRef{Resource: res.ID, Property: prop.Path},
Template: prop,
EdgeRules: make(map[construct.SimpleEdge][]knowledgebase.OperationalRule),
}

errs = errors.Join(errs, changes.AddVertexAndDeps(eval, vertex))
return nil
}
tmpl.LoopProperties(res, addProp)
errs = errors.Join(errs, tmpl.LoopProperties(res, addProp))
return changes, errs
}

Expand Down
253 changes: 253 additions & 0 deletions pkg/engine2/path_selection/edge_validity.go
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
}
18 changes: 14 additions & 4 deletions pkg/engine2/path_selection/path_expansion.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,15 @@ func handleProperties(
continue
}

handleProp := func(prop *knowledgebase.Property) {
handleProp := func(prop *knowledgebase.Property) error {
oldId := res.ID
opRuleCtx := operational_rule.OperationalRuleContext{
Solution: ctx,
Property: prop,
Data: knowledgebase.DynamicValueData{Resource: res.ID},
}
if prop.OperationalRule == nil {
return
return nil
}
for _, step := range prop.OperationalRule.Steps {
for _, selector := range step.Resources {
Expand Down Expand Up @@ -219,8 +219,9 @@ func handleProperties(
if prop.Namespace && oldId.Namespace != res.ID.Namespace {
errs = errors.Join(errs, construct.ReplaceResource(g, oldId, res))
}
return nil
}
rt.LoopProperties(res, handleProp)
errs = errors.Join(errs, rt.LoopProperties(res, handleProp))
}
return errs
}
Expand Down Expand Up @@ -334,6 +335,7 @@ func ExpandPath(
construct.SimpleEdge{Source: input.Dep.Source.ID, Target: input.Dep.Target.ID},
source.id, target.id,
source.divideWeightBy, target.divideWeightBy,
input.Classification,
ctx.KnowledgeBase())

tmpl := ctx.KnowledgeBase().GetEdgeTemplate(source.id, target.id)
Expand All @@ -358,7 +360,15 @@ func ExpandPath(
}
}

err := input.TempGraph.AddEdge(source.id, target.id, graph.EdgeWeight(weight))
valid, err := checkUniquenessValidity(ctx, source.id, target.id)
if err != nil {
errs = errors.Join(errs, err)
}
if !valid {
return
}

err = input.TempGraph.AddEdge(source.id, target.id, graph.EdgeWeight(weight))
if err != nil && !errors.Is(err, graph.ErrEdgeAlreadyExists) && !errors.Is(err, graph.ErrEdgeCreatesCycle) {
errs = errors.Join(errs, err)
}
Expand Down
20 changes: 18 additions & 2 deletions pkg/engine2/path_selection/path_selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func BuildPathSelectionGraph(
if err != nil && !errors.Is(err, graph.ErrVertexAlreadyExists) {
return nil, fmt.Errorf("failed to add target vertex to path selection graph for %s: %w", dep, err)
}
err = tempGraph.AddEdge(dep.Source, dep.Target, graph.EdgeWeight(calculateEdgeWeight(dep, dep.Source, dep.Target, 0, 0, kb)))
err = tempGraph.AddEdge(dep.Source, dep.Target, graph.EdgeWeight(calculateEdgeWeight(dep, dep.Source, dep.Target, 0, 0, classification, kb)))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -103,7 +103,7 @@ func BuildPathSelectionGraph(
if !prevRes.IsZero() {
edgeTemplate := kb.GetEdgeTemplate(prevRes, id)
if edgeTemplate != nil && !edgeTemplate.DirectEdgeOnly {
err := tempGraph.AddEdge(prevRes, id, graph.EdgeWeight(calculateEdgeWeight(dep, prevRes, id, 0, 0, kb)))
err := tempGraph.AddEdge(prevRes, id, graph.EdgeWeight(calculateEdgeWeight(dep, prevRes, id, 0, 0, classification, kb)))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -162,6 +162,7 @@ func calculateEdgeWeight(
dep construct.SimpleEdge,
source, target construct.ResourceId,
divideSourceBy, divideTargetBy int,
classification string,
kb knowledgebase.TemplateKB,
) int {
if divideSourceBy == 0 {
Expand All @@ -170,6 +171,21 @@ func calculateEdgeWeight(
if divideTargetBy == 0 {
divideTargetBy = 1
}

// check to see if the resources match the classification being solved and account for their weights accordingly
sourceTemplate, err := kb.GetResourceTemplate(source)
if err == nil || sourceTemplate != nil {
if collectionutil.Contains(sourceTemplate.Classification.Is, classification) {
divideSourceBy += 10
}
}
targetTemplate, err := kb.GetResourceTemplate(target)
if err == nil || targetTemplate != nil {
if collectionutil.Contains(targetTemplate.Classification.Is, classification) {
divideTargetBy += 10
}
}

// We start with a weight of 10 for glue and 10000 for functionality for newly created edges of "phantom" resources
// We do so to allow for the preference of existing resources since we can multiply these weights by a decimal
// This will achieve priority for existing resources over newly created ones
Expand Down
1 change: 1 addition & 0 deletions pkg/infra/iac3/templates/aws/eks_cluster/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ function properties(object: aws.eks.Cluster, args: Args) {
Name: object.name,
ClusterEndpoint: object.endpoint,
CertificateAuthorityData: object.certificateAuthorities[0].data,
ClusterSecurityGroup: object.vpcConfig.clusterSecurityGroupId,
}
}
Loading

0 comments on commit 883119f

Please sign in to comment.