mirror of https://github.com/hashicorp/terraform
Just as with the old installer mechanism, our goal is that explicit provider installation is the only way that new provider versions can be selected. To achieve that, we conclude each call to EnsureProviderVersions by writing a selections lock file into the target directory. A later caller can then recall the selections from that file by calling SelectedPackages, which both ensures that it selects the same set of versions and also verifies that the checksums recorded by the installer still match. This new selections.json file has a different layout than our old plugins.json lock file. Not only does it use a different hashing algorithm than before, we also record explicitly which version of each provider was selected. In the old model, we'd repeat normal discovery when reloading the lock file and then fail with a confusing error message if discovery happened to select a different version, but now we'll be able to distinguish between a package that's gone missing since installation (which could previously have then selected a different available version) from a package that has been modified.pull/24508/head
parent
7891454d10
commit
3cd48dec16
@ -0,0 +1,114 @@
|
||||
package providercache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
)
|
||||
|
||||
// lockFile represents a file on disk that captures selected versions and
|
||||
// their associated package checksums resulting from an install process, so
|
||||
// that later consumers of that install process can be sure they are reading
|
||||
// an identical set of providers to what the install process intended.
|
||||
//
|
||||
// This is an internal type used to encapsulate the reading, parsing,
|
||||
// serializing, and writing of lock files. Its public interface is via methods
|
||||
// on type Installer.
|
||||
type lockFile struct {
|
||||
filename string
|
||||
}
|
||||
|
||||
// LockFileEntry represents an entry for a specific provider in a LockFile.
|
||||
type lockFileEntry struct {
|
||||
SelectedVersion getproviders.Version
|
||||
PackageHash string
|
||||
}
|
||||
|
||||
var _ json.Marshaler = (*lockFileEntry)(nil)
|
||||
var _ json.Unmarshaler = (*lockFileEntry)(nil)
|
||||
|
||||
// Read returns the current locks captured in the lock file.
|
||||
//
|
||||
// If the file does not exist, the result is successful but empty to indicate
|
||||
// that no providers at all are available for use.
|
||||
func (lf *lockFile) Read() (map[addrs.Provider]lockFileEntry, error) {
|
||||
buf, err := ioutil.ReadFile(lf.filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil // no file means no locks yet
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rawEntries map[string]*lockFileEntry
|
||||
err = json.Unmarshal(buf, &rawEntries)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing %s: %s", lf.filename, err)
|
||||
}
|
||||
|
||||
ret := make(map[addrs.Provider]lockFileEntry, len(rawEntries))
|
||||
for providerStr, entry := range rawEntries {
|
||||
provider, diags := addrs.ParseProviderSourceString(providerStr)
|
||||
if diags.HasErrors() {
|
||||
// This file is both generated and consumed by Terraform, so we
|
||||
// don't use super-detailed error messages for problems in it.
|
||||
// If we get here without someone tampering with the file then
|
||||
// it's presumably a bug in either our serializer or our parser.
|
||||
return nil, fmt.Errorf("error parsing %s: invalid provider address %q", lf.filename, providerStr)
|
||||
}
|
||||
ret[provider] = *entry
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Write stores a new set of entries in the lock file, disarding any
|
||||
// selections previously stored there.
|
||||
func (lf *lockFile) Write(new map[addrs.Provider]lockFileEntry) error {
|
||||
toStore := make(map[string]*lockFileEntry, len(new))
|
||||
for provider := range new {
|
||||
entry := new[provider] // so that each reference below is to a different object
|
||||
toStore[provider.String()] = &entry
|
||||
}
|
||||
|
||||
buf, err := json.MarshalIndent(toStore, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing %s: %s", lf.filename, err)
|
||||
}
|
||||
|
||||
os.MkdirAll(
|
||||
filepath.Dir(lf.filename), 0660,
|
||||
) // ignore error since WriteFile below will generate a better one anyway
|
||||
return ioutil.WriteFile(lf.filename, buf, 0660)
|
||||
}
|
||||
|
||||
func (lfe *lockFileEntry) UnmarshalJSON(src []byte) error {
|
||||
type Raw struct {
|
||||
VersionStr string `json:"version"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
var raw Raw
|
||||
err := json.Unmarshal(src, &raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version, err := getproviders.ParseVersion(raw.VersionStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid version number: %s", err)
|
||||
}
|
||||
lfe.SelectedVersion = version
|
||||
lfe.PackageHash = raw.Hash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (lfe *lockFileEntry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"version": lfe.SelectedVersion.String(),
|
||||
"hash": lfe.PackageHash,
|
||||
})
|
||||
}
|
||||
Loading…
Reference in new issue