diff --git a/pkg/engine/operational_eval/dependency_capture_mock_test.go b/pkg/engine/operational_eval/dependency_capture_mock_test.go index 336425fe7..10154b47b 100644 --- a/pkg/engine/operational_eval/dependency_capture_mock_test.go +++ b/pkg/engine/operational_eval/dependency_capture_mock_test.go @@ -12,8 +12,8 @@ package operational_eval import ( reflect "reflect" - construct2 "github.com/klothoplatform/klotho/pkg/construct" - knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledgebase" + construct "github.com/klothoplatform/klotho/pkg/construct" + knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" gomock "go.uber.org/mock/gomock" ) @@ -41,10 +41,10 @@ func (m *MockdependencyCapturer) EXPECT() *MockdependencyCapturerMockRecorder { } // DAG mocks base method. -func (m *MockdependencyCapturer) DAG() construct2.Graph { +func (m *MockdependencyCapturer) DAG() construct.Graph { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DAG") - ret0, _ := ret[0].(construct2.Graph) + ret0, _ := ret[0].(construct.Graph) return ret0 } @@ -55,7 +55,7 @@ func (mr *MockdependencyCapturerMockRecorder) DAG() *gomock.Call { } // ExecuteDecode mocks base method. -func (m *MockdependencyCapturer) ExecuteDecode(tmpl string, data knowledgebase2.DynamicValueData, value any) error { +func (m *MockdependencyCapturer) ExecuteDecode(tmpl string, data knowledgebase.DynamicValueData, value any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ExecuteDecode", tmpl, data, value) ret0, _ := ret[0].(error) @@ -69,7 +69,7 @@ func (mr *MockdependencyCapturerMockRecorder) ExecuteDecode(tmpl, data, value an } // ExecuteOpRule mocks base method. -func (m *MockdependencyCapturer) ExecuteOpRule(data knowledgebase2.DynamicValueData, rule knowledgebase2.OperationalRule) error { +func (m *MockdependencyCapturer) ExecuteOpRule(data knowledgebase.DynamicValueData, rule knowledgebase.OperationalRule) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ExecuteOpRule", data, rule) ret0, _ := ret[0].(error) @@ -83,7 +83,7 @@ func (mr *MockdependencyCapturerMockRecorder) ExecuteOpRule(data, rule any) *gom } // ExecutePropertyRule mocks base method. -func (m *MockdependencyCapturer) ExecutePropertyRule(data knowledgebase2.DynamicValueData, rule knowledgebase2.PropertyRule) error { +func (m *MockdependencyCapturer) ExecutePropertyRule(data knowledgebase.DynamicValueData, rule knowledgebase.PropertyRule) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ExecutePropertyRule", data, rule) ret0, _ := ret[0].(error) @@ -111,10 +111,10 @@ func (mr *MockdependencyCapturerMockRecorder) GetChanges() *gomock.Call { } // KB mocks base method. -func (m *MockdependencyCapturer) KB() knowledgebase2.TemplateKB { +func (m *MockdependencyCapturer) KB() knowledgebase.TemplateKB { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "KB") - ret0, _ := ret[0].(knowledgebase2.TemplateKB) + ret0, _ := ret[0].(knowledgebase.TemplateKB) return ret0 } diff --git a/pkg/engine/operational_eval/graph.go b/pkg/engine/operational_eval/graph.go index 838015b56..d0122efec 100644 --- a/pkg/engine/operational_eval/graph.go +++ b/pkg/engine/operational_eval/graph.go @@ -146,9 +146,10 @@ func (eval *Evaluator) resourceVertices( var errs error addProp := func(prop knowledgebase.Property) error { vertex := &propertyVertex{ - Ref: construct.PropertyRef{Resource: res.ID, Property: prop.Details().Path}, - Template: prop, - EdgeRules: make(map[construct.SimpleEdge][]knowledgebase.OperationalRule), + Ref: construct.PropertyRef{Resource: res.ID, Property: prop.Details().Path}, + Template: prop, + EdgeRules: make(map[construct.SimpleEdge][]knowledgebase.OperationalRule), + TransformRules: make(map[construct.SimpleEdge]*set.HashedSet[string, knowledgebase.OperationalRule]), } errs = errors.Join(errs, changes.AddVertexAndDeps(eval, vertex)) @@ -157,15 +158,10 @@ func (eval *Evaluator) resourceVertices( errs = errors.Join(errs, tmpl.LoopProperties(res, addProp)) for _, rule := range tmpl.AdditionalRules { - hash, err := rule.Hash() - if err != nil { - errs = errors.Join(errs, fmt.Errorf("could not hash rule for resource id %s: %w", res.ID, err)) - continue - } vertex := &resourceRuleVertex{ Resource: res.ID, Rule: rule, - hash: hash, + hash: rule.Hash(), } errs = errors.Join(errs, changes.AddVertexAndDeps(eval, vertex)) } diff --git a/pkg/engine/operational_eval/graph_test.go b/pkg/engine/operational_eval/graph_test.go index a9fe75e02..1c6c46b61 100644 --- a/pkg/engine/operational_eval/graph_test.go +++ b/pkg/engine/operational_eval/graph_test.go @@ -52,7 +52,8 @@ func TestEvaluator_resourceVertices(t *testing.T) { Path: "prop1", }, }, - EdgeRules: map[construct.SimpleEdge][]knowledgebase.OperationalRule{}, + EdgeRules: map[construct.SimpleEdge][]knowledgebase.OperationalRule{}, + TransformRules: map[construct.SimpleEdge]*set.HashedSet[string, knowledgebase.OperationalRule]{}, }, }, edges: map[Key]set.Set[Key]{}, diff --git a/pkg/engine/operational_eval/operational_rule_mock_test.go b/pkg/engine/operational_eval/operational_rule_mock_test.go index e2595f1d9..8b8f2ed87 100644 --- a/pkg/engine/operational_eval/operational_rule_mock_test.go +++ b/pkg/engine/operational_eval/operational_rule_mock_test.go @@ -12,7 +12,8 @@ package operational_eval import ( reflect "reflect" - knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledgebase" + constraints "github.com/klothoplatform/klotho/pkg/engine/constraints" + knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" gomock "go.uber.org/mock/gomock" ) @@ -40,21 +41,21 @@ func (m *MockOpRuleHandler) EXPECT() *MockOpRuleHandlerMockRecorder { } // HandleOperationalRule mocks base method. -func (m *MockOpRuleHandler) HandleOperationalRule(rule knowledgebase2.OperationalRule) error { +func (m *MockOpRuleHandler) HandleOperationalRule(rule knowledgebase.OperationalRule, configurationOperator constraints.ConstraintOperator) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "HandleOperationalRule", rule) + ret := m.ctrl.Call(m, "HandleOperationalRule", rule, configurationOperator) ret0, _ := ret[0].(error) return ret0 } // HandleOperationalRule indicates an expected call of HandleOperationalRule. -func (mr *MockOpRuleHandlerMockRecorder) HandleOperationalRule(rule any) *gomock.Call { +func (mr *MockOpRuleHandlerMockRecorder) HandleOperationalRule(rule, configurationOperator any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleOperationalRule", reflect.TypeOf((*MockOpRuleHandler)(nil).HandleOperationalRule), rule) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandleOperationalRule", reflect.TypeOf((*MockOpRuleHandler)(nil).HandleOperationalRule), rule, configurationOperator) } // HandlePropertyRule mocks base method. -func (m *MockOpRuleHandler) HandlePropertyRule(rule knowledgebase2.PropertyRule) error { +func (m *MockOpRuleHandler) HandlePropertyRule(rule knowledgebase.PropertyRule) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HandlePropertyRule", rule) ret0, _ := ret[0].(error) @@ -68,7 +69,7 @@ func (mr *MockOpRuleHandlerMockRecorder) HandlePropertyRule(rule any) *gomock.Ca } // SetData mocks base method. -func (m *MockOpRuleHandler) SetData(data knowledgebase2.DynamicValueData) { +func (m *MockOpRuleHandler) SetData(data knowledgebase.DynamicValueData) { m.ctrl.T.Helper() m.ctrl.Call(m, "SetData", data) } diff --git a/pkg/engine/operational_eval/resource_configurer_mock_test.go b/pkg/engine/operational_eval/resource_configurer_mock_test.go index bf63b9ca5..388702dbb 100644 --- a/pkg/engine/operational_eval/resource_configurer_mock_test.go +++ b/pkg/engine/operational_eval/resource_configurer_mock_test.go @@ -12,8 +12,9 @@ package operational_eval import ( reflect "reflect" - construct2 "github.com/klothoplatform/klotho/pkg/construct" - knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledgebase" + construct "github.com/klothoplatform/klotho/pkg/construct" + constraints "github.com/klothoplatform/klotho/pkg/engine/constraints" + knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" gomock "go.uber.org/mock/gomock" ) @@ -41,7 +42,7 @@ func (m *MockResourceConfigurer) EXPECT() *MockResourceConfigurerMockRecorder { } // ConfigureResource mocks base method. -func (m *MockResourceConfigurer) ConfigureResource(resource *construct2.Resource, configuration knowledgebase2.Configuration, data knowledgebase2.DynamicValueData, action string, userInitiated bool) error { +func (m *MockResourceConfigurer) ConfigureResource(resource *construct.Resource, configuration knowledgebase.Configuration, data knowledgebase.DynamicValueData, action constraints.ConstraintOperator, userInitiated bool) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ConfigureResource", resource, configuration, data, action, userInitiated) ret0, _ := ret[0].(error) diff --git a/pkg/engine/operational_eval/template_kb_mock_test.go b/pkg/engine/operational_eval/template_kb_mock_test.go index edd8173b6..434d2d1e5 100644 --- a/pkg/engine/operational_eval/template_kb_mock_test.go +++ b/pkg/engine/operational_eval/template_kb_mock_test.go @@ -3,7 +3,7 @@ // // Generated by this command: // -// mockgen --source=./kb.go -destination=../engine2/operational_eval/template_kb_mock_test.go -package=operational_eval +// mockgen --source=./kb.go -destination=../engine/operational_eval/template_kb_mock_test.go -package=operational_eval // // Package operational_eval is a generated GoMock package. @@ -13,8 +13,8 @@ import ( reflect "reflect" graph "github.com/dominikbraun/graph" - construct2 "github.com/klothoplatform/klotho/pkg/construct" - knowledgebase2 "github.com/klothoplatform/klotho/pkg/knowledgebase" + construct "github.com/klothoplatform/klotho/pkg/construct" + knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" gomock "go.uber.org/mock/gomock" ) @@ -42,7 +42,7 @@ func (m *MockTemplateKB) EXPECT() *MockTemplateKBMockRecorder { } // AddEdgeTemplate mocks base method. -func (m *MockTemplateKB) AddEdgeTemplate(template *knowledgebase2.EdgeTemplate) error { +func (m *MockTemplateKB) AddEdgeTemplate(template *knowledgebase.EdgeTemplate) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddEdgeTemplate", template) ret0, _ := ret[0].(error) @@ -56,7 +56,7 @@ func (mr *MockTemplateKBMockRecorder) AddEdgeTemplate(template any) *gomock.Call } // AddResourceTemplate mocks base method. -func (m *MockTemplateKB) AddResourceTemplate(template *knowledgebase2.ResourceTemplate) error { +func (m *MockTemplateKB) AddResourceTemplate(template *knowledgebase.ResourceTemplate) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddResourceTemplate", template) ret0, _ := ret[0].(error) @@ -70,10 +70,10 @@ func (mr *MockTemplateKBMockRecorder) AddResourceTemplate(template any) *gomock. } // AllPaths mocks base method. -func (m *MockTemplateKB) AllPaths(from, to construct2.ResourceId) ([][]*knowledgebase2.ResourceTemplate, error) { +func (m *MockTemplateKB) AllPaths(from, to construct.ResourceId) ([][]*knowledgebase.ResourceTemplate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AllPaths", from, to) - ret0, _ := ret[0].([][]*knowledgebase2.ResourceTemplate) + ret0, _ := ret[0].([][]*knowledgebase.ResourceTemplate) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -85,10 +85,10 @@ func (mr *MockTemplateKBMockRecorder) AllPaths(from, to any) *gomock.Call { } // Edges mocks base method. -func (m *MockTemplateKB) Edges() ([]graph.Edge[*knowledgebase2.ResourceTemplate], error) { +func (m *MockTemplateKB) Edges() ([]graph.Edge[*knowledgebase.ResourceTemplate], error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Edges") - ret0, _ := ret[0].([]graph.Edge[*knowledgebase2.ResourceTemplate]) + ret0, _ := ret[0].([]graph.Edge[*knowledgebase.ResourceTemplate]) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -100,10 +100,10 @@ func (mr *MockTemplateKBMockRecorder) Edges() *gomock.Call { } // GetAllowedNamespacedResourceIds mocks base method. -func (m *MockTemplateKB) GetAllowedNamespacedResourceIds(ctx knowledgebase2.DynamicValueContext, resourceId construct2.ResourceId) ([]construct2.ResourceId, error) { +func (m *MockTemplateKB) GetAllowedNamespacedResourceIds(ctx knowledgebase.DynamicValueContext, resourceId construct.ResourceId) ([]construct.ResourceId, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllowedNamespacedResourceIds", ctx, resourceId) - ret0, _ := ret[0].([]construct2.ResourceId) + ret0, _ := ret[0].([]construct.ResourceId) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -115,10 +115,10 @@ func (mr *MockTemplateKBMockRecorder) GetAllowedNamespacedResourceIds(ctx, resou } // GetClassification mocks base method. -func (m *MockTemplateKB) GetClassification(id construct2.ResourceId) knowledgebase2.Classification { +func (m *MockTemplateKB) GetClassification(id construct.ResourceId) knowledgebase.Classification { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetClassification", id) - ret0, _ := ret[0].(knowledgebase2.Classification) + ret0, _ := ret[0].(knowledgebase.Classification) return ret0 } @@ -129,10 +129,10 @@ func (mr *MockTemplateKBMockRecorder) GetClassification(id any) *gomock.Call { } // GetEdgeTemplate mocks base method. -func (m *MockTemplateKB) GetEdgeTemplate(from, to construct2.ResourceId) *knowledgebase2.EdgeTemplate { +func (m *MockTemplateKB) GetEdgeTemplate(from, to construct.ResourceId) *knowledgebase.EdgeTemplate { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEdgeTemplate", from, to) - ret0, _ := ret[0].(*knowledgebase2.EdgeTemplate) + ret0, _ := ret[0].(*knowledgebase.EdgeTemplate) return ret0 } @@ -143,10 +143,10 @@ func (mr *MockTemplateKBMockRecorder) GetEdgeTemplate(from, to any) *gomock.Call } // GetModel mocks base method. -func (m *MockTemplateKB) GetModel(model string) *knowledgebase2.Model { +func (m *MockTemplateKB) GetModel(model string) *knowledgebase.Model { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetModel", model) - ret0, _ := ret[0].(*knowledgebase2.Model) + ret0, _ := ret[0].(*knowledgebase.Model) return ret0 } @@ -157,10 +157,10 @@ func (mr *MockTemplateKBMockRecorder) GetModel(model any) *gomock.Call { } // GetPathSatisfactionsFromEdge mocks base method. -func (m *MockTemplateKB) GetPathSatisfactionsFromEdge(source, target construct2.ResourceId) ([]knowledgebase2.EdgePathSatisfaction, error) { +func (m *MockTemplateKB) GetPathSatisfactionsFromEdge(source, target construct.ResourceId) ([]knowledgebase.EdgePathSatisfaction, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPathSatisfactionsFromEdge", source, target) - ret0, _ := ret[0].([]knowledgebase2.EdgePathSatisfaction) + ret0, _ := ret[0].([]knowledgebase.EdgePathSatisfaction) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -172,7 +172,7 @@ func (mr *MockTemplateKBMockRecorder) GetPathSatisfactionsFromEdge(source, targe } // GetResourcePropertyType mocks base method. -func (m *MockTemplateKB) GetResourcePropertyType(resource construct2.ResourceId, propertyName string) string { +func (m *MockTemplateKB) GetResourcePropertyType(resource construct.ResourceId, propertyName string) string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetResourcePropertyType", resource, propertyName) ret0, _ := ret[0].(string) @@ -186,10 +186,10 @@ func (mr *MockTemplateKBMockRecorder) GetResourcePropertyType(resource, property } // GetResourceTemplate mocks base method. -func (m *MockTemplateKB) GetResourceTemplate(id construct2.ResourceId) (*knowledgebase2.ResourceTemplate, error) { +func (m *MockTemplateKB) GetResourceTemplate(id construct.ResourceId) (*knowledgebase.ResourceTemplate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetResourceTemplate", id) - ret0, _ := ret[0].(*knowledgebase2.ResourceTemplate) + ret0, _ := ret[0].(*knowledgebase.ResourceTemplate) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -201,10 +201,10 @@ func (mr *MockTemplateKBMockRecorder) GetResourceTemplate(id any) *gomock.Call { } // GetResourcesNamespaceResource mocks base method. -func (m *MockTemplateKB) GetResourcesNamespaceResource(resource *construct2.Resource) (construct2.ResourceId, error) { +func (m *MockTemplateKB) GetResourcesNamespaceResource(resource *construct.Resource) (construct.ResourceId, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetResourcesNamespaceResource", resource) - ret0, _ := ret[0].(construct2.ResourceId) + ret0, _ := ret[0].(construct.ResourceId) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -216,7 +216,7 @@ func (mr *MockTemplateKBMockRecorder) GetResourcesNamespaceResource(resource any } // HasDirectPath mocks base method. -func (m *MockTemplateKB) HasDirectPath(from, to construct2.ResourceId) bool { +func (m *MockTemplateKB) HasDirectPath(from, to construct.ResourceId) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasDirectPath", from, to) ret0, _ := ret[0].(bool) @@ -230,7 +230,7 @@ func (mr *MockTemplateKBMockRecorder) HasDirectPath(from, to any) *gomock.Call { } // HasFunctionalPath mocks base method. -func (m *MockTemplateKB) HasFunctionalPath(from, to construct2.ResourceId) bool { +func (m *MockTemplateKB) HasFunctionalPath(from, to construct.ResourceId) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasFunctionalPath", from, to) ret0, _ := ret[0].(bool) @@ -244,10 +244,10 @@ func (mr *MockTemplateKBMockRecorder) HasFunctionalPath(from, to any) *gomock.Ca } // ListResources mocks base method. -func (m *MockTemplateKB) ListResources() []*knowledgebase2.ResourceTemplate { +func (m *MockTemplateKB) ListResources() []*knowledgebase.ResourceTemplate { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListResources") - ret0, _ := ret[0].([]*knowledgebase2.ResourceTemplate) + ret0, _ := ret[0].([]*knowledgebase.ResourceTemplate) return ret0 } diff --git a/pkg/engine/operational_eval/vertex_edge.go b/pkg/engine/operational_eval/vertex_edge.go index 26ec02b1a..8f4468887 100644 --- a/pkg/engine/operational_eval/vertex_edge.go +++ b/pkg/engine/operational_eval/vertex_edge.go @@ -14,6 +14,7 @@ import ( type edgeVertex struct { Edge construct.SimpleEdge + // Rules are run in order of how they exist in the template so that the order of operations handles the rules inter dependencies Rules []knowledgebase.OperationalRule } @@ -75,7 +76,7 @@ func (ev *edgeVertex) UpdateFrom(other Vertex) { if ev.Edge != otherEdge.Edge { panic(fmt.Sprintf("cannot merge edges with different refs: %s != %s", ev.Edge, otherEdge.Edge)) } - ev.Rules = append(ev.Rules, otherEdge.Rules...) + ev.Rules = otherEdge.Rules } func (ev *edgeVertex) Evaluate(eval *Evaluator) error { @@ -95,7 +96,7 @@ func (ev *edgeVertex) Evaluate(eval *Evaluator) error { rule.ConfigurationRules = nil if len(rule.Steps) > 0 { - err := opCtx.HandleOperationalRule(rule) + err := opCtx.HandleOperationalRule(rule, constraints.AddConstraintOperator) if err != nil { errs = errors.Join(errs, fmt.Errorf( "could not apply edge %s operational rule: %w", @@ -125,7 +126,7 @@ func (ev *edgeVertex) Evaluate(eval *Evaluator) error { for res, configRules := range configuration { opCtx.Data.Resource = res rule.ConfigurationRules = configRules - err := opCtx.HandleOperationalRule(rule) + err := opCtx.HandleOperationalRule(rule, constraints.AddConstraintOperator) if err != nil { errs = errors.Join(errs, fmt.Errorf( "could not apply edge %s (res: %s) operational rule: %w", diff --git a/pkg/engine/operational_eval/vertex_property.go b/pkg/engine/operational_eval/vertex_property.go index 5ca6e6815..e5438fde1 100644 --- a/pkg/engine/operational_eval/vertex_property.go +++ b/pkg/engine/operational_eval/vertex_property.go @@ -11,15 +11,20 @@ import ( "github.com/klothoplatform/klotho/pkg/engine/operational_rule" "github.com/klothoplatform/klotho/pkg/engine/solution_context" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" + "github.com/klothoplatform/klotho/pkg/set" ) type ( propertyVertex struct { Ref construct.PropertyRef - Template knowledgebase.Property - EdgeRules map[construct.SimpleEdge][]knowledgebase.OperationalRule - ResourceRules map[string][]knowledgebase.OperationalRule + Template knowledgebase.Property + EdgeRules map[construct.SimpleEdge][]knowledgebase.OperationalRule + // TransformRules are Rules found in edge templates where the property depends on itself, thus transforming the existing value + // Transform rules are initially a part of the edge vertex + // when a TransformRule is found it is removed from the EdgeRules and added to the TransformRules + TransformRules map[construct.SimpleEdge]*set.HashedSet[string, knowledgebase.OperationalRule] + ResourceRules map[string][]knowledgebase.OperationalRule } ) @@ -53,19 +58,50 @@ func (prop *propertyVertex) Dependencies(eval *Evaluator, propCtx dependencyCapt } if prop.shouldEvalEdges(eval.Solution.Constraints().Resources) { + current_edges := make(map[Key]set.Set[Key]) + for k, v := range propCtx.GetChanges().edges { + current_edges[k] = v + } + for edge, rule := range prop.EdgeRules { edgeData := knowledgebase.DynamicValueData{ Resource: prop.Ref.Resource, Edge: &construct.Edge{Source: edge.Source, Target: edge.Target}, } + var corrected_edge_rules []knowledgebase.OperationalRule for _, opRule := range rule { + addRule := true if err := propCtx.ExecuteOpRule(edgeData, opRule); err != nil { return fmt.Errorf("could not execute edge operational rule for %s: %w", prop.Ref, err) } + + // Analyze the changes to ensure there are no self dependencies + // If there are then we want to label the operational rule as a transform rule to be operated on at the end + curr_deps := propCtx.GetChanges().edges[prop.Key()] + existing_deps := current_edges[prop.Key()] + for v := range curr_deps { + if v == prop.Key() && !existing_deps.Contains(v) { + current_set := prop.TransformRules[edge] + if current_set == nil { + current_set = &set.HashedSet[string, knowledgebase.OperationalRule]{ + Hasher: func(s knowledgebase.OperationalRule) string { + return fmt.Sprintf("%v", s) + }, + } + } + current_set.Add(opRule) + prop.TransformRules[edge] = current_set + propCtx.GetChanges().edges[prop.Key()].Remove(v) + addRule = false + } + } + if addRule { + corrected_edge_rules = append(corrected_edge_rules, opRule) + } } + prop.EdgeRules[edge] = corrected_edge_rules } } - return nil } @@ -141,6 +177,10 @@ func (v *propertyVertex) Evaluate(eval *Evaluator) error { } } + if err := v.evaluateTransforms(res, &opCtx); err != nil { + return err + } + if err := eval.UpdateId(v.Ref.Resource, res.ID); err != nil { return err } @@ -215,7 +255,7 @@ func (v *propertyVertex) evaluateConstraints( res, knowledgebase.Configuration{Field: v.Ref.Property, Value: defaultVal}, dynData, - "set", + constraints.EqualsConstraintOperator, false, ) if err != nil { @@ -227,7 +267,7 @@ func (v *propertyVertex) evaluateConstraints( res, knowledgebase.Configuration{Field: v.Ref.Property, Value: setConstraint.Value}, dynData, - "set", + constraints.EqualsConstraintOperator, true, ) if err != nil { @@ -241,16 +281,11 @@ func (v *propertyVertex) evaluateConstraints( if c.Operator == constraints.EqualsConstraintOperator { continue } - action, err := solution_context.ConstraintOperatorToAction(c.Operator) - if err != nil { - errs = errors.Join(errs, fmt.Errorf("could not apply constraint for %s: %w", v.Ref, err)) - continue - } errs = errors.Join(errs, rc.ConfigureResource( res, knowledgebase.Configuration{Field: v.Ref.Property, Value: c.Value}, dynData, - action, + c.Operator, true, )) dynData.Resource = res.ID @@ -312,7 +347,7 @@ func (v *propertyVertex) evaluateEdgeOperational( Edge: &graph.Edge[construct.ResourceId]{Source: edge.Source, Target: edge.Target}, }) - err := opCtx.HandleOperationalRule(rule) + err := opCtx.HandleOperationalRule(rule, constraints.AddConstraintOperator) if err != nil { errs = errors.Join(errs, fmt.Errorf( "could not apply edge %s -> %s operational rule for %s: %w", @@ -324,6 +359,33 @@ func (v *propertyVertex) evaluateEdgeOperational( return errs } +func (v *propertyVertex) evaluateTransforms( + res *construct.Resource, + opCtx operational_rule.OpRuleHandler, +) error { + var errs error + oldId := v.Ref.Resource + for edge, rules := range v.TransformRules { + for _, rule := range rules.ToSlice() { + // In case one of the previous rules changed the ID, update it + edge = UpdateEdgeId(edge, oldId, res.ID) + opCtx.SetData(knowledgebase.DynamicValueData{ + Resource: res.ID, + Edge: &graph.Edge[construct.ResourceId]{Source: edge.Source, Target: edge.Target}, + }) + + err := opCtx.HandleOperationalRule(rule, constraints.EqualsConstraintOperator) + if err != nil { + errs = errors.Join(errs, fmt.Errorf( + "could not apply transform rule for %s: %w", + v.Ref, err, + )) + } + } + } + return errs +} + func (v *propertyVertex) Ready(eval *Evaluator) (ReadyPriority, error) { if v.Template == nil { // wait until we have a template diff --git a/pkg/engine/operational_eval/vertex_property_test.go b/pkg/engine/operational_eval/vertex_property_test.go index ead3169fc..26502cdf3 100644 --- a/pkg/engine/operational_eval/vertex_property_test.go +++ b/pkg/engine/operational_eval/vertex_property_test.go @@ -11,6 +11,7 @@ import ( "github.com/klothoplatform/klotho/pkg/engine/enginetesting" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" "github.com/klothoplatform/klotho/pkg/knowledgebase/properties" + "github.com/klothoplatform/klotho/pkg/set" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" gomock "go.uber.org/mock/gomock" @@ -188,7 +189,7 @@ func Test_propertyVertex_evaluateEdgeOperational(t *testing.T) { Resource: tt.args.v.Ref.Resource, Edge: &graph.Edge[construct.ResourceId]{Source: construct.ResourceId{Name: "test"}, Target: construct.ResourceId{Name: "test2"}}, }).Times(1) - opctx.EXPECT().HandleOperationalRule(rule).Return(nil).Times(1) + opctx.EXPECT().HandleOperationalRule(rule, constraints.AddConstraintOperator).Return(nil).Times(1) err := tt.args.v.evaluateEdgeOperational(tt.args.res, opctx) if tt.wantErr { assert.Error(err) @@ -200,6 +201,66 @@ func Test_propertyVertex_evaluateEdgeOperational(t *testing.T) { } } +func Test_propertyVertex_evaluateTransforms(t *testing.T) { + rule := knowledgebase.OperationalRule{ + If: "test", + } + type args struct { + v *propertyVertex + res *construct.Resource + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "property rule", + args: args{ + v: &propertyVertex{ + Ref: construct.PropertyRef{ + Property: "test", + Resource: construct.ResourceId{Name: "test"}, + }, + TransformRules: map[construct.SimpleEdge]*set.HashedSet[string, knowledgebase.OperationalRule]{ + { + Source: construct.ResourceId{Name: "test"}, + Target: construct.ResourceId{Name: "test2"}, + }: { + Hasher: func(s knowledgebase.OperationalRule) string { + return fmt.Sprintf("%v", s) + }, + M: map[string]knowledgebase.OperationalRule{ + "{testE [] []}": rule, + }, + }, + }, + }, + res: &construct.Resource{ID: construct.ResourceId{Name: "test"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + ctrl := gomock.NewController(t) + opctx := NewMockOpRuleHandler(ctrl) + opctx.EXPECT().SetData(knowledgebase.DynamicValueData{ + Resource: tt.args.v.Ref.Resource, + Edge: &graph.Edge[construct.ResourceId]{Source: construct.ResourceId{Name: "test"}, Target: construct.ResourceId{Name: "test2"}}, + }).Times(1) + opctx.EXPECT().HandleOperationalRule(rule, constraints.EqualsConstraintOperator).Return(nil).Times(1) + err := tt.args.v.evaluateTransforms(tt.args.res, opctx) + if tt.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + ctrl.Finish() + }) + } +} + func Test_propertyVertex_Dependencies(t *testing.T) { tests := []struct { @@ -207,6 +268,7 @@ func Test_propertyVertex_Dependencies(t *testing.T) { v *propertyVertex constraints constraints.Constraints mocks func(dcap *MockdependencyCapturer, resource *construct.Resource, path construct.PropertyPath) + want *propertyVertex wantErr bool }{ { @@ -235,6 +297,7 @@ func Test_propertyVertex_Dependencies(t *testing.T) { If: "test", }, ).Return(nil) + dcap.EXPECT().GetChanges().Times(1) }, }, { @@ -262,6 +325,79 @@ func Test_propertyVertex_Dependencies(t *testing.T) { }, knowledgebase.OperationalRule{ If: "testE", }).Return(nil) + dcap.EXPECT().GetChanges().Return(graphChanges{ + edges: map[Key]set.Set[Key]{}, + }).Times(2) + }, + }, + { + name: "property vertex with edge rules that depend on itself", + v: &propertyVertex{ + Ref: construct.PropertyRef{ + Property: "test", + Resource: construct.ResourceId{Name: "test"}, + }, + EdgeRules: map[construct.SimpleEdge][]knowledgebase.OperationalRule{ + { + Source: construct.ResourceId{Name: "test"}, + Target: construct.ResourceId{Name: "test2"}, + }: { + { + If: "testE", + }, + }, + }, + TransformRules: map[construct.SimpleEdge]*set.HashedSet[string, knowledgebase.OperationalRule]{}, + }, + mocks: func(dcap *MockdependencyCapturer, resource *construct.Resource, path construct.PropertyPath) { + dcap.EXPECT().ExecuteOpRule(knowledgebase.DynamicValueData{ + Resource: resource.ID, + Edge: &graph.Edge[construct.ResourceId]{Source: construct.ResourceId{Name: "test"}, Target: construct.ResourceId{Name: "test2"}}, + }, knowledgebase.OperationalRule{ + If: "testE", + }).Return(nil) + dcap.EXPECT().GetChanges().Return(graphChanges{ + edges: map[Key]set.Set[Key]{}, + }).Times(1) + dcap.EXPECT().GetChanges().Return(graphChanges{ + edges: map[Key]set.Set[Key]{ + {Ref: construct.PropertyRef{ + Resource: construct.ResourceId{Name: "test"}, + Property: "test", + }}: set.SetOf( + Key{Ref: construct.PropertyRef{ + Resource: construct.ResourceId{Name: "test"}, + Property: "test", + }}), + }, + }).Times(2) + }, + want: &propertyVertex{ + Ref: construct.PropertyRef{ + Property: "test", + Resource: construct.ResourceId{Name: "test"}, + }, + EdgeRules: map[construct.SimpleEdge][]knowledgebase.OperationalRule{ + { + Source: construct.ResourceId{Name: "test"}, + Target: construct.ResourceId{Name: "test2"}, + }: nil, + }, + TransformRules: map[construct.SimpleEdge]*set.HashedSet[string, knowledgebase.OperationalRule]{ + { + Source: construct.ResourceId{Name: "test"}, + Target: construct.ResourceId{Name: "test2"}, + }: { + Hasher: func(s knowledgebase.OperationalRule) string { + return fmt.Sprintf("%v", s) + }, + M: map[string]knowledgebase.OperationalRule{ + "{testE [] []}": { + If: "testE", + }, + }, + }, + }, }, }, { @@ -295,6 +431,7 @@ func Test_propertyVertex_Dependencies(t *testing.T) { mocks: func(dcap *MockdependencyCapturer, resource *construct.Resource, path construct.PropertyPath) { // expect no calls to ExecuteOpRule due to shouldEvalEdges returning false dcap.EXPECT().ExecuteOpRule(gomock.Any(), gomock.Any()).Times(0) + dcap.EXPECT().GetChanges().Times(0) }, }, } @@ -326,6 +463,12 @@ func Test_propertyVertex_Dependencies(t *testing.T) { return } assert.NoError(t, err) + if tt.want != nil { + assert.Equal(t, tt.want.EdgeRules, tt.v.EdgeRules) + for k, v := range tt.want.TransformRules { + assert.Equal(t, v.M, tt.v.TransformRules[k].M) + } + } ctrl.Finish() }) } @@ -365,7 +508,7 @@ func Test_propertyVertex_evaluateConstraints(t *testing.T) { mockRc.EXPECT().ConfigureResource(data.res, knowledgebase.Configuration{Field: "test", Value: "test"}, knowledgebase.DynamicValueData{Resource: id}, - "set", + constraints.EqualsConstraintOperator, false).Times(1) }, }, @@ -397,7 +540,7 @@ func Test_propertyVertex_evaluateConstraints(t *testing.T) { mockRc.EXPECT().ConfigureResource(data.res, knowledgebase.Configuration{Field: "test", Value: "test"}, knowledgebase.DynamicValueData{Resource: id}, - "set", + constraints.EqualsConstraintOperator, true).Times(1) }, }, @@ -428,17 +571,17 @@ func Test_propertyVertex_evaluateConstraints(t *testing.T) { mockRc.EXPECT().ConfigureResource(data.res, knowledgebase.Configuration{Field: "test", Value: "test"}, knowledgebase.DynamicValueData{Resource: id}, - "set", + constraints.EqualsConstraintOperator, false).Times(1) mockRc.EXPECT().ConfigureResource(data.res, knowledgebase.Configuration{Field: "test", Value: "test"}, knowledgebase.DynamicValueData{Resource: id}, - "add", + constraints.AddConstraintOperator, true).Times(1) mockRc.EXPECT().ConfigureResource(data.res, knowledgebase.Configuration{Field: "test", Value: "test2"}, knowledgebase.DynamicValueData{Resource: id}, - "add", + constraints.AddConstraintOperator, true).Times(1) }, }, diff --git a/pkg/engine/operational_eval/vertex_resource_rule.go b/pkg/engine/operational_eval/vertex_resource_rule.go index 7e38bf953..cbbfca7c1 100644 --- a/pkg/engine/operational_eval/vertex_resource_rule.go +++ b/pkg/engine/operational_eval/vertex_resource_rule.go @@ -5,6 +5,7 @@ import ( "fmt" construct "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/engine/constraints" "github.com/klothoplatform/klotho/pkg/engine/operational_rule" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" ) @@ -75,7 +76,7 @@ func (v *resourceRuleVertex) evaluateResourceRule( err := opCtx.HandleOperationalRule(knowledgebase.OperationalRule{ If: v.Rule.If, Steps: v.Rule.Steps, - }) + }, constraints.AddConstraintOperator) if err != nil { return fmt.Errorf( "could not apply resource %s operational rule: %w", diff --git a/pkg/engine/operational_eval/vertex_resource_rule_test.go b/pkg/engine/operational_eval/vertex_resource_rule_test.go index 57f8ed7bb..918bb4c10 100644 --- a/pkg/engine/operational_eval/vertex_resource_rule_test.go +++ b/pkg/engine/operational_eval/vertex_resource_rule_test.go @@ -5,6 +5,7 @@ import ( "testing" construct "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/engine/constraints" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" "github.com/stretchr/testify/assert" gomock "go.uber.org/mock/gomock" @@ -151,7 +152,7 @@ func Test_resourceRuleVertex_evaluateResourceRule(t *testing.T) { mocks: func() { opctx.EXPECT().HandleOperationalRule(knowledgebase.OperationalRule{ If: "test", - }).Return(nil) + }, constraints.AddConstraintOperator).Return(nil) }, }, { @@ -165,7 +166,7 @@ func Test_resourceRuleVertex_evaluateResourceRule(t *testing.T) { mocks: func() { opctx.EXPECT().HandleOperationalRule(knowledgebase.OperationalRule{ If: "test", - }).Return(fmt.Errorf("err")) + }, constraints.AddConstraintOperator).Return(fmt.Errorf("err")) }, wantErr: true, }, diff --git a/pkg/engine/operational_rule/operational_configuration.go b/pkg/engine/operational_rule/operational_configuration.go index f3d0d0ee5..3bb6e36d9 100644 --- a/pkg/engine/operational_rule/operational_configuration.go +++ b/pkg/engine/operational_rule/operational_configuration.go @@ -3,11 +3,15 @@ package operational_rule import ( "fmt" + "github.com/klothoplatform/klotho/pkg/engine/constraints" "github.com/klothoplatform/klotho/pkg/engine/solution_context" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" ) -func (ctx OperationalRuleContext) HandleConfigurationRule(config knowledgebase.ConfigurationRule) error { +func (ctx OperationalRuleContext) HandleConfigurationRule( + config knowledgebase.ConfigurationRule, + configurationOperator constraints.ConstraintOperator, +) error { dyn := solution_context.DynamicCtx(ctx.Solution) res, err := knowledgebase.ExecuteDecodeAsResourceId(dyn, config.Resource, ctx.Data) if err != nil { @@ -26,7 +30,7 @@ func (ctx OperationalRuleContext) HandleConfigurationRule(config knowledgebase.C config.Config.Field = resolvedField configurer := &solution_context.Configurer{Ctx: ctx.Solution} - err = configurer.ConfigureResource(resource, config.Config, ctx.Data, "add", false) + err = configurer.ConfigureResource(resource, config.Config, ctx.Data, configurationOperator, false) if err != nil { return err } diff --git a/pkg/engine/operational_rule/operational_rule.go b/pkg/engine/operational_rule/operational_rule.go index 1089da0dc..975b3b06f 100644 --- a/pkg/engine/operational_rule/operational_rule.go +++ b/pkg/engine/operational_rule/operational_rule.go @@ -6,6 +6,7 @@ import ( "github.com/dominikbraun/graph" construct "github.com/klothoplatform/klotho/pkg/construct" + "github.com/klothoplatform/klotho/pkg/engine/constraints" "github.com/klothoplatform/klotho/pkg/engine/reconciler" "github.com/klothoplatform/klotho/pkg/engine/solution_context" knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" @@ -23,13 +24,16 @@ type ( } OpRuleHandler interface { - HandleOperationalRule(rule knowledgebase.OperationalRule) error + HandleOperationalRule(rule knowledgebase.OperationalRule, configurationOperator constraints.ConstraintOperator) error HandlePropertyRule(rule knowledgebase.PropertyRule) error SetData(data knowledgebase.DynamicValueData) } ) -func (ctx *OperationalRuleContext) HandleOperationalRule(rule knowledgebase.OperationalRule) error { +func (ctx *OperationalRuleContext) HandleOperationalRule( + rule knowledgebase.OperationalRule, + configurationOperator constraints.ConstraintOperator, +) error { shouldRun, err := EvaluateIfCondition(rule.If, ctx.Solution, ctx.Data) if err != nil { return err @@ -48,7 +52,7 @@ func (ctx *OperationalRuleContext) HandleOperationalRule(rule knowledgebase.Oper } for i, operationalConfig := range rule.ConfigurationRules { - err := ctx.HandleConfigurationRule(operationalConfig) + err := ctx.HandleConfigurationRule(operationalConfig, configurationOperator) if err != nil { errs = errors.Join(errs, fmt.Errorf("could not apply configuration rule %d: %w", i, err)) } diff --git a/pkg/engine/path_selection/path_expansion.go b/pkg/engine/path_selection/path_expansion.go index 4635c828e..2193eaacb 100644 --- a/pkg/engine/path_selection/path_expansion.go +++ b/pkg/engine/path_selection/path_expansion.go @@ -435,16 +435,19 @@ func expandPath( return } - // if the edge doesnt exist in the actual graph we need to check uniqueness validity + // if the edge doesnt exist in the actual graph and there is any uniqueness constraint, + // then we need to check uniqueness validity _, err := ctx.RawView().Edge(source.id, target.id) if errors.Is(err, graph.ErrEdgeNotFound) { - valid, err := checkUniquenessValidity(ctx, source.id, target.id) - if err != nil { - errs = errors.Join(errs, err) - return - } - if !valid { - return + if tmpl.Unique.Source || tmpl.Unique.Target { + valid, err := checkUniquenessValidity(ctx, source.id, target.id) + if err != nil { + errs = errors.Join(errs, err) + return + } + if !valid { + return + } } } else if err != nil { errs = errors.Join(errs, fmt.Errorf("unexpected error from raw edge: %v", err)) diff --git a/pkg/engine/solution_context/interface.go b/pkg/engine/solution_context/interface.go index bcf9b8e05..6013258d7 100644 --- a/pkg/engine/solution_context/interface.go +++ b/pkg/engine/solution_context/interface.go @@ -6,8 +6,6 @@ import ( knowledgebase "github.com/klothoplatform/klotho/pkg/knowledgebase" ) -//go:generate mockgen -source=./interface.go --destination=../operational_eval/solution_context_mock_test.go --package=operational_eval - type ( SolutionContext interface { // With returns a new context with a new key/value pair pushed onto the context stack. diff --git a/pkg/engine/solution_context/resource_configuration.go b/pkg/engine/solution_context/resource_configuration.go index f9051fc5e..e68289b31 100644 --- a/pkg/engine/solution_context/resource_configuration.go +++ b/pkg/engine/solution_context/resource_configuration.go @@ -20,7 +20,7 @@ type ( resource *construct.Resource, configuration knowledgebase.Configuration, data knowledgebase.DynamicValueData, - action string, + action constraints.ConstraintOperator, userInitiated bool, ) error } @@ -34,7 +34,7 @@ func (c *Configurer) ConfigureResource( resource *construct.Resource, configuration knowledgebase.Configuration, data knowledgebase.DynamicValueData, - action string, + action constraints.ConstraintOperator, userInitiated bool, ) error { if resource == nil { @@ -67,7 +67,7 @@ func (c *Configurer) ConfigureResource( } switch action { - case "set": + case constraints.EqualsConstraintOperator: err = property.SetProperty(resource, val) if err != nil { return fmt.Errorf("failed to set property %s on resource %s: %w", field, resource.ID, err) @@ -76,7 +76,7 @@ func (c *Configurer) ConfigureResource( if err != nil { return fmt.Errorf("failed to add deployment dependencies from property %s on resource %s: %w", field, resource.ID, err) } - case "add": + case constraints.AddConstraintOperator: err = property.AppendProperty(resource, val) if err != nil { return fmt.Errorf("failed to add property %s on resource %s: %w", field, resource.ID, err) @@ -85,7 +85,7 @@ func (c *Configurer) ConfigureResource( if err != nil { return fmt.Errorf("failed to add deployment dependencies from property %s on resource %s: %w", field, resource.ID, err) } - case "remove": + case constraints.RemoveConstraintOperator: err = property.RemoveProperty(resource, val) if err != nil { return fmt.Errorf("failed to remove property %s on resource %s: %w", field, resource.ID, err) @@ -120,19 +120,6 @@ func AddDeploymentDependenciesFromVal( return errs } -func ConstraintOperatorToAction(op constraints.ConstraintOperator) (string, error) { - switch op { - case constraints.AddConstraintOperator: - return "add", nil - case constraints.RemoveConstraintOperator: - return "remove", nil - case constraints.EqualsConstraintOperator: - return "set", nil - default: - return "", fmt.Errorf("invalid operator %s", op) - } -} - func getResourcesFromValue(val any) (ids []construct.ResourceId) { if val == nil { return diff --git a/pkg/engine/testdata/k8s_api.err.json b/pkg/engine/testdata/k8s_api.err.json index e6c8e2076..bf8939362 100644 --- a/pkg/engine/testdata/k8s_api.err.json +++ b/pkg/engine/testdata/k8s_api.err.json @@ -10,57 +10,5 @@ "resource": "aws:ecr_image:pod2-ecr_image", "validation_error": "required property BaseImage is not set on resource aws:ecr_image:pod2-ecr_image", "value": null - }, - { - "error": { - "children": [ - { - "chain": [ - "invalid int value 80" - ] - }, - { - "chain": [ - "invalid int value 80" - ] - } - ] - }, - "error_code": "config_invalid", - "property": "Object.spec.ports", - "resource": "kubernetes:service:eks_cluster-0:restapi4integration0-pod2", - "validation_error": "invalid int value 80\ninvalid int value 80", - "value": [ - { - "name": "pod2-pod2-80", - "port": "80", - "protocol": "TCP", - "targetPort": "80" - } - ] - }, - { - "error": { - "chain": [ - "invalid int value 80" - ] - }, - "error_code": "config_invalid", - "property": "Object.spec.ports[0].port", - "resource": "kubernetes:service:eks_cluster-0:restapi4integration0-pod2", - "validation_error": "invalid int value 80", - "value": "80" - }, - { - "error": { - "chain": [ - "invalid int value 80" - ] - }, - "error_code": "config_invalid", - "property": "Object.spec.ports[0].targetPort", - "resource": "kubernetes:service:eks_cluster-0:restapi4integration0-pod2", - "validation_error": "invalid int value 80", - "value": "80" } ] diff --git a/pkg/engine/testdata/k8s_api.expect.yaml b/pkg/engine/testdata/k8s_api.expect.yaml index 23c5c3835..d8a89265e 100755 --- a/pkg/engine/testdata/k8s_api.expect.yaml +++ b/pkg/engine/testdata/k8s_api.expect.yaml @@ -197,9 +197,9 @@ resources: spec: ports: - name: pod2-pod2-80 - port: "80" + port: 80 protocol: TCP - targetPort: "80" + targetPort: 80 selector: KLOTHO_ID_LABEL: pod2 elbv2.k8s.aws/pod-readiness-gate-inject: enabled diff --git a/pkg/engine/testdata/lambda_efs.dataflow-viz.yaml b/pkg/engine/testdata/lambda_efs.dataflow-viz.yaml index 0285324f6..a527cc806 100755 --- a/pkg/engine/testdata/lambda_efs.dataflow-viz.yaml +++ b/pkg/engine/testdata/lambda_efs.dataflow-viz.yaml @@ -15,7 +15,6 @@ resources: - aws:efs_mount_target:test-efs-fs:subnet-1-test-efs-fs - aws:iam_role:lambda_test_app-ExecutionRole - aws:security_group:vpc-0:lambda_test_app-test-efs-fs - - aws:security_group:vpc-0:subnet-1-test-efs-fs - aws:subnet:vpc-0:lambda_test_app-test-efs-fs - aws:subnet:vpc-0:subnet-1 @@ -28,7 +27,6 @@ resources: - aws:route_table:vpc-0:subnet-3-route_table - aws:security_group:vpc-0:lambda_test_app-security_group - aws:security_group:vpc-0:lambda_test_app-test-efs-fs - - aws:security_group:vpc-0:subnet-1-test-efs-fs - aws:subnet:vpc-0:lambda_test_app-test-efs-fs - aws:subnet:vpc-0:subnet-1 - aws:subnet:vpc-0:subnet-2 diff --git a/pkg/engine/testdata/lambda_efs.expect.yaml b/pkg/engine/testdata/lambda_efs.expect.yaml index 48bb58a93..293290f5f 100755 --- a/pkg/engine/testdata/lambda_efs.expect.yaml +++ b/pkg/engine/testdata/lambda_efs.expect.yaml @@ -164,7 +164,7 @@ resources: aws:efs_mount_target:test-efs-fs:subnet-1-test-efs-fs: FileSystem: aws:efs_file_system:test-efs-fs SecurityGroups: - - aws:security_group:vpc-0:subnet-1-test-efs-fs + - aws:security_group:vpc-0:lambda_test_app-test-efs-fs Subnet: aws:subnet:vpc-0:subnet-1 aws:subnet:vpc-0:subnet-1: AvailabilityZone: aws:availability_zone:region-0:availability_zone-0 @@ -209,30 +209,6 @@ resources: GLOBAL_KLOTHO_TAG: "" RESOURCE_NAME: lambda_test_app-test-efs-fs Vpc: aws:vpc:vpc-0 - aws:security_group:vpc-0:subnet-1-test-efs-fs: - EgressRules: - - CidrBlocks: - - 0.0.0.0/0 - Description: Allows all outbound IPv4 traffic - FromPort: 0 - Protocol: "-1" - ToPort: 0 - IngressRules: - - CidrBlocks: - - 10.0.128.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 - Tags: - GLOBAL_KLOTHO_TAG: "" - RESOURCE_NAME: subnet-1-test-efs-fs - Vpc: aws:vpc:vpc-0 aws:route_table:vpc-0:subnet-1-route_table: Routes: - CidrBlock: 0.0.0.0/0 @@ -329,13 +305,11 @@ edges: aws:subnet:vpc-0:subnet-1 -> aws:availability_zone:region-0:availability_zone-0: 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:lambda_test_app-test-efs-fs: - aws:subnet:vpc-0:subnet-1 -> aws:security_group:vpc-0:subnet-1-test-efs-fs: aws:subnet:vpc-0:subnet-1 -> aws:vpc:vpc-0: aws:route_table_association:subnet-1-subnet-1-route_table -> aws:route_table:vpc-0:subnet-1-route_table: aws:security_group:vpc-0:lambda_test_app-test-efs-fs -> aws:efs_mount_target:test-efs-fs:lambda_test_app-test-efs-fs: + aws:security_group:vpc-0:lambda_test_app-test-efs-fs -> aws:efs_mount_target:test-efs-fs:subnet-1-test-efs-fs: aws:security_group:vpc-0:lambda_test_app-test-efs-fs -> aws:vpc:vpc-0: - aws:security_group:vpc-0:subnet-1-test-efs-fs -> aws:efs_mount_target:test-efs-fs:subnet-1-test-efs-fs: - aws:security_group:vpc-0:subnet-1-test-efs-fs -> 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: aws:nat_gateway:subnet-3:subnet-1-route_table-nat_gateway -> aws:elastic_ip:subnet-1-route_table-nat_gateway-elastic_ip: diff --git a/pkg/engine/testdata/lambda_efs.iac-viz.yaml b/pkg/engine/testdata/lambda_efs.iac-viz.yaml index 169696800..2b041e8a9 100755 --- a/pkg/engine/testdata/lambda_efs.iac-viz.yaml +++ b/pkg/engine/testdata/lambda_efs.iac-viz.yaml @@ -8,7 +8,7 @@ resources: aws:efs_mount_target:test-efs-fs/subnet-1-test-efs-fs: aws:efs_mount_target:test-efs-fs/subnet-1-test-efs-fs -> efs_file_system/test-efs-fs: - aws:efs_mount_target:test-efs-fs/subnet-1-test-efs-fs -> aws:security_group:vpc-0/subnet-1-test-efs-fs: + aws:efs_mount_target:test-efs-fs/subnet-1-test-efs-fs -> aws:security_group:vpc-0/lambda_test_app-test-efs-fs: aws:efs_mount_target:test-efs-fs/subnet-1-test-efs-fs -> aws:subnet:vpc-0/subnet-1: lambda_function/lambda_test_app: @@ -64,7 +64,6 @@ resources: aws:subnet:vpc-0/subnet-1 -> aws:availability_zone:region-0/availability_zone-0: aws:subnet:vpc-0/subnet-1 -> aws:security_group:vpc-0/lambda_test_app-test-efs-fs: - aws:subnet:vpc-0/subnet-1 -> aws:security_group:vpc-0/subnet-1-test-efs-fs: aws:subnet:vpc-0/subnet-1 -> vpc/vpc-0: aws:route_table:vpc-0/subnet-2-route_table: @@ -92,9 +91,6 @@ resources: aws:security_group:vpc-0/lambda_test_app-test-efs-fs: aws:security_group:vpc-0/lambda_test_app-test-efs-fs -> vpc/vpc-0: - aws:security_group:vpc-0/subnet-1-test-efs-fs: - - aws:security_group:vpc-0/subnet-1-test-efs-fs -> vpc/vpc-0: aws:internet_gateway:vpc-0/internet_gateway-0: aws:internet_gateway:vpc-0/internet_gateway-0 -> vpc/vpc-0: diff --git a/pkg/infra/iac/templates/aws/ecs_service/factory.ts b/pkg/infra/iac/templates/aws/ecs_service/factory.ts index 841e86188..3e572fd61 100644 --- a/pkg/infra/iac/templates/aws/ecs_service/factory.ts +++ b/pkg/infra/iac/templates/aws/ecs_service/factory.ts @@ -20,6 +20,7 @@ interface Args { LoadBalancers: TemplateWrapper dependsOn?: pulumi.Input[]> | pulumi.Input ServiceRegistries: pulumi.Input + ServiceConnectConfiguration: pulumi.Input Tags: ModelCaseWrapper> } @@ -65,6 +66,9 @@ function create(args: Args): aws.ecs.Service { //TMPL {{- if .ServiceRegistries }} serviceRegistries: args.ServiceRegistries, //TMPL {{- end }} + //TMP {{- if .ServiceConnectConfiguration }} + serviceConnectConfiguration: args.ServiceConnectConfiguration, + //TMP {{- end }} //TMPL {{- if .Tags }} tags: args.Tags, //TMPL {{- end }} diff --git a/pkg/infra/iac/templates/aws/service_discovery_http_namespace/factory.ts b/pkg/infra/iac/templates/aws/service_discovery_http_namespace/factory.ts new file mode 100644 index 000000000..446818666 --- /dev/null +++ b/pkg/infra/iac/templates/aws/service_discovery_http_namespace/factory.ts @@ -0,0 +1,27 @@ +import * as aws from '@pulumi/aws' +import { ModelCaseWrapper } from '../../wrappers' + +interface Args { + Name: string + Description: string + Tags: ModelCaseWrapper> +} + +// noinspection JSUnusedLocalSymbols +function create(args: Args): aws.servicediscovery.HttpNamespace { + return new aws.servicediscovery.HttpNamespace(args.Name, { + //TMPL {{- if .Description }} + description: args.Description, + //TMPL {{- end }} + //TMPL {{- if .Tags }} + tags: args.Tags, + //TMPL {{- end }} + }) +} + +function properties(object: aws.servicediscovery.HttpNamespace, args: Args) { + return { + Id: object.id, + Arn: object.arn, + } +} diff --git a/pkg/infra/iac/templates/aws/service_discovery_http_namespace/package.json b/pkg/infra/iac/templates/aws/service_discovery_http_namespace/package.json new file mode 100644 index 000000000..93d99b5d3 --- /dev/null +++ b/pkg/infra/iac/templates/aws/service_discovery_http_namespace/package.json @@ -0,0 +1,6 @@ +{ + "name": "service_discovery_http_namespace", + "dependencies": { + "@pulumi/aws": "^5.37.0" + } +} diff --git a/pkg/infra/state_reader/state_template/mappings/pulumi/subnet.yaml b/pkg/infra/state_reader/state_template/mappings/pulumi/subnet.yaml index 635927fa0..c0f6e7cb4 100644 --- a/pkg/infra/state_reader/state_template/mappings/pulumi/subnet.yaml +++ b/pkg/infra/state_reader/state_template/mappings/pulumi/subnet.yaml @@ -8,4 +8,4 @@ property_mappings: mapPublicIpOnLaunch: MapPublicIpOnLaunch tags: Tags id: Id - arn: Arn \ No newline at end of file + arn: Arn diff --git a/pkg/knowledgebase/kb.go b/pkg/knowledgebase/kb.go index e597f21bc..4dfd85f11 100644 --- a/pkg/knowledgebase/kb.go +++ b/pkg/knowledgebase/kb.go @@ -11,8 +11,8 @@ import ( "go.uber.org/zap" ) -//go:generate mockgen --source=./kb.go -destination=./template_kb_mock_test.go -package=knowledgebase2 -//go:generate mockgen --source=./kb.go -destination=../engine2/operational_eval/template_kb_mock_test.go -package=operational_eval +//go:generate mockgen --source=./kb.go -destination=./template_kb_mock_test.go -package=knowledgebase +//go:generate mockgen --source=./kb.go -destination=../engine/operational_eval/template_kb_mock_test.go -package=operational_eval type ( TemplateKB interface { diff --git a/pkg/knowledgebase/operational_rule.go b/pkg/knowledgebase/operational_rule.go index 887d72ca6..e8dd3eafc 100644 --- a/pkg/knowledgebase/operational_rule.go +++ b/pkg/knowledgebase/operational_rule.go @@ -98,22 +98,23 @@ const ( ClosestSelectionOperator SelectionOperator = "" ) -func (rule AdditionalRule) Hash() (string, error) { - // Convert the struct to a byte slice. - // Note that the struct must be able to be converted to JSON, - // so all fields must be exported (i.e., start with a capital letter). - byteSlice, err := json.Marshal(rule) +func hashStruct(s any) string { + hash := sha256.New() + err := json.NewEncoder(hash).Encode(s) if err != nil { - return "", err + // All types that use this function should be able to be encoded to JSON. + // If this panic occurs, there's a programming error. + panic(fmt.Errorf("error hashing struct %v (%[1]T): %w", s, err)) } + return hex.EncodeToString(hash.Sum(nil)) +} - // Hash the byte slice. - hash := sha256.Sum256(byteSlice) - - // Convert the hash to a hexadecimal string. - hashString := hex.EncodeToString(hash[:]) +func (rule AdditionalRule) Hash() string { + return hashStruct(rule) +} - return hashString, nil +func (rule OperationalRule) Hash() string { + return hashStruct(rule) } func (d Direction) Edge(resource, dep construct.ResourceId) construct.SimpleEdge { diff --git a/pkg/knowledgebase/operational_rule_test.go b/pkg/knowledgebase/operational_rule_test.go index 8966816fd..557daa860 100644 --- a/pkg/knowledgebase/operational_rule_test.go +++ b/pkg/knowledgebase/operational_rule_test.go @@ -75,7 +75,7 @@ func TestAdditionalRule_Hash(t *testing.T) { }, }, }, - want: "02c61dd3cd718a1cb28439705f2291018dbffe8aaa110f4e5eb72b67f0963b4f", + want: "121890af892b7324a133b58b36e4e54a03a192f5268546a56f799cd60ba3fbdb", }, { name: "simple rule 2", @@ -87,14 +87,13 @@ func TestAdditionalRule_Hash(t *testing.T) { }, }, }, - want: "ec11f23efba8646d56563e96ddf8d7963d09cede7290b6242efe49bb68e91c40", + want: "a23bc24879d4b78eddef0f4c779fd7b26cc7c8c04cc65cd4b29683342b36961c", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert := assert.New(t) - got, err := tt.rule.Hash() - assert.NoError(err) + got := tt.rule.Hash() assert.Equal(tt.want, got) }) } diff --git a/pkg/knowledgebase/properties/list_property.go b/pkg/knowledgebase/properties/list_property.go index d6bade667..58460ab77 100644 --- a/pkg/knowledgebase/properties/list_property.go +++ b/pkg/knowledgebase/properties/list_property.go @@ -113,9 +113,11 @@ func (list *ListProperty) Parse(value any, ctx knowledgebase.DynamicContext, dat if strVal, ok := value.(string); ok { var result []any err := ctx.ExecuteDecode(strVal, data, &result) - return result, err + if err != nil { + return nil, fmt.Errorf("invalid list value %v: %w", value, err) + } + val = result } - return nil, fmt.Errorf("invalid list value %v", value) } for _, v := range val { diff --git a/pkg/knowledgebase/resource_template.go b/pkg/knowledgebase/resource_template.go index 268f8adbd..41548c007 100644 --- a/pkg/knowledgebase/resource_template.go +++ b/pkg/knowledgebase/resource_template.go @@ -13,7 +13,7 @@ import ( "gopkg.in/yaml.v3" ) -//go:generate mockgen -source=./resource_template.go --destination=./resource_template_mock_test.go --package=knowledgebase2 +//go:generate mockgen -source=./resource_template.go --destination=./resource_template_mock_test.go --package=knowledgebase //go:generate mockgen -source=./resource_template.go --destination=../engine2/operational_eval/resource_template_mock_test.go --package=operational_eval type ( diff --git a/pkg/knowledgebase/resource_template_mock_test.go b/pkg/knowledgebase/resource_template_mock_test.go index 02fb16f6b..dc14b8b7b 100644 --- a/pkg/knowledgebase/resource_template_mock_test.go +++ b/pkg/knowledgebase/resource_template_mock_test.go @@ -3,16 +3,16 @@ // // Generated by this command: // -// mockgen -source=./resource_template.go --destination=./resource_template_mock_test.go --package=knowledgebase2 +// mockgen -source=./resource_template.go --destination=./resource_template_mock_test.go --package=knowledgebase // -// Package knowledgebase2 is a generated GoMock package. +// Package knowledgebase is a generated GoMock package. package knowledgebase import ( reflect "reflect" - construct2 "github.com/klothoplatform/klotho/pkg/construct" + construct "github.com/klothoplatform/klotho/pkg/construct" gomock "go.uber.org/mock/gomock" ) @@ -40,7 +40,7 @@ func (m *MockProperty) EXPECT() *MockPropertyMockRecorder { } // AppendProperty mocks base method. -func (m *MockProperty) AppendProperty(resource *construct2.Resource, value any) error { +func (m *MockProperty) AppendProperty(resource *construct.Resource, value any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AppendProperty", resource, value) ret0, _ := ret[0].(error) @@ -126,7 +126,7 @@ func (mr *MockPropertyMockRecorder) Parse(value, ctx, data any) *gomock.Call { } // RemoveProperty mocks base method. -func (m *MockProperty) RemoveProperty(resource *construct2.Resource, value any) error { +func (m *MockProperty) RemoveProperty(resource *construct.Resource, value any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RemoveProperty", resource, value) ret0, _ := ret[0].(error) @@ -140,7 +140,7 @@ func (mr *MockPropertyMockRecorder) RemoveProperty(resource, value any) *gomock. } // SetProperty mocks base method. -func (m *MockProperty) SetProperty(resource *construct2.Resource, value any) error { +func (m *MockProperty) SetProperty(resource *construct.Resource, value any) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetProperty", resource, value) ret0, _ := ret[0].(error) @@ -182,7 +182,7 @@ func (mr *MockPropertyMockRecorder) Type() *gomock.Call { } // Validate mocks base method. -func (m *MockProperty) Validate(resource *construct2.Resource, value any, ctx DynamicContext) error { +func (m *MockProperty) Validate(resource *construct.Resource, value any, ctx DynamicContext) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Validate", resource, value, ctx) ret0, _ := ret[0].(error) diff --git a/pkg/knowledgebase/template_kb_mock_test.go b/pkg/knowledgebase/template_kb_mock_test.go index d80b9baae..64519feb9 100644 --- a/pkg/knowledgebase/template_kb_mock_test.go +++ b/pkg/knowledgebase/template_kb_mock_test.go @@ -3,17 +3,17 @@ // // Generated by this command: // -// mockgen --source=./kb.go -destination=./template_kb_mock_test.go -package=knowledgebase2 +// mockgen --source=./kb.go -destination=./template_kb_mock_test.go -package=knowledgebase // -// Package knowledgebase2 is a generated GoMock package. +// Package knowledgebase is a generated GoMock package. package knowledgebase import ( reflect "reflect" graph "github.com/dominikbraun/graph" - construct2 "github.com/klothoplatform/klotho/pkg/construct" + construct "github.com/klothoplatform/klotho/pkg/construct" gomock "go.uber.org/mock/gomock" ) @@ -69,7 +69,7 @@ func (mr *MockTemplateKBMockRecorder) AddResourceTemplate(template any) *gomock. } // AllPaths mocks base method. -func (m *MockTemplateKB) AllPaths(from, to construct2.ResourceId) ([][]*ResourceTemplate, error) { +func (m *MockTemplateKB) AllPaths(from, to construct.ResourceId) ([][]*ResourceTemplate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AllPaths", from, to) ret0, _ := ret[0].([][]*ResourceTemplate) @@ -99,10 +99,10 @@ func (mr *MockTemplateKBMockRecorder) Edges() *gomock.Call { } // GetAllowedNamespacedResourceIds mocks base method. -func (m *MockTemplateKB) GetAllowedNamespacedResourceIds(ctx DynamicValueContext, resourceId construct2.ResourceId) ([]construct2.ResourceId, error) { +func (m *MockTemplateKB) GetAllowedNamespacedResourceIds(ctx DynamicValueContext, resourceId construct.ResourceId) ([]construct.ResourceId, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAllowedNamespacedResourceIds", ctx, resourceId) - ret0, _ := ret[0].([]construct2.ResourceId) + ret0, _ := ret[0].([]construct.ResourceId) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -114,7 +114,7 @@ func (mr *MockTemplateKBMockRecorder) GetAllowedNamespacedResourceIds(ctx, resou } // GetClassification mocks base method. -func (m *MockTemplateKB) GetClassification(id construct2.ResourceId) Classification { +func (m *MockTemplateKB) GetClassification(id construct.ResourceId) Classification { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetClassification", id) ret0, _ := ret[0].(Classification) @@ -128,7 +128,7 @@ func (mr *MockTemplateKBMockRecorder) GetClassification(id any) *gomock.Call { } // GetEdgeTemplate mocks base method. -func (m *MockTemplateKB) GetEdgeTemplate(from, to construct2.ResourceId) *EdgeTemplate { +func (m *MockTemplateKB) GetEdgeTemplate(from, to construct.ResourceId) *EdgeTemplate { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetEdgeTemplate", from, to) ret0, _ := ret[0].(*EdgeTemplate) @@ -156,7 +156,7 @@ func (mr *MockTemplateKBMockRecorder) GetModel(model any) *gomock.Call { } // GetPathSatisfactionsFromEdge mocks base method. -func (m *MockTemplateKB) GetPathSatisfactionsFromEdge(source, target construct2.ResourceId) ([]EdgePathSatisfaction, error) { +func (m *MockTemplateKB) GetPathSatisfactionsFromEdge(source, target construct.ResourceId) ([]EdgePathSatisfaction, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPathSatisfactionsFromEdge", source, target) ret0, _ := ret[0].([]EdgePathSatisfaction) @@ -171,7 +171,7 @@ func (mr *MockTemplateKBMockRecorder) GetPathSatisfactionsFromEdge(source, targe } // GetResourcePropertyType mocks base method. -func (m *MockTemplateKB) GetResourcePropertyType(resource construct2.ResourceId, propertyName string) string { +func (m *MockTemplateKB) GetResourcePropertyType(resource construct.ResourceId, propertyName string) string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetResourcePropertyType", resource, propertyName) ret0, _ := ret[0].(string) @@ -185,7 +185,7 @@ func (mr *MockTemplateKBMockRecorder) GetResourcePropertyType(resource, property } // GetResourceTemplate mocks base method. -func (m *MockTemplateKB) GetResourceTemplate(id construct2.ResourceId) (*ResourceTemplate, error) { +func (m *MockTemplateKB) GetResourceTemplate(id construct.ResourceId) (*ResourceTemplate, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetResourceTemplate", id) ret0, _ := ret[0].(*ResourceTemplate) @@ -200,10 +200,10 @@ func (mr *MockTemplateKBMockRecorder) GetResourceTemplate(id any) *gomock.Call { } // GetResourcesNamespaceResource mocks base method. -func (m *MockTemplateKB) GetResourcesNamespaceResource(resource *construct2.Resource) (construct2.ResourceId, error) { +func (m *MockTemplateKB) GetResourcesNamespaceResource(resource *construct.Resource) (construct.ResourceId, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetResourcesNamespaceResource", resource) - ret0, _ := ret[0].(construct2.ResourceId) + ret0, _ := ret[0].(construct.ResourceId) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -215,7 +215,7 @@ func (mr *MockTemplateKBMockRecorder) GetResourcesNamespaceResource(resource any } // HasDirectPath mocks base method. -func (m *MockTemplateKB) HasDirectPath(from, to construct2.ResourceId) bool { +func (m *MockTemplateKB) HasDirectPath(from, to construct.ResourceId) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasDirectPath", from, to) ret0, _ := ret[0].(bool) @@ -229,7 +229,7 @@ func (mr *MockTemplateKBMockRecorder) HasDirectPath(from, to any) *gomock.Call { } // HasFunctionalPath mocks base method. -func (m *MockTemplateKB) HasFunctionalPath(from, to construct2.ResourceId) bool { +func (m *MockTemplateKB) HasFunctionalPath(from, to construct.ResourceId) bool { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "HasFunctionalPath", from, to) ret0, _ := ret[0].(bool) diff --git a/pkg/templates/aws/edges/ecs_cluster-service_discovery_http_namespace.yaml b/pkg/templates/aws/edges/ecs_cluster-service_discovery_http_namespace.yaml new file mode 100644 index 000000000..c4d872d3c --- /dev/null +++ b/pkg/templates/aws/edges/ecs_cluster-service_discovery_http_namespace.yaml @@ -0,0 +1,3 @@ +source: aws:ecs_cluster +target: aws:service_discovery_http_namespace +unique: many-to-one diff --git a/pkg/templates/aws/edges/ecs_service-service_discovery_http_namespace.yaml b/pkg/templates/aws/edges/ecs_service-service_discovery_http_namespace.yaml new file mode 100644 index 000000000..89ac9cf27 --- /dev/null +++ b/pkg/templates/aws/edges/ecs_service-service_discovery_http_namespace.yaml @@ -0,0 +1,2 @@ +source: aws:ecs_service +target: aws:service_discovery_http_namespace diff --git a/pkg/templates/aws/edges/service_discovery_http_namespace-ecs_service.yaml b/pkg/templates/aws/edges/service_discovery_http_namespace-ecs_service.yaml new file mode 100644 index 000000000..99bd6952a --- /dev/null +++ b/pkg/templates/aws/edges/service_discovery_http_namespace-ecs_service.yaml @@ -0,0 +1,53 @@ +source: aws:service_discovery_http_namespace +target: aws:ecs_service +deployment_order_reversed: true + +operational_rules: + - configuration_rules: + - resource: '{{ downstream "aws:ecs_task_definition" .Target }}' + configuration: + field: ContainerDefinitions[0].PortMappings + value: | + [ + {{ $portMappings := (fieldValue "ContainerDefinitions[0].PortMappings" (fieldValue "TaskDefinition" .Target)) }} + {{ range $i, $portMapping := $portMappings }} + { + "ContainerPort": {{ $portMapping.ContainerPort }}, + "HostPort": {{ $portMapping.HostPort }}, + "Protocol": "{{ $portMapping.Protocol }}", + "AppProtocol": "http", + {{ if $portMapping.Name }} + "Name": "{{ $portMapping.Name }}" + {{ else }} + "Name": "{{ $.Target.Name }}-port-{{ $i }}" + {{ end }} + }{{ if ne $i (sub (len $portMappings) 1) }},{{ end }} + {{ end }} + ] + - resource: '{{ .Target }}' + configuration: + field: ServiceConnectConfiguration + # the first value call to ContainerDefinitions[0].PortMappings is just here to force the dependency on the first portmapping + value: | + { + "Enabled": true, + "Services": [ + {{ $pms := (fieldValue "ContainerDefinitions[0].PortMappings" (fieldValue "TaskDefinition" .Target)) }} + {{ $containerDefinitions := (fieldValue "ContainerDefinitions" (fieldValue "TaskDefinition" .Target))}} + {{ range $j, $containerDefinition := $containerDefinitions }} + {{ $portMappings := $containerDefinition.PortMappings }} + {{ range $i, $portMapping := $portMappings }} + { + "PortName": "{{ $portMapping.Name}}", + "ClientAliases": [ + { + "Port": {{ $portMapping.HostPort }}, + "DnsName": "{{ $portMapping.Name }}" + } + ] + }{{ if ne $j (sub (len $containerDefinitions) 1) }},{{ end }} + {{ end }} + {{ end }} + ] + } + \ No newline at end of file diff --git a/pkg/templates/aws/edges/service_discovery_service-ecs_service.yaml b/pkg/templates/aws/edges/service_discovery_service-ecs_service.yaml index 8b9e11715..6fdd0a783 100644 --- a/pkg/templates/aws/edges/service_discovery_service-ecs_service.yaml +++ b/pkg/templates/aws/edges/service_discovery_service-ecs_service.yaml @@ -18,4 +18,4 @@ operational_rules: configuration: field: ServiceRegistries value: - RegistryArn: '{{ fieldRef "Arn" .Source }}' + RegistryArn: '{{ fieldRef "Arn" .Source }}' \ No newline at end of file diff --git a/pkg/templates/aws/resources/ecs_cluster.yaml b/pkg/templates/aws/resources/ecs_cluster.yaml index 6bac06404..bd18f6e92 100644 --- a/pkg/templates/aws/resources/ecs_cluster.yaml +++ b/pkg/templates/aws/resources/ecs_cluster.yaml @@ -2,6 +2,21 @@ qualified_type_name: aws:ecs_cluster display_name: ECS Cluster properties: + ServiceConnectDefaults: + type: map + properties: + Namespace: + type: string + operational_rule: + if: | + {{ gt (len (allUpstream "aws:ecs_service" .Self)) 1 }} + step: + direction: downstream + resources: + - aws:service_discovery_http_namespace + unique: true + use_property_ref: Arn + description: The ARN of the aws.servicediscovery.HttpNamespace that's used when you create a service and don't specify a Service Connect configuration. aws:tags: type: model Arn: diff --git a/pkg/templates/aws/resources/ecs_service.yaml b/pkg/templates/aws/resources/ecs_service.yaml index 0ee88f37a..0be0593dc 100644 --- a/pkg/templates/aws/resources/ecs_service.yaml +++ b/pkg/templates/aws/resources/ecs_service.yaml @@ -88,6 +88,112 @@ properties: RegistryArn: type: string description: ARN of the Service Registry. The currently supported service registry is Amazon Route 53 Auto Naming Service + ServiceConnectConfiguration: + type: map + properties: + Enabled: + type: bool + LogConfiguration: + type: map + description: The log configuration specification for the container + properties: + LogDriver: + type: string + description: The log driver to use for the container + operational_rule: + if: | + {{ hasField "ServiceConnectConfiguration" .Self }} + value: awslogs + Options: + type: map(string,string) + description: The configuration options to send to the log driver + properties: + awslogs-group: + type: string + description: The log group to send stdout to + operational_rule: + if: | + {{ hasField "ServiceConnectConfiguration" .Self }} + step: + direction: downstream + resources: + - aws:log_group:{{ .Self.Name }}-log-group + unique: true + use_property_ref: LogGroupName + awslogs-region: + type: string + description: The region which your log group will exist in + operational_rule: + if: | + {{ hasField "ServiceConnectConfiguration" .Self }} + value: '{{ fieldRef "Name" (downstream "aws:region" .Self) }}' + SecretOptions: + type: list + properties: + Name: + type: string + description: The name of the secret + ValueFrom: + type: string + description: The secret to expose to the container + description: The secrets to pass to the log configuration + Namespace: + type: string + description: The namespace name or ARN of the aws.servicediscovery.HttpNamespace for use with Service Connect. + operational_rule: + if: | + {{ hasField "ServiceConnectConfiguration" .Self }} + step: + direction: upstream + resources: + - aws:service_discovery_http_namespace + use_property_ref: Arn + Services: + type: list + properties: + PortName: + type: string + description: The name of one of the portMappings from all the containers in the task definition of this Amazon ECS service. + ClientAlias: + type: set + properties: + Port: + type: int + description: The listening port number for the Service Connect proxy. This port is available inside of all of the tasks within the same namespace. + DnsName: + type: string + description: The name that you use in the applications of client tasks to connect to this service. + DiscoveryName: + type: string + description: The name of the new AWS Cloud Map service that Amazon ECS creates for this Amazon ECS service. + IngressPortOverride: + type: int + description: The port number for the Service Connect proxy to listen on. + Timeout: + type: map + properties: + IdleTimeoutSeconds: + type: int + description: The amount of time in seconds a connection will stay active while idle. A value of 0 can be set to disable idleTimeout. + PerRequestTimeoutSeconds: + type: int + description: The amount of time in seconds for the upstream to respond with a complete response per request. A value of 0 can be set to disable perRequestTimeout. Can only be set when appProtocol isn't TCP. + Tls: + type: map + properties: + IssuerCertAuthority: + type: map + description: The details of the certificate authority which will issue the certificate. + properties: + AwsPcaAuthorityArn: + type: string + description: The ARN of the aws.acmpca.CertificateAuthority used to create the TLS Certificates. + KmsKey: + type: string + description: The KMS key used to encrypt the private key in Secrets Manager. + RoleArn: + type: string + description: The ARN of the IAM Role that's associated with the Service Connect TLS. aws:tags: type: model @@ -114,7 +220,7 @@ consumption: path_satisfaction: as_target: - network - - service_endpoint + - service_discovery as_source: - network#Subnets diff --git a/pkg/templates/aws/resources/ecs_task_definition.yaml b/pkg/templates/aws/resources/ecs_task_definition.yaml index c1db726e6..7a4ce9edc 100644 --- a/pkg/templates/aws/resources/ecs_task_definition.yaml +++ b/pkg/templates/aws/resources/ecs_task_definition.yaml @@ -143,6 +143,16 @@ properties: HostPort: 80 Protocol: TCP properties: + Name: + type: string + description: The name of the port mapping + AppProtocol: + type: string + description: The application protocol that's used for the port mapping. This parameter only applies to Service Connect. + allowed_values: + - http + - http2 + - grpc ContainerPort: type: int description: The port number on the container diff --git a/pkg/templates/aws/resources/service_discovery_http_namespace.yaml b/pkg/templates/aws/resources/service_discovery_http_namespace.yaml new file mode 100644 index 000000000..939ef1875 --- /dev/null +++ b/pkg/templates/aws/resources/service_discovery_http_namespace.yaml @@ -0,0 +1,40 @@ +qualified_type_name: aws:service_discovery_http_namespace +display_name: Service Discovery HTTP Namespace + +properties: + Description: + type: string + description: A description for the namespace + Name: + type: string + description: The name of the namespace + aws:tags: + type: model + Id: + type: string + deploy_time: true + configuration_disabled: true + Arn: + type: string + deploy_time: true + configuration_disabled: true + +path_satisfaction: + as_target: + - permissions + +classification: + is: + - service_discovery + +delete_context: + requires_no_upstream: true + +views: + dataflow: small + + +aws:private_dns_namespace: + deploy: ["servicediscovery:CreatePrivateDnsNamespace"] + tear_down: ["servicediscovery:DeleteNamespace"] + update: ["servicediscovery:UpdatePrivateDnsNamespace"] \ No newline at end of file