diff --git a/command/init.go b/command/init.go index db6ec0fe2..7afeb8830 100644 --- a/command/init.go +++ b/command/init.go @@ -216,8 +216,9 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { return err } + available := c.providerPluginSet() requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() - missing := c.missingProviders(requirements) + missing := c.missingPlugins(available, requirements) dst := c.pluginDir() for provider, reqd := range missing { @@ -227,6 +228,26 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { return err } } + + // With all the providers downloaded, we'll generate our lock file + // that ensures the provider binaries remain unchanged until we init + // again. If anything changes, other commands that use providers will + // fail with an error instructing the user to re-run this command. + available = c.providerPluginSet() // re-discover to see newly-installed plugins + chosen := choosePlugins(available, requirements) + digests := map[string][]byte{} + for name, meta := range chosen { + digest, err := meta.SHA256() + if err != nil { + return fmt.Errorf("failed to read provider plugin %s: %s", meta.Path, err) + } + digests[name] = digest + } + err = c.providerPluginsLock().Write(digests) + if err != nil { + return fmt.Errorf("failed to save provider manifest: %s", err) + } + return nil } diff --git a/command/init_test.go b/command/init_test.go index a95a5155b..f41f44f4e 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -1,9 +1,11 @@ package command import ( + "fmt" "io/ioutil" "os" "path/filepath" + "runtime" "strings" "testing" @@ -618,6 +620,52 @@ func TestInit_getProviderMissing(t *testing.T) { } } +func TestInit_providerLockFile(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-provider-lock-file"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + getter := &mockGetProvider{ + Providers: map[string][]string{ + "test": []string{"1.2.3"}, + }, + } + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + getProvider: getter.GetProvider, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + providersLockFile := fmt.Sprintf( + ".terraform/plugins/%s_%s/providers.json", + runtime.GOOS, runtime.GOARCH, + ) + buf, err := ioutil.ReadFile(providersLockFile) + if err != nil { + t.Fatalf("failed to read providers lock file %s: %s", providersLockFile, err) + } + // The hash in here is for the empty files that mockGetProvider produces + wantLockFile := strings.TrimSpace(` +{ + "test": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +} +`) + if string(buf) != wantLockFile { + t.Errorf("wrong provider lock file contents\ngot: %s\nwant: %s", buf, wantLockFile) + } +} + /* func TestInit_remoteState(t *testing.T) { tmp, cwd := testCwd(t) diff --git a/command/plugins.go b/command/plugins.go index 020e83946..9539ef51d 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -23,17 +23,27 @@ type multiVersionProviderResolver struct { Available discovery.PluginMetaSet } +func choosePlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta { + candidates := avail.ConstrainVersions(reqd) + ret := map[string]discovery.PluginMeta{} + for name, metas := range candidates { + if len(metas) == 0 { + continue + } + ret[name] = metas.Newest() + } + return ret +} + func (r *multiVersionProviderResolver) ResolveProviders( reqd discovery.PluginRequirements, ) (map[string]terraform.ResourceProviderFactory, []error) { factories := make(map[string]terraform.ResourceProviderFactory, len(reqd)) var errs []error - candidates := r.Available.ConstrainVersions(reqd) + chosen := choosePlugins(r.Available, reqd) for name := range reqd { - if metas := candidates[name]; metas != nil { - newest := metas.Newest() - + if newest, available := chosen[name]; available { digest, err := newest.SHA256() if err != nil { errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err)) @@ -45,7 +55,7 @@ func (r *multiVersionProviderResolver) ResolveProviders( // here is that they need to run "terraform init" to // fix this, which is covered by the UI code reporting these // error messages. - errs = append(errs, fmt.Errorf("provider.%s: not yet initialized", name)) + errs = append(errs, fmt.Errorf("provider.%s: installed but not yet initialized", name)) continue } @@ -108,10 +118,10 @@ func (m *Meta) providerResolver() terraform.ResourceProviderResolver { } // filter the requirements returning only the providers that we can't resolve -func (m *Meta) missingProviders(reqd discovery.PluginRequirements) discovery.PluginRequirements { +func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements { missing := make(discovery.PluginRequirements) - candidates := m.providerPluginSet().ConstrainVersions(reqd) + candidates := avail.ConstrainVersions(reqd) for name, versionSet := range reqd { if metas := candidates[name]; metas == nil { diff --git a/command/push_test.go b/command/push_test.go index 473a31843..ec83abaa3 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -4,10 +4,12 @@ import ( "archive/tar" "bytes" "compress/gzip" + "fmt" "io" "os" "path/filepath" "reflect" + "runtime" "sort" "strings" "testing" @@ -121,6 +123,9 @@ func TestPush_goodBackendInit(t *testing.T) { // Expected weird behavior, doesn't affect unpackaging ".terraform/", ".terraform/", + ".terraform/plugins/", + fmt.Sprintf(".terraform/plugins/%s_%s/", runtime.GOOS, runtime.GOARCH), + fmt.Sprintf(".terraform/plugins/%s_%s/providers.json", runtime.GOOS, runtime.GOARCH), ".terraform/terraform.tfstate", ".terraform/terraform.tfstate", "main.tf", diff --git a/command/test-fixtures/init-provider-lock-file/main.tf b/command/test-fixtures/init-provider-lock-file/main.tf new file mode 100644 index 000000000..7eed7c561 --- /dev/null +++ b/command/test-fixtures/init-provider-lock-file/main.tf @@ -0,0 +1,3 @@ +provider "test" { + version = "1.2.3" +} diff --git a/command/test-fixtures/init-providers-lock/main.tf b/command/test-fixtures/init-providers-lock/main.tf new file mode 100644 index 000000000..7eed7c561 --- /dev/null +++ b/command/test-fixtures/init-providers-lock/main.tf @@ -0,0 +1,3 @@ +provider "test" { + version = "1.2.3" +}