214 lines
7.0 KiB
Go
214 lines
7.0 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/terraform/configs"
|
|
"github.com/hashicorp/terraform/configs/configload"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// normalizePath normalizes a given path so that it is, if possible, relative
|
|
// to the current working directory. This is primarily used to prepare
|
|
// paths used to load configuration, because we want to prefer recording
|
|
// relative paths in source code references within the configuration.
|
|
func (m *Meta) normalizePath(path string) string {
|
|
var err error
|
|
|
|
// First we will make it absolute so that we have a consistent place
|
|
// to start.
|
|
path, err = filepath.Abs(path)
|
|
if err != nil {
|
|
// We'll just accept what we were given, then.
|
|
return path
|
|
}
|
|
|
|
cwd, err := os.Getwd()
|
|
if err != nil || !filepath.IsAbs(cwd) {
|
|
return path
|
|
}
|
|
|
|
ret, err := filepath.Rel(cwd, path)
|
|
if err != nil {
|
|
return path
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// loadConfig reads a configuration from the given directory, which should
|
|
// contain a root module and have already have any required descendent modules
|
|
// installed.
|
|
func (m *Meta) loadConfig(rootDir string) (*configs.Config, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
rootDir = m.normalizePath(rootDir)
|
|
|
|
loader, err := m.initConfigLoader()
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return nil, diags
|
|
}
|
|
|
|
config, hclDiags := loader.LoadConfig(rootDir)
|
|
diags = diags.Append(hclDiags)
|
|
return config, diags
|
|
}
|
|
|
|
// loadSingleModule reads configuration from the given directory and returns
|
|
// a description of that module only, without attempting to assemble a module
|
|
// tree for referenced child modules.
|
|
//
|
|
// Most callers should use loadConfig. This method exists to support early
|
|
// initialization use-cases where the root module must be inspected in order
|
|
// to determine what else needs to be installed before the full configuration
|
|
// can be used.
|
|
func (m *Meta) loadSingleModule(dir string) (*configs.Module, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
dir = m.normalizePath(dir)
|
|
|
|
loader, err := m.initConfigLoader()
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return nil, diags
|
|
}
|
|
|
|
module, hclDiags := loader.Parser().LoadConfigDir(dir)
|
|
diags = diags.Append(hclDiags)
|
|
return module, diags
|
|
}
|
|
|
|
// loadBackendConfig reads configuration from the given directory and returns
|
|
// the backend configuration defined by that module, if any. Nil is returned
|
|
// if the specified module does not have an explicit backend configuration.
|
|
//
|
|
// This is a convenience method for command code that will delegate to the
|
|
// configured backend to do most of its work, since in that case it is the
|
|
// backend that will do the full configuration load.
|
|
//
|
|
// Although this method returns only the backend configuration, at present it
|
|
// actually loads and validates the entire configuration first. Therefore errors
|
|
// returned may be about other aspects of the configuration. This behavior may
|
|
// change in future, so callers must not rely on it. (That is, they must expect
|
|
// that a call to loadSingleModule or loadConfig could fail on the same
|
|
// directory even if loadBackendConfig succeeded.)
|
|
func (m *Meta) loadBackendConfig(rootDir string) (*configs.Backend, tfdiags.Diagnostics) {
|
|
mod, diags := m.loadSingleModule(rootDir)
|
|
return mod.Backend, diags
|
|
}
|
|
|
|
// installModules reads a root module from the given directory and attempts
|
|
// recursively install all of its descendent modules.
|
|
//
|
|
// The given hooks object will be notified of installation progress, which
|
|
// can then be relayed to the end-user. The moduleUiInstallHooks type in
|
|
// this package has a reasonable implementation for displaying notifications
|
|
// via a provided cli.Ui.
|
|
func (m *Meta) installModules(rootDir string, upgrade bool, hooks configload.InstallHooks) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
rootDir = m.normalizePath(rootDir)
|
|
|
|
err := os.MkdirAll(m.modulesDir(), os.ModePerm)
|
|
if err != nil {
|
|
diags = diags.Append(fmt.Errorf("failed to create local modules directory: %s", err))
|
|
return diags
|
|
}
|
|
|
|
loader, err := m.initConfigLoader()
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return diags
|
|
}
|
|
|
|
hclDiags := loader.InstallModules(rootDir, upgrade, hooks)
|
|
diags = diags.Append(hclDiags)
|
|
return diags
|
|
}
|
|
|
|
// initDirFromModule initializes the given directory (which should be
|
|
// pre-verified as empty by the caller) by copying the source code from the
|
|
// given module address.
|
|
//
|
|
// Internally this runs similar steps to installModules.
|
|
// The given hooks object will be notified of installation progress, which
|
|
// can then be relayed to the end-user. The moduleUiInstallHooks type in
|
|
// this package has a reasonable implementation for displaying notifications
|
|
// via a provided cli.Ui.
|
|
func (m *Meta) initDirFromModule(targetDir string, addr string, hooks configload.InstallHooks) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
targetDir = m.normalizePath(targetDir)
|
|
|
|
loader, err := m.initConfigLoader()
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return diags
|
|
}
|
|
|
|
hclDiags := loader.InitDirFromModule(targetDir, addr, hooks)
|
|
diags = diags.Append(hclDiags)
|
|
return diags
|
|
}
|
|
|
|
// loadVarsFile reads a file from the given path and interprets it as a
|
|
// "vars file", returning the contained values as a map.
|
|
//
|
|
// The file is read using the parser associated with the receiver's
|
|
// configuration loader, which means that the file's contents will be added
|
|
// to the source cache that is used for config snippets in diagnostic messages.
|
|
func (m *Meta) loadVarsFile(filename string) (map[string]cty.Value, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
loader, err := m.initConfigLoader()
|
|
if err != nil {
|
|
diags = diags.Append(err)
|
|
return nil, diags
|
|
}
|
|
|
|
parser := loader.Parser()
|
|
ret, hclDiags := parser.LoadValuesFile(filename)
|
|
diags = diags.Append(hclDiags)
|
|
return ret, diags
|
|
}
|
|
|
|
// configSources returns the source cache from the receiver's config loader,
|
|
// which the caller must not modify.
|
|
//
|
|
// If a config loader has not yet been instantiated then no files could have
|
|
// been loaded already, so this method returns a nil map in that case.
|
|
func (m *Meta) configSources() map[string][]byte {
|
|
if m.configLoader == nil {
|
|
return nil
|
|
}
|
|
|
|
return m.configLoader.Sources()
|
|
}
|
|
|
|
func (m *Meta) modulesDir() string {
|
|
return filepath.Join(m.DataDir(), "modules")
|
|
}
|
|
|
|
// initConfigLoader initializes the shared configuration loader if it isn't
|
|
// already initialized.
|
|
//
|
|
// If the loader cannot be created for some reason then an error is returned
|
|
// and no loader is created. Subsequent calls will presumably see the same
|
|
// error. Loader initialization errors will tend to prevent any further use
|
|
// of most Terraform features, so callers should report any error and safely
|
|
// terminate.
|
|
func (m *Meta) initConfigLoader() (*configload.Loader, error) {
|
|
if m.configLoader == nil {
|
|
loader, err := configload.NewLoader(&configload.Config{
|
|
ModulesDir: m.modulesDir(),
|
|
Services: m.Services,
|
|
Creds: m.Credentials,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m.configLoader = loader
|
|
}
|
|
return m.configLoader, nil
|
|
}
|