| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| package terraform |
| |
| import ( |
| "log" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/configs" |
| "github.com/hashicorp/terraform/internal/dag" |
| ) |
| |
| type checkTransformer struct { |
| // Config for the entire module. |
| Config *configs.Config |
| |
| // Operation is the current operation this node will be part of. |
| Operation walkOperation |
| } |
| |
| var _ GraphTransformer = (*checkTransformer)(nil) |
| |
| func (t *checkTransformer) Transform(graph *Graph) error { |
| return t.transform(graph, t.Config, graph.Vertices()) |
| } |
| |
| func (t *checkTransformer) transform(g *Graph, cfg *configs.Config, allNodes []dag.Vertex) error { |
| |
| if t.Operation == walkDestroy || t.Operation == walkPlanDestroy { |
| // Don't include anything about checks during destroy operations. |
| // |
| // For other plan and normal apply operations we do everything, for |
| // destroy operations we do nothing. For any other operations we still |
| // include the check nodes, but we don't actually execute the checks |
| // instead we still validate their references and make sure their |
| // conditions make sense etc. |
| return nil |
| } |
| |
| moduleAddr := cfg.Path |
| |
| for _, check := range cfg.Module.Checks { |
| configAddr := check.Addr().InModule(moduleAddr) |
| |
| // We want to create a node for each check block. This node will execute |
| // after anything it references, and will update the checks object |
| // embedded in the plan and/or state. |
| |
| log.Printf("[TRACE] checkTransformer: Nodes and edges for %s", configAddr) |
| expand := &nodeExpandCheck{ |
| addr: configAddr, |
| config: check, |
| makeInstance: func(addr addrs.AbsCheck, cfg *configs.Check) dag.Vertex { |
| return &nodeCheckAssert{ |
| addr: addr, |
| config: cfg, |
| executeChecks: t.ExecuteChecks(), |
| } |
| }, |
| } |
| g.Add(expand) |
| |
| // We also need to report the checks we are going to execute before we |
| // try and execute them. |
| if t.ReportChecks() { |
| report := &nodeReportCheck{ |
| addr: configAddr, |
| } |
| g.Add(report) |
| |
| // Make sure we report our checks before we start executing the |
| // actual checks. |
| g.Connect(dag.BasicEdge(expand, report)) |
| |
| if check.DataResource != nil { |
| // If we have a nested data source, we need to make sure we |
| // also report the check before the data source executes. |
| // |
| // We loop through all the nodes in the graph to find the one |
| // that contains our data source and connect it. |
| for _, other := range allNodes { |
| if resource, isResource := other.(GraphNodeConfigResource); isResource { |
| resourceAddr := resource.ResourceAddr() |
| if !resourceAddr.Module.Equal(moduleAddr) { |
| // This resource isn't in the same module as our check |
| // so skip it. |
| continue |
| } |
| |
| resourceCfg := cfg.Module.ResourceByAddr(resourceAddr.Resource) |
| if resourceCfg != nil && resourceCfg.Container != nil && resourceCfg.Container.Accessible(check.Addr()) { |
| // Make sure we report our checks before we execute any |
| // embedded data resource. |
| g.Connect(dag.BasicEdge(other, report)) |
| |
| // There's at most one embedded data source, and |
| // we've found it so stop looking. |
| break |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| for _, child := range cfg.Children { |
| if err := t.transform(g, child, allNodes); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // ReportChecks returns true if this operation should report any check blocks |
| // that it is about to execute. |
| // |
| // This is true for planning operations, as apply operations recreate the |
| // expected checks from the plan. |
| // |
| // We'll also report the checks during an import operation. We still execute |
| // our check blocks during an import operation so they need to be reported |
| // first. |
| func (t *checkTransformer) ReportChecks() bool { |
| return t.Operation == walkPlan || t.Operation == walkImport |
| } |
| |
| // ExecuteChecks returns true if this operation should actually execute any |
| // check blocks in the config. |
| // |
| // If this returns false we will still create and execute check nodes in the |
| // graph, but they will only validate things like references and syntax. |
| func (t *checkTransformer) ExecuteChecks() bool { |
| switch t.Operation { |
| case walkPlan, walkApply, walkImport: |
| // We only actually execute the checks for plan and apply operations. |
| return true |
| default: |
| // For everything else, we still want to validate the checks make sense |
| // logically and syntactically, but we won't actually resolve the check |
| // conditions. |
| return false |
| } |
| } |