blob: 2dba1cf39db6e21300074395dfa110ffdc7de239 [file] [log] [blame] [edit]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package terraform
import (
"log"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// ValidateOpts are the various options the affect the details of how Terraform
// will validate a configuration.
type ValidateOpts struct {
// ExternalProviders are clients for pre-configured providers that are
// treated as being passed into the root module from the caller. This
// is equivalent to writing a "providers" argument inside a "module"
// block in the Terraform language, but for the root module the caller
// is written in Go rather than the Terraform language.
//
// Note, that while Terraform Core will not call ValidateProviderConfig or
// ConfigureProvider on any providers in this map, as with the other context
// functions, the Validate function never calls ConfigureProvider anyway.
//
// Normally, the validate function would call the ValidateProviderConfig
// function on the provider, but the config may rely on variables that are
// not available to this function. Therefore, it is the responsibility of
// the caller to ensure that the provider configurations are valid.
ExternalProviders map[addrs.RootProviderConfig]providers.Interface
}
// Validate performs semantic validation of a configuration, and returns
// any warnings or errors.
//
// Syntax and structural checks are performed by the configuration loader,
// and so are not repeated here.
//
// Validate considers only the configuration and so it won't catch any
// errors caused by current values in the state, or other external information
// such as root module input variables. However, the Plan function includes
// all of the same checks as Validate, in addition to the other work it does
// to consider the previous run state and the planning options.
//
// The opts can be nil, and the ExternalProviders field of the opts can be nil.
func (c *Context) Validate(config *configs.Config, opts *ValidateOpts) tfdiags.Diagnostics {
defer c.acquireRun("validate")()
var diags tfdiags.Diagnostics
if opts == nil {
// Just make sure we don't get any nil pointer exceptions later.
opts = &ValidateOpts{}
}
moreDiags := c.checkConfigDependencies(config)
diags = diags.Append(moreDiags)
// If required dependencies are not available then we'll bail early since
// otherwise we're likely to just see a bunch of other errors related to
// incompatibilities, which could be overwhelming for the user.
if diags.HasErrors() {
return diags
}
// There are some validation checks that happen when loading the provider
// schemas, and we can catch them early to ensure we are in a position to
// handle any errors.
_, moreDiags = c.Schemas(config, nil)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags
}
log.Printf("[DEBUG] Building and walking validate graph")
// Validate is to check if the given module is valid regardless of
// input values, current state, etc. Therefore we populate all of the
// input values with unknown values of the expected type, allowing us
// to perform a type check without assuming any particular values.
varValues := make(InputValues)
for name, variable := range config.Module.Variables {
ty := variable.Type
if ty == cty.NilType {
// Can't predict the type at all, so we'll just mark it as
// cty.DynamicVal (unknown value of cty.DynamicPseudoType).
ty = cty.DynamicPseudoType
}
varValues[name] = &InputValue{
Value: cty.UnknownVal(ty),
SourceType: ValueFromUnknown,
}
}
graph, moreDiags := (&PlanGraphBuilder{
Config: config,
Plugins: c.plugins,
State: states.NewState(),
RootVariableValues: varValues,
Operation: walkValidate,
ExternalProviderConfigs: opts.ExternalProviders,
ImportTargets: c.findImportTargets(config),
}).Build(addrs.RootModuleInstance)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return diags
}
walker, walkDiags := c.walk(graph, walkValidate, &graphWalkOpts{
Config: config,
ProviderFuncResults: providers.NewFunctionResultsTable(nil),
ExternalProviderConfigs: opts.ExternalProviders,
})
diags = diags.Append(walker.NonFatalDiagnostics)
diags = diags.Append(walkDiags)
if walkDiags.HasErrors() {
return diags
}
return diags
}