command: produce provider lock file during "terraform init"
Once we've installed the necessary plugins, we'll do one more walk of the available plugins and record the SHA256 hashes of all of the plugins we select in the provider lock file. The file we write here gets read when we're building ContextOpts to initialize the main terraform context, so any command that works with the context will then fail if any of the provider binaries change.
This commit is contained in:
parent
04bcece59c
commit
032f71f1ff
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
provider "test" {
|
||||
version = "1.2.3"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
provider "test" {
|
||||
version = "1.2.3"
|
||||
}
|
Loading…
Reference in New Issue