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 }