github.com/opentofu/opentofu@v1.7.1/internal/tofu/context.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 "context" 10 "fmt" 11 "log" 12 "sort" 13 "sync" 14 15 "github.com/opentofu/opentofu/internal/addrs" 16 "github.com/opentofu/opentofu/internal/configs" 17 "github.com/opentofu/opentofu/internal/encryption" 18 "github.com/opentofu/opentofu/internal/logging" 19 "github.com/opentofu/opentofu/internal/providers" 20 "github.com/opentofu/opentofu/internal/provisioners" 21 "github.com/opentofu/opentofu/internal/states" 22 "github.com/opentofu/opentofu/internal/tfdiags" 23 "github.com/zclconf/go-cty/cty" 24 ) 25 26 // InputMode defines what sort of input will be asked for when Input 27 // is called on Context. 28 type InputMode byte 29 30 const ( 31 // InputModeProvider asks for provider variables 32 InputModeProvider InputMode = 1 << iota 33 34 // InputModeStd is the standard operating mode and asks for both variables 35 // and providers. 36 InputModeStd = InputModeProvider 37 ) 38 39 // ContextOpts are the user-configurable options to create a context with 40 // NewContext. 41 type ContextOpts struct { 42 Meta *ContextMeta 43 Hooks []Hook 44 Parallelism int 45 Providers map[addrs.Provider]providers.Factory 46 Provisioners map[string]provisioners.Factory 47 Encryption encryption.Encryption 48 49 UIInput UIInput 50 } 51 52 // ContextMeta is metadata about the running context. This is information 53 // that this package or structure cannot determine on its own but exposes 54 // into OpenTofu in various ways. This must be provided by the Context 55 // initializer. 56 type ContextMeta struct { 57 Env string // Env is the state environment 58 59 // OriginalWorkingDir is the working directory where the OpenTofu CLI 60 // was run from, which may no longer actually be the current working 61 // directory if the user included the -chdir=... option. 62 // 63 // If this string is empty then the original working directory is the same 64 // as the current working directory. 65 // 66 // In most cases we should respect the user's override by ignoring this 67 // path and just using the current working directory, but this is here 68 // for some exceptional cases where the original working directory is 69 // needed. 70 OriginalWorkingDir string 71 } 72 73 // Context represents all the context that OpenTofu needs in order to 74 // perform operations on infrastructure. This structure is built using 75 // NewContext. 76 type Context struct { 77 // meta captures some misc. information about the working directory where 78 // we're taking these actions, and thus which should remain steady between 79 // operations. 80 meta *ContextMeta 81 82 plugins *contextPlugins 83 84 hooks []Hook 85 sh *stopHook 86 uiInput UIInput 87 88 l sync.Mutex // Lock acquired during any task 89 parallelSem Semaphore 90 providerInputConfig map[string]map[string]cty.Value 91 runCond *sync.Cond 92 runContext context.Context 93 runContextCancel context.CancelFunc 94 95 encryption encryption.Encryption 96 } 97 98 // (additional methods on Context can be found in context_*.go files.) 99 100 // NewContext creates a new Context structure. 101 // 102 // Once a Context is created, the caller must not access or mutate any of 103 // the objects referenced (directly or indirectly) by the ContextOpts fields. 104 // 105 // If the returned diagnostics contains errors then the resulting context is 106 // invalid and must not be used. 107 func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) { 108 var diags tfdiags.Diagnostics 109 110 log.Printf("[TRACE] tofu.NewContext: starting") 111 112 // Copy all the hooks and add our stop hook. We don't append directly 113 // to the Config so that we're not modifying that in-place. 114 sh := new(stopHook) 115 hooks := make([]Hook, len(opts.Hooks)+1) 116 copy(hooks, opts.Hooks) 117 hooks[len(opts.Hooks)] = sh 118 119 // Determine parallelism, default to 10. We do this both to limit 120 // CPU pressure but also to have an extra guard against rate throttling 121 // from providers. 122 // We throw an error in case of negative parallelism 123 par := opts.Parallelism 124 if par < 0 { 125 diags = diags.Append(tfdiags.Sourceless( 126 tfdiags.Error, 127 "Invalid parallelism value", 128 fmt.Sprintf("The parallelism must be a positive value. Not %d.", par), 129 )) 130 return nil, diags 131 } 132 133 if par == 0 { 134 par = 10 135 } 136 137 plugins, err := newContextPlugins(opts.Providers, opts.Provisioners) 138 if err != nil { 139 return nil, diags.Append(err) 140 } 141 142 log.Printf("[TRACE] tofu.NewContext: complete") 143 144 return &Context{ 145 hooks: hooks, 146 meta: opts.Meta, 147 uiInput: opts.UIInput, 148 149 plugins: plugins, 150 151 parallelSem: NewSemaphore(par), 152 providerInputConfig: make(map[string]map[string]cty.Value), 153 sh: sh, 154 155 encryption: opts.Encryption, 156 }, diags 157 } 158 159 func (c *Context) Schemas(config *configs.Config, state *states.State) (*Schemas, tfdiags.Diagnostics) { 160 var diags tfdiags.Diagnostics 161 162 ret, err := loadSchemas(config, state, c.plugins) 163 if err != nil { 164 diags = diags.Append(tfdiags.Sourceless( 165 tfdiags.Error, 166 "Failed to load plugin schemas", 167 fmt.Sprintf("Error while loading schemas for plugin components: %s.", err), 168 )) 169 return nil, diags 170 } 171 return ret, diags 172 } 173 174 type ContextGraphOpts struct { 175 // If true, validates the graph structure (checks for cycles). 176 Validate bool 177 178 // Legacy graphs only: won't prune the graph 179 Verbose bool 180 } 181 182 // Stop stops the running task. 183 // 184 // Stop will block until the task completes. 185 func (c *Context) Stop() { 186 log.Printf("[WARN] tofu: Stop called, initiating interrupt sequence") 187 188 c.l.Lock() 189 defer c.l.Unlock() 190 191 // If we're running, then stop 192 if c.runContextCancel != nil { 193 log.Printf("[WARN] tofu: run context exists, stopping") 194 195 // Tell the hook we want to stop 196 c.sh.Stop() 197 198 // Stop the context 199 c.runContextCancel() 200 c.runContextCancel = nil 201 } 202 203 // Notify all of the hooks that we're stopping, in case they want to try 204 // to flush in-memory state to disk before a subsequent hard kill. 205 for _, hook := range c.hooks { 206 hook.Stopping() 207 } 208 209 // Grab the condition var before we exit 210 if cond := c.runCond; cond != nil { 211 log.Printf("[INFO] tofu: waiting for graceful stop to complete") 212 cond.Wait() 213 } 214 215 log.Printf("[WARN] tofu: stop complete") 216 } 217 218 func (c *Context) acquireRun(phase string) func() { 219 // With the run lock held, grab the context lock to make changes 220 // to the run context. 221 c.l.Lock() 222 defer c.l.Unlock() 223 224 // Wait until we're no longer running 225 for c.runCond != nil { 226 c.runCond.Wait() 227 } 228 229 // Build our lock 230 c.runCond = sync.NewCond(&c.l) 231 232 // Create a new run context 233 c.runContext, c.runContextCancel = context.WithCancel(context.Background()) 234 235 // Reset the stop hook so we're not stopped 236 c.sh.Reset() 237 238 return c.releaseRun 239 } 240 241 func (c *Context) releaseRun() { 242 // Grab the context lock so that we can make modifications to fields 243 c.l.Lock() 244 defer c.l.Unlock() 245 246 // End our run. We check if runContext is non-nil because it can be 247 // set to nil if it was cancelled via Stop() 248 if c.runContextCancel != nil { 249 c.runContextCancel() 250 } 251 252 // Unlock all waiting our condition 253 cond := c.runCond 254 c.runCond = nil 255 cond.Broadcast() 256 257 // Unset the context 258 c.runContext = nil 259 } 260 261 // watchStop immediately returns a `stop` and a `wait` chan after dispatching 262 // the watchStop goroutine. This will watch the runContext for cancellation and 263 // stop the providers accordingly. When the watch is no longer needed, the 264 // `stop` chan should be closed before waiting on the `wait` chan. 265 // The `wait` chan is important, because without synchronizing with the end of 266 // the watchStop goroutine, the runContext may also be closed during the select 267 // incorrectly causing providers to be stopped. Even if the graph walk is done 268 // at that point, stopping a provider permanently cancels its StopContext which 269 // can cause later actions to fail. 270 func (c *Context) watchStop(walker *ContextGraphWalker) (chan struct{}, <-chan struct{}) { 271 stop := make(chan struct{}) 272 wait := make(chan struct{}) 273 274 // get the runContext cancellation channel now, because releaseRun will 275 // write to the runContext field. 276 done := c.runContext.Done() 277 278 panicHandler := logging.PanicHandlerWithTraceFn() 279 go func() { 280 defer panicHandler() 281 282 defer close(wait) 283 // Wait for a stop or completion 284 select { 285 case <-done: 286 // done means the context was canceled, so we need to try and stop 287 // providers. 288 case <-stop: 289 // our own stop channel was closed. 290 return 291 } 292 293 // If we're here, we're stopped, trigger the call. 294 log.Printf("[TRACE] Context: requesting providers and provisioners to gracefully stop") 295 296 { 297 // Copy the providers so that a misbehaved blocking Stop doesn't 298 // completely hang OpenTofu. 299 walker.providerLock.Lock() 300 ps := make([]providers.Interface, 0, len(walker.providerCache)) 301 for _, p := range walker.providerCache { 302 ps = append(ps, p) 303 } 304 defer walker.providerLock.Unlock() 305 306 for _, p := range ps { 307 // We ignore the error for now since there isn't any reasonable 308 // action to take if there is an error here, since the stop is still 309 // advisory: OpenTofu will exit once the graph node completes. 310 p.Stop() 311 } 312 } 313 314 { 315 // Call stop on all the provisioners 316 walker.provisionerLock.Lock() 317 ps := make([]provisioners.Interface, 0, len(walker.provisionerCache)) 318 for _, p := range walker.provisionerCache { 319 ps = append(ps, p) 320 } 321 defer walker.provisionerLock.Unlock() 322 323 for _, p := range ps { 324 // We ignore the error for now since there isn't any reasonable 325 // action to take if there is an error here, since the stop is still 326 // advisory: OpenTofu will exit once the graph node completes. 327 p.Stop() 328 } 329 } 330 }() 331 332 return stop, wait 333 } 334 335 // checkConfigDependencies checks whether the recieving context is able to 336 // support the given configuration, returning error diagnostics if not. 337 // 338 // Currently this function checks whether the current OpenTofu CLI version 339 // matches the version requirements of all of the modules, and whether our 340 // plugin library contains all of the plugin names/addresses needed. 341 // 342 // This function does *not* check that external modules are installed (that's 343 // the responsibility of the configuration loader) and doesn't check that the 344 // plugins are of suitable versions to match any version constraints (which is 345 // the responsibility of the code which installed the plugins and then 346 // constructed the Providers/Provisioners maps passed in to NewContext). 347 // 348 // In most cases we should typically catch the problems this function detects 349 // before we reach this point, but this function can come into play in some 350 // unusual cases outside of the main workflow, and can avoid some 351 // potentially-more-confusing errors from later operations. 352 func (c *Context) checkConfigDependencies(config *configs.Config) tfdiags.Diagnostics { 353 var diags tfdiags.Diagnostics 354 355 // This checks the OpenTofu CLI version constraints specified in all of 356 // the modules. 357 diags = diags.Append(CheckCoreVersionRequirements(config)) 358 359 // We only check that we have a factory for each required provider, and 360 // assume the caller already assured that any separately-installed 361 // plugins are of a suitable version, match expected checksums, etc. 362 providerReqs, hclDiags := config.ProviderRequirements() 363 diags = diags.Append(hclDiags) 364 if hclDiags.HasErrors() { 365 return diags 366 } 367 for providerAddr := range providerReqs { 368 if !c.plugins.HasProvider(providerAddr) { 369 if !providerAddr.IsBuiltIn() { 370 diags = diags.Append(tfdiags.Sourceless( 371 tfdiags.Error, 372 "Missing required provider", 373 fmt.Sprintf( 374 "This configuration requires provider %s, but that provider isn't available. You may be able to install it automatically by running:\n tofu init", 375 providerAddr, 376 ), 377 )) 378 } else { 379 // Built-in providers can never be installed by "tofu init", 380 // so no point in confusing the user by suggesting that. 381 diags = diags.Append(tfdiags.Sourceless( 382 tfdiags.Error, 383 "Missing required provider", 384 fmt.Sprintf( 385 "This configuration requires built-in provider %s, but that provider isn't available in this OpenTofu version.", 386 providerAddr, 387 ), 388 )) 389 } 390 } 391 } 392 393 // Our handling of provisioners is much less sophisticated than providers 394 // because they are in many ways a legacy system. We need to go hunting 395 // for them more directly in the configuration. 396 config.DeepEach(func(modCfg *configs.Config) { 397 if modCfg == nil || modCfg.Module == nil { 398 return // should not happen, but we'll be robust 399 } 400 for _, rc := range modCfg.Module.ManagedResources { 401 if rc.Managed == nil { 402 continue // should not happen, but we'll be robust 403 } 404 for _, pc := range rc.Managed.Provisioners { 405 if !c.plugins.HasProvisioner(pc.Type) { 406 // This is not a very high-quality error, because really 407 // the caller of tofu.NewContext should've already 408 // done equivalent checks when doing plugin discovery. 409 // This is just to make sure we return a predictable 410 // error in a central place, rather than failing somewhere 411 // later in the non-deterministically-ordered graph walk. 412 diags = diags.Append(tfdiags.Sourceless( 413 tfdiags.Error, 414 "Missing required provisioner plugin", 415 fmt.Sprintf( 416 "This configuration requires provisioner plugin %q, which isn't available. If you're intending to use an external provisioner plugin, you must install it manually into one of the plugin search directories before running OpenTofu.", 417 pc.Type, 418 ), 419 )) 420 } 421 } 422 } 423 }) 424 425 // Because we were doing a lot of map iteration above, and we're only 426 // generating sourceless diagnostics anyway, our diagnostics will not be 427 // in a deterministic order. To ensure stable output when there are 428 // multiple errors to report, we'll sort these particular diagnostics 429 // so they are at least always consistent alone. This ordering is 430 // arbitrary and not a compatibility constraint. 431 sort.Slice(diags, func(i, j int) bool { 432 // Because these are sourcelss diagnostics and we know they are all 433 // errors, we know they'll only differ in their description fields. 434 descI := diags[i].Description() 435 descJ := diags[j].Description() 436 switch { 437 case descI.Summary != descJ.Summary: 438 return descI.Summary < descJ.Summary 439 default: 440 return descI.Detail < descJ.Detail 441 } 442 }) 443 444 return diags 445 }