| package terraform |
| |
| import ( |
| "bytes" |
| "context" |
| "fmt" |
| "log" |
| "strings" |
| "sync" |
| |
| "github.com/hashicorp/hcl" |
| "github.com/zclconf/go-cty/cty" |
| |
| "github.com/hashicorp/terraform/addrs" |
| "github.com/hashicorp/terraform/config" |
| "github.com/hashicorp/terraform/configs" |
| "github.com/hashicorp/terraform/lang" |
| "github.com/hashicorp/terraform/plans" |
| "github.com/hashicorp/terraform/providers" |
| "github.com/hashicorp/terraform/provisioners" |
| "github.com/hashicorp/terraform/states" |
| "github.com/hashicorp/terraform/states/statefile" |
| "github.com/hashicorp/terraform/tfdiags" |
| ) |
| |
| // InputMode defines what sort of input will be asked for when Input |
| // is called on Context. |
| type InputMode byte |
| |
| const ( |
| // InputModeVar asks for all variables |
| InputModeVar InputMode = 1 << iota |
| |
| // InputModeVarUnset asks for variables which are not set yet. |
| // InputModeVar must be set for this to have an effect. |
| InputModeVarUnset |
| |
| // InputModeProvider asks for provider variables |
| InputModeProvider |
| |
| // InputModeStd is the standard operating mode and asks for both variables |
| // and providers. |
| InputModeStd = InputModeVar | InputModeProvider |
| ) |
| |
| var ( |
| // contextFailOnShadowError will cause Context operations to return |
| // errors when shadow operations fail. This is only used for testing. |
| contextFailOnShadowError = false |
| |
| // contextTestDeepCopyOnPlan will perform a Diff DeepCopy on every |
| // Plan operation, effectively testing the Diff DeepCopy whenever |
| // a Plan occurs. This is enabled for tests. |
| contextTestDeepCopyOnPlan = false |
| ) |
| |
| // ContextOpts are the user-configurable options to create a context with |
| // NewContext. |
| type ContextOpts struct { |
| Config *configs.Config |
| Changes *plans.Changes |
| State *states.State |
| Targets []addrs.Targetable |
| Variables InputValues |
| Meta *ContextMeta |
| Destroy bool |
| |
| Hooks []Hook |
| Parallelism int |
| ProviderResolver providers.Resolver |
| Provisioners map[string]ProvisionerFactory |
| |
| // If non-nil, will apply as additional constraints on the provider |
| // plugins that will be requested from the provider resolver. |
| ProviderSHA256s map[string][]byte |
| SkipProviderVerify bool |
| |
| UIInput UIInput |
| } |
| |
| // ContextMeta is metadata about the running context. This is information |
| // that this package or structure cannot determine on its own but exposes |
| // into Terraform in various ways. This must be provided by the Context |
| // initializer. |
| type ContextMeta struct { |
| Env string // Env is the state environment |
| } |
| |
| // Context represents all the context that Terraform needs in order to |
| // perform operations on infrastructure. This structure is built using |
| // NewContext. |
| type Context struct { |
| config *configs.Config |
| changes *plans.Changes |
| state *states.State |
| targets []addrs.Targetable |
| variables InputValues |
| meta *ContextMeta |
| destroy bool |
| |
| hooks []Hook |
| components contextComponentFactory |
| schemas *Schemas |
| sh *stopHook |
| uiInput UIInput |
| |
| l sync.Mutex // Lock acquired during any task |
| parallelSem Semaphore |
| providerInputConfig map[string]map[string]cty.Value |
| providerSHA256s map[string][]byte |
| runLock sync.Mutex |
| runCond *sync.Cond |
| runContext context.Context |
| runContextCancel context.CancelFunc |
| shadowErr error |
| } |
| |
| // (additional methods on Context can be found in context_*.go files.) |
| |
| // NewContext creates a new Context structure. |
| // |
| // Once a Context is created, the caller must not access or mutate any of |
| // the objects referenced (directly or indirectly) by the ContextOpts fields. |
| // |
| // If the returned diagnostics contains errors then the resulting context is |
| // invalid and must not be used. |
| func NewContext(opts *ContextOpts) (*Context, tfdiags.Diagnostics) { |
| log.Printf("[TRACE] terraform.NewContext: starting") |
| diags := CheckCoreVersionRequirements(opts.Config) |
| // If version constraints are not met 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 nil, diags |
| } |
| |
| // Copy all the hooks and add our stop hook. We don't append directly |
| // to the Config so that we're not modifying that in-place. |
| sh := new(stopHook) |
| hooks := make([]Hook, len(opts.Hooks)+1) |
| copy(hooks, opts.Hooks) |
| hooks[len(opts.Hooks)] = sh |
| |
| state := opts.State |
| if state == nil { |
| state = states.NewState() |
| } |
| |
| // Determine parallelism, default to 10. We do this both to limit |
| // CPU pressure but also to have an extra guard against rate throttling |
| // from providers. |
| par := opts.Parallelism |
| if par == 0 { |
| par = 10 |
| } |
| |
| // Set up the variables in the following sequence: |
| // 0 - Take default values from the configuration |
| // 1 - Take values from TF_VAR_x environment variables |
| // 2 - Take values specified in -var flags, overriding values |
| // set by environment variables if necessary. This includes |
| // values taken from -var-file in addition. |
| var variables InputValues |
| if opts.Config != nil { |
| // Default variables from the configuration seed our map. |
| variables = DefaultVariableValues(opts.Config.Module.Variables) |
| } |
| // Variables provided by the caller (from CLI, environment, etc) can |
| // override the defaults. |
| variables = variables.Override(opts.Variables) |
| |
| // Bind available provider plugins to the constraints in config |
| var providerFactories map[string]providers.Factory |
| if opts.ProviderResolver != nil { |
| deps := ConfigTreeDependencies(opts.Config, state) |
| reqd := deps.AllPluginRequirements() |
| if opts.ProviderSHA256s != nil && !opts.SkipProviderVerify { |
| reqd.LockExecutables(opts.ProviderSHA256s) |
| } |
| log.Printf("[TRACE] terraform.NewContext: resolving provider version selections") |
| |
| var providerDiags tfdiags.Diagnostics |
| providerFactories, providerDiags = resourceProviderFactories(opts.ProviderResolver, reqd) |
| diags = diags.Append(providerDiags) |
| |
| if diags.HasErrors() { |
| return nil, diags |
| } |
| } else { |
| providerFactories = make(map[string]providers.Factory) |
| } |
| |
| components := &basicComponentFactory{ |
| providers: providerFactories, |
| provisioners: opts.Provisioners, |
| } |
| |
| log.Printf("[TRACE] terraform.NewContext: loading provider schemas") |
| schemas, err := LoadSchemas(opts.Config, opts.State, components) |
| if err != nil { |
| diags = diags.Append(err) |
| return nil, diags |
| } |
| |
| changes := opts.Changes |
| if changes == nil { |
| changes = plans.NewChanges() |
| } |
| |
| config := opts.Config |
| if config == nil { |
| config = configs.NewEmptyConfig() |
| } |
| |
| log.Printf("[TRACE] terraform.NewContext: complete") |
| |
| return &Context{ |
| components: components, |
| schemas: schemas, |
| destroy: opts.Destroy, |
| changes: changes, |
| hooks: hooks, |
| meta: opts.Meta, |
| config: config, |
| state: state, |
| targets: opts.Targets, |
| uiInput: opts.UIInput, |
| variables: variables, |
| |
| parallelSem: NewSemaphore(par), |
| providerInputConfig: make(map[string]map[string]cty.Value), |
| providerSHA256s: opts.ProviderSHA256s, |
| sh: sh, |
| }, nil |
| } |
| |
| func (c *Context) Schemas() *Schemas { |
| return c.schemas |
| } |
| |
| type ContextGraphOpts struct { |
| // If true, validates the graph structure (checks for cycles). |
| Validate bool |
| |
| // Legacy graphs only: won't prune the graph |
| Verbose bool |
| } |
| |
| // Graph returns the graph used for the given operation type. |
| // |
| // The most extensive or complex graph type is GraphTypePlan. |
| func (c *Context) Graph(typ GraphType, opts *ContextGraphOpts) (*Graph, tfdiags.Diagnostics) { |
| if opts == nil { |
| opts = &ContextGraphOpts{Validate: true} |
| } |
| |
| log.Printf("[INFO] terraform: building graph: %s", typ) |
| switch typ { |
| case GraphTypeApply: |
| return (&ApplyGraphBuilder{ |
| Config: c.config, |
| Changes: c.changes, |
| State: c.state, |
| Components: c.components, |
| Schemas: c.schemas, |
| Targets: c.targets, |
| Destroy: c.destroy, |
| Validate: opts.Validate, |
| }).Build(addrs.RootModuleInstance) |
| |
| case GraphTypeValidate: |
| // The validate graph is just a slightly modified plan graph |
| fallthrough |
| case GraphTypePlan: |
| // Create the plan graph builder |
| p := &PlanGraphBuilder{ |
| Config: c.config, |
| State: c.state, |
| Components: c.components, |
| Schemas: c.schemas, |
| Targets: c.targets, |
| Validate: opts.Validate, |
| } |
| |
| // Some special cases for other graph types shared with plan currently |
| var b GraphBuilder = p |
| switch typ { |
| case GraphTypeValidate: |
| b = ValidateGraphBuilder(p) |
| } |
| |
| return b.Build(addrs.RootModuleInstance) |
| |
| case GraphTypePlanDestroy: |
| return (&DestroyPlanGraphBuilder{ |
| Config: c.config, |
| State: c.state, |
| Components: c.components, |
| Schemas: c.schemas, |
| Targets: c.targets, |
| Validate: opts.Validate, |
| }).Build(addrs.RootModuleInstance) |
| |
| case GraphTypeRefresh: |
| return (&RefreshGraphBuilder{ |
| Config: c.config, |
| State: c.state, |
| Components: c.components, |
| Schemas: c.schemas, |
| Targets: c.targets, |
| Validate: opts.Validate, |
| }).Build(addrs.RootModuleInstance) |
| |
| case GraphTypeEval: |
| return (&EvalGraphBuilder{ |
| Config: c.config, |
| State: c.state, |
| Components: c.components, |
| Schemas: c.schemas, |
| }).Build(addrs.RootModuleInstance) |
| |
| default: |
| // Should never happen, because the above is exhaustive for all graph types. |
| panic(fmt.Errorf("unsupported graph type %s", typ)) |
| } |
| } |
| |
| // ShadowError returns any errors caught during a shadow operation. |
| // |
| // A shadow operation is an operation run in parallel to a real operation |
| // that performs the same tasks using new logic on copied state. The results |
| // are compared to ensure that the new logic works the same as the old logic. |
| // The shadow never affects the real operation or return values. |
| // |
| // The result of the shadow operation are only available through this function |
| // call after a real operation is complete. |
| // |
| // For API consumers of Context, you can safely ignore this function |
| // completely if you have no interest in helping report experimental feature |
| // errors to Terraform maintainers. Otherwise, please call this function |
| // after every operation and report this to the user. |
| // |
| // IMPORTANT: Shadow errors are _never_ critical: they _never_ affect |
| // the real state or result of a real operation. They are purely informational |
| // to assist in future Terraform versions being more stable. Please message |
| // this effectively to the end user. |
| // |
| // This must be called only when no other operation is running (refresh, |
| // plan, etc.). The result can be used in parallel to any other operation |
| // running. |
| func (c *Context) ShadowError() error { |
| return c.shadowErr |
| } |
| |
| // State returns a copy of the current state associated with this context. |
| // |
| // This cannot safely be called in parallel with any other Context function. |
| func (c *Context) State() *states.State { |
| return c.state.DeepCopy() |
| } |
| |
| // Eval produces a scope in which expressions can be evaluated for |
| // the given module path. |
| // |
| // This method must first evaluate any ephemeral values (input variables, local |
| // values, and output values) in the configuration. These ephemeral values are |
| // not included in the persisted state, so they must be re-computed using other |
| // values in the state before they can be properly evaluated. The updated |
| // values are retained in the main state associated with the receiving context. |
| // |
| // This function takes no action against remote APIs but it does need access |
| // to all provider and provisioner instances in order to obtain their schemas |
| // for type checking. |
| // |
| // The result is an evaluation scope that can be used to resolve references |
| // against the root module. If the returned diagnostics contains errors then |
| // the returned scope may be nil. If it is not nil then it may still be used |
| // to attempt expression evaluation or other analysis, but some expressions |
| // may not behave as expected. |
| func (c *Context) Eval(path addrs.ModuleInstance) (*lang.Scope, tfdiags.Diagnostics) { |
| // This is intended for external callers such as the "terraform console" |
| // command. Internally, we create an evaluator in c.walk before walking |
| // the graph, and create scopes in ContextGraphWalker. |
| |
| var diags tfdiags.Diagnostics |
| defer c.acquireRun("eval")() |
| |
| // Start with a copy of state so that we don't affect any instances |
| // that other methods may have already returned. |
| c.state = c.state.DeepCopy() |
| var walker *ContextGraphWalker |
| |
| graph, graphDiags := c.Graph(GraphTypeEval, nil) |
| diags = diags.Append(graphDiags) |
| if !diags.HasErrors() { |
| var walkDiags tfdiags.Diagnostics |
| walker, walkDiags = c.walk(graph, walkEval) |
| diags = diags.Append(walker.NonFatalDiagnostics) |
| diags = diags.Append(walkDiags) |
| } |
| |
| if walker == nil { |
| // If we skipped walking the graph (due to errors) then we'll just |
| // use a placeholder graph walker here, which'll refer to the |
| // unmodified state. |
| walker = c.graphWalker(walkEval) |
| } |
| |
| // This is a bit weird since we don't normally evaluate outside of |
| // the context of a walk, but we'll "re-enter" our desired path here |
| // just to get hold of an EvalContext for it. GraphContextBuiltin |
| // caches its contexts, so we should get hold of the context that was |
| // previously used for evaluation here, unless we skipped walking. |
| evalCtx := walker.EnterPath(path) |
| return evalCtx.EvaluationScope(nil, EvalDataForNoInstanceKey), diags |
| } |
| |
| // Interpolater is no longer used. Use Evaluator instead. |
| // |
| // The interpolator returned from this function will return an error on any use. |
| func (c *Context) Interpolater() *Interpolater { |
| // FIXME: Remove this once all callers are updated to no longer use it. |
| return &Interpolater{} |
| } |
| |
| // Apply applies the changes represented by this context and returns |
| // the resulting state. |
| // |
| // Even in the case an error is returned, the state may be returned and will |
| // potentially be partially updated. In addition to returning the resulting |
| // state, this context is updated with the latest state. |
| // |
| // If the state is required after an error, the caller should call |
| // Context.State, rather than rely on the return value. |
| // |
| // TODO: Apply and Refresh should either always return a state, or rely on the |
| // State() method. Currently the helper/resource testing framework relies |
| // on the absence of a returned state to determine if Destroy can be |
| // called, so that will need to be refactored before this can be changed. |
| func (c *Context) Apply() (*states.State, tfdiags.Diagnostics) { |
| defer c.acquireRun("apply")() |
| |
| // Copy our own state |
| c.state = c.state.DeepCopy() |
| |
| // Build the graph. |
| graph, diags := c.Graph(GraphTypeApply, nil) |
| if diags.HasErrors() { |
| return nil, diags |
| } |
| |
| // Determine the operation |
| operation := walkApply |
| if c.destroy { |
| operation = walkDestroy |
| } |
| |
| // Walk the graph |
| walker, walkDiags := c.walk(graph, operation) |
| diags = diags.Append(walker.NonFatalDiagnostics) |
| diags = diags.Append(walkDiags) |
| |
| if c.destroy && !diags.HasErrors() { |
| // If we know we were trying to destroy objects anyway, and we |
| // completed without any errors, then we'll also prune out any |
| // leftover empty resource husks (left after all of the instances |
| // of a resource with "count" or "for_each" are destroyed) to |
| // help ensure we end up with an _actually_ empty state, assuming |
| // we weren't destroying with -target here. |
| // |
| // (This doesn't actually take into account -target, but that should |
| // be okay because it doesn't throw away anything we can't recompute |
| // on a subsequent "terraform plan" run, if the resources are still |
| // present in the configuration. However, this _will_ cause "count = 0" |
| // resources to read as unknown during the next refresh walk, which |
| // may cause some additional churn if used in a data resource or |
| // provider block, until we remove refreshing as a separate walk and |
| // just do it as part of the plan walk.) |
| c.state.PruneResourceHusks() |
| } |
| |
| return c.state, diags |
| } |
| |
| // Plan generates an execution plan for the given context. |
| // |
| // The execution plan encapsulates the context and can be stored |
| // in order to reinstantiate a context later for Apply. |
| // |
| // Plan also updates the diff of this context to be the diff generated |
| // by the plan, so Apply can be called after. |
| func (c *Context) Plan() (*plans.Plan, tfdiags.Diagnostics) { |
| defer c.acquireRun("plan")() |
| c.changes = plans.NewChanges() |
| |
| var diags tfdiags.Diagnostics |
| |
| varVals := make(map[string]plans.DynamicValue, len(c.variables)) |
| for k, iv := range c.variables { |
| // We use cty.DynamicPseudoType here so that we'll save both the |
| // value _and_ its dynamic type in the plan, so we can recover |
| // exactly the same value later. |
| dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType) |
| if err != nil { |
| diags = diags.Append(tfdiags.Sourceless( |
| tfdiags.Error, |
| "Failed to prepare variable value for plan", |
| fmt.Sprintf("The value for variable %q could not be serialized to store in the plan: %s.", k, err), |
| )) |
| continue |
| } |
| varVals[k] = dv |
| } |
| |
| p := &plans.Plan{ |
| VariableValues: varVals, |
| TargetAddrs: c.targets, |
| ProviderSHA256s: c.providerSHA256s, |
| } |
| |
| var operation walkOperation |
| if c.destroy { |
| operation = walkPlanDestroy |
| } else { |
| // Set our state to be something temporary. We do this so that |
| // the plan can update a fake state so that variables work, then |
| // we replace it back with our old state. |
| old := c.state |
| if old == nil { |
| c.state = states.NewState() |
| } else { |
| c.state = old.DeepCopy() |
| } |
| defer func() { |
| c.state = old |
| }() |
| |
| operation = walkPlan |
| } |
| |
| // Build the graph. |
| graphType := GraphTypePlan |
| if c.destroy { |
| graphType = GraphTypePlanDestroy |
| } |
| graph, graphDiags := c.Graph(graphType, nil) |
| diags = diags.Append(graphDiags) |
| if graphDiags.HasErrors() { |
| return nil, diags |
| } |
| |
| // Do the walk |
| walker, walkDiags := c.walk(graph, operation) |
| diags = diags.Append(walker.NonFatalDiagnostics) |
| diags = diags.Append(walkDiags) |
| if walkDiags.HasErrors() { |
| return nil, diags |
| } |
| p.Changes = c.changes |
| |
| return p, diags |
| } |
| |
| // Refresh goes through all the resources in the state and refreshes them |
| // to their latest state. This will update the state that this context |
| // works with, along with returning it. |
| // |
| // Even in the case an error is returned, the state may be returned and |
| // will potentially be partially updated. |
| func (c *Context) Refresh() (*states.State, tfdiags.Diagnostics) { |
| defer c.acquireRun("refresh")() |
| |
| // Copy our own state |
| c.state = c.state.DeepCopy() |
| |
| // Refresh builds a partial changeset as part of its work because it must |
| // create placeholder stubs for any resource instances that'll be created |
| // in subsequent plan so that provider configurations and data resources |
| // can interpolate from them. This plan is always thrown away after |
| // the operation completes, restoring any existing changeset. |
| oldChanges := c.changes |
| defer func() { c.changes = oldChanges }() |
| c.changes = plans.NewChanges() |
| |
| // Build the graph. |
| graph, diags := c.Graph(GraphTypeRefresh, nil) |
| if diags.HasErrors() { |
| return nil, diags |
| } |
| |
| // Do the walk |
| _, walkDiags := c.walk(graph, walkRefresh) |
| diags = diags.Append(walkDiags) |
| if walkDiags.HasErrors() { |
| return nil, diags |
| } |
| |
| // During our walk we will have created planned object placeholders in |
| // state for resource instances that are in configuration but not yet |
| // created. These were created only to allow expression evaluation to |
| // work properly in provider and data blocks during the walk and must |
| // now be discarded, since a subsequent plan walk is responsible for |
| // creating these "for real". |
| // TODO: Consolidate refresh and plan into a single walk, so that the |
| // refresh walk doesn't need to emulate various aspects of the plan |
| // walk in order to properly evaluate provider and data blocks. |
| c.state.SyncWrapper().RemovePlannedResourceInstanceObjects() |
| |
| return c.state, diags |
| } |
| |
| // Stop stops the running task. |
| // |
| // Stop will block until the task completes. |
| func (c *Context) Stop() { |
| log.Printf("[WARN] terraform: Stop called, initiating interrupt sequence") |
| |
| c.l.Lock() |
| defer c.l.Unlock() |
| |
| // If we're running, then stop |
| if c.runContextCancel != nil { |
| log.Printf("[WARN] terraform: run context exists, stopping") |
| |
| // Tell the hook we want to stop |
| c.sh.Stop() |
| |
| // Stop the context |
| c.runContextCancel() |
| c.runContextCancel = nil |
| } |
| |
| // Grab the condition var before we exit |
| if cond := c.runCond; cond != nil { |
| log.Printf("[INFO] terraform: waiting for graceful stop to complete") |
| cond.Wait() |
| } |
| |
| log.Printf("[WARN] terraform: stop complete") |
| } |
| |
| // Validate performs semantic validation of the configuration, and returning |
| // any warnings or errors. |
| // |
| // Syntax and structural checks are performed by the configuration loader, |
| // and so are not repeated here. |
| func (c *Context) Validate() tfdiags.Diagnostics { |
| defer c.acquireRun("validate")() |
| |
| var diags tfdiags.Diagnostics |
| |
| // Validate input variables. We do this only for the values supplied |
| // by the root module, since child module calls are validated when we |
| // visit their graph nodes. |
| if c.config != nil { |
| varDiags := checkInputVariables(c.config.Module.Variables, c.variables) |
| diags = diags.Append(varDiags) |
| } |
| |
| // If we have errors at this point then we probably won't be able to |
| // construct a graph without producing redundant errors, so we'll halt early. |
| if diags.HasErrors() { |
| return diags |
| } |
| |
| // Build the graph so we can walk it and run Validate on nodes. |
| // We also validate the graph generated here, but this graph doesn't |
| // necessarily match the graph that Plan will generate, so we'll validate the |
| // graph again later after Planning. |
| graph, graphDiags := c.Graph(GraphTypeValidate, nil) |
| diags = diags.Append(graphDiags) |
| if graphDiags.HasErrors() { |
| return diags |
| } |
| |
| // Walk |
| walker, walkDiags := c.walk(graph, walkValidate) |
| diags = diags.Append(walker.NonFatalDiagnostics) |
| diags = diags.Append(walkDiags) |
| if walkDiags.HasErrors() { |
| return diags |
| } |
| |
| return diags |
| } |
| |
| // Config returns the configuration tree associated with this context. |
| func (c *Context) Config() *configs.Config { |
| return c.config |
| } |
| |
| // Variables will return the mapping of variables that were defined |
| // for this Context. If Input was called, this mapping may be different |
| // than what was given. |
| func (c *Context) Variables() InputValues { |
| return c.variables |
| } |
| |
| // SetVariable sets a variable after a context has already been built. |
| func (c *Context) SetVariable(k string, v cty.Value) { |
| c.variables[k] = &InputValue{ |
| Value: v, |
| SourceType: ValueFromCaller, |
| } |
| } |
| |
| func (c *Context) acquireRun(phase string) func() { |
| // With the run lock held, grab the context lock to make changes |
| // to the run context. |
| c.l.Lock() |
| defer c.l.Unlock() |
| |
| // Wait until we're no longer running |
| for c.runCond != nil { |
| c.runCond.Wait() |
| } |
| |
| // Build our lock |
| c.runCond = sync.NewCond(&c.l) |
| |
| // Create a new run context |
| c.runContext, c.runContextCancel = context.WithCancel(context.Background()) |
| |
| // Reset the stop hook so we're not stopped |
| c.sh.Reset() |
| |
| // Reset the shadow errors |
| c.shadowErr = nil |
| |
| return c.releaseRun |
| } |
| |
| func (c *Context) releaseRun() { |
| // Grab the context lock so that we can make modifications to fields |
| c.l.Lock() |
| defer c.l.Unlock() |
| |
| // End our run. We check if runContext is non-nil because it can be |
| // set to nil if it was cancelled via Stop() |
| if c.runContextCancel != nil { |
| c.runContextCancel() |
| } |
| |
| // Unlock all waiting our condition |
| cond := c.runCond |
| c.runCond = nil |
| cond.Broadcast() |
| |
| // Unset the context |
| c.runContext = nil |
| } |
| |
| func (c *Context) walk(graph *Graph, operation walkOperation) (*ContextGraphWalker, tfdiags.Diagnostics) { |
| log.Printf("[DEBUG] Starting graph walk: %s", operation.String()) |
| |
| walker := c.graphWalker(operation) |
| |
| // Watch for a stop so we can call the provider Stop() API. |
| watchStop, watchWait := c.watchStop(walker) |
| |
| // Walk the real graph, this will block until it completes |
| diags := graph.Walk(walker) |
| |
| // Close the channel so the watcher stops, and wait for it to return. |
| close(watchStop) |
| <-watchWait |
| |
| return walker, diags |
| } |
| |
| func (c *Context) graphWalker(operation walkOperation) *ContextGraphWalker { |
| return &ContextGraphWalker{ |
| Context: c, |
| State: c.state.SyncWrapper(), |
| Changes: c.changes.SyncWrapper(), |
| Operation: operation, |
| StopContext: c.runContext, |
| RootVariableValues: c.variables, |
| } |
| } |
| |
| // watchStop immediately returns a `stop` and a `wait` chan after dispatching |
| // the watchStop goroutine. This will watch the runContext for cancellation and |
| // stop the providers accordingly. When the watch is no longer needed, the |
| // `stop` chan should be closed before waiting on the `wait` chan. |
| // The `wait` chan is important, because without synchronizing with the end of |
| // the watchStop goroutine, the runContext may also be closed during the select |
| // incorrectly causing providers to be stopped. Even if the graph walk is done |
| // at that point, stopping a provider permanently cancels its StopContext which |
| // can cause later actions to fail. |
| func (c *Context) watchStop(walker *ContextGraphWalker) (chan struct{}, <-chan struct{}) { |
| stop := make(chan struct{}) |
| wait := make(chan struct{}) |
| |
| // get the runContext cancellation channel now, because releaseRun will |
| // write to the runContext field. |
| done := c.runContext.Done() |
| |
| go func() { |
| defer close(wait) |
| // Wait for a stop or completion |
| select { |
| case <-done: |
| // done means the context was canceled, so we need to try and stop |
| // providers. |
| case <-stop: |
| // our own stop channel was closed. |
| return |
| } |
| |
| // If we're here, we're stopped, trigger the call. |
| log.Printf("[TRACE] Context: requesting providers and provisioners to gracefully stop") |
| |
| { |
| // Copy the providers so that a misbehaved blocking Stop doesn't |
| // completely hang Terraform. |
| walker.providerLock.Lock() |
| ps := make([]providers.Interface, 0, len(walker.providerCache)) |
| for _, p := range walker.providerCache { |
| ps = append(ps, p) |
| } |
| defer walker.providerLock.Unlock() |
| |
| for _, p := range ps { |
| // We ignore the error for now since there isn't any reasonable |
| // action to take if there is an error here, since the stop is still |
| // advisory: Terraform will exit once the graph node completes. |
| p.Stop() |
| } |
| } |
| |
| { |
| // Call stop on all the provisioners |
| walker.provisionerLock.Lock() |
| ps := make([]provisioners.Interface, 0, len(walker.provisionerCache)) |
| for _, p := range walker.provisionerCache { |
| ps = append(ps, p) |
| } |
| defer walker.provisionerLock.Unlock() |
| |
| for _, p := range ps { |
| // We ignore the error for now since there isn't any reasonable |
| // action to take if there is an error here, since the stop is still |
| // advisory: Terraform will exit once the graph node completes. |
| p.Stop() |
| } |
| } |
| }() |
| |
| return stop, wait |
| } |
| |
| // parseVariableAsHCL parses the value of a single variable as would have been specified |
| // on the command line via -var or in an environment variable named TF_VAR_x, where x is |
| // the name of the variable. In order to get around the restriction of HCL requiring a |
| // top level object, we prepend a sentinel key, decode the user-specified value as its |
| // value and pull the value back out of the resulting map. |
| func parseVariableAsHCL(name string, input string, targetType config.VariableType) (interface{}, error) { |
| // expecting a string so don't decode anything, just strip quotes |
| if targetType == config.VariableTypeString { |
| return strings.Trim(input, `"`), nil |
| } |
| |
| // return empty types |
| if strings.TrimSpace(input) == "" { |
| switch targetType { |
| case config.VariableTypeList: |
| return []interface{}{}, nil |
| case config.VariableTypeMap: |
| return make(map[string]interface{}), nil |
| } |
| } |
| |
| const sentinelValue = "SENTINEL_TERRAFORM_VAR_OVERRIDE_KEY" |
| inputWithSentinal := fmt.Sprintf("%s = %s", sentinelValue, input) |
| |
| var decoded map[string]interface{} |
| err := hcl.Decode(&decoded, inputWithSentinal) |
| if err != nil { |
| return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", name, input, err) |
| } |
| |
| if len(decoded) != 1 { |
| return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", name, input) |
| } |
| |
| parsedValue, ok := decoded[sentinelValue] |
| if !ok { |
| return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. One value must be specified.", name, input) |
| } |
| |
| switch targetType { |
| case config.VariableTypeList: |
| return parsedValue, nil |
| case config.VariableTypeMap: |
| if list, ok := parsedValue.([]map[string]interface{}); ok { |
| return list[0], nil |
| } |
| |
| return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. One value must be specified.", name, input) |
| default: |
| panic(fmt.Errorf("unknown type %s", targetType.Printable())) |
| } |
| } |
| |
| // ShimLegacyState is a helper that takes the legacy state type and |
| // converts it to the new state type. |
| // |
| // This is implemented as a state file upgrade, so it will not preserve |
| // parts of the state structure that are not included in a serialized state, |
| // such as the resolved results of any local values, outputs in non-root |
| // modules, etc. |
| func ShimLegacyState(legacy *State) (*states.State, error) { |
| if legacy == nil { |
| return nil, nil |
| } |
| var buf bytes.Buffer |
| err := WriteState(legacy, &buf) |
| if err != nil { |
| return nil, err |
| } |
| f, err := statefile.Read(&buf) |
| if err != nil { |
| return nil, err |
| } |
| return f.State, err |
| } |
| |
| // MustShimLegacyState is a wrapper around ShimLegacyState that panics if |
| // the conversion does not succeed. This is primarily intended for tests where |
| // the given legacy state is an object constructed within the test. |
| func MustShimLegacyState(legacy *State) *states.State { |
| ret, err := ShimLegacyState(legacy) |
| if err != nil { |
| panic(err) |
| } |
| return ret |
| } |