kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/configs/configload/loader.go (about)

     1  package configload
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  
     7  	"github.com/hashicorp/terraform-svchost/disco"
     8  	"kubeform.dev/terraform-backend-sdk/configs"
     9  	"kubeform.dev/terraform-backend-sdk/registry"
    10  	"github.com/spf13/afero"
    11  )
    12  
    13  // A Loader instance is the main entry-point for loading configurations via
    14  // this package.
    15  //
    16  // It extends the general config-loading functionality in the parent package
    17  // "configs" to support installation of modules from remote sources and
    18  // loading full configurations using modules that were previously installed.
    19  type Loader struct {
    20  	// parser is used to read configuration
    21  	parser *configs.Parser
    22  
    23  	// modules is used to install and locate descendent modules that are
    24  	// referenced (directly or indirectly) from the root module.
    25  	modules moduleMgr
    26  }
    27  
    28  // Config is used with NewLoader to specify configuration arguments for the
    29  // loader.
    30  type Config struct {
    31  	// ModulesDir is a path to a directory where descendent modules are
    32  	// (or should be) installed. (This is usually the
    33  	// .terraform/modules directory, in the common case where this package
    34  	// is being loaded from the main Terraform CLI package.)
    35  	ModulesDir string
    36  
    37  	// Services is the service discovery client to use when locating remote
    38  	// module registry endpoints. If this is nil then registry sources are
    39  	// not supported, which should be true only in specialized circumstances
    40  	// such as in tests.
    41  	Services *disco.Disco
    42  }
    43  
    44  // NewLoader creates and returns a loader that reads configuration from the
    45  // real OS filesystem.
    46  //
    47  // The loader has some internal state about the modules that are currently
    48  // installed, which is read from disk as part of this function. If that
    49  // manifest cannot be read then an error will be returned.
    50  func NewLoader(config *Config) (*Loader, error) {
    51  	fs := afero.NewOsFs()
    52  	parser := configs.NewParser(fs)
    53  	reg := registry.NewClient(config.Services, nil)
    54  
    55  	ret := &Loader{
    56  		parser: parser,
    57  		modules: moduleMgr{
    58  			FS:         afero.Afero{Fs: fs},
    59  			CanInstall: true,
    60  			Dir:        config.ModulesDir,
    61  			Services:   config.Services,
    62  			Registry:   reg,
    63  		},
    64  	}
    65  
    66  	err := ret.modules.readModuleManifestSnapshot()
    67  	if err != nil {
    68  		return nil, fmt.Errorf("failed to read module manifest: %s", err)
    69  	}
    70  
    71  	return ret, nil
    72  }
    73  
    74  // ModulesDir returns the path to the directory where the loader will look for
    75  // the local cache of remote module packages.
    76  func (l *Loader) ModulesDir() string {
    77  	return l.modules.Dir
    78  }
    79  
    80  // RefreshModules updates the in-memory cache of the module manifest from the
    81  // module manifest file on disk. This is not necessary in normal use because
    82  // module installation and configuration loading are separate steps, but it
    83  // can be useful in tests where module installation is done as a part of
    84  // configuration loading by a helper function.
    85  //
    86  // Call this function after any module installation where an existing loader
    87  // is already alive and may be used again later.
    88  //
    89  // An error is returned if the manifest file cannot be read.
    90  func (l *Loader) RefreshModules() error {
    91  	if l == nil {
    92  		// Nothing to do, then.
    93  		return nil
    94  	}
    95  	return l.modules.readModuleManifestSnapshot()
    96  }
    97  
    98  // Parser returns the underlying parser for this loader.
    99  //
   100  // This is useful for loading other sorts of files than the module directories
   101  // that a loader deals with, since then they will share the source code cache
   102  // for this loader and can thus be shown as snippets in diagnostic messages.
   103  func (l *Loader) Parser() *configs.Parser {
   104  	return l.parser
   105  }
   106  
   107  // Sources returns the source code cache for the underlying parser of this
   108  // loader. This is a shorthand for l.Parser().Sources().
   109  func (l *Loader) Sources() map[string][]byte {
   110  	return l.parser.Sources()
   111  }
   112  
   113  // IsConfigDir returns true if and only if the given directory contains at
   114  // least one Terraform configuration file. This is a wrapper around calling
   115  // the same method name on the loader's parser.
   116  func (l *Loader) IsConfigDir(path string) bool {
   117  	return l.parser.IsConfigDir(path)
   118  }
   119  
   120  // ImportSources writes into the receiver's source code the given source
   121  // code buffers.
   122  //
   123  // This is useful in the situation where an ancillary loader is created for
   124  // some reason (e.g. loading config from a plan file) but the cached source
   125  // code from that loader must be imported into the "main" loader in order
   126  // to return source code snapshots in diagnostic messages.
   127  //
   128  //     loader.ImportSources(otherLoader.Sources())
   129  func (l *Loader) ImportSources(sources map[string][]byte) {
   130  	p := l.Parser()
   131  	for name, src := range sources {
   132  		p.ForceFileSource(name, src)
   133  	}
   134  }
   135  
   136  // ImportSourcesFromSnapshot writes into the receiver's source code the
   137  // source files from the given snapshot.
   138  //
   139  // This is similar to ImportSources but knows how to unpack and flatten a
   140  // snapshot data structure to get the corresponding flat source file map.
   141  func (l *Loader) ImportSourcesFromSnapshot(snap *Snapshot) {
   142  	p := l.Parser()
   143  	for _, m := range snap.Modules {
   144  		baseDir := m.Dir
   145  		for fn, src := range m.Files {
   146  			fullPath := filepath.Join(baseDir, fn)
   147  			p.ForceFileSource(fullPath, src)
   148  		}
   149  	}
   150  }