Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 83 additions & 52 deletions internal/command/e2etest/providers_mirror_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
Expand All @@ -18,67 +19,97 @@ import (
// interacts directly with Terraform Registry and the full details of that are
// tricky to mock. Such a mock is _possible_, but we're using e2etest as a
// compromise for now to keep these tests relatively simple.

func TestTerraformProvidersMirror(t *testing.T) {
testTerraformProvidersMirror(t, "terraform-providers-mirror")
}

func TestTerraformProvidersMirrorWithLockFile(t *testing.T) {
testTerraformProvidersMirror(t, "terraform-providers-mirror-with-lock-file")
}

func testTerraformProvidersMirror(t *testing.T, fixture string) {
// This test reaches out to releases.hashicorp.com to download the
// template and null providers, so it can only run if network access is
// allowed.
skipIfCannotAccessNetwork(t)

outputDir := t.TempDir()
t.Logf("creating mirror directory in %s", outputDir)
for _, test := range []struct {
name string
args []string
err string
}{
{
name: "terraform-providers-mirror",
args: []string{"-platform=linux_amd64", "-platform=windows_386"},
},
{
name: "terraform-providers-mirror-with-lock-file",
args: []string{"-platform=linux_amd64", "-platform=windows_386"},
},
{
// should ignore lock file
name: "terraform-providers-mirror-with-broken-lock-file",
args: []string{"-platform=linux_amd64", "-platform=windows_386", "-lock-file=false"},
},
{
name: "terraform-providers-mirror-with-broken-lock-file",
args: []string{"-platform=linux_amd64", "-platform=windows_386", "-lock-file=true"},
err: "Inconsistent dependency lock file",
},
} {
t.Run(test.name, func(t *testing.T) {
outputDir := t.TempDir()
t.Logf("creating mirror directory in %s", outputDir)

fixturePath := filepath.Join("testdata", fixture)
tf := e2e.NewBinary(t, terraformBin, fixturePath)
fixturePath := filepath.Join("testdata", test.name)
tf := e2e.NewBinary(t, terraformBin, fixturePath)

stdout, stderr, err := tf.Run("providers", "mirror", "-platform=linux_amd64", "-platform=windows_386", outputDir)
if err != nil {
t.Fatalf("unexpected error: %s\nstdout:\n%s\nstderr:\n%s", err, stdout, stderr)
}
args := []string{"providers", "mirror"}
args = append(args, test.args...)
args = append(args, outputDir)

// The test fixture includes exact version constraints for the two
// providers it depends on so that the following should remain stable.
// In the (unlikely) event that these particular versions of these
// providers are removed from the registry, this test will start to fail.
want := []string{
"registry.terraform.io/hashicorp/null/2.1.0.json",
"registry.terraform.io/hashicorp/null/index.json",
"registry.terraform.io/hashicorp/null/terraform-provider-null_2.1.0_linux_amd64.zip",
"registry.terraform.io/hashicorp/null/terraform-provider-null_2.1.0_windows_386.zip",
"registry.terraform.io/hashicorp/template/2.1.1.json",
"registry.terraform.io/hashicorp/template/index.json",
"registry.terraform.io/hashicorp/template/terraform-provider-template_2.1.1_linux_amd64.zip",
"registry.terraform.io/hashicorp/template/terraform-provider-template_2.1.1_windows_386.zip",
}
var got []string
walkErr := filepath.Walk(outputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil // we only care about leaf files for this test
}
relPath, err := filepath.Rel(outputDir, path)
if err != nil {
return err
}
got = append(got, filepath.ToSlash(relPath))
return nil
})
if walkErr != nil {
t.Fatal(walkErr)
}
sort.Strings(got)
stdout, stderr, err := tf.Run(args...)
if test.err != "" {
if !strings.Contains(stderr, test.err) {
t.Fatalf("expected error %q, got %q\n", test.err, stderr)
}
return
}

if err != nil {
t.Fatalf("unexpected error: %s\nstdout:\n%s\nstderr:\n%s", err, stdout, stderr)
}

// The test fixture includes exact version constraints for the two
// providers it depends on so that the following should remain stable.
// In the (unlikely) event that these particular versions of these
// providers are removed from the registry, this test will start to fail.
want := []string{
"registry.terraform.io/hashicorp/null/2.1.0.json",
"registry.terraform.io/hashicorp/null/index.json",
"registry.terraform.io/hashicorp/null/terraform-provider-null_2.1.0_linux_amd64.zip",
"registry.terraform.io/hashicorp/null/terraform-provider-null_2.1.0_windows_386.zip",
"registry.terraform.io/hashicorp/template/2.1.1.json",
"registry.terraform.io/hashicorp/template/index.json",
"registry.terraform.io/hashicorp/template/terraform-provider-template_2.1.1_linux_amd64.zip",
"registry.terraform.io/hashicorp/template/terraform-provider-template_2.1.1_windows_386.zip",
}
var got []string
walkErr := filepath.Walk(outputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil // we only care about leaf files for this test
}
relPath, err := filepath.Rel(outputDir, path)
if err != nil {
return err
}
got = append(got, filepath.ToSlash(relPath))
return nil
})
if walkErr != nil {
t.Fatal(walkErr)
}
sort.Strings(got)

if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("unexpected files in result\n%s", diff)
}

if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("unexpected files in result\n%s", diff)
})
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
terraform {
required_providers {
template = { version = "2.1.1" }
null = { source = "hashicorp/null", version = "2.1.0" }
terraform = { source = "terraform.io/builtin/terraform" }
}
}
26 changes: 18 additions & 8 deletions internal/command/providers_mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,13 @@ func (c *ProvidersMirrorCommand) Synopsis() string {
func (c *ProvidersMirrorCommand) Run(args []string) int {
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("providers mirror")

var optPlatforms arguments.FlagStringSlice
cmdFlags.Var(&optPlatforms, "platform", "target platform")

var optLockFile bool
cmdFlags.BoolVar(&optLockFile, "lock-file", true, "use lock file")

cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
Expand Down Expand Up @@ -88,14 +93,8 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
lockedDeps, lockedDepsDiags := c.Meta.lockedDependencies()
diags = diags.Append(lockedDepsDiags)

// If we have any error diagnostics already then we won't proceed further.
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

// If lock file is present, validate it against configuration
if !lockedDeps.Empty() {
if !lockedDeps.Empty() && optLockFile {
if errs := config.VerifyDependencySelections(lockedDeps); len(errs) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
Expand All @@ -105,6 +104,12 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
}
}

// If we have any error diagnostics already then we won't proceed further.
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}

// Unlike other commands, this command always consults the origin registry
// for every provider so that it can be used to update a local mirror
// directory without needing to first disable that local mirror
Expand Down Expand Up @@ -161,7 +166,7 @@ func (c *ProvidersMirrorCommand) Run(args []string) int {
continue
}
selected := candidates.Newest()
if !lockedDeps.Empty() {
if !lockedDeps.Empty() && optLockFile {
selected = lockedDeps.Provider(provider).Version()
c.Ui.Output(fmt.Sprintf(" - Selected v%s to match dependency lock file", selected.String()))
} else if len(constraintsStr) > 0 {
Expand Down Expand Up @@ -379,5 +384,10 @@ Options:
Linux operating system running on an AMD64 or x86_64
CPU. Each provider is available only for a limited
set of target platforms.

-lock-file=false Ignore the provider lock file when fetching providers.
By default the mirror command will use the version info
in the lock file if the configuration directory has been
previously initialized.
`
}
4 changes: 4 additions & 0 deletions website/docs/cli/commands/providers/mirror.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ This command supports the following additional option:
architecture. For example, `linux_amd64` selects the Linux operating system
running on an AMD64 or x86_64 CPU.

* `-lock-file=false` - Ignore the provider lock file when fetching providers.
By default the mirror command will use the version info in the lock file if the
configuration directory has been previously initialized.

You can run `terraform providers mirror` again on an existing mirror directory
to update it with new packages. For example, you can add packages for a new
target platform by re-running the command with the desired new `-platform=...`
Expand Down