internal/depsfile: SaveLocksToFile implementation
This is an initial implementation of writing locks back to a file on disk. This initial implementation is incomplete because it does not write the changes to the new file atomically. We'll revisit that in a later commit as we return to polish these codepaths, once we've proven out this package's design by integrating it with Terraform's provider installer.
This commit is contained in:
parent
92723661d0
commit
98e2e69abb
|
@ -1,6 +1,8 @@
|
|||
package depsfile
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
)
|
||||
|
@ -51,6 +53,11 @@ func (l *Locks) Provider(addr addrs.Provider) *ProviderLock {
|
|||
// invalidates any ProviderLock object previously returned from Provider or
|
||||
// SetProvider for the given provider address.
|
||||
func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes map[getproviders.Platform][]string) *ProviderLock {
|
||||
// Normalize the hash lists into a consistent order.
|
||||
for _, slice := range hashes {
|
||||
sort.Strings(slice)
|
||||
}
|
||||
|
||||
new := &ProviderLock{
|
||||
addr: addr,
|
||||
version: version,
|
||||
|
|
|
@ -2,10 +2,16 @@ package depsfile
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
"github.com/hashicorp/hcl/v2/hclparse"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/internal/getproviders"
|
||||
|
@ -49,7 +55,95 @@ func LoadLocksFromFile(filename string) (*Locks, tfdiags.Diagnostics) {
|
|||
// temporary files may be temporarily created in the same directory as the
|
||||
// given filename during the operation.
|
||||
func SaveLocksToFile(locks *Locks, filename string) tfdiags.Diagnostics {
|
||||
panic("SaveLocksToFile is not implemented yet")
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// In other uses of the "hclwrite" package we typically try to make
|
||||
// surgical updates to the author's existing files, preserving their
|
||||
// block ordering, comments, etc. We intentionally don't do that here
|
||||
// to reinforce the fact that this file primarily belongs to Terraform,
|
||||
// and to help ensure that VCS diffs of the file primarily reflect
|
||||
// changes that actually affect functionality rather than just cosmetic
|
||||
// changes, by maintaining it in a highly-normalized form.
|
||||
|
||||
f := hclwrite.NewEmptyFile()
|
||||
rootBody := f.Body()
|
||||
|
||||
// End-users _may_ edit the lock file in exceptional situations, like
|
||||
// working around potential dependency selection bugs, but we intend it
|
||||
// to be primarily maintained automatically by the "terraform init"
|
||||
// command.
|
||||
rootBody.AppendUnstructuredTokens(hclwrite.Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenComment,
|
||||
Bytes: []byte("# This file is maintained automatically by \"terraform init\".\n"),
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenComment,
|
||||
Bytes: []byte("# Manual edits may be lost in future updates.\n"),
|
||||
},
|
||||
})
|
||||
|
||||
providers := make([]addrs.Provider, 0, len(locks.providers))
|
||||
for provider := range locks.providers {
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
sort.Slice(providers, func(i, j int) bool {
|
||||
return providers[i].LessThan(providers[j])
|
||||
})
|
||||
|
||||
for _, provider := range providers {
|
||||
lock := locks.providers[provider]
|
||||
rootBody.AppendNewline()
|
||||
block := rootBody.AppendNewBlock("provider", []string{lock.addr.String()})
|
||||
body := block.Body()
|
||||
body.SetAttributeValue("version", cty.StringVal(lock.version.String()))
|
||||
if constraintsStr := getproviders.VersionConstraintsString(lock.versionConstraints); constraintsStr != "" {
|
||||
body.SetAttributeValue("constraints", cty.StringVal(constraintsStr))
|
||||
}
|
||||
if len(lock.hashes) != 0 {
|
||||
platforms := make([]getproviders.Platform, 0, len(lock.hashes))
|
||||
for platform := range lock.hashes {
|
||||
platforms = append(platforms, platform)
|
||||
}
|
||||
sort.Slice(platforms, func(i, j int) bool {
|
||||
return platforms[i].LessThan(platforms[j])
|
||||
})
|
||||
body.AppendNewline()
|
||||
hashesBlock := body.AppendNewBlock("hashes", nil)
|
||||
hashesBody := hashesBlock.Body()
|
||||
for platform, hashes := range lock.hashes {
|
||||
vals := make([]cty.Value, len(hashes))
|
||||
for i := range hashes {
|
||||
vals[i] = cty.StringVal(hashes[i])
|
||||
}
|
||||
var hashList cty.Value
|
||||
if len(vals) > 0 {
|
||||
hashList = cty.ListVal(vals)
|
||||
} else {
|
||||
hashList = cty.ListValEmpty(cty.String)
|
||||
}
|
||||
hashesBody.SetAttributeValue(platform.String(), hashList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newContent := f.Bytes()
|
||||
|
||||
// TODO: Create the content in a new file and atomically pivot it into
|
||||
// the target, so that there isn't a brief period where an incomplete
|
||||
// file can be seen at the given location.
|
||||
// But for now, this gets us started.
|
||||
err := ioutil.WriteFile(filename, newContent, os.ModePerm)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to update dependency lock file",
|
||||
fmt.Sprintf("Error while writing new dependency lock information to %s: %s.", filename, err),
|
||||
))
|
||||
return diags
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func decodeLocksFromHCL(locks *Locks, body hcl.Body) tfdiags.Diagnostics {
|
||||
|
|
|
@ -162,3 +162,67 @@ func TestLoadLocksFromFile(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveLocksToFile(t *testing.T) {
|
||||
locks := NewLocks()
|
||||
|
||||
fooProvider := addrs.MustParseProviderSourceString("test/foo")
|
||||
barProvider := addrs.MustParseProviderSourceString("test/bar")
|
||||
bazProvider := addrs.MustParseProviderSourceString("test/baz")
|
||||
oneDotOh := getproviders.MustParseVersion("1.0.0")
|
||||
oneDotTwo := getproviders.MustParseVersion("1.2.0")
|
||||
atLeastOneDotOh := getproviders.MustParseVersionConstraints(">= 1.0.0")
|
||||
pessimisticOneDotOh := getproviders.MustParseVersionConstraints("~> 1")
|
||||
hashes := map[getproviders.Platform][]string{
|
||||
{OS: "riscos", Arch: "arm"}: {
|
||||
"cccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
},
|
||||
}
|
||||
locks.SetProvider(fooProvider, oneDotOh, atLeastOneDotOh, hashes)
|
||||
locks.SetProvider(barProvider, oneDotTwo, pessimisticOneDotOh, nil)
|
||||
locks.SetProvider(bazProvider, oneDotTwo, nil, nil)
|
||||
|
||||
dir, err := ioutil.TempDir("", "terraform-internal-depsfile-savelockstofile")
|
||||
if err != nil {
|
||||
t.Fatal(err.Error())
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
filename := filepath.Join(dir, LockFilePath)
|
||||
diags := SaveLocksToFile(locks, filename)
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
|
||||
}
|
||||
|
||||
gotContentBytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
gotContent := string(gotContentBytes)
|
||||
wantContent := `# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/test/bar" {
|
||||
version = "1.2.0"
|
||||
constraints = "~> 1.0"
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/test/baz" {
|
||||
version = "1.2.0"
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/test/foo" {
|
||||
version = "1.0.0"
|
||||
constraints = ">= 1.0.0"
|
||||
|
||||
hashes {
|
||||
riscos_arm = ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "cccccccccccccccccccccccccccccccccccccccccccccccc"]
|
||||
}
|
||||
}
|
||||
`
|
||||
if diff := cmp.Diff(wantContent, gotContent); diff != "" {
|
||||
t.Errorf("wrong result\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue