From c7f53388bf534b1f3f264d2908b64eaf767e30b8 Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Tue, 10 Feb 2026 17:19:52 +0100 Subject: [PATCH] move providers lock command to new arguments / views architecture --- internal/command/arguments/providers_lock.go | 70 +++++++++ .../command/arguments/providers_lock_test.go | 142 ++++++++++++++++++ internal/command/providers_lock.go | 109 +++++--------- internal/command/providers_lock_test.go | 28 ++-- internal/command/views/providers_lock.go | 100 ++++++++++++ internal/command/views/providers_lock_test.go | 141 +++++++++++++++++ 6 files changed, 511 insertions(+), 79 deletions(-) create mode 100644 internal/command/arguments/providers_lock.go create mode 100644 internal/command/arguments/providers_lock_test.go create mode 100644 internal/command/views/providers_lock.go create mode 100644 internal/command/views/providers_lock_test.go diff --git a/internal/command/arguments/providers_lock.go b/internal/command/arguments/providers_lock.go new file mode 100644 index 0000000000..06e966b9d4 --- /dev/null +++ b/internal/command/arguments/providers_lock.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package arguments + +import ( + "github.com/hashicorp/terraform/internal/tfdiags" +) + +// ProvidersLock represents the command-line arguments for the providers lock command. +type ProvidersLock struct { + // Platforms is the list of target platforms to request package checksums for. + Platforms FlagStringSlice + + // FSMirrorDir is the filesystem mirror directory to consult instead of the + // origin registry. + FSMirrorDir string + + // NetMirrorURL is the network mirror base URL to consult instead of the + // origin registry. + NetMirrorURL string + + // TestDirectory is the directory containing test files, defaults to "tests". + TestDirectory string + + // EnablePluginCache enables the usage of the globally configured plugin cache. + EnablePluginCache bool + + // Providers is the list of provider source addresses given as positional arguments. + Providers []string +} + +// ParseProvidersLock processes CLI arguments, returning a ProvidersLock value and error +// diagnostics. If there are any diagnostics present, a ProvidersLock value is still +// returned representing the best effort interpretation of the arguments. +func ParseProvidersLock(args []string) (*ProvidersLock, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + result := &ProvidersLock{ + TestDirectory: "tests", + } + + cmdFlags := defaultFlagSet("providers lock") + cmdFlags.Var(&result.Platforms, "platform", "target platform") + cmdFlags.StringVar(&result.FSMirrorDir, "fs-mirror", "", "filesystem mirror directory") + cmdFlags.StringVar(&result.NetMirrorURL, "net-mirror", "", "network mirror base URL") + cmdFlags.StringVar(&result.TestDirectory, "test-directory", "tests", "test-directory") + cmdFlags.BoolVar(&result.EnablePluginCache, "enable-plugin-cache", false, "enable plugin cache") + + if err := cmdFlags.Parse(args); err != nil { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Failed to parse command-line flags", + err.Error(), + )) + } + + if remaining := cmdFlags.Args(); len(remaining) > 0 { + result.Providers = remaining + } + + if result.FSMirrorDir != "" && result.NetMirrorURL != "" { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Invalid installation method options", + "The -fs-mirror and -net-mirror command line options are mutually-exclusive.", + )) + } + + return result, diags +} diff --git a/internal/command/arguments/providers_lock_test.go b/internal/command/arguments/providers_lock_test.go new file mode 100644 index 0000000000..c9173caaa5 --- /dev/null +++ b/internal/command/arguments/providers_lock_test.go @@ -0,0 +1,142 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package arguments + +import ( + "reflect" + "testing" + + "github.com/hashicorp/terraform/internal/tfdiags" +) + +func TestParseProvidersLock_valid(t *testing.T) { + testCases := map[string]struct { + args []string + want *ProvidersLock + }{ + "defaults": { + nil, + &ProvidersLock{ + TestDirectory: "tests", + }, + }, + "fs-mirror": { + []string{"-fs-mirror=/path/to/mirror"}, + &ProvidersLock{ + FSMirrorDir: "/path/to/mirror", + TestDirectory: "tests", + }, + }, + "net-mirror": { + []string{"-net-mirror=https://mirror.example.com/"}, + &ProvidersLock{ + NetMirrorURL: "https://mirror.example.com/", + TestDirectory: "tests", + }, + }, + "single platform": { + []string{"-platform=linux_amd64"}, + &ProvidersLock{ + Platforms: FlagStringSlice{"linux_amd64"}, + TestDirectory: "tests", + }, + }, + "multiple platforms": { + []string{"-platform=linux_amd64", "-platform=darwin_arm64"}, + &ProvidersLock{ + Platforms: FlagStringSlice{"linux_amd64", "darwin_arm64"}, + TestDirectory: "tests", + }, + }, + "enable-plugin-cache": { + []string{"-enable-plugin-cache"}, + &ProvidersLock{ + EnablePluginCache: true, + TestDirectory: "tests", + }, + }, + "test-directory": { + []string{"-test-directory=mytests"}, + &ProvidersLock{ + TestDirectory: "mytests", + }, + }, + "provider arguments": { + []string{"hashicorp/aws", "hashicorp/random"}, + &ProvidersLock{ + Providers: []string{"hashicorp/aws", "hashicorp/random"}, + TestDirectory: "tests", + }, + }, + "all options": { + []string{"-fs-mirror=/mirror", "-platform=linux_amd64", "-enable-plugin-cache", "-test-directory=mytests", "hashicorp/aws"}, + &ProvidersLock{ + FSMirrorDir: "/mirror", + Platforms: FlagStringSlice{"linux_amd64"}, + EnablePluginCache: true, + TestDirectory: "mytests", + Providers: []string{"hashicorp/aws"}, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got, diags := ParseProvidersLock(tc.args) + if len(diags) > 0 { + t.Fatalf("unexpected diags: %v", diags) + } + if !reflect.DeepEqual(got, tc.want) { + t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want) + } + }) + } +} + +func TestParseProvidersLock_invalid(t *testing.T) { + testCases := map[string]struct { + args []string + want *ProvidersLock + wantDiags tfdiags.Diagnostics + }{ + "unknown flag": { + []string{"-unknown"}, + &ProvidersLock{ + TestDirectory: "tests", + }, + tfdiags.Diagnostics{ + tfdiags.Sourceless( + tfdiags.Error, + "Failed to parse command-line flags", + "flag provided but not defined: -unknown", + ), + }, + }, + "mirror collision": { + []string{"-fs-mirror=/foo/", "-net-mirror=https://example.com/"}, + &ProvidersLock{ + FSMirrorDir: "/foo/", + NetMirrorURL: "https://example.com/", + TestDirectory: "tests", + }, + tfdiags.Diagnostics{ + tfdiags.Sourceless( + tfdiags.Error, + "Invalid installation method options", + "The -fs-mirror and -net-mirror command line options are mutually-exclusive.", + ), + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + got, gotDiags := ParseProvidersLock(tc.args) + if !reflect.DeepEqual(got, tc.want) { + t.Fatalf("unexpected result\n got: %#v\nwant: %#v", got, tc.want) + } + tfdiags.AssertDiagnosticsMatch(t, gotDiags, tc.wantDiags) + }) + } +} diff --git a/internal/command/providers_lock.go b/internal/command/providers_lock.go index aba227122c..eefebc9bfd 100644 --- a/internal/command/providers_lock.go +++ b/internal/command/providers_lock.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/command/arguments" + "github.com/hashicorp/terraform/internal/command/views" "github.com/hashicorp/terraform/internal/depsfile" "github.com/hashicorp/terraform/internal/getproviders" "github.com/hashicorp/terraform/internal/providercache" @@ -38,45 +39,33 @@ func (c *ProvidersLockCommand) Synopsis() string { return "Write out dependency locks for the configured providers" } -func (c *ProvidersLockCommand) Run(args []string) int { - args = c.Meta.process(args) - cmdFlags := c.Meta.defaultFlagSet("providers lock") - var optPlatforms arguments.FlagStringSlice - var fsMirrorDir string - var netMirrorURL string - var testDirectory string +func (c *ProvidersLockCommand) Run(rawArgs []string) int { + // Parse and apply global view arguments + common, rawArgs := arguments.ParseView(rawArgs) + c.View.Configure(common) - cmdFlags.Var(&optPlatforms, "platform", "target platform") - cmdFlags.StringVar(&fsMirrorDir, "fs-mirror", "", "filesystem mirror directory") - cmdFlags.StringVar(&netMirrorURL, "net-mirror", "", "network mirror base URL") - cmdFlags.StringVar(&testDirectory, "test-directory", "tests", "test-directory") - pluginCache := cmdFlags.Bool("enable-plugin-cache", false, "") - 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())) + // Propagate -no-color for legacy use of Ui + c.Meta.color = !common.NoColor + c.Meta.Color = c.Meta.color + + // Parse and validate command-specific flags + args, diags := arguments.ParseProvidersLock(rawArgs) + + // Instantiate the view, even if there are flag errors + view := views.NewProvidersLock(c.View) + + if diags.HasErrors() { + view.Diagnostics(diags) + view.HelpPrompt() return 1 } - var diags tfdiags.Diagnostics - - if fsMirrorDir != "" && netMirrorURL != "" { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid installation method options", - "The -fs-mirror and -net-mirror command line options are mutually-exclusive.", - )) - c.showDiagnostics(diags) - return 1 - } - - providerStrs := cmdFlags.Args() - var platforms []getproviders.Platform - if len(optPlatforms) == 0 { + if len(args.Platforms) == 0 { platforms = []getproviders.Platform{getproviders.CurrentPlatform} } else { - platforms = make([]getproviders.Platform, 0, len(optPlatforms)) - for _, platformStr := range optPlatforms { + platforms = make([]getproviders.Platform, 0, len(args.Platforms)) + for _, platformStr := range args.Platforms { platform, err := getproviders.ParsePlatform(platformStr) if err != nil { diags = diags.Append(tfdiags.Sourceless( @@ -104,17 +93,17 @@ func (c *ProvidersLockCommand) Run(args []string) int { // against the upstream checksums. var source getproviders.Source switch { - case fsMirrorDir != "": - source = getproviders.NewFilesystemMirrorSource(fsMirrorDir) - case netMirrorURL != "": - u, err := url.Parse(netMirrorURL) + case args.FSMirrorDir != "": + source = getproviders.NewFilesystemMirrorSource(args.FSMirrorDir) + case args.NetMirrorURL != "": + u, err := url.Parse(args.NetMirrorURL) if err != nil || u.Scheme != "https" { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Invalid network mirror URL", "The -net-mirror option requires a valid https: URL as the mirror base URL.", )) - c.showDiagnostics(diags) + view.Diagnostics(diags) return 1 } source = getproviders.NewHTTPMirrorSource(u, c.Services.CredentialsSource()) @@ -125,7 +114,7 @@ func (c *ProvidersLockCommand) Run(args []string) int { source = getproviders.NewRegistrySource(c.Services) } - config, confDiags := c.loadConfigWithTests(".", testDirectory) + config, confDiags := c.loadConfigWithTests(".", args.TestDirectory) diags = diags.Append(confDiags) reqs, hclDiags := config.ProviderRequirements() diags = diags.Append(hclDiags) @@ -134,9 +123,9 @@ func (c *ProvidersLockCommand) Run(args []string) int { // we'll modify "reqs" to only include those. Modifying this is okay // because config.ProviderRequirements generates a fresh map result // for each call. - if len(providerStrs) != 0 { + if len(args.Providers) != 0 { providers := map[addrs.Provider]struct{}{} - for _, raw := range providerStrs { + for _, raw := range args.Providers { addr, moreDiags := addrs.ParseProviderSourceString(raw) diags = diags.Append(moreDiags) if moreDiags.HasErrors() { @@ -176,7 +165,7 @@ func (c *ProvidersLockCommand) Run(args []string) int { // If we have any error diagnostics already then we won't proceed further. if diags.HasErrors() { - c.showDiagnostics(diags) + view.Diagnostics(diags) return 1 } @@ -210,7 +199,7 @@ func (c *ProvidersLockCommand) Run(args []string) int { // Our output from this command is minimal just to show that // we're making progress, rather than just silently hanging. FetchPackageBegin: func(provider addrs.Provider, version getproviders.Version, loc getproviders.PackageLocation) { - c.Ui.Output(fmt.Sprintf("- Fetching %s %s for %s...", provider.ForDisplay(), version, platform)) + view.Fetching(provider, version, platform) if prevVersion, exists := selectedVersions[provider]; exists && version != prevVersion { // This indicates a weird situation where we ended up // selecting a different version for one platform than @@ -240,10 +229,7 @@ func (c *ProvidersLockCommand) Run(args []string) int { if auth != nil && auth.ThirdPartySigned() { keyID = auth.KeyID } - if keyID != "" { - keyID = c.Colorize().Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID)) - } - c.Ui.Output(fmt.Sprintf("- Retrieved %s %s for %s (%s%s)", provider.ForDisplay(), version, platform, auth, keyID)) + view.FetchSuccess(provider, version, platform, auth.String(), keyID) }, } ctx := evts.OnContext(ctx) @@ -253,7 +239,7 @@ func (c *ProvidersLockCommand) Run(args []string) int { // Use global plugin cache for extra speed if present and flag is set globalCacheDir := c.providerGlobalCacheDir() - if *pluginCache && globalCacheDir != nil { + if args.EnablePluginCache && globalCacheDir != nil { installer.SetGlobalCacheDir(globalCacheDir.WithPlatform(platform)) installer.SetGlobalCacheDirMayBreakDependencyLockFile(c.PluginCacheMayBreakDependencyLockFile) } @@ -273,7 +259,7 @@ func (c *ProvidersLockCommand) Run(args []string) int { // If we have any error diagnostics from installation then we won't // proceed to merging and updating the lock file on disk. if diags.HasErrors() { - c.showDiagnostics(diags) + view.Diagnostics(diags) return 1 } @@ -318,24 +304,12 @@ func (c *ProvidersLockCommand) Run(args []string) int { switch providersLockCalculateChangeType(oldLock, platformLock) { case providersLockChangeTypeNewProvider: madeAnyChange = true - c.Ui.Output( - fmt.Sprintf( - "- Obtained %s checksums for %s; This was a new provider and the checksums for this platform are now tracked in the lock file", - provider.ForDisplay(), - platform)) + view.NewProvider(provider, platform) case providersLockChangeTypeNewHashes: madeAnyChange = true - c.Ui.Output( - fmt.Sprintf( - "- Obtained %s checksums for %s; Additional checksums for this platform are now tracked in the lock file", - provider.ForDisplay(), - platform)) + view.NewHashes(provider, platform) case providersLockChangeTypeNoChange: - c.Ui.Output( - fmt.Sprintf( - "- Obtained %s checksums for %s; All checksums for this platform were already tracked in the lock file", - provider.ForDisplay(), - platform)) + view.ExistingHashes(provider, platform) } } newLocks.SetProvider(provider, version, constraints, hashes) @@ -344,17 +318,12 @@ func (c *ProvidersLockCommand) Run(args []string) int { moreDiags = c.replaceLockedDependencies(newLocks) diags = diags.Append(moreDiags) - c.showDiagnostics(diags) + view.Diagnostics(diags) if diags.HasErrors() { return 1 } - if madeAnyChange { - c.Ui.Output(c.Colorize().Color("\n[bold][green]Success![reset] [bold]Terraform has updated the lock file.[reset]")) - c.Ui.Output("\nReview the changes in .terraform.lock.hcl and then commit to your\nversion control system to retain the new checksums.\n") - } else { - c.Ui.Output(c.Colorize().Color("\n[bold][green]Success![reset] [bold]Terraform has validated the lock file and found no need for changes.[reset]")) - } + view.Success(madeAnyChange) return 0 } diff --git a/internal/command/providers_lock_test.go b/internal/command/providers_lock_test.go index 5125c92af8..89c5d712d0 100644 --- a/internal/command/providers_lock_test.go +++ b/internal/command/providers_lock_test.go @@ -27,9 +27,11 @@ func TestProvidersLock(t *testing.T) { t.Chdir(td) ui := new(cli.MockUi) + view, _ := testView(t) c := &ProvidersLockCommand{ Meta: Meta{ - Ui: ui, + Ui: ui, + View: view, }, } code := c.Run([]string{}) @@ -119,9 +121,11 @@ func runProviderLockGenericTest(t *testing.T, testDirectory, expected string, in p := testProvider() ui := new(cli.MockUi) + view, _ := testView(t) c := &ProvidersLockCommand{ Meta: Meta{ Ui: ui, + View: view, testingOverrides: metaOverridesForProvider(p), }, } @@ -146,9 +150,11 @@ func TestProvidersLock_args(t *testing.T) { t.Run("mirror collision", func(t *testing.T) { ui := new(cli.MockUi) + view, done := testView(t) c := &ProvidersLockCommand{ Meta: Meta{ - Ui: ui, + Ui: ui, + View: view, }, } @@ -162,7 +168,7 @@ func TestProvidersLock_args(t *testing.T) { if code != 1 { t.Fatalf("wrong exit code; expected 1, got %d", code) } - output := ui.ErrorWriter.String() + output := done(t).Stderr() if !strings.Contains(output, "The -fs-mirror and -net-mirror command line options are mutually-exclusive.") { t.Fatalf("missing expected error message: %s", output) } @@ -170,9 +176,11 @@ func TestProvidersLock_args(t *testing.T) { t.Run("invalid platform", func(t *testing.T) { ui := new(cli.MockUi) + view, done := testView(t) c := &ProvidersLockCommand{ Meta: Meta{ - Ui: ui, + Ui: ui, + View: view, }, } @@ -183,17 +191,19 @@ func TestProvidersLock_args(t *testing.T) { if code != 1 { t.Fatalf("wrong exit code; expected 1, got %d", code) } - output := ui.ErrorWriter.String() - if !strings.Contains(output, "must be two words separated by an underscore.") { + output := done(t).Stderr() + if !strings.Contains(output, "not a valid target platform") { t.Fatalf("missing expected error message: %s", output) } }) t.Run("invalid provider argument", func(t *testing.T) { ui := new(cli.MockUi) + view, done := testView(t) c := &ProvidersLockCommand{ Meta: Meta{ - Ui: ui, + Ui: ui, + View: view, }, } @@ -204,8 +214,8 @@ func TestProvidersLock_args(t *testing.T) { if code != 1 { t.Fatalf("wrong exit code; expected 1, got %d", code) } - output := ui.ErrorWriter.String() - if !strings.Contains(output, "The provider registry.terraform.io/hashicorp/random is not required by the\ncurrent configuration.") { + output := done(t).Stderr() + if !strings.Contains(output, "is not required by") { t.Fatalf("missing expected error message: %s", output) } }) diff --git a/internal/command/views/providers_lock.go b/internal/command/views/providers_lock.go new file mode 100644 index 0000000000..517840ec84 --- /dev/null +++ b/internal/command/views/providers_lock.go @@ -0,0 +1,100 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package views + +import ( + "fmt" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/getproviders" + "github.com/hashicorp/terraform/internal/tfdiags" +) + +// ProvidersLock is the view interface for the providers lock command. +type ProvidersLock interface { + // Fetching announces that a provider package is being fetched. + Fetching(provider addrs.Provider, version getproviders.Version, platform getproviders.Platform) + + // FetchSuccess announces that a provider package was fetched successfully. + FetchSuccess(provider addrs.Provider, version getproviders.Version, platform getproviders.Platform, auth string, keyID string) + + // NewProvider announces that checksums for a new provider have been added to the lock file. + NewProvider(provider addrs.Provider, platform getproviders.Platform) + + // NewHashes announces that additional checksums have been added for an existing provider. + NewHashes(provider addrs.Provider, platform getproviders.Platform) + + // ExistingHashes announces that all checksums were already tracked. + ExistingHashes(provider addrs.Provider, platform getproviders.Platform) + + // Success announces a successful completion, with whether any changes were made. + Success(madeChanges bool) + + // Diagnostics renders diagnostics. + Diagnostics(diags tfdiags.Diagnostics) + + // HelpPrompt directs the user to the help output. + HelpPrompt() +} + +// NewProvidersLock returns an initialized ProvidersLock implementation. +func NewProvidersLock(view *View) ProvidersLock { + return &ProvidersLockHuman{view: view} +} + +// ProvidersLockHuman is the human-readable implementation of the ProvidersLock view. +type ProvidersLockHuman struct { + view *View +} + +var _ ProvidersLock = (*ProvidersLockHuman)(nil) + +func (v *ProvidersLockHuman) Fetching(provider addrs.Provider, version getproviders.Version, platform getproviders.Platform) { + v.view.streams.Println(fmt.Sprintf("- Fetching %s %s for %s...", provider.ForDisplay(), version, platform)) +} + +func (v *ProvidersLockHuman) FetchSuccess(provider addrs.Provider, version getproviders.Version, platform getproviders.Platform, auth string, keyID string) { + if keyID != "" { + keyID = v.view.colorize.Color(fmt.Sprintf(", key ID [reset][bold]%s[reset]", keyID)) + } + v.view.streams.Println(fmt.Sprintf("- Retrieved %s %s for %s (%s%s)", provider.ForDisplay(), version, platform, auth, keyID)) +} + +func (v *ProvidersLockHuman) NewProvider(provider addrs.Provider, platform getproviders.Platform) { + v.view.streams.Println(fmt.Sprintf( + "- Obtained %s checksums for %s; This was a new provider and the checksums for this platform are now tracked in the lock file", + provider.ForDisplay(), + platform)) +} + +func (v *ProvidersLockHuman) NewHashes(provider addrs.Provider, platform getproviders.Platform) { + v.view.streams.Println(fmt.Sprintf( + "- Obtained %s checksums for %s; Additional checksums for this platform are now tracked in the lock file", + provider.ForDisplay(), + platform)) +} + +func (v *ProvidersLockHuman) ExistingHashes(provider addrs.Provider, platform getproviders.Platform) { + v.view.streams.Println(fmt.Sprintf( + "- Obtained %s checksums for %s; All checksums for this platform were already tracked in the lock file", + provider.ForDisplay(), + platform)) +} + +func (v *ProvidersLockHuman) Success(madeChanges bool) { + if madeChanges { + v.view.streams.Println(v.view.colorize.Color("\n[bold][green]Success![reset] [bold]Terraform has updated the lock file.[reset]")) + v.view.streams.Println("\nReview the changes in .terraform.lock.hcl and then commit to your\nversion control system to retain the new checksums.") + } else { + v.view.streams.Println(v.view.colorize.Color("\n[bold][green]Success![reset] [bold]Terraform has validated the lock file and found no need for changes.[reset]")) + } +} + +func (v *ProvidersLockHuman) Diagnostics(diags tfdiags.Diagnostics) { + v.view.Diagnostics(diags) +} + +func (v *ProvidersLockHuman) HelpPrompt() { + v.view.HelpPrompt("providers lock") +} diff --git a/internal/command/views/providers_lock_test.go b/internal/command/views/providers_lock_test.go new file mode 100644 index 0000000000..093c375491 --- /dev/null +++ b/internal/command/views/providers_lock_test.go @@ -0,0 +1,141 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package views + +import ( + "strings" + "testing" + + "github.com/hashicorp/terraform/internal/addrs" + "github.com/hashicorp/terraform/internal/command/arguments" + "github.com/hashicorp/terraform/internal/getproviders" + "github.com/hashicorp/terraform/internal/terminal" +) + +func TestProvidersLockHuman_Fetching(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + v.(*ProvidersLockHuman).view.Configure(&arguments.View{NoColor: true}) + + provider := addrs.NewDefaultProvider("test") + version := getproviders.MustParseVersion("1.0.0") + platform := getproviders.Platform{OS: "linux", Arch: "amd64"} + + v.Fetching(provider, version, platform) + + got := done(t).Stdout() + want := "- Fetching hashicorp/test 1.0.0 for linux_amd64...\n" + if got != want { + t.Fatalf("wrong output\n got: %q\nwant: %q", got, want) + } +} + +func TestProvidersLockHuman_FetchSuccess(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + v.(*ProvidersLockHuman).view.Configure(&arguments.View{NoColor: true}) + + provider := addrs.NewDefaultProvider("test") + version := getproviders.MustParseVersion("1.0.0") + platform := getproviders.Platform{OS: "linux", Arch: "amd64"} + + v.FetchSuccess(provider, version, platform, "signed", "") + + got := done(t).Stdout() + want := "- Retrieved hashicorp/test 1.0.0 for linux_amd64 (signed)\n" + if got != want { + t.Fatalf("wrong output\n got: %q\nwant: %q", got, want) + } +} + +func TestProvidersLockHuman_FetchSuccess_withKeyID(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + v.(*ProvidersLockHuman).view.Configure(&arguments.View{NoColor: true}) + + provider := addrs.NewDefaultProvider("test") + version := getproviders.MustParseVersion("1.0.0") + platform := getproviders.Platform{OS: "linux", Arch: "amd64"} + + v.FetchSuccess(provider, version, platform, "signed", "ABC123") + + got := done(t).Stdout() + if !strings.Contains(got, "ABC123") { + t.Fatalf("expected output to contain key ID, got: %q", got) + } +} + +func TestProvidersLockHuman_NewProvider(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + + provider := addrs.NewDefaultProvider("test") + platform := getproviders.Platform{OS: "linux", Arch: "amd64"} + + v.NewProvider(provider, platform) + + got := done(t).Stdout() + if !strings.Contains(got, "new provider") { + t.Fatalf("expected output to mention new provider, got: %q", got) + } +} + +func TestProvidersLockHuman_NewHashes(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + + provider := addrs.NewDefaultProvider("test") + platform := getproviders.Platform{OS: "linux", Arch: "amd64"} + + v.NewHashes(provider, platform) + + got := done(t).Stdout() + if !strings.Contains(got, "Additional checksums") { + t.Fatalf("expected output to mention additional checksums, got: %q", got) + } +} + +func TestProvidersLockHuman_ExistingHashes(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + + provider := addrs.NewDefaultProvider("test") + platform := getproviders.Platform{OS: "linux", Arch: "amd64"} + + v.ExistingHashes(provider, platform) + + got := done(t).Stdout() + if !strings.Contains(got, "already tracked") { + t.Fatalf("expected output to mention already tracked, got: %q", got) + } +} + +func TestProvidersLockHuman_Success_withChanges(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + v.(*ProvidersLockHuman).view.Configure(&arguments.View{NoColor: true}) + + v.Success(true) + + got := done(t).Stdout() + if !strings.Contains(got, "updated the lock file") { + t.Fatalf("expected output to mention updated lock file, got: %q", got) + } + if !strings.Contains(got, "Review the changes") { + t.Fatalf("expected output to mention reviewing changes, got: %q", got) + } +} + +func TestProvidersLockHuman_Success_noChanges(t *testing.T) { + streams, done := terminal.StreamsForTesting(t) + v := NewProvidersLock(NewView(streams)) + v.(*ProvidersLockHuman).view.Configure(&arguments.View{NoColor: true}) + + v.Success(false) + + got := done(t).Stdout() + if !strings.Contains(got, "no need for changes") { + t.Fatalf("expected output to mention no need for changes, got: %q", got) + } +}