Skip to content

Commit

Permalink
fix handling of cross-module scenarios
Browse files Browse the repository at this point in the history
This is not the cleanest solution, but it serves the interest
in integrating hcl-lang & terraform-schema.

Some refactoring in the rootmodule package is certainly due by now.
  • Loading branch information
radeksimko committed Nov 5, 2020
1 parent 2831edd commit 56a5593
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 202 deletions.
7 changes: 6 additions & 1 deletion commands/completion_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ func (c *CompletionCommand) Run(args []string) int {
c.Ui.Error(fmt.Sprintf("failed to load root module: %s", err.Error()))
return 1
}
d, err := rm.Decoder()
schema, err := rm.MergedSchema()
if err != nil {
c.Ui.Error(fmt.Sprintf("failed to find schema: %s", err.Error()))
return 1
}
d, err := rm.DecoderWithSchema(schema)
if err != nil {
c.Ui.Error(fmt.Sprintf("failed to find parser: %s", err.Error()))
return 1
Expand Down
19 changes: 3 additions & 16 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ var (
ctxTfExecTimeout = &contextKey{"terraform execution timeout"}
ctxWatcher = &contextKey{"watcher"}
ctxRootModuleMngr = &contextKey{"root module manager"}
ctxDecoderFinder = &contextKey{"decoder finder"}
ctxTfFormatterFinder = &contextKey{"terraform formatter finder"}
ctxRootModuleCaFi = &contextKey{"root module candidate finder"}
ctxRootModuleWalker = &contextKey{"root module walker"}
Expand Down Expand Up @@ -124,18 +123,6 @@ func RootModuleManager(ctx context.Context) (rootmodule.RootModuleManager, error
return wm, nil
}

func WithDecoderFinder(ctx context.Context, pf rootmodule.DecoderFinder) context.Context {
return context.WithValue(ctx, ctxDecoderFinder, pf)
}

func DecoderFinder(ctx context.Context) (rootmodule.DecoderFinder, error) {
pf, ok := ctx.Value(ctxDecoderFinder).(rootmodule.DecoderFinder)
if !ok {
return nil, missingContextErr(ctxDecoderFinder)
}
return pf, nil
}

func WithTerraformFormatterFinder(ctx context.Context, tef rootmodule.TerraformFormatterFinder) context.Context {
return context.WithValue(ctx, ctxTfFormatterFinder, tef)
}
Expand All @@ -157,12 +144,12 @@ func TerraformExecPath(ctx context.Context) (string, bool) {
return path, ok
}

func WithRootModuleCandidateFinder(ctx context.Context, rmcf rootmodule.RootModuleCandidateFinder) context.Context {
func WithRootModuleFinder(ctx context.Context, rmcf rootmodule.RootModuleFinder) context.Context {
return context.WithValue(ctx, ctxRootModuleCaFi, rmcf)
}

func RootModuleCandidateFinder(ctx context.Context) (rootmodule.RootModuleCandidateFinder, error) {
cf, ok := ctx.Value(ctxRootModuleCaFi).(rootmodule.RootModuleCandidateFinder)
func RootModuleFinder(ctx context.Context) (rootmodule.RootModuleFinder, error) {
cf, ok := ctx.Value(ctxRootModuleCaFi).(rootmodule.RootModuleFinder)
if !ok {
return nil, missingContextErr(ctxRootModuleCaFi)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/terraform/rootmodule/module_manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func ParseModuleManifestFromFile(path string) (*moduleManifest, error) {
if err != nil {
return nil, err
}
mm.rootDir = rootModuleDirFromFilePath(path)
mm.rootDir = trimLockFilePath(path)

return mm, nil
}
Expand Down
72 changes: 48 additions & 24 deletions internal/terraform/rootmodule/root_module.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ type rootModule struct {
coreSchemaMu *sync.RWMutex

// decoder
decoder *decoder.Decoder
isParsed bool
isParsedMu *sync.RWMutex
pFilesMap map[string]*hcl.File
Expand All @@ -79,9 +78,6 @@ type rootModule struct {
}

func newRootModule(fs filesystem.Filesystem, dir string) *rootModule {
d := decoder.NewDecoder()
d.SetSchema(tfschema.UniversalCoreModuleSchema())

return &rootModule{
path: dir,
filesystem: fs,
Expand All @@ -94,7 +90,6 @@ func newRootModule(fs filesystem.Filesystem, dir string) *rootModule {
tfLoadingMu: &sync.RWMutex{},
coreSchemaMu: &sync.RWMutex{},
isParsedMu: &sync.RWMutex{},
decoder: d,
pFilesMap: make(map[string]*hcl.File, 0),
parserMu: &sync.RWMutex{},
}
Expand Down Expand Up @@ -133,6 +128,25 @@ func (rm *rootModule) discoverCaches(ctx context.Context, dir string) error {
return errs.ErrorOrNil()
}

func (rm *rootModule) WasInitialized() (bool, error) {
tfDirPath := filepath.Join(rm.Path(), ".terraform")

f, err := rm.filesystem.Open(tfDirPath)
if err != nil {
return false, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return false, err
}
if !fi.IsDir() {
return false, fmt.Errorf("%s is not a directory", tfDirPath)
}

return true, nil
}

func (rm *rootModule) discoverPluginCache(dir string) error {
rm.pluginMu.Lock()
defer rm.pluginMu.Unlock()
Expand Down Expand Up @@ -313,7 +327,7 @@ func (rm *rootModule) findAndSetCoreSchema() error {
rm.coreSchema = coreSchema
rm.setCoreSchemaLoaded(true)

return rm.mergeAndSetDecoderSchema()
return nil
}

func (rm *rootModule) LoadError() error {
Expand Down Expand Up @@ -358,8 +372,27 @@ func (rm *rootModule) UpdateModuleManifest(lockFile File) error {
return nil
}

func (rm *rootModule) DecoderWithSchema(schema *schema.BodySchema) (*decoder.Decoder, error) {
d, err := rm.Decoder()
if err != nil {
return nil, err
}

d.SetSchema(schema)

return d, nil
}

func (rm *rootModule) Decoder() (*decoder.Decoder, error) {
return rm.decoder, nil
d := decoder.NewDecoder()

for name, f := range rm.parsedFiles() {
err := d.LoadFile(name, f)
if err != nil {
return nil, fmt.Errorf("failed to load a file: %w", err)
}
}
return d, nil
}

func (rm *rootModule) IsCoreSchemaLoaded() bool {
Expand Down Expand Up @@ -392,7 +425,7 @@ func (rm *rootModule) setIsParsed(parsed bool) {
rm.isParsed = parsed
}

func (rm *rootModule) ParseAndLoadFiles() error {
func (rm *rootModule) ParseFiles() error {
rm.parserMu.Lock()
defer rm.parserMu.Unlock()

Expand Down Expand Up @@ -424,6 +457,7 @@ func (rm *rootModule) ParseAndLoadFiles() error {
return fmt.Errorf("failed to read %q: %s", name, err)
}

rm.logger.Printf("parsing file %q", name)
f, pDiags := hclsyntax.ParseConfig(src, name, hcl.InitialPos)
diags = append(diags, pDiags...)
if f != nil {
Expand All @@ -435,13 +469,6 @@ func (rm *rootModule) ParseAndLoadFiles() error {
rm.parsedDiags = diags
rm.setIsParsed(true)

for name, f := range files {
err := rm.decoder.LoadFile(name, f)
if err != nil {
return fmt.Errorf("failed to load a file: %w", err)
}
}

return nil
}

Expand All @@ -458,7 +485,7 @@ func (rm *rootModule) parsedFiles() map[string]*hcl.File {
return rm.pFilesMap
}

func (rm *rootModule) mergeAndSetDecoderSchema() error {
func (rm *rootModule) MergedSchema() (*schema.BodySchema, error) {
var mergedSchema *schema.BodySchema

if rm.IsCoreSchemaLoaded() {
Expand All @@ -467,21 +494,19 @@ func (rm *rootModule) mergeAndSetDecoderSchema() error {

if rm.IsProviderSchemaLoaded() {
if !rm.IsParsed() {
err := rm.ParseAndLoadFiles()
err := rm.ParseFiles()
if err != nil {
return err
return nil, err
}
}
s, err := tfschema.MergeCoreWithJsonProviderSchemas(rm.parsedFiles(), mergedSchema, rm.providerSchema)
if err != nil {
return err
return nil, err
}
mergedSchema = s
}

rm.decoder.SetSchema(mergedSchema)

return nil
return mergedSchema, nil
}

// IsIgnoredFile returns true if the given filename (which must not have a
Expand Down Expand Up @@ -509,7 +534,6 @@ func (rm *rootModule) ReferencesModulePath(path string) bool {
continue
}
absPath := filepath.Join(rm.moduleManifest.rootDir, m.Dir)
rm.logger.Printf("checking if %q equals %q", absPath, path)
if pathEquals(absPath, path) {
return true
}
Expand Down Expand Up @@ -571,7 +595,7 @@ func (rm *rootModule) UpdateProviderSchemaCache(ctx context.Context, lockFile Fi
rm.providerSchema = schemas
rm.providerSchemaMu.Unlock()

return rm.mergeAndSetDecoderSchema()
return nil
}

func (rm *rootModule) PathsToWatch() []string {
Expand Down
67 changes: 45 additions & 22 deletions internal/terraform/rootmodule/root_module_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"time"

"github.com/gammazero/workerpool"
"github.com/hashicorp/hcl-lang/decoder"
"github.com/hashicorp/hcl-lang/schema"
"github.com/hashicorp/terraform-ls/internal/filesystem"
"github.com/hashicorp/terraform-ls/internal/terraform/discovery"
"github.com/hashicorp/terraform-ls/internal/terraform/exec"
Expand Down Expand Up @@ -129,6 +129,22 @@ func (rmm *rootModuleManager) AddAndStartLoadingRootModule(ctx context.Context,
return rm, nil
}

func (rmm *rootModuleManager) SchemaForPath(path string) (*schema.BodySchema, error) {
candidates := rmm.RootModuleCandidatesByPath(path)
for _, rm := range candidates {
schema, err := rm.MergedSchema()
if err != nil {
rmm.logger.Printf("failed to merge schema for %s: %s", rm.Path(), err)
continue
}
if schema != nil {
rmm.logger.Printf("found schema for %s at %s", path, rm.Path())
return schema, nil
}
}
return nil, fmt.Errorf("failed to find schema for %s", path)
}

func (rmm *rootModuleManager) rootModuleByPath(dir string) (*rootModule, bool) {
for _, rm := range rmm.rms {
if pathEquals(rm.Path(), dir) {
Expand All @@ -138,26 +154,35 @@ func (rmm *rootModuleManager) rootModuleByPath(dir string) (*rootModule, bool) {
return nil, false
}

// RootModuleCandidatesByPath finds any initialized root modules
func (rmm *rootModuleManager) RootModuleCandidatesByPath(path string) RootModules {
path = filepath.Clean(path)

candidates := make([]RootModule, 0)

// TODO: Follow symlinks (requires proper test data)

if rm, ok := rmm.rootModuleByPath(path); ok {
return []RootModule{rm}
rm, foundPath := rmm.rootModuleByPath(path)
if foundPath {
inited, _ := rm.WasInitialized()
if inited {
candidates = append(candidates, rm)
}
}

dir := rootModuleDirFromFilePath(path)
if rm, ok := rmm.rootModuleByPath(dir); ok {
rmm.logger.Printf("dir-based root module lookup succeeded: %s", dir)
return []RootModule{rm}
if !foundPath {
dir := trimLockFilePath(path)
rm, ok := rmm.rootModuleByPath(dir)
if ok {
inited, _ := rm.WasInitialized()
if inited {
candidates = append(candidates, rm)
}
}
}

candidates := make([]RootModule, 0)
for _, rm := range rmm.rms {
rmm.logger.Printf("looking up %s in module references of %s", dir, rm.Path())
if rm.ReferencesModulePath(dir) {
rmm.logger.Printf("module-ref-based root module lookup succeeded: %s", dir)
if rm.ReferencesModulePath(path) {
candidates = append(candidates, rm)
}
}
Expand All @@ -173,21 +198,19 @@ func (rmm *rootModuleManager) ListRootModules() RootModules {
return modules
}
func (rmm *rootModuleManager) RootModuleByPath(path string) (RootModule, error) {
candidates := rmm.RootModuleCandidatesByPath(path)
if len(candidates) > 0 {
return candidates[0], nil
path = filepath.Clean(path)

if rm, ok := rmm.rootModuleByPath(path); ok {
return rm, nil
}

return nil, &RootModuleNotFoundErr{path}
}
dir := trimLockFilePath(path)

func (rmm *rootModuleManager) DecoderForDir(path string) (*decoder.Decoder, error) {
rm, err := rmm.RootModuleByPath(path)
if err != nil {
return nil, err
if rm, ok := rmm.rootModuleByPath(dir); ok {
return rm, nil
}

return rm.Decoder()
return nil, &RootModuleNotFoundErr{path}
}

func (rmm *rootModuleManager) IsCoreSchemaLoaded(path string) (bool, error) {
Expand Down Expand Up @@ -283,7 +306,7 @@ func (rmm *rootModuleManager) CancelLoading() {

// rootModuleDirFromPath strips known lock file paths and filenames
// to get the directory path of the relevant rootModule
func rootModuleDirFromFilePath(filePath string) string {
func trimLockFilePath(filePath string) string {
pluginLockFileSuffixes := pluginLockFilePaths(string(os.PathSeparator))
for _, s := range pluginLockFileSuffixes {
if strings.HasSuffix(filePath, s) {
Expand Down
Loading

0 comments on commit 56a5593

Please sign in to comment.