func (c *walkContext) resourceVariableInfo( v *config.ResourceVariable) (*ModuleState, *config.Resource, error) { // Get the module tree that contains our current path. This is // either the current module (path is empty) or a child. var modTree *module.Tree childPath := c.Path[1:len(c.Path)] if len(childPath) == 0 { modTree = c.Context.module } else { modTree = c.Context.module.Child(childPath) } // Get the resource from the configuration so we can verify // that the resource is in the configuration and so we can access // the configuration if we need to. var cr *config.Resource for _, r := range modTree.Config().Resources { if r.Id() == v.ResourceId() { cr = r break } } if cr == nil { return nil, nil, fmt.Errorf( "Resource '%s' not found for variable '%s'", v.ResourceId(), v.FullKey()) } // Get the relevant module module := c.Context.state.ModuleByPath(c.Path) return module, cr, nil }
func (t *ConfigTransformer) transform(g *Graph, m *module.Tree) error { // If no config, do nothing if m == nil { return nil } // Add our resources if err := t.transformSingle(g, m); err != nil { return err } // Transform all the children. for _, c := range m.Children() { if err := t.transform(g, c); err != nil { return err } } return nil }
func (t *ConfigTransformer) transformSingle(g *Graph, m *module.Tree) error { log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", m.Path()) // Get the configuration for this module config := m.Config() // Build the path we're at path := m.Path() // Write all the resources out for _, r := range config.Resources { // Build the resource address addr, err := parseResourceAddressConfig(r) if err != nil { panic(fmt.Sprintf( "Error parsing config address, this is a bug: %#v", r)) } addr.Path = path // Build the abstract node and the concrete one abstract := &NodeAbstractResource{Addr: addr} var node dag.Vertex = abstract if f := t.Concrete; f != nil { node = f(abstract) } // Add it to the graph g.Add(node) } return nil }
func (t *ModuleVariableTransformer) transform(g *Graph, parent, m *module.Tree) error { // If no config, no variables if m == nil { return nil } // Transform all the children. This must be done BEFORE the transform // above since child module variables can reference parent module variables. for _, c := range m.Children() { if err := t.transform(g, m, c); err != nil { return err } } // If we have a parent, we can determine if a module variable is being // used, so we transform this. if parent != nil { if err := t.transformSingle(g, parent, m); err != nil { return err } } return nil }
// checkRequiredVersion verifies that any version requirements specified by // the configuration are met. // // This checks the root module as well as any additional version requirements // from child modules. // // This is tested in context_test.go. func checkRequiredVersion(m *module.Tree) error { // Check any children for _, c := range m.Children() { if err := checkRequiredVersion(c); err != nil { return err } } var tf *config.Terraform if c := m.Config(); c != nil { tf = c.Terraform } // If there is no Terraform config or the required version isn't set, // we move on. if tf == nil || tf.RequiredVersion == "" { return nil } // Path for errors module := "root" if path := normalizeModulePath(m.Path()); len(path) > 1 { module = modulePrefixStr(path) } // Check this version requirement of this module cs, err := version.NewConstraint(tf.RequiredVersion) if err != nil { return fmt.Errorf( "%s: terraform.required_version %q syntax error: %s", module, tf.RequiredVersion, err) } if !cs.Check(SemVersion) { return fmt.Errorf( "The currently running version of Terraform doesn't meet the\n"+ "version requirements explicitly specified by the configuration.\n"+ "Please use the required version or update the configuration.\n"+ "Note that version requirements are usually set for a reason, so\n"+ "we recommend verifying with whoever set the version requirements\n"+ "prior to making any manual changes.\n\n"+ " Module: %s\n"+ " Required version: %s\n"+ " Current version: %s", module, tf.RequiredVersion, SemVersion) } return nil }
func (t *FlatConfigTransformer) transform(g *Graph, m *module.Tree) error { // If no module, no problem if m == nil { return nil } // Transform all the children. for _, c := range m.Children() { if err := t.transform(g, c); err != nil { return err } } // Get the configuration for this module config := m.Config() // Write all the resources out for _, r := range config.Resources { // Grab the address for this resource addr, err := parseResourceAddressConfig(r) if err != nil { return err } addr.Path = m.Path() // Build the abstract resource. We have the config already so // we'll just pre-populate that. abstract := &NodeAbstractResource{ Addr: addr, Config: r, } var node dag.Vertex = abstract if f := t.Concrete; f != nil { node = f(abstract) } g.Add(node) } return nil }
func (t *OutputTransformer) transform(g *Graph, m *module.Tree) error { // If no config, no outputs if m == nil { return nil } // Transform all the children. We must do this first because // we can reference module outputs and they must show up in the // reference map. for _, c := range m.Children() { if err := t.transform(g, c); err != nil { return err } } // If we have no outputs, we're done! os := m.Config().Outputs if len(os) == 0 { return nil } // Add all outputs here for _, o := range os { // Build the node. // // NOTE: For now this is just an "applyable" output. As we build // new graph builders for the other operations I suspect we'll // find a way to parameterize this, require new transforms, etc. node := &NodeApplyableOutput{ PathValue: normalizeModulePath(m.Path()), Config: o, } // Add it! g.Add(node) } return nil }
func (t *OrphanOutputTransformer) transform(g *Graph, m *module.Tree) error { // Get our configuration, and recurse into children var c *config.Config if m != nil { c = m.Config() for _, child := range m.Children() { if err := t.transform(g, child); err != nil { return err } } } // Get the state. If there is no state, then we have no orphans! path := normalizeModulePath(m.Path()) state := t.State.ModuleByPath(path) if state == nil { return nil } // Make a map of the valid outputs valid := make(map[string]struct{}) for _, o := range c.Outputs { valid[o.Name] = struct{}{} } // Go through the outputs and find the ones that aren't in our config. for n, _ := range state.Outputs { // If it is in the valid map, then ignore if _, ok := valid[n]; ok { continue } // Orphan! g.Add(&NodeOutputOrphan{OutputName: n, PathValue: path}) } return nil }
// Context returns a Terraform Context taking into account the context // options used to initialize this meta configuration. func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { opts := m.contextOpts() // First try to just read the plan directly from the path given. f, err := os.Open(copts.Path) if err == nil { plan, err := terraform.ReadPlan(f) f.Close() if err == nil { // Setup our state state, statePath, err := StateFromPlan(m.statePath, m.stateOutPath, plan) if err != nil { return nil, false, fmt.Errorf("Error loading plan: %s", err) } // Set our state m.state = state // this is used for printing the saved location later if m.stateOutPath == "" { m.stateOutPath = statePath } if len(m.variables) > 0 { return nil, false, fmt.Errorf( "You can't set variables with the '-var' or '-var-file' flag\n" + "when you're applying a plan file. The variables used when\n" + "the plan was created will be used. If you wish to use different\n" + "variable values, create a new plan file.") } ctx, err := plan.Context(opts) return ctx, true, err } } // Load the statePath if not given if copts.StatePath != "" { m.statePath = copts.StatePath } // Tell the context if we're in a destroy plan / apply opts.Destroy = copts.Destroy // Store the loaded state state, err := m.State() if err != nil { return nil, false, err } // Load the root module var mod *module.Tree if copts.Path != "" { mod, err = module.NewTreeModule("", copts.Path) if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } } else { mod = module.NewEmptyTree() } err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode) if err != nil { return nil, false, fmt.Errorf("Error downloading modules: %s", err) } opts.Module = mod opts.Parallelism = copts.Parallelism opts.State = state.State() ctx, err := terraform.NewContext(opts) return ctx, false, err }
// Variables returns the fully loaded set of variables to use with // ContextOpts and NewContext, loading any additional variables from // the environment or any other sources. // // The given module tree doesn't need to be loaded. func Variables( m *module.Tree, override map[string]interface{}) (map[string]interface{}, error) { result := make(map[string]interface{}) // Variables are loaded in the following sequence. Each additional step // will override conflicting variable keys from prior steps: // // * Take default values from config // * Take values from TF_VAR_x env vars // * Take values specified in the "override" param which is usually // from -var, -var-file, etc. // // First load from the config for _, v := range m.Config().Variables { // If the var has no default, ignore if v.Default == nil { continue } // If the type isn't a string, we use it as-is since it is a rich type if v.Type() != config.VariableTypeString { result[v.Name] = v.Default continue } // v.Default has already been parsed as HCL but it may be an int type switch typedDefault := v.Default.(type) { case string: if typedDefault == "" { continue } result[v.Name] = typedDefault case int, int64: result[v.Name] = fmt.Sprintf("%d", typedDefault) case float32, float64: result[v.Name] = fmt.Sprintf("%f", typedDefault) case bool: result[v.Name] = fmt.Sprintf("%t", typedDefault) default: panic(fmt.Sprintf( "Unknown default var type: %T\n\n"+ "THIS IS A BUG. Please report it.", v.Default)) } } // Load from env vars for _, v := range os.Environ() { if !strings.HasPrefix(v, VarEnvPrefix) { continue } // Strip off the prefix and get the value after the first "=" idx := strings.Index(v, "=") k := v[len(VarEnvPrefix):idx] v = v[idx+1:] // Override the configuration-default values. Note that *not* finding the variable // in configuration is OK, as we don't want to preclude people from having multiple // sets of TF_VAR_whatever in their environment even if it is a little weird. for _, schema := range m.Config().Variables { if schema.Name != k { continue } varType := schema.Type() varVal, err := parseVariableAsHCL(k, v, varType) if err != nil { return nil, err } switch varType { case config.VariableTypeMap: varSetMap(result, k, varVal) default: result[k] = varVal } } } // Load from overrides for k, v := range override { for _, schema := range m.Config().Variables { if schema.Name != k { continue } switch schema.Type() { case config.VariableTypeList: result[k] = v case config.VariableTypeMap: varSetMap(result, k, v) case config.VariableTypeString: // Convert to a string and set. We don't catch any errors // here because the validation step later should catch // any type errors. var strVal string if err := hilmapstructure.WeakDecode(v, &strVal); err == nil { result[k] = strVal } else { result[k] = v } default: panic(fmt.Sprintf( "Unhandled var type: %T\n\n"+ "THIS IS A BUG. Please report it.", schema.Type())) } } } return result, nil }
// Context returns a Terraform Context taking into account the context // options used to initialize this meta configuration. func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { opts := m.contextOpts() // First try to just read the plan directly from the path given. f, err := os.Open(copts.Path) if err == nil { plan, err := terraform.ReadPlan(f) f.Close() if err == nil { // Setup our state, force it to use our plan's state stateOpts := m.StateOpts() if plan != nil { stateOpts.ForceState = plan.State } // Get the state result, err := State(stateOpts) if err != nil { return nil, false, fmt.Errorf("Error loading plan: %s", err) } // Set our state m.state = result.State // this is used for printing the saved location later if m.stateOutPath == "" { m.stateOutPath = result.StatePath } if len(m.variables) > 0 { return nil, false, fmt.Errorf( "You can't set variables with the '-var' or '-var-file' flag\n" + "when you're applying a plan file. The variables used when\n" + "the plan was created will be used. If you wish to use different\n" + "variable values, create a new plan file.") } ctx, err := plan.Context(opts) return ctx, true, err } } // Load the statePath if not given if copts.StatePath != "" { m.statePath = copts.StatePath } // Tell the context if we're in a destroy plan / apply opts.Destroy = copts.Destroy // Store the loaded state state, err := m.State() if err != nil { return nil, false, err } // Load the root module var mod *module.Tree if copts.Path != "" { mod, err = module.NewTreeModule("", copts.Path) // Check for the error where we have no config files but // allow that. If that happens, clear the error. if errwrap.ContainsType(err, new(config.ErrNoConfigsFound)) && copts.PathEmptyOk { log.Printf( "[WARN] Empty configuration dir, ignoring: %s", copts.Path) err = nil mod = module.NewEmptyTree() } if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) } } else { mod = module.NewEmptyTree() } err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode) if err != nil { return nil, false, fmt.Errorf("Error downloading modules: %s", err) } // Validate the module right away if err := mod.Validate(); err != nil { return nil, false, err } opts.Module = mod opts.Parallelism = copts.Parallelism opts.State = state.State() ctx, err := terraform.NewContext(opts) return ctx, false, err }
func (t *ModuleVariableTransformer) transformSingle(g *Graph, parent, m *module.Tree) error { // If we have no vars, we're done! vars := m.Config().Variables if len(vars) == 0 { log.Printf("[TRACE] Module %#v has no variables, skipping.", m.Path()) return nil } // Look for usage of this module var mod *config.Module for _, modUse := range parent.Config().Modules { if modUse.Name == m.Name() { mod = modUse break } } if mod == nil { log.Printf("[INFO] Module %#v not used, not adding variables", m.Path()) return nil } // Build the reference map so we can determine if we're referencing things. refMap := NewReferenceMap(g.Vertices()) // Add all variables here for _, v := range vars { // Determine the value of the variable. If it isn't in the // configuration then it was never set and that's not a problem. var value *config.RawConfig if raw, ok := mod.RawConfig.Raw[v.Name]; ok { var err error value, err = config.NewRawConfig(map[string]interface{}{ v.Name: raw, }) if err != nil { // This shouldn't happen because it is already in // a RawConfig above meaning it worked once before. panic(err) } } // Build the node. // // NOTE: For now this is just an "applyable" variable. As we build // new graph builders for the other operations I suspect we'll // find a way to parameterize this, require new transforms, etc. node := &NodeApplyableModuleVariable{ PathValue: normalizeModulePath(m.Path()), Config: v, Value: value, Module: t.Module, } if !t.DisablePrune { // If the node is not referenced by anything, then we don't need // to include it since it won't be used. if matches := refMap.ReferencedBy(node); len(matches) == 0 { log.Printf( "[INFO] Not including %q in graph, nothing depends on it", dag.VertexName(node)) continue } } // Add it! g.Add(node) } return nil }