diff --git a/internal/context/context.go b/internal/context/context.go index d3f149fdb..b3e7e31be 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -7,7 +7,6 @@ import ( "github.com/hashicorp/terraform-ls/internal/langserver/diagnostics" lsp "github.com/hashicorp/terraform-ls/internal/protocol" "github.com/hashicorp/terraform-ls/internal/settings" - "github.com/hashicorp/terraform-ls/internal/terraform/module" ) type contextKey struct { @@ -22,7 +21,6 @@ var ( ctxTfExecPath = &contextKey{"terraform executable path"} ctxTfExecLogPath = &contextKey{"terraform executor log path"} ctxTfExecTimeout = &contextKey{"terraform execution timeout"} - ctxWatcher = &contextKey{"watcher"} ctxRootDir = &contextKey{"root directory"} ctxCommandPrefix = &contextKey{"command prefix"} ctxDiagsNotifier = &contextKey{"diagnostics notifier"} @@ -53,18 +51,6 @@ func TerraformExecTimeout(ctx context.Context) (time.Duration, bool) { return path, ok } -func WithWatcher(ctx context.Context, w module.Watcher) context.Context { - return context.WithValue(ctx, ctxWatcher, w) -} - -func Watcher(ctx context.Context) (module.Watcher, error) { - w, ok := ctx.Value(ctxWatcher).(module.Watcher) - if !ok { - return nil, missingContextErr(ctxWatcher) - } - return w, nil -} - func WithTerraformExecPath(ctx context.Context, path string) context.Context { return context.WithValue(ctx, ctxTfExecPath, path) } diff --git a/internal/langserver/handlers/did_change_workspace_folders.go b/internal/langserver/handlers/did_change_workspace_folders.go index 77518f116..4a07bd53e 100644 --- a/internal/langserver/handlers/did_change_workspace_folders.go +++ b/internal/langserver/handlers/did_change_workspace_folders.go @@ -33,12 +33,6 @@ func (svc *service) indexNewModule(ctx context.Context, modURI string) { }) return } - - err = svc.watcher.AddModule(modHandle.Path()) - if err != nil { - svc.logger.Printf("failed to add module to watcher: %s", err) - return - } } func (svc *service) removeIndexedModule(ctx context.Context, modURI string) { @@ -54,12 +48,6 @@ func (svc *service) removeIndexedModule(ctx context.Context, modURI string) { return } - err = svc.watcher.RemoveModule(modHandle.Path()) - if err != nil { - svc.logger.Printf("failed to remove module from watcher: %s", err) - return - } - err = svc.stateStore.JobStore.DequeueJobsForDir(modHandle) if err != nil { svc.logger.Printf("failed to dequeue jobs for module: %s", err) diff --git a/internal/langserver/handlers/did_open.go b/internal/langserver/handlers/did_open.go index 2e544c3d7..0d41f52c5 100644 --- a/internal/langserver/handlers/did_open.go +++ b/internal/langserver/handlers/did_open.go @@ -3,7 +3,6 @@ package handlers import ( "context" - lsctx "github.com/hashicorp/terraform-ls/internal/context" "github.com/hashicorp/terraform-ls/internal/document" "github.com/hashicorp/terraform-ls/internal/job" ilsp "github.com/hashicorp/terraform-ls/internal/lsp" @@ -65,11 +64,6 @@ func (svc *service) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpenT jobIds = append(jobIds, jobId) } - watcher, err := lsctx.Watcher(ctx) - if err != nil { - return err - } - if svc.singleFileMode { err = svc.stateStore.WalkerPaths.EnqueueDir(modHandle) if err != nil { @@ -77,13 +71,6 @@ func (svc *service) TextDocumentDidOpen(ctx context.Context, params lsp.DidOpenT } } - if !watcher.IsModuleWatched(mod.Path) { - err := watcher.AddModule(mod.Path) - if err != nil { - return err - } - } - return svc.stateStore.JobStore.WaitForJobs(ctx, jobIds...) } diff --git a/internal/langserver/handlers/initialize.go b/internal/langserver/handlers/initialize.go index 04eb90653..fad5a4436 100644 --- a/internal/langserver/handlers/initialize.go +++ b/internal/langserver/handlers/initialize.go @@ -281,27 +281,6 @@ func (svc *service) setupWalker(ctx context.Context, params lsp.InitializeParams svc.openDirWalker.SetIgnoreDirectoryNames(options.IgnoreDirectoryNames) svc.openDirWalker.SetExcludeModulePaths(excludeModulePaths) - if len(options.ModulePaths) > 0 { - svc.logger.Printf("Attempting to add %d static module paths", len(options.ModulePaths)) - for _, rawPath := range options.ModulePaths { - modPath, err := resolvePath(root.Path(), rawPath) - if err != nil { - jrpc2.ServerFromContext(ctx).Notify(ctx, "window/showMessage", &lsp.ShowMessageParams{ - Type: lsp.Warning, - Message: fmt.Sprintf("Ignoring module path %s: %s", rawPath, err), - }) - continue - } - - err = svc.watcher.AddModule(modPath) - if err != nil { - return err - } - } - - return nil - } - return nil } diff --git a/internal/langserver/handlers/initialize_benchmarks_test.go b/internal/langserver/handlers/initialize_benchmarks_test.go index 4ff5c318f..80d752bae 100644 --- a/internal/langserver/handlers/initialize_benchmarks_test.go +++ b/internal/langserver/handlers/initialize_benchmarks_test.go @@ -129,7 +129,6 @@ func BenchmarkInitializeFolder_basic(b *testing.B) { srvCtx: ctx, sessCtx: sessCtx, stopSession: stopSession, - newWatcher: module.MockWatcher(), tfDiscoFunc: d.LookPath, tfExecFactory: exec.NewExecutor, walkerCollector: wc, diff --git a/internal/langserver/handlers/service.go b/internal/langserver/handlers/service.go index 29da1320d..2c7fbfd63 100644 --- a/internal/langserver/handlers/service.go +++ b/internal/langserver/handlers/service.go @@ -49,8 +49,6 @@ type service struct { fs *filesystem.Filesystem modStore *state.ModuleStore schemaStore *state.ProviderSchemaStore - watcher module.Watcher - newWatcher module.WatcherFactory tfDiscoFunc discovery.DiscoveryFunc tfExecFactory exec.ExecutorFactory tfExecOpts *exec.ExecutorOpts @@ -79,7 +77,6 @@ func NewSession(srvCtx context.Context) session.Session { srvCtx: srvCtx, sessCtx: sessCtx, stopSession: stopSession, - newWatcher: module.NewWatcher, tfDiscoFunc: d.LookPath, tfExecFactory: exec.NewExecutor, telemetry: &telemetry.NoopSender{}, @@ -153,7 +150,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { if err != nil { return nil, err } - ctx = lsctx.WithWatcher(ctx, svc.watcher) return handle(ctx, req, svc.TextDocumentDidOpen) }, "textDocument/didClose": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { @@ -288,8 +284,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { return nil, err } - ctx = lsctx.WithWatcher(ctx, svc.watcher) - return handle(ctx, req, svc.DidChangeWorkspaceFolders) }, "workspace/didChangeWatchedFiles": func(ctx context.Context, req *jrpc2.Request) (interface{}, error) { @@ -315,7 +309,6 @@ func (svc *service) Assigner() (jrpc2.Assigner, error) { } ctx = lsctx.WithCommandPrefix(ctx, &commandPrefix) - ctx = lsctx.WithWatcher(ctx, svc.watcher) ctx = lsctx.WithRootDirectory(ctx, &rootDir) ctx = lsctx.WithDiagnosticsNotifier(ctx, svc.diagsNotifier) ctx = ilsp.ContextWithClientName(ctx, &clientName) @@ -501,17 +494,6 @@ func (svc *service) configureSessionDependencies(ctx context.Context, cfgOpts *s svc.closedDirWalker.Collector = svc.walkerCollector svc.openDirWalker.SetLogger(svc.logger) - ww, err := svc.newWatcher(svc.fs, svc.modStore, svc.stateStore.ProviderSchemas, svc.stateStore.JobStore, svc.tfExecFactory) - if err != nil { - return err - } - svc.watcher = ww - svc.watcher.SetLogger(svc.logger) - err = svc.watcher.Start(ctx) - if err != nil { - return err - } - return nil } @@ -546,16 +528,6 @@ func (svc *service) shutdown() { svc.logger.Printf("openDirWalker stopped") } - if svc.watcher != nil { - svc.logger.Println("stopping watcher for session ...") - err := svc.watcher.Stop() - if err != nil { - svc.logger.Println("unable to stop watcher for session:", err) - } else { - svc.logger.Println("watcher stopped") - } - } - if svc.closedDirIndexer != nil { svc.closedDirIndexer.Stop() } diff --git a/internal/langserver/handlers/session_mock_test.go b/internal/langserver/handlers/session_mock_test.go index 523e33b4a..0a7dd2cd3 100644 --- a/internal/langserver/handlers/session_mock_test.go +++ b/internal/langserver/handlers/session_mock_test.go @@ -58,7 +58,6 @@ func (ms *mockSession) new(srvCtx context.Context) session.Session { srvCtx: srvCtx, sessCtx: sessCtx, stopSession: ms.stop, - newWatcher: module.MockWatcher(), tfDiscoFunc: d.LookPath, tfExecFactory: exec.NewMockExecutor(tfCalls), additionalHandlers: handlers, diff --git a/internal/terraform/module/module_manager_test.go b/internal/terraform/module/module_manager_test.go index 0910f961b..5c0e01f01 100644 --- a/internal/terraform/module/module_manager_test.go +++ b/internal/terraform/module/module_manager_test.go @@ -14,6 +14,7 @@ import ( tfjson "github.com/hashicorp/terraform-json" "github.com/hashicorp/terraform-ls/internal/document" "github.com/hashicorp/terraform-ls/internal/filesystem" + "github.com/hashicorp/terraform-ls/internal/job" "github.com/hashicorp/terraform-ls/internal/scheduler" "github.com/hashicorp/terraform-ls/internal/state" "github.com/hashicorp/terraform-ls/internal/terraform/exec" @@ -351,3 +352,23 @@ func testLogger() *log.Logger { return log.New(ioutil.Discard, "", 0) } + +type closedJobStore struct { + js *state.JobStore +} + +func (js *closedJobStore) EnqueueJob(newJob job.Job) (job.ID, error) { + return js.js.EnqueueJob(newJob) +} + +func (js *closedJobStore) AwaitNextJob(ctx context.Context) (job.ID, job.Job, error) { + return js.js.AwaitNextJob(ctx, false) +} + +func (js *closedJobStore) FinishJob(id job.ID, jobErr error, deferredJobIds ...job.ID) error { + return js.js.FinishJob(id, jobErr, deferredJobIds...) +} + +func (js *closedJobStore) WaitForJobs(ctx context.Context, jobIds ...job.ID) error { + return js.js.WaitForJobs(ctx, jobIds...) +} diff --git a/internal/terraform/module/watcher.go b/internal/terraform/module/watcher.go deleted file mode 100644 index 79af214cb..000000000 --- a/internal/terraform/module/watcher.go +++ /dev/null @@ -1,390 +0,0 @@ -package module - -import ( - "context" - "io/ioutil" - "log" - "os" - "path/filepath" - - "github.com/fsnotify/fsnotify" - "github.com/hashicorp/terraform-ls/internal/document" - "github.com/hashicorp/terraform-ls/internal/job" - "github.com/hashicorp/terraform-ls/internal/pathcmp" - "github.com/hashicorp/terraform-ls/internal/state" - "github.com/hashicorp/terraform-ls/internal/terraform/datadir" - "github.com/hashicorp/terraform-ls/internal/terraform/exec" - op "github.com/hashicorp/terraform-ls/internal/terraform/module/operation" -) - -// Watcher is a wrapper around native fsnotify.Watcher -// It provides the ability to detect actual file changes -// (rather than just events that may not be changing any bytes) -type watcher struct { - fw *fsnotify.Watcher - - fs ReadOnlyFS - modStore *state.ModuleStore - schemaStore *state.ProviderSchemaStore - jobStore job.JobStore - tfExecFactory exec.ExecutorFactory - - modules []*watchedModule - logger *log.Logger - - watching bool - cancelFunc context.CancelFunc -} - -type WatcherFactory func(fs ReadOnlyFS, ms *state.ModuleStore, pss *state.ProviderSchemaStore, js job.JobStore, tfExec exec.ExecutorFactory) (Watcher, error) - -type watchedModule struct { - Path string - Watched []string - Watchable *datadir.WatchablePaths -} - -func NewWatcher(fs ReadOnlyFS, ms *state.ModuleStore, pss *state.ProviderSchemaStore, js job.JobStore, tfExec exec.ExecutorFactory) (Watcher, error) { - fw, err := fsnotify.NewWatcher() - if err != nil { - return nil, err - } - - return &watcher{ - fw: fw, - fs: fs, - modStore: ms, - schemaStore: pss, - jobStore: js, - tfExecFactory: tfExec, - logger: defaultLogger, - modules: make([]*watchedModule, 0), - }, nil -} - -var defaultLogger = log.New(ioutil.Discard, "", 0) - -func (w *watcher) SetLogger(logger *log.Logger) { - w.logger = logger -} - -func (w *watcher) IsModuleWatched(modPath string) bool { - modPath = filepath.Clean(modPath) - - for _, m := range w.modules { - if pathcmp.PathEquals(m.Path, modPath) { - return true - } - } - - return false -} - -func (w *watcher) AddModule(modPath string) error { - modPath = filepath.Clean(modPath) - - w.logger.Printf("adding module for watching: %s", modPath) - - wm := &watchedModule{ - Path: modPath, - Watched: make([]string, 0), - Watchable: datadir.WatchableModulePaths(modPath), - } - w.modules = append(w.modules, wm) - - // We watch individual dirs (instead of individual files). - // This does result in more events but fewer watched paths. - // fsnotify does not support recursive watching yet. - // See https://github.com/fsnotify/fsnotify/issues/18 - - err := w.fw.Add(modPath) - if err != nil { - return err - } - - for _, dirPath := range wm.Watchable.Dirs { - err := w.fw.Add(dirPath) - if err == nil { - wm.Watched = append(wm.Watched, dirPath) - } - } - - return nil -} - -func (w *watcher) RemoveModule(modPath string) error { - modPath = filepath.Clean(modPath) - - w.logger.Printf("removing module from watching: %s", modPath) - - for modI, mod := range w.modules { - if pathcmp.PathEquals(mod.Path, modPath) { - for _, wPath := range mod.Watched { - w.fw.Remove(wPath) - } - w.fw.Remove(mod.Path) - w.modules = append(w.modules[:modI], w.modules[modI+1:]...) - } - - for i, wp := range mod.Watched { - if pathcmp.PathEquals(wp, modPath) { - w.fw.Remove(wp) - mod.Watched = append(mod.Watched[:i], mod.Watched[i+1:]...) - } - } - } - - return nil -} - -func (w *watcher) run(ctx context.Context) { - for { - select { - case event, ok := <-w.fw.Events: - if !ok { - return - } - w.processEvent(ctx, event) - case err, ok := <-w.fw.Errors: - if !ok { - return - } - w.logger.Println("watch error:", err) - } - } -} - -func (w *watcher) processEvent(ctx context.Context, event fsnotify.Event) { - eventPath := event.Name - - if event.Op&fsnotify.Write == fsnotify.Write { - for _, mod := range w.modules { - modHandle := document.DirHandleFromPath(mod.Path) - if containsPath(mod.Watchable.ModuleManifests, eventPath) { - id, err := w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - return ParseModuleManifest(w.fs, w.modStore, mod.Path) - }, - Type: op.OpTypeParseModuleManifest.String(), - Defer: decodeInstalledModuleCalls(w.fs, w.modStore, w.schemaStore, mod.Path), - }) - if err == nil { - w.jobStore.WaitForJobs(ctx, id) - collectReferences(ctx, modHandle, w.modStore, w.schemaStore) - } - - return - } - if containsPath(mod.Watchable.PluginLockFiles, eventPath) { - w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, w.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return ObtainSchema(ctx, w.modStore, w.schemaStore, mod.Path) - }, - Type: op.OpTypeObtainSchema.String(), - }) - w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, w.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return GetTerraformVersion(ctx, w.modStore, mod.Path) - }, - Type: op.OpTypeGetTerraformVersion.String(), - }) - return - } - } - } - - if event.Op&fsnotify.Create == fsnotify.Create { - for _, mod := range w.modules { - modHandle := document.DirHandleFromPath(mod.Path) - - if containsPath(mod.Watchable.Dirs, eventPath) { - w.fw.Add(eventPath) - mod.Watched = append(mod.Watched, eventPath) - - filepath.Walk(eventPath, func(path string, info os.FileInfo, err error) error { - if info.IsDir() { - if containsPath(mod.Watchable.Dirs, path) { - w.fw.Add(path) - mod.Watched = append(mod.Watched, path) - } - return nil - } - - modHandle := document.DirHandleFromPath(path) - - if containsPath(mod.Watchable.ModuleManifests, path) { - id, err := w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - return ParseModuleManifest(w.fs, w.modStore, mod.Path) - }, - Type: op.OpTypeParseModuleManifest.String(), - Defer: decodeInstalledModuleCalls(w.fs, w.modStore, w.schemaStore, mod.Path), - }) - if err == nil { - w.jobStore.WaitForJobs(ctx, id) - collectReferences(ctx, modHandle, w.modStore, w.schemaStore) - } - - return nil - } - if containsPath(mod.Watchable.PluginLockFiles, path) { - w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, w.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return ObtainSchema(ctx, w.modStore, w.schemaStore, mod.Path) - }, - Type: op.OpTypeObtainSchema.String(), - }) - w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, w.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return GetTerraformVersion(ctx, w.modStore, mod.Path) - }, - Type: op.OpTypeGetTerraformVersion.String(), - }) - return nil - } - return nil - }) - - return - } - - if containsPath(mod.Watchable.ModuleManifests, eventPath) { - id, err := w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - return ParseModuleManifest(w.fs, w.modStore, mod.Path) - }, - Type: op.OpTypeParseModuleManifest.String(), - Defer: decodeInstalledModuleCalls(w.fs, w.modStore, w.schemaStore, mod.Path), - }) - if err == nil { - w.jobStore.WaitForJobs(ctx, id) - collectReferences(ctx, modHandle, w.modStore, w.schemaStore) - } - return - } - - if containsPath(mod.Watchable.PluginLockFiles, eventPath) { - w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(jCtx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, w.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return ObtainSchema(ctx, w.modStore, w.schemaStore, mod.Path) - }, - Type: op.OpTypeObtainSchema.String(), - }) - w.jobStore.EnqueueJob(job.Job{ - Dir: modHandle, - Func: func(ctx context.Context) error { - ctx = exec.WithExecutorFactory(ctx, w.tfExecFactory) - eo, ok := exec.ExecutorOptsFromContext(ctx) - if ok { - ctx = exec.WithExecutorOpts(ctx, eo) - } - - return GetTerraformVersion(ctx, w.modStore, mod.Path) - }, - Type: op.OpTypeGetTerraformVersion.String(), - }) - return - } - } - } - - if event.Op&fsnotify.Remove == fsnotify.Remove { - for modI, mod := range w.modules { - // Whole module being removed - if pathcmp.PathEquals(mod.Path, eventPath) { - for _, wPath := range mod.Watched { - w.fw.Remove(wPath) - } - w.fw.Remove(mod.Path) - w.modules = append(w.modules[:modI], w.modules[modI+1:]...) - return - } - - for i, wp := range mod.Watched { - if pathcmp.PathEquals(wp, eventPath) { - w.fw.Remove(wp) - mod.Watched = append(mod.Watched[:i], mod.Watched[i+1:]...) - return - } - } - } - } -} - -func containsPath(paths []string, path string) bool { - for _, p := range paths { - if pathcmp.PathEquals(p, path) { - return true - } - } - return false -} - -func (w *watcher) Start(ctx context.Context) error { - if w.watching { - w.logger.Println("watching already in progress") - return nil - } - - ctx, cancelFunc := context.WithCancel(ctx) - w.cancelFunc = cancelFunc - w.watching = true - - w.logger.Printf("watching for changes ...") - go w.run(ctx) - - return nil -} - -func (w *watcher) Stop() error { - if !w.watching { - return nil - } - - w.cancelFunc() - - err := w.fw.Close() - if err == nil { - w.watching = false - } - - return err -} diff --git a/internal/terraform/module/watcher_mock.go b/internal/terraform/module/watcher_mock.go deleted file mode 100644 index 5068750ca..000000000 --- a/internal/terraform/module/watcher_mock.go +++ /dev/null @@ -1,39 +0,0 @@ -package module - -import ( - "context" - "log" - - "github.com/hashicorp/terraform-ls/internal/job" - "github.com/hashicorp/terraform-ls/internal/state" - "github.com/hashicorp/terraform-ls/internal/terraform/exec" -) - -func MockWatcher() WatcherFactory { - return func(fs ReadOnlyFS, ms *state.ModuleStore, pss *state.ProviderSchemaStore, js job.JobStore, tfExec exec.ExecutorFactory) (Watcher, error) { - return &mockWatcher{}, nil - } -} - -type mockWatcher struct{} - -func (w *mockWatcher) Start(context.Context) error { - return nil -} -func (w *mockWatcher) Stop() error { - return nil -} - -func (w *mockWatcher) SetLogger(*log.Logger) {} - -func (w *mockWatcher) AddModule(string) error { - return nil -} - -func (w *mockWatcher) RemoveModule(string) error { - return nil -} - -func (w *mockWatcher) IsModuleWatched(string) bool { - return false -} diff --git a/internal/terraform/module/watcher_test.go b/internal/terraform/module/watcher_test.go deleted file mode 100644 index 7e9bc168f..000000000 --- a/internal/terraform/module/watcher_test.go +++ /dev/null @@ -1,197 +0,0 @@ -package module - -import ( - "context" - "io/ioutil" - "os" - "path/filepath" - "testing" - "time" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/go-version" - "github.com/hashicorp/hcl-lang/schema" - "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-ls/internal/filesystem" - "github.com/hashicorp/terraform-ls/internal/job" - "github.com/hashicorp/terraform-ls/internal/scheduler" - "github.com/hashicorp/terraform-ls/internal/state" - "github.com/hashicorp/terraform-ls/internal/terraform/exec" - tfaddr "github.com/hashicorp/terraform-registry-address" - tfschema "github.com/hashicorp/terraform-schema/schema" - "github.com/stretchr/testify/mock" -) - -func TestWatcher_initFromScratch(t *testing.T) { - ss, err := state.NewStateStore() - if err != nil { - t.Fatal(err) - } - ss.SetLogger(testLogger()) - - fs := filesystem.NewFilesystem(ss.DocumentStore) - - modPath := filepath.Join(t.TempDir(), "module") - err = os.Mkdir(modPath, 0755) - if err != nil { - t.Fatal(err) - } - - psMock := &tfjson.ProviderSchemas{ - FormatVersion: "0.1", - Schemas: map[string]*tfjson.ProviderSchema{ - "registry.terraform.io/hashicorp/aws": {}, - }, - } - tfCalls := &exec.TerraformMockCalls{ - PerWorkDir: map[string][]*mock.Call{ - modPath: { - { - Method: "ProviderSchemas", - Arguments: []interface{}{ - mock.AnythingOfType("*context.valueCtx"), - }, - ReturnArguments: []interface{}{ - psMock, - nil, - }, - }, - { - Method: "Version", - Arguments: []interface{}{ - mock.AnythingOfType("*context.valueCtx"), - }, - ReturnArguments: []interface{}{ - version.Must(version.NewVersion("1.0.0")), - nil, - nil, - }, - }, - }, - }, - } - - ctx := context.Background() - - ctx = exec.WithExecutorOpts(ctx, &exec.ExecutorOpts{ - ExecPath: "tf-mock", - }) - - w, err := NewWatcher(fs, ss.Modules, ss.ProviderSchemas, ss.JobStore, exec.NewMockExecutor(tfCalls)) - if err != nil { - t.Fatal(err) - } - w.SetLogger(testLogger()) - - err = ss.Modules.Add(modPath) - if err != nil { - t.Fatal(err) - } - - b := []byte(` -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = "~> 3.0" - } - } -} - -provider "aws" { - region = "us-east-1" -} - -resource "aws_vpc" "example" { - cidr_block = "10.0.0.0/16" -} -`) - err = ioutil.WriteFile(filepath.Join(modPath, "main.tf"), b, 0755) - if err != nil { - t.Fatal(err) - } - - err = w.AddModule(modPath) - if err != nil { - t.Fatal(err) - } - - err = w.Start(ctx) - if err != nil { - t.Fatal(err) - } - t.Cleanup(func() { - w.Stop() - }) - - err = ioutil.WriteFile(filepath.Join(modPath, ".terraform.lock.hcl"), b, 0755) - if err != nil { - t.Fatal(err) - } - - // Give watcher some time to react - time.Sleep(250 * time.Millisecond) - - jobIds, err := ss.JobStore.ListQueuedJobs() - if err != nil { - t.Fatal(err) - } - t.Logf("queued jobs: %q", jobIds) - - scheduler := scheduler.NewScheduler(&closedJobStore{ss.JobStore}, 1) - scheduler.Start(ctx) - t.Cleanup(scheduler.Stop) - - err = ss.JobStore.WaitForJobs(ctx, jobIds...) - if err != nil { - t.Fatal(err) - } - - vc, err := version.NewConstraint("~> 3.0") - if err != nil { - t.Fatal(err) - } - ps, err := ss.ProviderSchemas.ProviderSchema(modPath, tfaddr.NewDefaultProvider("aws"), vc) - if err != nil { - t.Fatal(err) - } - expectedSchema := &tfschema.ProviderSchema{ - Resources: map[string]*schema.BodySchema{}, - DataSources: map[string]*schema.BodySchema{}, - } - if diff := cmp.Diff(expectedSchema, ps); diff != "" { - t.Fatalf("schema mismatch: %s", diff) - } - - mod, err := ss.Modules.ModuleByPath(modPath) - if err != nil { - t.Fatal(err) - } - if mod.TerraformVersion == nil { - t.Fatal("expected non-nil version") - } - if mod.TerraformVersion.String() != "1.0.0" { - t.Fatalf("version mismatch.\ngiven: %q\nexpected: %q", - mod.TerraformVersion.String(), "1.0.0") - } -} - -type closedJobStore struct { - js *state.JobStore -} - -func (js *closedJobStore) EnqueueJob(newJob job.Job) (job.ID, error) { - return js.js.EnqueueJob(newJob) -} - -func (js *closedJobStore) AwaitNextJob(ctx context.Context) (job.ID, job.Job, error) { - return js.js.AwaitNextJob(ctx, false) -} - -func (js *closedJobStore) FinishJob(id job.ID, jobErr error, deferredJobIds ...job.ID) error { - return js.js.FinishJob(id, jobErr, deferredJobIds...) -} - -func (js *closedJobStore) WaitForJobs(ctx context.Context, jobIds ...job.ID) error { - return js.js.WaitForJobs(ctx, jobIds...) -}