From 45ab4d6f2feb44de6e4054fb848984d30fbbc363 Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Thu, 7 Jul 2022 13:24:08 +0000 Subject: [PATCH 1/5] backport of commit d08b485e3513a8cbdd40c306fc9bf541d44ec65e --- internal/command/init.go | 22 ++++++++++++++++++++++ internal/command/init_test.go | 12 ++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 415cbe06d96a..b105fa5eb4ba 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -869,6 +869,28 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, return true, false, diags } + // Jump in here and add a warning if any of the providers we've validated + // have only a single checksum. This usually means we didn't manage to + // pull checksums for all architectures remotely and only generated the + // checksum locally for the current architecture. There is a simple fix + // for this so terraform can print out some help. + + var incompleteProviders []string + for provider, locks := range newLocks.AllProviders() { + if len(locks.AllHashes()) == 1 { + incompleteProviders = append(incompleteProviders, provider.ForDisplay()) + } + } + + if len(incompleteProviders) > 0 { + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + "Incomplete validation for providers", + fmt.Sprintf( + "Terraform has only been able to record incomplete checksums for some providers (%s) so you may encounter problems when the lock file is used on machines with a different architecture. This can normally be fixed by running `terraform providers lock -platform=os_arch` for all target architectures.", + strings.Join(incompleteProviders, ", ")))) + } + if previousLocks.Empty() { // A change from empty to non-empty is special because it suggests // we're running "terraform init" for the first time against a diff --git a/internal/command/init_test.go b/internal/command/init_test.go index c97be55faa18..b956f79204fd 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -1644,7 +1644,7 @@ func TestInit_providerSource(t *testing.T) { }) defer close() - ui := new(cli.MockUi) + ui := cli.NewMockUi() view, _ := testView(t) m := Meta{ testingOverrides: metaOverridesForProvider(testProvider()), @@ -1726,13 +1726,17 @@ func TestInit_providerSource(t *testing.T) { }, ), } + if diff := cmp.Diff(gotProviderLocks, wantProviderLocks, depsfile.ProviderLockComparer); diff != "" { t.Errorf("wrong version selections after upgrade\n%s", diff) } - outputStr := ui.OutputWriter.String() - if want := "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(outputStr, want) { - t.Fatalf("unexpected output: %s\nexpected to include %q", outputStr, want) + if got, want := ui.OutputWriter.String(), "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(got, want) { + t.Fatalf("unexpected output: %s\nexpected to include %q", got, want) + } + + if got, want := ui.ErrorWriter.String(), "(hashicorp/test, hashicorp/source, hashicorp/test-beta)"; !strings.Contains(got, want) { + t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got) } } From 46a0c26b79357ff47844cf1aaa084270e737b241 Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Thu, 7 Jul 2022 14:02:00 +0000 Subject: [PATCH 2/5] backport of commit d19d6e14825c4daa805007f1bb6cd6ea9413d0c1 --- internal/command/init.go | 5 +++++ internal/command/init_test.go | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/command/init.go b/internal/command/init.go index b105fa5eb4ba..41faff9df346 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "sort" "strings" "github.com/hashicorp/hcl/v2" @@ -883,6 +884,10 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, } if len(incompleteProviders) > 0 { + // We don't really care about the order here, we just want the output to be deterministic. + sort.Slice(incompleteProviders, func(i, j int) bool { + return incompleteProviders[i] < incompleteProviders[j] + }) diags = diags.Append(tfdiags.Sourceless( tfdiags.Warning, "Incomplete validation for providers", diff --git a/internal/command/init_test.go b/internal/command/init_test.go index b956f79204fd..565ec29a6e95 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -1735,7 +1735,7 @@ func TestInit_providerSource(t *testing.T) { t.Fatalf("unexpected output: %s\nexpected to include %q", got, want) } - if got, want := ui.ErrorWriter.String(), "(hashicorp/test, hashicorp/source, hashicorp/test-beta)"; !strings.Contains(got, want) { + if got, want := ui.ErrorWriter.String(), "(hashicorp/source, hashicorp/test, hashicorp/test-beta)"; !strings.Contains(got, want) { t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got) } } From 8f7d700b23475a2208587f83b75332e74ac281e8 Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Tue, 12 Jul 2022 10:37:48 +0000 Subject: [PATCH 3/5] backport of commit 2695db4a6aba872a1f8d41a4227e3869adef8c34 --- internal/command/init.go | 70 +++++++++------ internal/providercache/installer.go | 38 ++++++-- internal/providercache/installer_events.go | 25 ++++-- .../providercache/installer_events_test.go | 22 ++--- internal/providercache/installer_test.go | 90 +++++++++++++++++++ 5 files changed, 197 insertions(+), 48 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 41faff9df346..2dacf100a1cf 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "reflect" "sort" "strings" @@ -544,6 +545,11 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, ctx, done := c.InterruptibleContext() defer done() + // We want to print out a nice warning if we don't manage to pull + // checksums for all our providers. This is tracked via callbacks + // and incomplete providers are stored here for later analysis. + var incompleteProviders []string + // Because we're currently just streaming a series of events sequentially // into the terminal, we're showing only a subset of the events to keep // things relatively concise. Later it'd be nice to have a progress UI @@ -785,6 +791,41 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, c.Ui.Info(fmt.Sprintf("- Installed %s v%s (%s%s)", provider.ForDisplay(), version, authResult, keyID)) }, + ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) { + // We're going to use this opportunity to track if we have any + // "incomplete" installs of providers. An incomplete install is + // when we are only going to write the local hashes into our lock + // file which means a `terraform init` command will fail in future + // when used on machines of a different architecture. + // + // We want to print a warning about this. + + if len(signedHashes) > 0 { + // If we have any signedHashes hashes then we don't worry - as + // we know we retrieved all available hashes for this version + // anyway. + return + } + + // If local hashes and prior hashes are exactly the same then + // it means we didn't record any signed hashes previously, and + // we know we're not adding any extra in now (because we already + // checked the signedHashes), so that's a problem. + // + // In the actual check here, if we have any priorHashes and those + // hashes are not the same as the local hashes then we're going to + // accept that this provider has been configured correctly. + if len(priorHashes) > 0 && !reflect.DeepEqual(localHashes, priorHashes) { + return + } + + // Now, either signedHashes is empty, or priorHashes is exactly the + // same as our localHashes which means we never retrieved the + // signedHashes previously. + // + // Either way, this is bad. Let's complain/warn. + incompleteProviders = append(incompleteProviders, provider.ForDisplay()) + }, ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) { thirdPartySigned := false for _, authResult := range authResults { @@ -799,18 +840,6 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, "https://www.terraform.io/docs/cli/plugins/signing.html")) } }, - HashPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Failed to validate installed provider", - fmt.Sprintf( - "Validating provider %s v%s failed: %s", - provider.ForDisplay(), - version, - err, - ), - )) - }, } ctx = evts.OnContext(ctx) @@ -870,21 +899,10 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, return true, false, diags } - // Jump in here and add a warning if any of the providers we've validated - // have only a single checksum. This usually means we didn't manage to - // pull checksums for all architectures remotely and only generated the - // checksum locally for the current architecture. There is a simple fix - // for this so terraform can print out some help. - - var incompleteProviders []string - for provider, locks := range newLocks.AllProviders() { - if len(locks.AllHashes()) == 1 { - incompleteProviders = append(incompleteProviders, provider.ForDisplay()) - } - } - + // Jump in here and add a warning if any of the providers are incomplete. if len(incompleteProviders) > 0 { - // We don't really care about the order here, we just want the output to be deterministic. + // We don't really care about the order here, we just want the + // output to be deterministic. sort.Slice(incompleteProviders, func(i, j int) bool { return incompleteProviders[i] < incompleteProviders[j] }) diff --git a/internal/providercache/installer.go b/internal/providercache/installer.go index e753a73c04af..129478868e7c 100644 --- a/internal/providercache/installer.go +++ b/internal/providercache/installer.go @@ -385,14 +385,14 @@ NeedProvider: // calculated from the package we just linked, which allows // the lock file to gradually transition to recording newer hash // schemes when they become available. - var newHashes []getproviders.Hash + var priorHashes []getproviders.Hash if lock != nil && lock.Version() == version { // If the version we're installing is identical to the // one we previously locked then we'll keep all of the // hashes we saved previously and add to it. Otherwise // we'll be starting fresh, because each version has its // own set of packages and thus its own hashes. - newHashes = append(newHashes, preferredHashes...) + priorHashes = append(priorHashes, preferredHashes...) // NOTE: The behavior here is unfortunate when a particular // provider version was already cached on the first time @@ -423,8 +423,17 @@ NeedProvider: // The hashes slice gets deduplicated in the lock file // implementation, so we don't worry about potentially // creating a duplicate here. + var newHashes []getproviders.Hash + newHashes = append(newHashes, priorHashes...) newHashes = append(newHashes, newHash) locks.SetProvider(provider, version, reqs[provider], newHashes) + if cb := evts.ProvidersLockUpdated; cb != nil { + // We want to ensure that newHash and priorHashes are + // sorted. newHash is a single value, so it's definitely + // sorted. priorHashes are pulled from the lock file, so + // are also already sorted. + cb(provider, version, []getproviders.Hash{newHash}, nil, priorHashes) + } if cb := evts.LinkFromCacheSuccess; cb != nil { cb(provider, version, new.PackageDir) @@ -524,14 +533,14 @@ NeedProvider: // The hashes slice gets deduplicated in the lock file // implementation, so we don't worry about potentially // creating duplicates here. - var newHashes []getproviders.Hash + var priorHashes []getproviders.Hash if lock != nil && lock.Version() == version { // If the version we're installing is identical to the // one we previously locked then we'll keep all of the // hashes we saved previously and add to it. Otherwise // we'll be starting fresh, because each version has its // own set of packages and thus its own hashes. - newHashes = append(newHashes, preferredHashes...) + priorHashes = append(priorHashes, preferredHashes...) } newHash, err := new.Hash() if err != nil { @@ -542,15 +551,32 @@ NeedProvider: } continue } - newHashes = append(newHashes, newHash) + + var signedHashes []getproviders.Hash if authResult.SignedByAnyParty() { // We'll trust new hashes from upstream only if they were verified // as signed by a suitable key. Otherwise, we'd record only // a new hash we just calculated ourselves from the bytes on disk, // and so the hashes would cover only the current platform. - newHashes = append(newHashes, meta.AcceptableHashes()...) + signedHashes = append(signedHashes, meta.AcceptableHashes()...) } + + var newHashes []getproviders.Hash + newHashes = append(newHashes, newHash) + newHashes = append(newHashes, priorHashes...) + newHashes = append(newHashes, signedHashes...) + locks.SetProvider(provider, version, reqs[provider], newHashes) + if cb := evts.ProvidersLockUpdated; cb != nil { + // newHash and priorHashes are already sorted. + // But we do need to sort signedHashes so we can reason about it + // sensibly. + sort.Slice(signedHashes, func(i, j int) bool { + return string(signedHashes[i]) < string(signedHashes[j]) + }) + + cb(provider, version, []getproviders.Hash{newHash}, signedHashes, priorHashes) + } if cb := evts.FetchPackageSuccess; cb != nil { cb(provider, version, new.PackageDir, authResult) diff --git a/internal/providercache/installer_events.go b/internal/providercache/installer_events.go index 8c27cc91421f..8fc579af2666 100644 --- a/internal/providercache/installer_events.go +++ b/internal/providercache/installer_events.go @@ -106,15 +106,28 @@ type InstallerEvents struct { FetchPackageSuccess func(provider addrs.Provider, version getproviders.Version, localDir string, authResult *getproviders.PackageAuthenticationResult) FetchPackageFailure func(provider addrs.Provider, version getproviders.Version, err error) + // The ProvidersLockUpdated event is called whenever the lock file will be + // updated. It provides the following information: + // + // - `localHashes`: Hashes computed on the local system by analyzing + // files on disk. + // - `signedHashes`: Hashes signed by the private key that the origin + // registry claims is the owner of this provider. + // - `priorHashes`: Hashes already present in the lock file before we + // made any changes. + // + // The final lock file will be updated with a union of all the provided + // hashes. It not just likely, but expected that there will be duplicates + // shared between all three collections of hashes i.e. the local hash and + // remote hashes could already be in the cached hashes. + // + // In addition, we place a guarantee that the hash slices will be ordered + // in the same manner enforced by the lock file within NewProviderLock. + ProvidersLockUpdated func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) + // The ProvidersFetched event is called after all fetch operations if at // least one provider was fetched successfully. ProvidersFetched func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) - - // HashPackageFailure is called if the installer is unable to determine - // the hash of the contents of an installed package after installation. - // In that case, the selection will not be recorded in the target cache - // directory's lock file. - HashPackageFailure func(provider addrs.Provider, version getproviders.Version, err error) } // OnContext produces a context with all of the same behaviors as the given diff --git a/internal/providercache/installer_events_test.go b/internal/providercache/installer_events_test.go index 8879fb68a734..cde5b7f0abf5 100644 --- a/internal/providercache/installer_events_test.go +++ b/internal/providercache/installer_events_test.go @@ -164,20 +164,22 @@ func installerLogEventsForTests(into chan<- *testInstallerEventLogItem) *Install }{version.String(), err.Error()}, } }, - ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) { + ProvidersLockUpdated: func(provider addrs.Provider, version getproviders.Version, localHashes []getproviders.Hash, signedHashes []getproviders.Hash, priorHashes []getproviders.Hash) { into <- &testInstallerEventLogItem{ - Event: "ProvidersFetched", - Args: authResults, - } - }, - HashPackageFailure: func(provider addrs.Provider, version getproviders.Version, err error) { - into <- &testInstallerEventLogItem{ - Event: "HashPackageFailure", + Event: "ProvidersLockUpdated", Provider: provider, Args: struct { Version string - Error string - }{version.String(), err.Error()}, + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{version.String(), localHashes, signedHashes, priorHashes}, + } + }, + ProvidersFetched: func(authResults map[addrs.Provider]*getproviders.PackageAuthenticationResult) { + into <- &testInstallerEventLogItem{ + Event: "ProvidersFetched", + Args: authResults, } }, } diff --git a/internal/providercache/installer_test.go b/internal/providercache/installer_test.go index 0ca70388833d..2284397047d8 100644 --- a/internal/providercache/installer_test.go +++ b/internal/providercache/installer_test.go @@ -171,6 +171,21 @@ func TestEnsureProviderVersions(t *testing.T) { Location getproviders.PackageLocation }{"2.1.0", beepProviderDir}, }, + { + Event: "ProvidersLockUpdated", + Provider: beepProvider, + Args: struct { + Version string + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{ + "2.1.0", + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + nil, + nil, + }, + }, { Event: "FetchPackageSuccess", Provider: beepProvider, @@ -287,6 +302,21 @@ func TestEnsureProviderVersions(t *testing.T) { Location getproviders.PackageLocation }{"2.1.0", beepProviderDir}, }, + { + Event: "ProvidersLockUpdated", + Provider: beepProvider, + Args: struct { + Version string + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{ + "2.1.0", + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + nil, + nil, + }, + }, { Event: "FetchPackageSuccess", Provider: beepProvider, @@ -411,6 +441,21 @@ func TestEnsureProviderVersions(t *testing.T) { inst.globalCacheDir.BasePath(), }, }, + { + Event: "ProvidersLockUpdated", + Provider: beepProvider, + Args: struct { + Version string + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{ + "2.1.0", + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + nil, + nil, + }, + }, { Event: "LinkFromCacheSuccess", Provider: beepProvider, @@ -535,6 +580,21 @@ func TestEnsureProviderVersions(t *testing.T) { Location getproviders.PackageLocation }{"2.0.0", beepProviderDir}, }, + { + Event: "ProvidersLockUpdated", + Provider: beepProvider, + Args: struct { + Version string + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{ + "2.0.0", + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + nil, + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + }, + }, { Event: "FetchPackageSuccess", Provider: beepProvider, @@ -763,6 +823,21 @@ func TestEnsureProviderVersions(t *testing.T) { Location getproviders.PackageLocation }{"2.1.0", beepProviderDir}, }, + { + Event: "ProvidersLockUpdated", + Provider: beepProvider, + Args: struct { + Version string + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{ + "2.1.0", + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + nil, + nil, + }, + }, { Event: "FetchPackageSuccess", Provider: beepProvider, @@ -929,6 +1004,21 @@ func TestEnsureProviderVersions(t *testing.T) { Location getproviders.PackageLocation }{"1.0.0", beepProviderDir}, }, + { + Event: "ProvidersLockUpdated", + Provider: beepProvider, + Args: struct { + Version string + Local []getproviders.Hash + Signed []getproviders.Hash + Prior []getproviders.Hash + }{ + "1.0.0", + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + nil, + []getproviders.Hash{"h1:2y06Ykj0FRneZfGCTxI9wRTori8iB7ZL5kQ6YyEnh84="}, + }, + }, { Event: "FetchPackageSuccess", Provider: beepProvider, From f782a6b6bdca06c476e8f8b9e81f72cf3095cb1b Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Tue, 12 Jul 2022 11:55:52 +0000 Subject: [PATCH 4/5] backport of commit f9fcbc736f1211297781424e4b32e0aa0b6c616a --- internal/command/init.go | 22 +++++++++++++++++++--- internal/command/init_test.go | 3 +-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/command/init.go b/internal/command/init.go index 2dacf100a1cf..213922902271 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -908,10 +908,11 @@ func (c *InitCommand) getProviders(config *configs.Config, state *states.State, }) diags = diags.Append(tfdiags.Sourceless( tfdiags.Warning, - "Incomplete validation for providers", + incompleteLockFileInformationHeader, fmt.Sprintf( - "Terraform has only been able to record incomplete checksums for some providers (%s) so you may encounter problems when the lock file is used on machines with a different architecture. This can normally be fixed by running `terraform providers lock -platform=os_arch` for all target architectures.", - strings.Join(incompleteProviders, ", ")))) + incompleteLockFileInformationBody, + strings.Join(incompleteProviders, "\n - "), + getproviders.CurrentPlatform.String()))) } if previousLocks.Empty() { @@ -1235,3 +1236,18 @@ Alternatively, upgrade to the latest version of Terraform for compatibility with // No version of the provider is compatible. const errProviderVersionIncompatible = `No compatible versions of provider %s were found.` + +// incompleteLockFileInformationHeader is the summary displayed to users when +// the lock file has only recorded local hashes. +const incompleteLockFileInformationHeader = `Incomplete lock file information for providers` + +// incompleteLockFileInformationBody is the body of text displayed to users when +// the lock file has only recorded local hashes. +const incompleteLockFileInformationBody = `Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers: + - %s + +The current .terraform.lock.hcl file only includes checksums for %s, so Terraform running running on another platform will fail to install these providers. + +To calculate additional checksums for another platform, run: + terraform providers lock -platform=linux_amd64 +(where linux_amd64 is the platform to generate)` diff --git a/internal/command/init_test.go b/internal/command/init_test.go index 565ec29a6e95..2609677bdf1c 100644 --- a/internal/command/init_test.go +++ b/internal/command/init_test.go @@ -1734,8 +1734,7 @@ func TestInit_providerSource(t *testing.T) { if got, want := ui.OutputWriter.String(), "Installed hashicorp/test v1.2.3 (verified checksum)"; !strings.Contains(got, want) { t.Fatalf("unexpected output: %s\nexpected to include %q", got, want) } - - if got, want := ui.ErrorWriter.String(), "(hashicorp/source, hashicorp/test, hashicorp/test-beta)"; !strings.Contains(got, want) { + if got, want := ui.ErrorWriter.String(), "\n - hashicorp/source\n - hashicorp/test\n - hashicorp/test-beta"; !strings.Contains(got, want) { t.Fatalf("wrong error message\nshould contain: %s\ngot:\n%s", want, got) } } From f17f41d05836b7c09b564174146c31ea6b795601 Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Wed, 13 Jul 2022 08:06:17 +0000 Subject: [PATCH 5/5] backport of commit b77fffd89d0119d551ee826e9d75bd6fefbb9a6a --- internal/command/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/command/init.go b/internal/command/init.go index 213922902271..625f5d39fdb8 100644 --- a/internal/command/init.go +++ b/internal/command/init.go @@ -1246,7 +1246,7 @@ const incompleteLockFileInformationHeader = `Incomplete lock file information fo const incompleteLockFileInformationBody = `Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers: - %s -The current .terraform.lock.hcl file only includes checksums for %s, so Terraform running running on another platform will fail to install these providers. +The current .terraform.lock.hcl file only includes checksums for %s, so Terraform running on another platform will fail to install these providers. To calculate additional checksums for another platform, run: terraform providers lock -platform=linux_amd64