github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_eval.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"log"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  	"github.com/opentofu/opentofu/internal/configs"
    13  	"github.com/opentofu/opentofu/internal/lang"
    14  	"github.com/opentofu/opentofu/internal/states"
    15  	"github.com/opentofu/opentofu/internal/tfdiags"
    16  )
    17  
    18  type EvalOpts struct {
    19  	SetVariables InputValues
    20  }
    21  
    22  // Eval produces a scope in which expressions can be evaluated for
    23  // the given module path.
    24  //
    25  // This method must first evaluate any ephemeral values (input variables, local
    26  // values, and output values) in the configuration. These ephemeral values are
    27  // not included in the persisted state, so they must be re-computed using other
    28  // values in the state before they can be properly evaluated. The updated
    29  // values are retained in the main state associated with the receiving context.
    30  //
    31  // This function takes no action against remote APIs but it does need access
    32  // to all provider and provisioner instances in order to obtain their schemas
    33  // for type checking.
    34  //
    35  // The result is an evaluation scope that can be used to resolve references
    36  // against the root module. If the returned diagnostics contains errors then
    37  // the returned scope may be nil. If it is not nil then it may still be used
    38  // to attempt expression evaluation or other analysis, but some expressions
    39  // may not behave as expected.
    40  func (c *Context) Eval(config *configs.Config, state *states.State, moduleAddr addrs.ModuleInstance, opts *EvalOpts) (*lang.Scope, tfdiags.Diagnostics) {
    41  	// This is intended for external callers such as the "tofu console"
    42  	// command. Internally, we create an evaluator in c.walk before walking
    43  	// the graph, and create scopes in ContextGraphWalker.
    44  
    45  	var diags tfdiags.Diagnostics
    46  	defer c.acquireRun("eval")()
    47  
    48  	// Start with a copy of state so that we don't affect the instance that
    49  	// the caller is holding.
    50  	state = state.DeepCopy()
    51  	var walker *ContextGraphWalker
    52  
    53  	variables := opts.SetVariables
    54  
    55  	// By the time we get here, we should have values defined for all of
    56  	// the root module variables, even if some of them are "unknown". It's the
    57  	// caller's responsibility to have already handled the decoding of these
    58  	// from the various ways the CLI allows them to be set and to produce
    59  	// user-friendly error messages if they are not all present, and so
    60  	// the error message from checkInputVariables should never be seen and
    61  	// includes language asking the user to report a bug.
    62  	varDiags := checkInputVariables(config.Module.Variables, variables)
    63  	diags = diags.Append(varDiags)
    64  
    65  	log.Printf("[DEBUG] Building and walking 'eval' graph")
    66  
    67  	graph, moreDiags := (&EvalGraphBuilder{
    68  		Config:             config,
    69  		State:              state,
    70  		RootVariableValues: variables,
    71  		Plugins:            c.plugins,
    72  	}).Build(addrs.RootModuleInstance)
    73  	diags = diags.Append(moreDiags)
    74  	if moreDiags.HasErrors() {
    75  		return nil, diags
    76  	}
    77  
    78  	walkOpts := &graphWalkOpts{
    79  		InputState: state,
    80  		Config:     config,
    81  	}
    82  
    83  	walker, moreDiags = c.walk(graph, walkEval, walkOpts)
    84  	diags = diags.Append(moreDiags)
    85  	if walker != nil {
    86  		diags = diags.Append(walker.NonFatalDiagnostics)
    87  	} else {
    88  		// If we skipped walking the graph (due to errors) then we'll just
    89  		// use a placeholder graph walker here, which'll refer to the
    90  		// unmodified state.
    91  		walker = c.graphWalker(walkEval, walkOpts)
    92  	}
    93  
    94  	// This is a bit weird since we don't normally evaluate outside of
    95  	// the context of a walk, but we'll "re-enter" our desired path here
    96  	// just to get hold of an EvalContext for it. ContextGraphWalker
    97  	// caches its contexts, so we should get hold of the context that was
    98  	// previously used for evaluation here, unless we skipped walking.
    99  	evalCtx := walker.EnterPath(moduleAddr)
   100  	return evalCtx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey), diags
   101  }