blob: d861a8d1e65689443f9c3bc0e56a5a8190036f4b [file] [log] [blame] [edit]
package configload
import (
"fmt"
"path/filepath"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/registry"
"github.com/spf13/afero"
)
// A Loader instance is the main entry-point for loading configurations via
// this package.
//
// It extends the general config-loading functionality in the parent package
// "configs" to support installation of modules from remote sources and
// loading full configurations using modules that were previously installed.
type Loader struct {
// parser is used to read configuration
parser *configs.Parser
// modules is used to install and locate descendent modules that are
// referenced (directly or indirectly) from the root module.
modules moduleMgr
}
// Config is used with NewLoader to specify configuration arguments for the
// loader.
type Config struct {
// ModulesDir is a path to a directory where descendent modules are
// (or should be) installed. (This is usually the
// .terraform/modules directory, in the common case where this package
// is being loaded from the main Terraform CLI package.)
ModulesDir string
// Services is the service discovery client to use when locating remote
// module registry endpoints. If this is nil then registry sources are
// not supported, which should be true only in specialized circumstances
// such as in tests.
Services *disco.Disco
}
// NewLoader creates and returns a loader that reads configuration from the
// real OS filesystem.
//
// The loader has some internal state about the modules that are currently
// installed, which is read from disk as part of this function. If that
// manifest cannot be read then an error will be returned.
func NewLoader(config *Config) (*Loader, error) {
fs := afero.NewOsFs()
parser := configs.NewParser(fs)
reg := registry.NewClient(config.Services, nil)
ret := &Loader{
parser: parser,
modules: moduleMgr{
FS: afero.Afero{Fs: fs},
CanInstall: true,
Dir: config.ModulesDir,
Services: config.Services,
Registry: reg,
},
}
err := ret.modules.readModuleManifestSnapshot()
if err != nil {
return nil, fmt.Errorf("failed to read module manifest: %s", err)
}
return ret, nil
}
// ModulesDir returns the path to the directory where the loader will look for
// the local cache of remote module packages.
func (l *Loader) ModulesDir() string {
return l.modules.Dir
}
// RefreshModules updates the in-memory cache of the module manifest from the
// module manifest file on disk. This is not necessary in normal use because
// module installation and configuration loading are separate steps, but it
// can be useful in tests where module installation is done as a part of
// configuration loading by a helper function.
//
// Call this function after any module installation where an existing loader
// is already alive and may be used again later.
//
// An error is returned if the manifest file cannot be read.
func (l *Loader) RefreshModules() error {
if l == nil {
// Nothing to do, then.
return nil
}
return l.modules.readModuleManifestSnapshot()
}
// Parser returns the underlying parser for this loader.
//
// This is useful for loading other sorts of files than the module directories
// that a loader deals with, since then they will share the source code cache
// for this loader and can thus be shown as snippets in diagnostic messages.
func (l *Loader) Parser() *configs.Parser {
return l.parser
}
// Sources returns the source code cache for the underlying parser of this
// loader. This is a shorthand for l.Parser().Sources().
func (l *Loader) Sources() map[string][]byte {
return l.parser.Sources()
}
// IsConfigDir returns true if and only if the given directory contains at
// least one Terraform configuration file. This is a wrapper around calling
// the same method name on the loader's parser.
func (l *Loader) IsConfigDir(path string) bool {
return l.parser.IsConfigDir(path)
}
// ImportSources writes into the receiver's source code the given source
// code buffers.
//
// This is useful in the situation where an ancillary loader is created for
// some reason (e.g. loading config from a plan file) but the cached source
// code from that loader must be imported into the "main" loader in order
// to return source code snapshots in diagnostic messages.
//
// loader.ImportSources(otherLoader.Sources())
func (l *Loader) ImportSources(sources map[string][]byte) {
p := l.Parser()
for name, src := range sources {
p.ForceFileSource(name, src)
}
}
// ImportSourcesFromSnapshot writes into the receiver's source code the
// source files from the given snapshot.
//
// This is similar to ImportSources but knows how to unpack and flatten a
// snapshot data structure to get the corresponding flat source file map.
func (l *Loader) ImportSourcesFromSnapshot(snap *Snapshot) {
p := l.Parser()
for _, m := range snap.Modules {
baseDir := m.Dir
for fn, src := range m.Files {
fullPath := filepath.Join(baseDir, fn)
p.ForceFileSource(fullPath, src)
}
}
}