command: helper to manage the provider plugins lock file
This is just a JSON file with the SHA256 digests of the plugin executables.
This commit is contained in:
parent
7d0a98af46
commit
720670fae7
|
@ -0,0 +1,86 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *Meta) providerPluginsLock() *pluginSHA256LockFile {
|
||||||
|
return &pluginSHA256LockFile{
|
||||||
|
Filename: filepath.Join(m.pluginDir(), "providers.json"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginSHA256LockFile struct {
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read loads the lock information from the file and returns it. If the file
|
||||||
|
// cannot be read, an empty map is returned to indicate that _no_ providers
|
||||||
|
// are acceptable, since the user must run "terraform init" to lock some
|
||||||
|
// providers before a context can be created.
|
||||||
|
func (pf *pluginSHA256LockFile) Read() map[string][]byte {
|
||||||
|
// Returning an empty map is different than nil because it causes
|
||||||
|
// us to reject all plugins as uninitialized, rather than applying no
|
||||||
|
// constraints at all.
|
||||||
|
//
|
||||||
|
// We don't surface any specific errors here because we want it to all
|
||||||
|
// roll up into our more-user-friendly error that appears when plugin
|
||||||
|
// constraint verification fails during context creation.
|
||||||
|
digests := make(map[string][]byte)
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadFile(pf.Filename)
|
||||||
|
if err != nil {
|
||||||
|
// This is expected if the user runs any context-using command before
|
||||||
|
// running "terraform init".
|
||||||
|
log.Printf("[INFO] Failed to read plugin lock file %s: %s", pf.Filename, err)
|
||||||
|
return digests
|
||||||
|
}
|
||||||
|
|
||||||
|
var strDigests map[string]string
|
||||||
|
err = json.Unmarshal(buf, &strDigests)
|
||||||
|
if err != nil {
|
||||||
|
// This should never happen unless the user directly edits the file.
|
||||||
|
log.Printf("[WARNING] Plugin lock file %s failed to parse as JSON: %s", pf.Filename, err)
|
||||||
|
return digests
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, strDigest := range strDigests {
|
||||||
|
var digest []byte
|
||||||
|
_, err := fmt.Sscanf(strDigest, "%x", &digest)
|
||||||
|
if err == nil {
|
||||||
|
digests[name] = digest
|
||||||
|
} else {
|
||||||
|
// This should never happen unless the user directly edits the file.
|
||||||
|
log.Printf("[WARNING] Plugin lock file %s has invalid digest for %q", pf.Filename, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return digests
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write persists lock information to disk, where it will be retrieved by
|
||||||
|
// future calls to Read. This entirely replaces any previous lock information,
|
||||||
|
// so the given map must be comprehensive.
|
||||||
|
func (pf *pluginSHA256LockFile) Write(digests map[string][]byte) error {
|
||||||
|
strDigests := map[string]string{}
|
||||||
|
for name, digest := range digests {
|
||||||
|
strDigests[name] = fmt.Sprintf("%x", digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json.MarshalIndent(strDigests, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
// should never happen
|
||||||
|
return fmt.Errorf("failed to serialize plugin lock as JSON: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.MkdirAll(
|
||||||
|
filepath.Dir(pf.Filename), os.ModePerm,
|
||||||
|
) // ignore error since WriteFile below will generate a better one anyway
|
||||||
|
|
||||||
|
return ioutil.WriteFile(pf.Filename, buf, os.ModePerm)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPluginSHA256LockFile(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "tf-pluginsha1lockfile-test-")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create temporary file: %s", err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
//defer os.Remove(f.Name())
|
||||||
|
t.Logf("working in %s", f.Name())
|
||||||
|
|
||||||
|
plf := &pluginSHA256LockFile{
|
||||||
|
Filename: f.Name(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially the file is invalid, so we should get an empty map.
|
||||||
|
digests := plf.Read()
|
||||||
|
if !reflect.DeepEqual(digests, map[string][]byte{}) {
|
||||||
|
t.Errorf("wrong initial content %#v; want empty map", digests)
|
||||||
|
}
|
||||||
|
|
||||||
|
digests = map[string][]byte{
|
||||||
|
"test": []byte("hello world"),
|
||||||
|
}
|
||||||
|
err = plf.Write(digests)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write lock file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := plf.Read()
|
||||||
|
if !reflect.DeepEqual(got, digests) {
|
||||||
|
t.Errorf("wrong content %#v after write; want %#v", got, digests)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue