github.com/opentofu/opentofu@v1.7.1/internal/command/validate.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 command 7 8 import ( 9 "fmt" 10 "path/filepath" 11 "strings" 12 13 "github.com/opentofu/opentofu/internal/addrs" 14 "github.com/opentofu/opentofu/internal/command/arguments" 15 "github.com/opentofu/opentofu/internal/command/views" 16 "github.com/opentofu/opentofu/internal/configs" 17 "github.com/opentofu/opentofu/internal/tfdiags" 18 "github.com/opentofu/opentofu/internal/tofu" 19 ) 20 21 // ValidateCommand is a Command implementation that validates the tofu files 22 type ValidateCommand struct { 23 Meta 24 } 25 26 func (c *ValidateCommand) Run(rawArgs []string) int { 27 // Parse and apply global view arguments 28 common, rawArgs := arguments.ParseView(rawArgs) 29 c.View.Configure(common) 30 31 // Parse and validate flags 32 args, diags := arguments.ParseValidate(rawArgs) 33 if diags.HasErrors() { 34 c.View.Diagnostics(diags) 35 c.View.HelpPrompt("validate") 36 return 1 37 } 38 39 view := views.NewValidate(args.ViewType, c.View) 40 41 // After this point, we must only produce JSON output if JSON mode is 42 // enabled, so all errors should be accumulated into diags and we'll 43 // print out a suitable result at the end, depending on the format 44 // selection. All returns from this point on must be tail-calls into 45 // view.Results in order to produce the expected output. 46 47 dir, err := filepath.Abs(args.Path) 48 if err != nil { 49 diags = diags.Append(fmt.Errorf("unable to locate module: %w", err)) 50 return view.Results(diags) 51 } 52 53 // Check for user-supplied plugin path 54 if c.pluginPath, err = c.loadPluginPath(); err != nil { 55 diags = diags.Append(fmt.Errorf("error loading plugin path: %w", err)) 56 return view.Results(diags) 57 } 58 59 validateDiags := c.validate(dir, args.TestDirectory, args.NoTests) 60 diags = diags.Append(validateDiags) 61 62 // Validating with dev overrides in effect means that the result might 63 // not be valid for a stable release, so we'll warn about that in case 64 // the user is trying to use "tofu validate" as a sort of pre-flight 65 // check before submitting a change. 66 diags = diags.Append(c.providerDevOverrideRuntimeWarnings()) 67 68 return view.Results(diags) 69 } 70 71 func (c *ValidateCommand) validate(dir, testDir string, noTests bool) tfdiags.Diagnostics { 72 var diags tfdiags.Diagnostics 73 var cfg *configs.Config 74 75 if noTests { 76 cfg, diags = c.loadConfig(dir) 77 } else { 78 cfg, diags = c.loadConfigWithTests(dir, testDir) 79 } 80 if diags.HasErrors() { 81 return diags 82 } 83 84 validate := func(cfg *configs.Config) tfdiags.Diagnostics { 85 var diags tfdiags.Diagnostics 86 87 opts, err := c.contextOpts() 88 if err != nil { 89 diags = diags.Append(err) 90 return diags 91 } 92 93 tfCtx, ctxDiags := tofu.NewContext(opts) 94 diags = diags.Append(ctxDiags) 95 if ctxDiags.HasErrors() { 96 return diags 97 } 98 99 return diags.Append(tfCtx.Validate(cfg)) 100 } 101 102 diags = diags.Append(validate(cfg)) 103 104 if noTests { 105 return diags 106 } 107 108 validatedModules := make(map[string]bool) 109 110 // We'll also do a quick validation of the OpenTofu test files. These live 111 // outside the OpenTofu graph so we have to do this separately. 112 for _, file := range cfg.Module.Tests { 113 for _, run := range file.Runs { 114 115 if run.Module != nil { 116 // Then we can also validate the referenced modules, but we are 117 // only going to do this is if they are local modules. 118 // 119 // Basically, local testing modules are something the user can 120 // reasonably go and fix. If it's a module being downloaded from 121 // the registry, the expectation is that the author of the 122 // module should have ran `tofu validate` themselves. 123 if _, ok := run.Module.Source.(addrs.ModuleSourceLocal); ok { 124 125 if validated := validatedModules[run.Module.Source.String()]; !validated { 126 127 // Since we can reference the same module twice, let's 128 // not validate the same thing multiple times. 129 130 validatedModules[run.Module.Source.String()] = true 131 diags = diags.Append(validate(run.ConfigUnderTest)) 132 } 133 134 } 135 } 136 137 diags = diags.Append(run.Validate()) 138 } 139 } 140 141 return diags 142 } 143 144 func (c *ValidateCommand) Synopsis() string { 145 return "Check whether the configuration is valid" 146 } 147 148 func (c *ValidateCommand) Help() string { 149 helpText := ` 150 Usage: tofu [global options] validate [options] 151 152 Validate the configuration files in a directory, referring only to the 153 configuration and not accessing any remote services such as remote state, 154 provider APIs, etc. 155 156 Validate runs checks that verify whether a configuration is syntactically 157 valid and internally consistent, regardless of any provided variables or 158 existing state. It is thus primarily useful for general verification of 159 reusable modules, including correctness of attribute names and value types. 160 161 It is safe to run this command automatically, for example as a post-save 162 check in a text editor or as a test step for a re-usable module in a CI 163 system. 164 165 Validation requires an initialized working directory with any referenced 166 plugins and modules installed. To initialize a working directory for 167 validation without accessing any configured remote backend, use: 168 tofu init -backend=false 169 170 To verify configuration in the context of a particular run (a particular 171 target workspace, input variable values, etc), use the 'tofu plan' 172 command instead, which includes an implied validation check. 173 174 Options: 175 176 -json Produce output in a machine-readable JSON format, 177 suitable for use in text editor integrations and other 178 automated systems. Always disables color. 179 180 -no-color If specified, output won't contain any color. 181 182 -no-tests If specified, OpenTofu will not validate test files. 183 184 -test-directory=path Set the OpenTofu test directory, defaults to "tests". When set, the 185 test command will search for test files in the current directory and 186 in the one specified by the flag. 187 ` 188 return strings.TrimSpace(helpText) 189 }