Adopt OpenTofu's own "svchost" module

Previously we were using a third-party library, but that doesn't have any
support for passing context.Context through its API and so isn't suitable
for our goals of adding OpenTelemetry tracing for all outgoing network
requests.

We now have our own fork that is updated to use context.Context. It also
has a slightly reduced scope no longer including various details that
are tightly-coupled to our cliconfig mechanism and so better placed in the
main OpenTofu codebase so we can evolve it in future without making
lockstep library releases.

The "registry-address" library also uses svchost and uses some of its types
in its public API, so this also incorporates v2 of that library that is
updated to use our own svchost module.

Unfortunately this commit is a mix of mechanical updates to the new
libraries and some new code dealing with the functionality that is removed
in our fork of svchost. The new code is primarily in the "svcauthconfig"
package, which is similar in purpose "ociauthconfig" but for OpenTofu's
own auth mechanism instead of the OCI Distribution protocol's auth
mechanism.

This includes some additional plumbing of context.Context where it was
possible to do so without broad changes to files that would not otherwise
have been included in this commit, but there are a few leftover spots that
are context.TODO() which we'll address separately in later commits.

This removes the temporary workaround from d079da6e9e, since we are now
able to plumb the OpenTelemetry span tree all the way to the service
discovery requests.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
Martin Atkins
2025-05-14 17:19:22 -07:00
parent 78a325732d
commit d2bef1fd47
62 changed files with 838 additions and 587 deletions

View File

@@ -12,10 +12,10 @@ import (
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/mitchellh/cli"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/command"
@@ -486,7 +486,7 @@ func makeShutdownCh() <-chan struct{} {
return resultCh
}
func credentialsSource(config *cliconfig.Config) (auth.CredentialsSource, error) {
func credentialsSource(config *cliconfig.Config) (svcauth.CredentialsSource, error) {
helperPlugins := pluginDiscovery.FindPlugins("credentials", globalPluginDirs())
return config.CredentialsSource(helperPlugins)
}

View File

@@ -14,7 +14,7 @@ import (
"path/filepath"
"github.com/apparentlymart/go-userdirs/userdirs"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/command/cliconfig"

View File

@@ -13,11 +13,11 @@ import (
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/opentofu/internal/logging"
"github.com/opentofu/opentofu/version"
)
const (
@@ -43,27 +43,15 @@ const (
// object should obtain authentication credentials for service discovery
// requests. Passing a nil credSrc is acceptable and means that all discovery
// requests are to be made anonymously.
func newServiceDiscovery(_ context.Context, credSrc auth.CredentialsSource) *disco.Disco {
services := disco.NewWithCredentialsSource(credSrc)
services.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
func newServiceDiscovery(ctx context.Context, credSrc svcauth.CredentialsSource) *disco.Disco {
// For historical reasons, the registry request retry policy also applies
// to all service discovery requests, which we implement by using transport
// from a HTTP client that is configured for registry client use.
//
// TEMP: The disco.Disco API isn't yet set up to pass through
// context.Context, so we're intentionally ignoring the passed-in ctx
// here to prevent the created client from having OpenTelemetry
// instrumentation added to it. This is just a low-risk temporary trick
// for the v1.10 release; we intend to update disco.Disco to properly
// support context.Context at some point during the v1.11 development
// period. This relies on the fact that httpclient.New uses the context
// we're (indirectly) passing it only to find out if there's an active
// OpenTelemetry span, which should be a valid assumption for as long as
// this very temporary workaround lasts.
client := newRegistryHTTPClient(context.TODO())
services.Transport = client.HTTPClient.Transport
// from a HTTP httpClient that is configured for registry httpClient use.
registryHTTPClient := newRegistryHTTPClient(ctx)
services := disco.New(
disco.WithHTTPClient(registryHTTPClient.HTTPClient),
disco.WithCredentials(credSrc),
)
return services
}

22
go.mod
View File

@@ -58,7 +58,6 @@ require (
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/hcl/v2 v2.20.1
github.com/hashicorp/jsonapi v1.3.1
github.com/hashicorp/terraform-svchost v0.1.1
github.com/jmespath/go-jmespath v0.4.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/lib/pq v1.10.3
@@ -78,7 +77,8 @@ require (
github.com/openbao/openbao/api/v2 v2.1.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633
github.com/opentofu/registry-address/v2 v2.0.0-20250611143131-d0a99bd8acdd
github.com/opentofu/svchost v0.0.0-20250610175836-86c9e5e3d8c8
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db
github.com/pkg/errors v0.9.1
github.com/posener/complete v1.2.3
@@ -99,14 +99,14 @@ require (
go.opentelemetry.io/otel/sdk v1.35.0
go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/mock v0.4.0
golang.org/x/crypto v0.35.0
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/mod v0.21.0
golang.org/x/net v0.36.0
golang.org/x/oauth2 v0.16.0
golang.org/x/sys v0.30.0
golang.org/x/term v0.29.0
golang.org/x/text v0.22.0
golang.org/x/net v0.40.0
golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
golang.org/x/text v0.25.0
golang.org/x/tools v0.25.0
google.golang.org/api v0.155.0
google.golang.org/grpc v1.62.1
@@ -122,8 +122,7 @@ require (
require (
cloud.google.com/go v0.112.0 // indirect
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/iam v1.1.5 // indirect
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@@ -258,9 +257,8 @@ require (
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect

45
go.sum
View File

@@ -70,10 +70,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4=
cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
@@ -723,8 +721,6 @@ github.com/hashicorp/serf v0.9.6 h1:uuEX1kLR6aoda1TBttmJQKDLZE1Ob7KN0NPdE7EtCDc=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
@@ -927,8 +923,10 @@ github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQ
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opentofu/hcl/v2 v2.20.2-0.20250121132637-504036cd70e7 h1:QHUIrylb/q3pQdQcnAr74cGWsXS1lmA8GqP+RWFMK6U=
github.com/opentofu/hcl/v2 v2.20.2-0.20250121132637-504036cd70e7/go.mod h1:k+HgkLpoWu9OS81sy4j1XKDXaWm/rLysG33v5ibdDnc=
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633 h1:81TBkM/XGIFlVvyabp0CJl00UHeVUiQjz0fddLMi848=
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633/go.mod h1:HzQhpVo/NJnGmN+7FPECCVCA5ijU7AUcvf39enBKYOc=
github.com/opentofu/registry-address/v2 v2.0.0-20250611143131-d0a99bd8acdd h1:YAAnzmyOoMvm5SuGXL4hhlfBgqz92XDfORGPV3kmQFc=
github.com/opentofu/registry-address/v2 v2.0.0-20250611143131-d0a99bd8acdd/go.mod h1:7M92SvuJm1WBriIpa4j0XmruU9pxkgPXmRdc6FfAvAk=
github.com/opentofu/svchost v0.0.0-20250610175836-86c9e5e3d8c8 h1:J3pmsVB+nGdfNp5HWdEAC96asYgc7S6J724ICrYDCTk=
github.com/opentofu/svchost v0.0.0-20250610175836-86c9e5e3d8c8/go.mod h1:0kKTcD9hUrbAz41GWp8USa/+OuI8QKirU3qdCWNa3jI=
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU=
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -1136,8 +1134,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1246,8 +1244,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA=
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1273,8 +1271,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1290,8 +1288,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1395,8 +1393,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1407,8 +1405,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1419,14 +1417,13 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1560,8 +1557,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

View File

@@ -6,7 +6,7 @@
package addrs
import (
tfaddr "github.com/opentofu/registry-address"
regaddr "github.com/opentofu/registry-address/v2"
)
// A ModulePackage represents a physical location where OpenTofu can retrieve
@@ -48,4 +48,4 @@ func (p ModulePackage) String() string {
// registry in order to find a real module package address. These being
// distinct is intended to help future maintainers more easily follow the
// series of steps in the module installer, with the help of the type checker.
type ModuleRegistryPackage = tfaddr.ModulePackage
type ModuleRegistryPackage = regaddr.ModulePackage

View File

@@ -11,7 +11,7 @@ import (
"strings"
"github.com/opentofu/opentofu/internal/getmodules"
tfaddr "github.com/opentofu/registry-address"
regaddr "github.com/opentofu/registry-address/v2"
)
// ModuleSource is the general type for all three of the possible module source
@@ -201,11 +201,11 @@ func (s ModuleSourceLocal) ForDisplay() string {
// combination of a ModuleSourceRegistry and a module version number into
// a concrete ModuleSourceRemote that OpenTofu will then download and
// install.
type ModuleSourceRegistry tfaddr.Module
type ModuleSourceRegistry regaddr.Module
// DefaultModuleRegistryHost is the hostname used for registry-based module
// source addresses that do not have an explicit hostname.
const DefaultModuleRegistryHost = tfaddr.DefaultModuleRegistryHost
const DefaultModuleRegistryHost = regaddr.DefaultModuleRegistryHost
// ParseModuleSourceRegistry is a variant of ParseModuleSource which only
// accepts module registry addresses, and will reject any other address type.
@@ -222,7 +222,7 @@ func ParseModuleSourceRegistry(raw string) (ModuleSource, error) {
return ModuleSourceRegistry{}, fmt.Errorf("can't use local directory %q as a module registry address", raw)
}
src, err := tfaddr.ParseModuleSource(raw)
src, err := regaddr.ParseModuleSource(raw)
if err != nil {
return nil, err
}

View File

@@ -10,7 +10,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
)
func TestParseModuleSource(t *testing.T) {

View File

@@ -7,23 +7,32 @@ package addrs
import (
"github.com/hashicorp/hcl/v2"
svchost "github.com/hashicorp/terraform-svchost"
regaddr "github.com/opentofu/registry-address/v2"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/tfdiags"
tfaddr "github.com/opentofu/registry-address"
)
// Provider encapsulates a single provider type. In the future this will be
// extended to include additional fields including Namespace and SourceHost
type Provider = tfaddr.Provider
type Provider = regaddr.Provider
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
// not have an explicit hostname.
const DefaultProviderRegistryHost = tfaddr.DefaultProviderRegistryHost
const DefaultProviderRegistryHost = regaddr.DefaultProviderRegistryHost
// BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
// namespace. Built-in provider addresses must also have their namespace set
// to BuiltInProviderNamespace in order to be considered as built-in.
const BuiltInProviderHost = tfaddr.BuiltInProviderHost
//
// Since we currently only have one built-in provider and it was inherited
// from OpenTofu's predecessor, we currently exclusively use the "transitional"
// builtin provider host that matches what the predecessor used, thereby
// helping with cross-compatibility. If we introduce any OpenTofu-specific
// built-in providers in future then we should consider using
// [regaddr.BuiltInProviderHost] for those ones instead, since that one
// uses a hostname that belongs to the OpenTofu project.
const BuiltInProviderHost = regaddr.TransitionalBuiltInProviderHost
// BuiltInProviderNamespace is the provider namespace used for "built-in"
// providers. Built-in provider addresses must also have their hostname
@@ -32,14 +41,14 @@ const BuiltInProviderHost = tfaddr.BuiltInProviderHost
// The this namespace is literally named "builtin", in the hope that users
// who see FQNs containing this will be able to infer the way in which they are
// special, even if they haven't encountered the concept formally yet.
const BuiltInProviderNamespace = tfaddr.BuiltInProviderNamespace
const BuiltInProviderNamespace = regaddr.BuiltInProviderNamespace
// LegacyProviderNamespace is the special string used in the Namespace field
// of type Provider to mark a legacy provider address. This special namespace
// value would normally be invalid, and can be used only when the hostname is
// DefaultRegistryHost because that host owns the mapping from legacy name to
// FQN.
const LegacyProviderNamespace = tfaddr.LegacyProviderNamespace
const LegacyProviderNamespace = regaddr.LegacyProviderNamespace
func IsDefaultProvider(addr Provider) bool {
return addr.Hostname == DefaultProviderRegistryHost && addr.Namespace == "hashicorp"
@@ -57,7 +66,7 @@ func IsDefaultProvider(addr Provider) bool {
// When accepting namespace or type values from outside the program, use
// ParseProviderPart first to check that the given value is valid.
func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider {
return tfaddr.NewProvider(hostname, namespace, typeName)
return regaddr.NewProvider(hostname, namespace, typeName)
}
// ImpliedProviderForUnqualifiedType represents the rules for inferring what
@@ -87,7 +96,7 @@ func ImpliedProviderForUnqualifiedType(typeName string) Provider {
// NewDefaultProvider returns the default address of a HashiCorp-maintained,
// Registry-hosted provider.
func NewDefaultProvider(name string) Provider {
return tfaddr.Provider{
return regaddr.Provider{
Type: MustParseProviderPart(name),
Namespace: "hashicorp",
Hostname: DefaultProviderRegistryHost,
@@ -97,7 +106,7 @@ func NewDefaultProvider(name string) Provider {
// NewBuiltInProvider returns the address of a "built-in" provider. See
// the docs for Provider.IsBuiltIn for more information.
func NewBuiltInProvider(name string) Provider {
return tfaddr.Provider{
return regaddr.Provider{
Type: MustParseProviderPart(name),
Namespace: BuiltInProviderNamespace,
Hostname: BuiltInProviderHost,
@@ -127,11 +136,11 @@ func NewLegacyProvider(name string) Provider {
// - name
// - namespace/name
// - hostname/namespace/name
func ParseProviderSourceString(str string) (tfaddr.Provider, tfdiags.Diagnostics) {
func ParseProviderSourceString(str string) (regaddr.Provider, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret, err := tfaddr.ParseProviderSource(str)
if pe, ok := err.(*tfaddr.ParserError); ok {
ret, err := regaddr.ParseProviderSource(str)
if pe, ok := err.(*regaddr.ParserError); ok {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: pe.Summary,
@@ -184,7 +193,7 @@ func MustParseProviderSourceString(str string) Provider {
// It's valid to pass the result of this function as the argument to a
// subsequent call, in which case the result will be identical.
func ParseProviderPart(given string) (string, error) {
return tfaddr.ParseProviderPart(given)
return regaddr.ParseProviderPart(given)
}
// MustParseProviderPart is a wrapper around ParseProviderPart that panics if

View File

@@ -9,7 +9,7 @@ import (
"testing"
"github.com/go-test/deep"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
)
func TestProviderString(t *testing.T) {

View File

@@ -15,8 +15,10 @@ import (
"log"
"os"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/mitchellh/go-homedir"
"github.com/opentofu/svchost"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/command/clistate"
"github.com/opentofu/opentofu/internal/command/views"
@@ -31,7 +33,6 @@ import (
"github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu"
"github.com/zclconf/go-cty/cty"
)
// DefaultStateName is the name of the default, initial state that every

View File

@@ -10,12 +10,10 @@ package init
import (
"sync"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/svchost/disco"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
backendRemote "github.com/opentofu/opentofu/internal/backend/remote"
backendAzure "github.com/opentofu/opentofu/internal/backend/remote-state/azure"
@@ -29,6 +27,8 @@ import (
backendPg "github.com/opentofu/opentofu/internal/backend/remote-state/pg"
backendS3 "github.com/opentofu/opentofu/internal/backend/remote-state/s3"
backendCloud "github.com/opentofu/opentofu/internal/cloud"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// backends is the list of available backends. This is a global variable

View File

@@ -19,13 +19,15 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/httpclient"
@@ -35,8 +37,6 @@ import (
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu"
tfversion "github.com/opentofu/opentofu/version"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
)
const (
@@ -273,15 +273,18 @@ func (b *Remote) Configure(ctx context.Context, obj cty.Value) tfdiags.Diagnosti
// Discover the service URL for this host to confirm that it provides
// a remote backend API and to get the version constraints.
service, constraints, err := b.discover(serviceID)
service, err := b.discover(serviceID)
// First check any constraints we might have received.
if constraints != nil {
diags = diags.Append(b.checkConstraints(constraints))
if diags.HasErrors() {
return diags
}
}
// Historical note: in OpenTofu's predecessor project there was an
// extra step here of checking some metadata returned by the remote
// API describing which versions of the predecessor's CLI it considers
// itself to be compatible with. Since OpenTofu's version numbers have
// little relationship with those of its predecessor, and since this
// API is intended for interacting with the commercial service offered
// by the predecessor's vendor (so highly unlikely to be set with
// OpenTofu's releases in mind) we just skip that here and let the
// subsequent requests fail if the remote API isn't compatible with
// the current implementation.
// When we don't have any constraints errors, also check for discovery
// errors before we continue.
@@ -389,127 +392,24 @@ func (b *Remote) Configure(ctx context.Context, obj cty.Value) tfdiags.Diagnosti
}
// discover the remote backend API service URL and version constraints.
func (b *Remote) discover(serviceID string) (*url.URL, *disco.Constraints, error) {
func (b *Remote) discover(serviceID string) (*url.URL, error) {
hostname, err := svchost.ForComparison(b.hostname)
if err != nil {
return nil, nil, err
return nil, err
}
host, err := b.services.Discover(hostname)
host, err := b.services.Discover(context.TODO(), hostname)
if err != nil {
return nil, nil, err
return nil, err
}
service, err := host.ServiceURL(serviceID)
// Return the error, unless its a disco.ErrVersionNotSupported error.
if _, ok := err.(*disco.ErrVersionNotSupported); !ok && err != nil {
return nil, nil, err
return nil, err
}
// We purposefully ignore the error and return the previous error, as
// checking for version constraints is considered optional.
constraints, _ := host.VersionConstraints(serviceID, "terraform")
return service, constraints, err
}
// checkConstraints checks service version constrains against our own
// version and returns rich and informational diagnostics in case any
// incompatibilities are detected.
func (b *Remote) checkConstraints(c *disco.Constraints) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
if c == nil || c.Minimum == "" || c.Maximum == "" {
return diags
}
// Generate a parsable constraints string.
excluding := ""
if len(c.Excluding) > 0 {
excluding = fmt.Sprintf(", != %s", strings.Join(c.Excluding, ", != "))
}
constStr := fmt.Sprintf(">= %s%s, <= %s", c.Minimum, excluding, c.Maximum)
// Create the constraints to check against.
constraints, err := version.NewConstraint(constStr)
if err != nil {
return diags.Append(checkConstraintsWarning(err))
}
// Create the version to check.
v, err := version.NewVersion(tfversion.Version)
if err != nil {
return diags.Append(checkConstraintsWarning(err))
}
// Return if we satisfy all constraints.
if constraints.Check(v) {
return diags
}
// Find out what action (upgrade/downgrade) we should advice.
minimum, err := version.NewVersion(c.Minimum)
if err != nil {
return diags.Append(checkConstraintsWarning(err))
}
maximum, err := version.NewVersion(c.Maximum)
if err != nil {
return diags.Append(checkConstraintsWarning(err))
}
var excludes []*version.Version
for _, exclude := range c.Excluding {
v, err := version.NewVersion(exclude)
if err != nil {
return diags.Append(checkConstraintsWarning(err))
}
excludes = append(excludes, v)
}
// Sort all the excludes.
sort.Sort(version.Collection(excludes))
var action, toVersion string
switch {
case minimum.GreaterThan(v):
action = "upgrade"
toVersion = ">= " + minimum.String()
case maximum.LessThan(v):
action = "downgrade"
toVersion = "<= " + maximum.String()
case len(excludes) > 0:
// Get the latest excluded version.
action = "upgrade"
toVersion = "> " + excludes[len(excludes)-1].String()
}
switch {
case len(excludes) == 1:
excluding = fmt.Sprintf(", excluding version %s", excludes[0].String())
case len(excludes) > 1:
var vs []string
for _, v := range excludes {
vs = append(vs, v.String())
}
excluding = fmt.Sprintf(", excluding versions %s", strings.Join(vs, ", "))
default:
excluding = ""
}
summary := fmt.Sprintf("Incompatible OpenTofu version v%s", v.String())
details := fmt.Sprintf(
"The configured remote backend is compatible with OpenTofu "+
"versions >= %s, <= %s%s.", c.Minimum, c.Maximum, excluding,
)
if action != "" && toVersion != "" {
summary = fmt.Sprintf("Please %s OpenTofu to %s", action, toVersion)
details += fmt.Sprintf(" Please %s to a supported version and try again.", action)
}
// Return the customized and informational error message.
return diags.Append(tfdiags.Sourceless(tfdiags.Error, summary, details))
return service, nil
}
// token returns the token for this host as configured in the credentials
@@ -520,12 +420,24 @@ func (b *Remote) token() (string, error) {
if err != nil {
return "", err
}
creds, err := b.services.CredentialsForHost(hostname)
creds, err := b.services.CredentialsForHost(context.TODO(), hostname)
if err != nil {
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", b.hostname, err)
return "", nil
}
if creds != nil {
// HostCredentialsWithToken is a variant of [svcauth.HostCredentials]
// that also offers direct access to a stored token. This is a weird
// need that applies only to this legacy cloud backend since it uses
// a client library for a particular vendor's API that isn't designed
// to integrate with svcauth. This is a surgical patch to keep this
// working similarly to how it did in our predecessor project until
// we decide on a more definite future for this backend.
type HostCredentialsWithToken interface {
svcauth.HostCredentials
Token() string
}
if creds, ok := creds.(HostCredentialsWithToken); ok {
return creds.Token(), nil
}
return "", nil

View File

@@ -14,15 +14,14 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/opentofu/opentofu/internal/tfdiags"
tfversion "github.com/opentofu/opentofu/version"
"github.com/zclconf/go-cty/cty"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
)
func TestRemote(t *testing.T) {
@@ -172,84 +171,6 @@ func TestRemote_config(t *testing.T) {
}
}
func TestRemote_versionConstraints(t *testing.T) {
cases := map[string]struct {
config cty.Value
prerelease string
version string
result string
}{
"compatible version": {
config: cty.ObjectVal(map[string]cty.Value{
"hostname": cty.StringVal(mockedBackendHost),
"organization": cty.StringVal("hashicorp"),
"token": cty.NullVal(cty.String),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"prefix": cty.NullVal(cty.String),
}),
}),
version: "0.11.1",
},
"version too old": {
config: cty.ObjectVal(map[string]cty.Value{
"hostname": cty.StringVal(mockedBackendHost),
"organization": cty.StringVal("hashicorp"),
"token": cty.NullVal(cty.String),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"prefix": cty.NullVal(cty.String),
}),
}),
version: "0.0.1",
result: "upgrade OpenTofu to >= 0.1.0",
},
"version too new": {
config: cty.ObjectVal(map[string]cty.Value{
"hostname": cty.StringVal(mockedBackendHost),
"organization": cty.StringVal("hashicorp"),
"token": cty.NullVal(cty.String),
"workspaces": cty.ObjectVal(map[string]cty.Value{
"name": cty.StringVal("prod"),
"prefix": cty.NullVal(cty.String),
}),
}),
version: "10.0.1",
result: "downgrade OpenTofu to <= 10.0.0",
},
}
// Save and restore the actual version.
p := tfversion.Prerelease
v := tfversion.Version
defer func() {
tfversion.Prerelease = p
tfversion.Version = v
}()
for name, tc := range cases {
s := testServer(t)
b := New(testDisco(s), encryption.StateEncryptionDisabled())
// Set the version for this test.
tfversion.Prerelease = tc.prerelease
tfversion.Version = tc.version
// Validate
_, valDiags := b.PrepareConfig(tc.config)
if valDiags.HasErrors() {
t.Fatalf("%s: unexpected validation result: %v", name, valDiags.Err())
}
// Configure
confDiags := b.Configure(t.Context(), tc.config)
if (confDiags.Err() != nil || tc.result != "") &&
(confDiags.Err() == nil || !strings.Contains(confDiags.Err().Error(), tc.result)) {
t.Fatalf("%s: unexpected configure result: %v", name, confDiags.Err())
}
}
}
func TestRemote_localBackend(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
@@ -371,108 +292,6 @@ func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
}
}
func TestRemote_checkConstraints(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
cases := map[string]struct {
constraints *disco.Constraints
prerelease string
version string
result string
}{
"compatible version": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Maximum: "0.11.11",
},
version: "0.11.1",
result: "",
},
"version too old": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Maximum: "0.11.11",
},
version: "0.10.1",
result: "upgrade OpenTofu to >= 0.11.0",
},
"version too new": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Maximum: "0.11.11",
},
version: "0.12.0",
result: "downgrade OpenTofu to <= 0.11.11",
},
"version excluded - ordered": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Excluding: []string{"0.11.7", "0.11.8"},
Maximum: "0.11.11",
},
version: "0.11.7",
result: "upgrade OpenTofu to > 0.11.8",
},
"version excluded - unordered": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Excluding: []string{"0.11.8", "0.11.6"},
Maximum: "0.11.11",
},
version: "0.11.6",
result: "upgrade OpenTofu to > 0.11.8",
},
"list versions": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Maximum: "0.11.11",
},
version: "0.10.1",
result: "versions >= 0.11.0, <= 0.11.11.",
},
"list exclusion": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Excluding: []string{"0.11.6"},
Maximum: "0.11.11",
},
version: "0.11.6",
result: "excluding version 0.11.6.",
},
"list exclusions": {
constraints: &disco.Constraints{
Minimum: "0.11.0",
Excluding: []string{"0.11.8", "0.11.6"},
Maximum: "0.11.11",
},
version: "0.11.6",
result: "excluding versions 0.11.6, 0.11.8.",
},
}
// Save and restore the actual version.
p := tfversion.Prerelease
v := tfversion.Version
defer func() {
tfversion.Prerelease = p
tfversion.Version = v
}()
for name, tc := range cases {
// Set the version for this test.
tfversion.Prerelease = tc.prerelease
tfversion.Version = tc.version
// Check the constraints.
diags := b.checkConstraints(tc.constraints)
if (diags.Err() != nil || tc.result != "") &&
(diags.Err() == nil || !strings.Contains(diags.Err().Error(), tc.result)) {
t.Fatalf("%s: unexpected constraints result: %v", name, diags.Err())
}
}
}
func TestRemote_StateMgr_versionCheck(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()

View File

@@ -16,24 +16,22 @@ import (
"time"
tfe "github.com/hashicorp/go-tfe"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/mitchellh/cli"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
"github.com/opentofu/opentofu/internal/cloud"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states/remote"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu"
"github.com/opentofu/opentofu/version"
"github.com/zclconf/go-cty/cty"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
)
const (
@@ -42,8 +40,8 @@ const (
var (
mockedBackendHost = "app.example.com"
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
svchost.Hostname(mockedBackendHost): {"token": testCred},
credsSrc = svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
svchost.Hostname(mockedBackendHost): svcauth.HostCredentialsToken(testCred),
})
)
@@ -68,10 +66,12 @@ func (m *mockInput) Input(ctx context.Context, opts *tofu.InputOpts) (string, er
}
func testInput(t *testing.T, answers map[string]string) *mockInput {
t.Helper()
return &mockInput{answers: answers}
}
func testBackendDefault(t *testing.T) (*Remote, func()) {
t.Helper()
obj := cty.ObjectVal(map[string]cty.Value{
"hostname": cty.StringVal(mockedBackendHost),
"organization": cty.StringVal("hashicorp"),
@@ -98,6 +98,7 @@ func testBackendNoDefault(t *testing.T) (*Remote, func()) {
}
func testBackendNoOperations(t *testing.T) (*Remote, func()) {
t.Helper()
obj := cty.ObjectVal(map[string]cty.Value{
"hostname": cty.StringVal(mockedBackendHost),
"organization": cty.StringVal("no-operations"),
@@ -111,6 +112,7 @@ func testBackendNoOperations(t *testing.T) (*Remote, func()) {
}
func testRemoteClient(t *testing.T) remote.Client {
t.Helper()
b, bCleanup := testBackendDefault(t)
defer bCleanup()
@@ -123,6 +125,8 @@ func testRemoteClient(t *testing.T) remote.Client {
}
func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
t.Helper()
s := testServer(t)
b := New(testDisco(s), encryption.StateEncryptionDisabled())
@@ -182,6 +186,7 @@ func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
}
func testLocalBackend(t *testing.T, remote *Remote) backend.Enhanced {
t.Helper()
b := backendLocal.NewWithBackend(remote, nil)
// Add a test provider to the local backend.
@@ -205,6 +210,7 @@ func testLocalBackend(t *testing.T, remote *Remote) backend.Enhanced {
// testServer returns a *httptest.Server used for local testing.
func testServer(t *testing.T) *httptest.Server {
t.Helper()
mux := http.NewServeMux()
// Respond to service discovery calls.
@@ -297,8 +303,10 @@ func testDisco(s *httptest.Server) *disco.Disco {
"tfe.v2.1": fmt.Sprintf("%s/api/v2/", s.URL),
"versions.v1": fmt.Sprintf("%s/v1/versions/", s.URL),
}
d := disco.NewWithCredentialsSource(credsSrc)
d.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
d := disco.New(
disco.WithCredentials(credsSrc),
disco.WithHTTPClient(s.Client()),
)
d.ForceHostServices(svchost.Hostname(mockedBackendHost), services)
d.ForceHostServices(svchost.Hostname("localhost"), services)

View File

@@ -20,10 +20,11 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
@@ -490,7 +491,7 @@ func (b *Cloud) discover() (*url.URL, error) {
return nil, err
}
host, err := b.services.Discover(hostname)
host, err := b.services.Discover(context.TODO(), hostname)
if err != nil {
var serviceDiscoErr *disco.ErrServiceDiscoveryNetworkRequest
@@ -520,12 +521,24 @@ func (b *Cloud) cliConfigToken() (string, error) {
if err != nil {
return "", err
}
creds, err := b.services.CredentialsForHost(hostname)
creds, err := b.services.CredentialsForHost(context.TODO(), hostname)
if err != nil {
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", b.hostname, err)
return "", nil
}
if creds != nil {
// HostCredentialsWithToken is a variant of [svcauth.HostCredentials]
// that also offers direct access to a stored token. This is a weird
// need that applies only to this legacy cloud backend since it uses
// a client library for a particular vendor's API that isn't designed
// to integrate with svcauth. This is a surgical patch to keep this
// working similarly to how it did in our predecessor project until
// we decide on a more definite future for this backend.
type HostCredentialsWithToken interface {
svcauth.HostCredentials
Token() string
}
if creds, ok := creds.(HostCredentialsWithToken); ok {
return creds.Token(), nil
}
return "", nil

View File

@@ -21,26 +21,23 @@ import (
"time"
tfe "github.com/hashicorp/go-tfe"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/backend"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/states/statefile"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu"
"github.com/opentofu/opentofu/version"
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
)
const (
@@ -49,8 +46,8 @@ const (
var (
tfeHost = "app.terraform.io"
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
svchost.Hostname(tfeHost): {"token": testCred},
credsSrc = svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
svchost.Hostname(tfeHost): svcauth.HostCredentialsToken("testCred"),
})
testBackendSingleWorkspaceName = "app-prod"
defaultTFCPing = map[string]func(http.ResponseWriter, *http.Request){
@@ -598,8 +595,10 @@ func testDisco(s *httptest.Server) *disco.Disco {
services := map[string]interface{}{
"tfe.v2": fmt.Sprintf("%s/api/v2/", s.URL),
}
d := disco.NewWithCredentialsSource(credsSrc)
d.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
d := disco.New(
disco.WithCredentials(credsSrc),
disco.WithHTTPClient(s.Client()),
)
d.ForceHostServices(svchost.Hostname(tfeHost), services)
d.ForceHostServices(svchost.Hostname("localhost"), services)

View File

@@ -24,7 +24,7 @@ import (
"strings"
"github.com/hashicorp/hcl"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/tfdiags"
)

View File

@@ -7,6 +7,7 @@ package cliconfig
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
@@ -14,12 +15,12 @@ import (
"path/filepath"
"strings"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
pluginDiscovery "github.com/opentofu/opentofu/internal/plugin/discovery"
"github.com/opentofu/opentofu/internal/replacefile"
@@ -47,7 +48,7 @@ func (c *Config) CredentialsSource(helperPlugins pluginDiscovery.PluginMetaSet)
return nil, fmt.Errorf("can't locate credentials file: %w", err)
}
var helper svcauth.CredentialsSource
var helper svcauth.CredentialsStore
var helperType string
for givenType, givenConfig := range c.CredentialsHelpers {
available := helperPlugins.WithName(givenType)
@@ -58,8 +59,8 @@ func (c *Config) CredentialsSource(helperPlugins pluginDiscovery.PluginMetaSet)
selected := available.Newest()
helperSource := svcauth.HelperProgramCredentialsSource(selected.Path, givenConfig.Args...)
helper = svcauth.CachingCredentialsSource(helperSource) // cached because external operation may be slow/expensive
helperSource := svcauthconfig.NewHelperProgramCredentialsStore(selected.Path, givenConfig.Args...)
helper = svcauth.CachingCredentialsStore(helperSource)
helperType = givenType
// There should only be zero or one "credentials_helper" blocks. We
@@ -85,7 +86,7 @@ func EmptyCredentialsSourceForTests(credentialsFilePath string) *CredentialsSour
// credentialsSource is an internal factory for the credentials source which
// allows overriding the credentials file path, which allows setting it to
// a temporary file location when testing.
func (c *Config) credentialsSource(helperType string, helper svcauth.CredentialsSource, credentialsFilePath string) *CredentialsSource {
func (c *Config) credentialsSource(helperType string, helper svcauth.CredentialsStore, credentialsFilePath string) *CredentialsSource {
configured := map[svchost.Hostname]cty.Value{}
for userHost, creds := range c.Credentials {
host, err := svchost.ForComparison(userHost)
@@ -221,7 +222,7 @@ type CredentialsSource struct {
// hostnames not explicitly represented in "configured". Any writes to
// the credentials store will also be sent to a configured helper instead
// of the credentials.tfrc.json file.
helper svcauth.CredentialsSource
helper svcauth.CredentialsStore
// helperType is the name of the type of credentials helper that is
// referenced in "helper", or the empty string if "helper" is nil.
@@ -231,7 +232,7 @@ type CredentialsSource struct {
// Assertion that credentialsSource implements CredentialsSource
var _ svcauth.CredentialsSource = (*CredentialsSource)(nil)
func (s *CredentialsSource) ForHost(host svchost.Hostname) (svcauth.HostCredentials, error) {
func (s *CredentialsSource) ForHost(ctx context.Context, host svchost.Hostname) (svcauth.HostCredentials, error) {
// The first order of precedence for credentials is a host-specific environment variable
if envCreds := hostCredentialsFromEnv(host); envCreds != nil {
return envCreds, nil
@@ -240,23 +241,23 @@ func (s *CredentialsSource) ForHost(host svchost.Hostname) (svcauth.HostCredenti
// Then, any credentials block present in the CLI config
v, ok := s.configured[host]
if ok {
return svcauth.HostCredentialsFromObject(v), nil
return svcauthconfig.HostCredentialsFromObject(v), nil
}
// And finally, the credentials helper
if s.helper != nil {
return s.helper.ForHost(host)
return s.helper.ForHost(ctx, host)
}
return nil, nil
}
func (s *CredentialsSource) StoreForHost(host svchost.Hostname, credentials svcauth.HostCredentialsWritable) error {
return s.updateHostCredentials(host, credentials)
func (s *CredentialsSource) StoreForHost(ctx context.Context, host svchost.Hostname, credentials svcauth.NewHostCredentials) error {
return s.updateHostCredentials(ctx, host, credentials)
}
func (s *CredentialsSource) ForgetForHost(host svchost.Hostname) error {
return s.updateHostCredentials(host, nil)
func (s *CredentialsSource) ForgetForHost(ctx context.Context, host svchost.Hostname) error {
return s.updateHostCredentials(ctx, host, nil)
}
// HostCredentialsLocation returns a value indicating what type of storage is
@@ -297,7 +298,7 @@ func (s *CredentialsSource) CredentialsHelperType() string {
return s.helperType
}
func (s *CredentialsSource) updateHostCredentials(host svchost.Hostname, new svcauth.HostCredentialsWritable) error {
func (s *CredentialsSource) updateHostCredentials(ctx context.Context, host svchost.Hostname, new svcauth.NewHostCredentials) error {
switch loc := s.HostCredentialsLocation(host); loc {
case CredentialsInOtherFile:
return ErrUnwritableHostCredentials(host)
@@ -311,16 +312,16 @@ func (s *CredentialsSource) updateHostCredentials(host svchost.Hostname, new svc
case CredentialsViaHelper:
// Delegate entirely to the helper, then.
if new == nil {
return s.helper.ForgetForHost(host)
return s.helper.ForgetForHost(ctx, host)
}
return s.helper.StoreForHost(host, new)
return s.helper.StoreForHost(ctx, host, new)
default:
// Should never happen because the above cases are exhaustive
return fmt.Errorf("invalid credentials location %#v", loc)
}
}
func (s *CredentialsSource) updateLocalHostCredentials(host svchost.Hostname, new svcauth.HostCredentialsWritable) error {
func (s *CredentialsSource) updateLocalHostCredentials(host svchost.Hostname, new svcauth.NewHostCredentials) error {
// This function updates the local credentials file in particular,
// regardless of whether a credentials helper is active. It should be
// called only indirectly via updateHostCredentials.

View File

@@ -6,15 +6,18 @@
package cliconfig
import (
"context"
"fmt"
"net/http"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
)
func TestCredentialsForHost(t *testing.T) {
@@ -32,17 +35,15 @@ func TestCredentialsForHost(t *testing.T) {
// a credentials helper program, since we're only testing the logic
// for choosing when to delegate to the helper here. The logic for
// interacting with a helper program is tested in the svcauth package.
helper: svcauth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
"from-helper.example.com": {
"token": "from-helper",
},
helper: readOnlyCredentialsStore{
svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
"from-helper.example.com": svcauth.HostCredentialsToken("from-helper"),
// This should be shadowed by the "configured" entry with the same
// hostname above.
"configured.example.com": {
"token": "incorrectly-from-helper",
},
"configured.example.com": svcauth.HostCredentialsToken("incorrectly-from-helper"),
}),
},
helperType: "fake",
}
@@ -62,7 +63,7 @@ func TestCredentialsForHost(t *testing.T) {
}
t.Run("configured", func(t *testing.T) {
creds, err := credSrc.ForHost(svchost.Hostname("configured.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("configured.example.com"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -71,7 +72,7 @@ func TestCredentialsForHost(t *testing.T) {
}
})
t.Run("from helper", func(t *testing.T) {
creds, err := credSrc.ForHost(svchost.Hostname("from-helper.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("from-helper.example.com"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -80,7 +81,7 @@ func TestCredentialsForHost(t *testing.T) {
}
})
t.Run("not available", func(t *testing.T) {
creds, err := credSrc.ForHost(svchost.Hostname("unavailable.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("unavailable.example.com"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -94,7 +95,7 @@ func TestCredentialsForHost(t *testing.T) {
expectedToken := "configured-by-env"
t.Setenv(envName, expectedToken)
creds, err := credSrc.ForHost(svchost.Hostname("configured.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("configured.example.com"))
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -103,7 +104,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Fatal("no credentials found")
}
if got := creds.Token(); got != expectedToken {
if got := svcauthconfig.HostCredentialsBearerToken(t, creds); got != expectedToken {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
}
})
@@ -115,7 +116,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Setenv(envName, expectedToken)
hostname, _ := svchost.ForComparison("env.ドメイン名例.com")
creds, err := credSrc.ForHost(hostname)
creds, err := credSrc.ForHost(t.Context(), hostname)
if err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -125,7 +126,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Fatal("no credentials found")
}
if got := creds.Token(); got != expectedToken {
if got := svcauthconfig.HostCredentialsBearerToken(t, creds); got != expectedToken {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
}
})
@@ -137,7 +138,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Setenv(envName, expectedToken)
hostname, _ := svchost.ForComparison("env.café.fr")
creds, err := credSrc.ForHost(hostname)
creds, err := credSrc.ForHost(t.Context(), hostname)
if err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -147,7 +148,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Fatal("no credentials found")
}
if got := creds.Token(); got != expectedToken {
if got := svcauthconfig.HostCredentialsBearerToken(t, creds); got != expectedToken {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
}
})
@@ -159,7 +160,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Setenv(envName, expectedToken)
hostname, _ := svchost.ForComparison("configured.example.com")
creds, err := credSrc.ForHost(hostname)
creds, err := credSrc.ForHost(t.Context(), hostname)
if err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -169,7 +170,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Fatal("no credentials found")
}
if got := creds.Token(); got != expectedToken {
if got := svcauthconfig.HostCredentialsBearerToken(t, creds); got != expectedToken {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
}
})
@@ -181,7 +182,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Setenv(envName, expectedToken)
hostname, _ := svchost.ForComparison("configureduppercase.example.com")
creds, err := credSrc.ForHost(hostname)
creds, err := credSrc.ForHost(t.Context(), hostname)
if err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -191,7 +192,7 @@ func TestCredentialsForHost(t *testing.T) {
t.Fatal("no credentials found")
}
if got := creds.Token(); got != expectedToken {
if got := svcauthconfig.HostCredentialsBearerToken(t, creds); got != expectedToken {
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
}
})
@@ -239,6 +240,7 @@ func TestCredentialsStoreForget(t *testing.T) {
// Otherwise downstream tests might fail in confusing ways.
{
err := credSrc.StoreForHost(
t.Context(),
svchost.Hostname("manually-configured.example.com"),
svcauth.HostCredentialsToken("not-manually-configured"),
)
@@ -251,6 +253,7 @@ func TestCredentialsStoreForget(t *testing.T) {
}
{
err := credSrc.ForgetForHost(
t.Context(),
svchost.Hostname("manually-configured.example.com"),
)
if err == nil {
@@ -264,6 +267,7 @@ func TestCredentialsStoreForget(t *testing.T) {
// We don't have a credentials file at all yet, so this first call
// must create it.
err := credSrc.StoreForHost(
t.Context(),
svchost.Hostname("stored-locally.example.com"),
svcauth.HostCredentialsToken("stored-locally"),
)
@@ -271,7 +275,7 @@ func TestCredentialsStoreForget(t *testing.T) {
t.Fatalf("unexpected error storing locally: %s", err)
}
creds, err := credSrc.ForHost(svchost.Hostname("stored-locally.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("stored-locally.example.com"))
if err != nil {
t.Fatalf("failed to read back stored-locally credentials: %s", err)
}
@@ -303,6 +307,7 @@ func TestCredentialsStoreForget(t *testing.T) {
)
{
err := credSrc.StoreForHost(
t.Context(),
svchost.Hostname("manually-configured.example.com"),
svcauth.HostCredentialsToken("not-manually-configured"),
)
@@ -312,6 +317,7 @@ func TestCredentialsStoreForget(t *testing.T) {
}
{
err := credSrc.StoreForHost(
t.Context(),
svchost.Hostname("stored-in-helper.example.com"),
svcauth.HostCredentialsToken("stored-in-helper"),
)
@@ -319,7 +325,7 @@ func TestCredentialsStoreForget(t *testing.T) {
t.Fatalf("unexpected error storing in helper: %s", err)
}
creds, err := credSrc.ForHost(svchost.Hostname("stored-in-helper.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("stored-in-helper.example.com"))
if err != nil {
t.Fatalf("failed to read back stored-in-helper credentials: %s", err)
}
@@ -341,6 +347,7 @@ func TestCredentialsStoreForget(t *testing.T) {
// Because stored-locally is already in the credentials file, a new
// store should be sent there rather than to the credentials helper.
err := credSrc.StoreForHost(
t.Context(),
svchost.Hostname("stored-locally.example.com"),
svcauth.HostCredentialsToken("stored-locally-again"),
)
@@ -348,7 +355,7 @@ func TestCredentialsStoreForget(t *testing.T) {
t.Fatalf("unexpected error storing locally again: %s", err)
}
creds, err := credSrc.ForHost(svchost.Hostname("stored-locally.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("stored-locally.example.com"))
if err != nil {
t.Fatalf("failed to read back stored-locally credentials: %s", err)
}
@@ -361,13 +368,14 @@ func TestCredentialsStoreForget(t *testing.T) {
// Forgetting a host already in the credentials file should remove it
// from the credentials file, not from the helper.
err := credSrc.ForgetForHost(
t.Context(),
svchost.Hostname("stored-locally.example.com"),
)
if err != nil {
t.Fatalf("unexpected error forgetting locally: %s", err)
}
creds, err := credSrc.ForHost(svchost.Hostname("stored-locally.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("stored-locally.example.com"))
if err != nil {
t.Fatalf("failed to read back stored-locally credentials: %s", err)
}
@@ -385,13 +393,14 @@ func TestCredentialsStoreForget(t *testing.T) {
}
{
err := credSrc.ForgetForHost(
t.Context(),
svchost.Hostname("stored-in-helper.example.com"),
)
if err != nil {
t.Fatalf("unexpected error forgetting in helper: %s", err)
}
creds, err := credSrc.ForHost(svchost.Hostname("stored-in-helper.example.com"))
creds, err := credSrc.ForHost(t.Context(), svchost.Hostname("stored-in-helper.example.com"))
if err != nil {
t.Fatalf("failed to read back stored-in-helper credentials: %s", err)
}
@@ -434,15 +443,15 @@ type mockCredentialsHelper struct {
// Assertion that mockCredentialsHelper implements svcauth.CredentialsSource
var _ svcauth.CredentialsSource = (*mockCredentialsHelper)(nil)
func (s *mockCredentialsHelper) ForHost(hostname svchost.Hostname) (svcauth.HostCredentials, error) {
func (s *mockCredentialsHelper) ForHost(_ context.Context, hostname svchost.Hostname) (svcauth.HostCredentials, error) {
v, ok := s.current[hostname]
if !ok {
return nil, nil
}
return svcauth.HostCredentialsFromObject(v), nil
return svcauthconfig.HostCredentialsFromObject(v), nil
}
func (s *mockCredentialsHelper) StoreForHost(hostname svchost.Hostname, new svcauth.HostCredentialsWritable) error {
func (s *mockCredentialsHelper) StoreForHost(_ context.Context, hostname svchost.Hostname, new svcauth.NewHostCredentials) error {
s.log = append(s.log, mockCredentialsHelperChange{
Host: hostname,
Action: "store",
@@ -451,7 +460,7 @@ func (s *mockCredentialsHelper) StoreForHost(hostname svchost.Hostname, new svca
return nil
}
func (s *mockCredentialsHelper) ForgetForHost(hostname svchost.Hostname) error {
func (s *mockCredentialsHelper) ForgetForHost(_ context.Context, hostname svchost.Hostname) error {
s.log = append(s.log, mockCredentialsHelperChange{
Host: hostname,
Action: "forget",
@@ -459,3 +468,29 @@ func (s *mockCredentialsHelper) ForgetForHost(hostname svchost.Hostname) error {
delete(s.current, hostname)
return nil
}
// readOnlyCredentialsStore is an adapter to make a [svcauth.CredentialsSource]
// appear to implement [svcauth.CredentialsStore] for testing purposes.
//
// It only statically implements that larger set of methods. If any of the
// store-specific methods are called they will immediately return an error.
type readOnlyCredentialsStore struct {
source svcauth.CredentialsSource
}
var _ svcauth.CredentialsStore = readOnlyCredentialsStore{}
// ForHost implements svcauth.CredentialsStore.
func (r readOnlyCredentialsStore) ForHost(ctx context.Context, host svchost.Hostname) (svcauth.HostCredentials, error) {
return r.source.ForHost(ctx, host)
}
// ForgetForHost implements svcauth.CredentialsStore.
func (r readOnlyCredentialsStore) ForgetForHost(ctx context.Context, host svchost.Hostname) error {
return fmt.Errorf("this credentials store is actually read-only, despite implementing svcauth.CredentialsSource")
}
// StoreForHost implements svcauth.CredentialsStore.
func (r readOnlyCredentialsStore) StoreForHost(ctx context.Context, host svchost.Hostname, credentials svcauth.NewHostCredentials) error {
return fmt.Errorf("this credentials store is actually read-only, despite implementing svcauth.CredentialsSource")
}

View File

@@ -14,7 +14,7 @@ import (
hcltoken "github.com/hashicorp/hcl/hcl/token"
hcl2 "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert"

View File

@@ -11,7 +11,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/getproviders"
)

View File

@@ -0,0 +1,54 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package svcauthconfig
import (
"github.com/opentofu/svchost/svcauth"
"github.com/zclconf/go-cty/cty"
)
// HostCredentialsFromMap converts a map of key-value pairs from a credentials
// definition provided by the user (e.g. in a config file, or via a credentials
// helper) into a HostCredentials object if possible, or returns nil if
// no credentials could be extracted from the map.
//
// This function ignores map keys it is unfamiliar with, to allow for future
// expansion of the credentials map format for new credential types.
func HostCredentialsFromMap(m map[string]any) svcauth.HostCredentials {
if m == nil {
return nil
}
if token, ok := m["token"].(string); ok {
return svcauth.HostCredentialsToken(token)
}
return nil
}
// HostCredentialsFromObject converts a cty.Value of an object type into a
// HostCredentials object if possible, or returns nil if no credentials could
// be extracted from the map.
//
// This function ignores object attributes it is unfamiliar with, to allow for
// future expansion of the credentials object structure for new credential types.
//
// If the given value is not of an object type, this function will panic.
func HostCredentialsFromObject(obj cty.Value) svcauth.HostCredentials {
if !obj.Type().HasAttribute("token") {
return nil
}
tokenV := obj.GetAttr("token")
if tokenV.IsNull() || !tokenV.IsKnown() {
return nil
}
if !cty.String.Equals(tokenV.Type()) {
// Weird, but maybe some future version accepts an object here for some
// reason, so we'll tolerate that for forward-compatibility.
return nil
}
return svcauth.HostCredentialsToken(tokenV.AsString())
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Package svcauthconfig contains some helper functions and types to support
// the cliconfig package's use of [github.com/opentofu/svchost/svcauth],
// which is our mechanism for representing the policy for authenticating to
// OpenTofu-native services such as implementations OpenTofu's provider registry
// protocol.
//
// The intended separation of concerns is that the upstream library provides
// the "vocabulary types" that other parts of OpenTofu interact with, while
// this package contains concrete implementations of those types and helpers
// to assist in constructing them which should be used _only_ by package
// cliconfig to satisfy the upstream interfaces. This separation means that
// we can evolve the implementation details of service authentication by
// changes only in this repository, thereby avoiding the complexity of always
// having to update both codebases in lockstep.
package svcauthconfig

View File

@@ -0,0 +1,153 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package svcauthconfig
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os/exec"
"path/filepath"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
ctyjson "github.com/zclconf/go-cty/cty/json"
)
type helperProgramCredentialsSource struct {
executable string
args []string
}
// NewHelperProgramCredentialsStore returns a [svcauth.CredentialsStore] that
// runs the given program with the given arguments in order to obtain or store
// credentials.
//
// The given executable path must be an absolute path; it is the caller's
// responsibility to validate and process a relative path or other input
// provided by an end-user. If the given path is not absolute, this
// function will panic.
//
// When credentials are requested, the program will be run in a child process
// with the given arguments along with two additional arguments added to the
// end of the list: the literal string "get", followed by the requested
// hostname in ASCII compatibility form (punycode form).
func NewHelperProgramCredentialsStore(executable string, args ...string) svcauth.CredentialsStore {
if !filepath.IsAbs(executable) {
panic("HelperProgramCredentialsStore requires absolute path to executable")
}
fullArgs := make([]string, len(args)+1)
fullArgs[0] = executable
copy(fullArgs[1:], args)
return &helperProgramCredentialsSource{
executable: executable,
args: fullArgs,
}
}
func (s *helperProgramCredentialsSource) ForHost(ctx context.Context, host svchost.Hostname) (svcauth.HostCredentials, error) {
args := make([]string, len(s.args), len(s.args)+2)
copy(args, s.args)
args = append(args, "get", string(host))
outBuf := bytes.Buffer{}
errBuf := bytes.Buffer{}
cmd := exec.Cmd{
Path: s.executable,
Args: args,
Stdin: nil,
Stdout: &outBuf,
Stderr: &errBuf,
}
err := cmd.Run()
if _, isExitErr := err.(*exec.ExitError); isExitErr {
errText := errBuf.String()
if errText == "" {
// Shouldn't happen for a well-behaved helper program
return nil, fmt.Errorf("error in %s, but it produced no error message", s.executable)
}
return nil, fmt.Errorf("error in %s: %s", s.executable, errText)
} else if err != nil {
return nil, fmt.Errorf("failed to run %s: %s", s.executable, err)
}
var m map[string]interface{}
err = json.Unmarshal(outBuf.Bytes(), &m)
if err != nil {
return nil, fmt.Errorf("malformed output from %s: %s", s.executable, err)
}
return HostCredentialsFromMap(m), nil
}
func (s *helperProgramCredentialsSource) StoreForHost(ctx context.Context, host svchost.Hostname, credentials svcauth.NewHostCredentials) error {
args := make([]string, len(s.args), len(s.args)+2)
copy(args, s.args)
args = append(args, "store", string(host))
toStore := credentials.ToStore()
toStoreRaw, err := ctyjson.Marshal(toStore, toStore.Type())
if err != nil {
return fmt.Errorf("can't serialize credentials to store: %s", err)
}
inReader := bytes.NewReader(toStoreRaw)
errBuf := bytes.Buffer{}
cmd := exec.Cmd{
Path: s.executable,
Args: args,
Stdin: inReader,
Stderr: &errBuf,
Stdout: nil,
}
err = cmd.Run()
if _, isExitErr := err.(*exec.ExitError); isExitErr {
errText := errBuf.String()
if errText == "" {
// Shouldn't happen for a well-behaved helper program
return fmt.Errorf("error in %s, but it produced no error message", s.executable)
}
return fmt.Errorf("error in %s: %s", s.executable, errText)
} else if err != nil {
return fmt.Errorf("failed to run %s: %s", s.executable, err)
}
return nil
}
func (s *helperProgramCredentialsSource) ForgetForHost(ctx context.Context, host svchost.Hostname) error {
args := make([]string, len(s.args), len(s.args)+2)
copy(args, s.args)
args = append(args, "forget", string(host))
errBuf := bytes.Buffer{}
cmd := exec.Cmd{
Path: s.executable,
Args: args,
Stdin: nil,
Stderr: &errBuf,
Stdout: nil,
}
err := cmd.Run()
if _, isExitErr := err.(*exec.ExitError); isExitErr {
errText := errBuf.String()
if errText == "" {
// Shouldn't happen for a well-behaved helper program
return fmt.Errorf("error in %s, but it produced no error message", s.executable)
}
return fmt.Errorf("error in %s: %s", s.executable, errText)
} else if err != nil {
return fmt.Errorf("failed to run %s: %s", s.executable, err)
}
return nil
}

View File

@@ -0,0 +1,98 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package svcauthconfig
import (
"os"
"path/filepath"
"runtime"
"testing"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
)
func TestHelperProgramCredentialsSource(t *testing.T) {
// The helper script used in this test currently assumes a Unix-like
// environment where scripts are directly executable based on their #!
// line and where bash is available. This is an assumption we inherited
// from our predecessor which we'd like to address someday, but for now
// we'll just skip this test unless we're on Linux or macOS since those
// are the two OSes most commonly used for OpenTofu development where
// we can expect this to work. (Other unixes could potentially work
// but we don't want to maintain a huge list here.)
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
t.Skip("this test only works on Unix-like systems")
}
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
program := filepath.Join(wd, "testdata", "helperprog", "test-helper")
t.Logf("testing with helper at %s", program)
src := NewHelperProgramCredentialsStore(program)
t.Run("happy path", func(t *testing.T) {
creds, err := src.ForHost(t.Context(), svchost.Hostname("example.com"))
if err != nil {
t.Fatal(err)
}
if got, want := HostCredentialsBearerToken(t, creds), "example-token"; got != want {
t.Errorf("wrong token %q; want %q", got, want)
}
})
t.Run("no credentials", func(t *testing.T) {
creds, err := src.ForHost(t.Context(), svchost.Hostname("nothing.example.com"))
if err != nil {
t.Fatal(err)
}
if creds != nil {
t.Errorf("got credentials; want nil")
}
})
t.Run("unsupported credentials type", func(t *testing.T) {
creds, err := src.ForHost(t.Context(), svchost.Hostname("other-cred-type.example.com"))
if err != nil {
t.Fatal(err)
}
if creds != nil {
t.Errorf("got credentials; want nil")
}
})
t.Run("lookup error", func(t *testing.T) {
_, err := src.ForHost(t.Context(), svchost.Hostname("fail.example.com"))
if err == nil {
t.Error("completed successfully; want error")
}
})
t.Run("store happy path", func(t *testing.T) {
err := src.StoreForHost(t.Context(), svchost.Hostname("example.com"), svcauth.HostCredentialsToken("example-token"))
if err != nil {
t.Fatal(err)
}
})
t.Run("store error", func(t *testing.T) {
err := src.StoreForHost(t.Context(), svchost.Hostname("fail.example.com"), svcauth.HostCredentialsToken("example-token"))
if err == nil {
t.Error("completed successfully; want error")
}
})
t.Run("forget happy path", func(t *testing.T) {
err := src.ForgetForHost(t.Context(), svchost.Hostname("example.com"))
if err != nil {
t.Fatal(err)
}
})
t.Run("forget error", func(t *testing.T) {
err := src.ForgetForHost(t.Context(), svchost.Hostname("fail.example.com"))
if err == nil {
t.Error("completed successfully; want error")
}
})
}

View File

@@ -0,0 +1 @@
main

View File

@@ -0,0 +1,72 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package main
import (
"encoding/json"
"fmt"
"io"
"os"
)
// This is a simple program that implements the "helper program" protocol
// for the svchost/auth package for unit testing purposes.
func main() {
args := os.Args
if len(args) < 3 {
die("not enough arguments\n")
}
host := args[2]
switch args[1] {
case "get":
switch host {
case "example.com":
fmt.Print(`{"token":"example-token"}`)
case "other-cred-type.example.com":
fmt.Print(`{"username":"alfred"}`) // unrecognized by main program
case "fail.example.com":
die("failing because you told me to fail\n")
default:
fmt.Print("{}") // no credentials available
}
case "store":
dataSrc, err := io.ReadAll(os.Stdin)
if err != nil {
die("invalid input: %s", err)
}
var data map[string]interface{}
err = json.Unmarshal(dataSrc, &data)
if err != nil {
die("json unmarshal failed")
}
switch host {
case "example.com":
if data["token"] != "example-token" {
die("incorrect token value to store")
}
default:
die("can't store credentials for %s", host)
}
case "forget":
switch host {
case "example.com":
// okay!
default:
die("can't forget credentials for %s", host)
}
default:
die("unknown subcommand %q\n", args[1])
}
}
func die(f string, args ...interface{}) {
fmt.Fprintf(os.Stderr, f, args...)
os.Exit(1)
}

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -eu
cd "$( dirname "${BASH_SOURCE[0]}" )"
[ -x main ] || go build -o main .
exec ./main "$@"

View File

@@ -0,0 +1,57 @@
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package svcauthconfig
import (
"net/http"
"strings"
"testing"
"github.com/opentofu/svchost/svcauth"
)
// The symbols in this file are intended for use in tests only, and are
// not appropriate for use in normal code.
// HostCredentialsBearerToken is a testing helper implementing a little
// abstraction inversion to get the bearer token used by a credentials source
// even though the [svcauth.HostCredentials] API is designed to be generic over
// what kind of credentials it encloses.
//
// This only works for a [svcauth.HostCredentials] implementation whose
// behavior is to add an Authorization header field to the request using
// the "Bearer" scheme, in which case it returns whatever content appears
// after that scheme. HostCredentials implementations that don't match that
// pattern must be tested in a different way.
//
// This helper should not be used in non-test code. The svcauth API
// intentionally encapsulates the details of how credentials are applied to
// a request so that it can potentially be extended in future to support
// other authentication schemes such as HTTP Basic authentication.
func HostCredentialsBearerToken(t testing.TB, creds svcauth.HostCredentials) string {
t.Helper()
fakeReq, err := http.NewRequest("GET", "http://example.com/", nil)
if err != nil {
t.Fatalf("failed to create fake request: %s", err) // should be impossible, since we control all the inputs
}
creds.PrepareRequest(fakeReq)
header := fakeReq.Header
authz := header.Values("authorization")
if len(authz) == 0 {
t.Fatal("the svcauth.HostCredentials implementation did not add an Authorization header field")
}
if len(authz) > 1 {
t.Fatalf("the svcauth.HostCredentials implementation added %d Authorization header fields; want exactly one", len(authz))
}
raw := strings.TrimPrefix(strings.ToLower(authz[0]), "bearer ")
if len(raw) == len(authz[0]) {
t.Fatal("the svchost.HostCredentials implemented added an Authorization header that does not use the Bearer scheme")
}
return strings.TrimSpace(raw)
}

View File

@@ -26,8 +26,8 @@ import (
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"

View File

@@ -14,7 +14,7 @@ import (
"strings"
"github.com/hashicorp/hcl/v2"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/posener/complete"
"github.com/zclconf/go-cty/cty"
otelAttr "go.opentelemetry.io/otel/attribute"

View File

@@ -22,9 +22,9 @@ import (
"strings"
tfe "github.com/hashicorp/go-tfe"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/opentofu/opentofu/internal/command/cliconfig"
"github.com/opentofu/opentofu/internal/httpclient"
@@ -98,7 +98,7 @@ func (c *LoginCommand) Run(args []string) int {
// working as expected. (Perhaps the normalization is part of the cause.)
dispHostname := hostname.ForDisplay()
host, err := c.Services.Discover(hostname)
host, err := c.Services.Discover(ctx, hostname)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@@ -220,7 +220,7 @@ func (c *LoginCommand) Run(args []string) int {
return 1
}
err = creds.StoreForHost(hostname, token)
err = creds.StoreForHost(ctx, hostname, token)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,

View File

@@ -13,16 +13,15 @@ import (
"testing"
"github.com/mitchellh/cli"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/command/cliconfig"
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
oauthserver "github.com/opentofu/opentofu/internal/command/testdata/login-oauth-server"
tfeserver "github.com/opentofu/opentofu/internal/command/testdata/login-tfe-server"
"github.com/opentofu/opentofu/internal/command/webbrowser"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/opentofu/version"
)
func TestLogin(t *testing.T) {
@@ -57,8 +56,10 @@ func TestLogin(t *testing.T) {
}
creds := cliconfig.EmptyCredentialsSourceForTests(filepath.Join(workDir, "credentials.tfrc.json"))
svcs := disco.NewWithCredentialsSource(creds)
svcs.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
svcs := disco.New(
disco.WithCredentials(creds),
disco.WithHTTPClient(httpclient.New(t.Context())),
)
svcs.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{
"login.v1": map[string]interface{}{
@@ -135,11 +136,11 @@ func TestLogin(t *testing.T) {
}
credsSrc := c.Services.CredentialsSource()
creds, err := credsSrc.ForHost(svchost.Hostname(tfeHost))
creds, err := credsSrc.ForHost(t.Context(), svchost.Hostname(tfeHost))
if err != nil {
t.Errorf("failed to retrieve credentials: %s", err)
}
if got, want := creds.Token(), "good-token"; got != want {
if got, want := svcauthconfig.HostCredentialsBearerToken(t, creds), "good-token"; got != want {
t.Errorf("wrong token %q; want %q", got, want)
}
if got, want := ui.OutputWriter.String(), "Welcome to the cloud backend!"; !strings.Contains(got, want) {
@@ -158,11 +159,11 @@ func TestLogin(t *testing.T) {
}
credsSrc := c.Services.CredentialsSource()
creds, err := credsSrc.ForHost(svchost.Hostname("example.com"))
creds, err := credsSrc.ForHost(t.Context(), svchost.Hostname("example.com"))
if err != nil {
t.Errorf("failed to retrieve credentials: %s", err)
}
if got, want := creds.Token(), "good-token"; got != want {
if got, want := svcauthconfig.HostCredentialsBearerToken(t, creds), "good-token"; got != want {
t.Errorf("wrong token %q; want %q", got, want)
}
@@ -173,7 +174,7 @@ func TestLogin(t *testing.T) {
t.Run("example.com results in no scopes", loginTestCase(func(t *testing.T, c *LoginCommand, ui *cli.MockUi) {
host, _ := c.Services.Discover("example.com")
host, _ := c.Services.Discover(t.Context(), "example.com")
client, _ := host.ServiceOAuthClient("login.v1")
if len(client.Scopes) != 0 {
t.Errorf("unexpected scopes %q; expected none", client.Scopes)
@@ -191,13 +192,13 @@ func TestLogin(t *testing.T) {
}
credsSrc := c.Services.CredentialsSource()
creds, err := credsSrc.ForHost(svchost.Hostname("with-scopes.example.com"))
creds, err := credsSrc.ForHost(t.Context(), svchost.Hostname("with-scopes.example.com"))
if err != nil {
t.Errorf("failed to retrieve credentials: %s", err)
}
if got, want := creds.Token(), "good-token"; got != want {
if got, want := svcauthconfig.HostCredentialsBearerToken(t, creds), "good-token"; got != want {
t.Errorf("wrong token %q; want %q", got, want)
}
@@ -208,7 +209,7 @@ func TestLogin(t *testing.T) {
t.Run("with-scopes.example.com results in expected scopes", loginTestCase(func(t *testing.T, c *LoginCommand, ui *cli.MockUi) {
host, _ := c.Services.Discover("with-scopes.example.com")
host, _ := c.Services.Discover(t.Context(), "with-scopes.example.com")
client, _ := host.ServiceOAuthClient("login.v1")
expectedScopes := [2]string{"app1.full_access", "app2.read_only"}
@@ -234,11 +235,11 @@ func TestLogin(t *testing.T) {
}
credsSrc := c.Services.CredentialsSource()
creds, err := credsSrc.ForHost(svchost.Hostname("tfe.acme.com"))
creds, err := credsSrc.ForHost(t.Context(), svchost.Hostname("tfe.acme.com"))
if err != nil {
t.Errorf("failed to retrieve credentials: %s", err)
}
if got, want := creds.Token(), "good-token"; got != want {
if got, want := svcauthconfig.HostCredentialsBearerToken(t, creds), "good-token"; got != want {
t.Errorf("wrong token %q; want %q", got, want)
}
@@ -259,12 +260,12 @@ func TestLogin(t *testing.T) {
}
credsSrc := c.Services.CredentialsSource()
creds, err := credsSrc.ForHost(svchost.Hostname("tfe.acme.com"))
creds, err := credsSrc.ForHost(t.Context(), svchost.Hostname("tfe.acme.com"))
if err != nil {
t.Errorf("failed to retrieve credentials: %s", err)
}
if creds != nil {
t.Errorf("wrong token %q; should have no token", creds.Token())
t.Errorf("wrong token %q; should have no token", svcauthconfig.HostCredentialsBearerToken(t, creds))
}
}, true))

View File

@@ -10,7 +10,7 @@ import (
"path/filepath"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/command/cliconfig"
"github.com/opentofu/opentofu/internal/tfdiags"
@@ -24,6 +24,8 @@ type LogoutCommand struct {
// Run implements cli.Command.
func (c *LogoutCommand) Run(args []string) int {
ctx := c.CommandContext()
args = c.Meta.process(args)
cmdFlags := c.Meta.defaultFlagSet("logout")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
@@ -91,7 +93,7 @@ func (c *LogoutCommand) Run(args []string) int {
c.Ui.Output(fmt.Sprintf("Removing the stored credentials for %s from the following file:\n %s\n", dispHostname, credsCtx.LocalFilename))
}
err = creds.ForgetForHost(hostname)
err = creds.ForgetForHost(ctx, hostname)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,

View File

@@ -11,11 +11,12 @@ import (
"testing"
"github.com/mitchellh/cli"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/opentofu/internal/command/cliconfig"
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
)
func TestLogout(t *testing.T) {
@@ -27,7 +28,9 @@ func TestLogout(t *testing.T) {
c := &LogoutCommand{
Meta: Meta{
Ui: ui,
Services: disco.NewWithCredentialsSource(credsSrc),
Services: disco.New(
disco.WithCredentials(credsSrc),
),
},
}
@@ -61,7 +64,7 @@ func TestLogout(t *testing.T) {
for _, tc := range testCases {
host := svchost.Hostname(tc.hostname)
token := svcauth.HostCredentialsToken("some-token")
err := credsSrc.StoreForHost(host, token)
err := credsSrc.StoreForHost(t.Context(), host, token)
if err != nil {
t.Fatalf("unexpected error storing credentials: %s", err)
}
@@ -71,16 +74,16 @@ func TestLogout(t *testing.T) {
t.Fatalf("unexpected error code %d\nstderr:\n%s", status, ui.ErrorWriter.String())
}
creds, err := credsSrc.ForHost(host)
creds, err := credsSrc.ForHost(t.Context(), host)
if err != nil {
t.Errorf("failed to retrieve credentials: %s", err)
}
if tc.shouldRemove {
if creds != nil {
t.Errorf("wrong token %q; should have no token", creds.Token())
t.Errorf("wrong token %q; should have no token", svcauthconfig.HostCredentialsBearerToken(t, creds))
}
} else {
if got, want := creds.Token(), "some-token"; got != want {
if got, want := svcauthconfig.HostCredentialsBearerToken(t, creds), "some-token"; got != want {
t.Errorf("wrong token %q; want %q", got, want)
}
}

View File

@@ -21,9 +21,9 @@ import (
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/backend"

View File

@@ -24,7 +24,7 @@ import (
version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2/hclsyntax"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/depsfile"

View File

@@ -9,7 +9,7 @@ import (
"fmt"
"sort"
svchost "github.com/hashicorp/terraform-svchost"
svchost "github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/getproviders"

View File

@@ -15,7 +15,8 @@ import (
"path"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -9,7 +9,8 @@ import (
"context"
"testing"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -9,7 +9,7 @@ import (
"fmt"
"net/url"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -12,8 +12,8 @@ import (
"github.com/apparentlymart/go-versions/versions"
"github.com/google/go-cmp/cmp"
"github.com/opentofu/svchost"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -12,7 +12,7 @@ import (
"path/filepath"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -19,8 +19,8 @@ import (
"time"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
"golang.org/x/net/idna"
"github.com/opentofu/opentofu/internal/addrs"
@@ -254,7 +254,7 @@ func (s *HTTPMirrorSource) mirrorHost() (svchost.Hostname, error) {
//
// It might return an error if the mirror base URL is invalid, or if the
// credentials lookup itself fails.
func (s *HTTPMirrorSource) mirrorHostCredentials() (svcauth.HostCredentials, error) {
func (s *HTTPMirrorSource) mirrorHostCredentials(ctx context.Context) (svcauth.HostCredentials, error) {
hostname, err := s.mirrorHost()
if err != nil {
return nil, fmt.Errorf("invalid provider mirror base URL %s: %w", s.baseURL.String(), err)
@@ -265,7 +265,7 @@ func (s *HTTPMirrorSource) mirrorHostCredentials() (svcauth.HostCredentials, err
return nil, nil
}
return s.creds.ForHost(hostname)
return s.creds.ForHost(ctx, hostname)
}
// get is the shared functionality for querying a JSON index from a mirror.
@@ -292,7 +292,7 @@ func (s *HTTPMirrorSource) get(ctx context.Context, relativePath string) (status
}
req = req.WithContext(ctx)
req.Request.Header.Set(terraformVersionHeader, version.String())
creds, err := s.mirrorHostCredentials()
creds, err := s.mirrorHostCredentials(ctx)
if err != nil {
return 0, nil, endpointURL, fmt.Errorf("failed to determine request credentials: %w", err)
}

View File

@@ -15,8 +15,8 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
"github.com/opentofu/opentofu/internal/addrs"
)
@@ -33,10 +33,8 @@ func TestHTTPMirrorSource(t *testing.T) {
if err != nil {
t.Fatalf("httptest.NewTLSServer returned a server with an invalid URL")
}
creds := svcauth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
svchost.Hostname(baseURL.Host): {
"token": "placeholder-token",
},
creds := svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
svchost.Hostname(baseURL.Host): svcauth.HostCredentialsToken("placeholder-token"),
})
retryHTTPClient := retryablehttp.NewClient()
retryHTTPClient.HTTPClient = httpClient

View File

@@ -10,7 +10,7 @@ import (
"fmt"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -17,10 +17,10 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
ociDigest "github.com/opencontainers/go-digest"
ociSpecs "github.com/opencontainers/image-spec/specs-go"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opentofu/svchost"
orasContent "oras.land/oras-go/v2/content"
orasOCI "oras.land/oras-go/v2/content/oci"
orasErrors "oras.land/oras-go/v2/errdef"

View File

@@ -18,8 +18,8 @@ import (
"path"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
svcauth "github.com/hashicorp/terraform-svchost/auth"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/svcauth"
otelAttr "go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"go.opentelemetry.io/otel/trace"

View File

@@ -16,8 +16,9 @@ import (
"github.com/apparentlymart/go-versions/versions"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
disco "github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
disco "github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -11,8 +11,8 @@ import (
"time"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
disco "github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
disco "github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/httpclient"
@@ -122,7 +122,7 @@ func (s *RegistrySource) PackageMeta(ctx context.Context, provider addrs.Provide
}
func (s *RegistrySource) registryClient(ctx context.Context, hostname svchost.Hostname) (*registryClient, error) {
host, err := s.services.Discover(hostname)
host, err := s.services.Discover(ctx, hostname)
if err != nil {
return nil, ErrHostUnreachable{
Hostname: hostname,
@@ -151,7 +151,7 @@ func (s *RegistrySource) registryClient(ctx context.Context, hostname svchost.Ho
}
// Check if we have credentials configured for this hostname.
creds, err := s.services.CredentialsForHost(hostname)
creds, err := s.services.CredentialsForHost(ctx, hostname)
if err != nil {
// This indicates that a credentials helper failed, which means we
// can't do anything better than just pass through the helper's

View File

@@ -12,11 +12,10 @@ import (
"strings"
"testing"
tfaddr "github.com/opentofu/registry-address"
"github.com/apparentlymart/go-versions/versions"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
regaddr "github.com/opentofu/registry-address/v2"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)
@@ -142,7 +141,7 @@ func TestSourcePackageMeta(t *testing.T) {
[]SigningKey{
{ASCIIArmor: TestingPublicKey},
},
tfaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
regaddr.Provider{Hostname: "example.com", Namespace: "awesomesauce", Type: "happycloud"},
),
)

View File

@@ -18,7 +18,7 @@ import (
"github.com/go-test/deep"
"github.com/google/go-cmp/cmp"
version "github.com/hashicorp/go-version"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"

View File

@@ -22,8 +22,8 @@ import (
"github.com/apparentlymart/go-versions/versions/constraints"
"github.com/davecgh/go-spew/spew"
"github.com/google/go-cmp/cmp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/depsfile"

View File

@@ -18,8 +18,8 @@ import (
"time"
"github.com/hashicorp/go-retryablehttp"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
otelAttr "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@@ -71,8 +71,8 @@ func NewClient(ctx context.Context, services *disco.Disco, client *retryablehttp
}
// Discover queries the host, and returns the url for the registry.
func (c *Client) Discover(host svchost.Hostname, serviceID string) (*url.URL, error) {
service, err := c.services.DiscoverServiceURL(host, serviceID)
func (c *Client) Discover(ctx context.Context, host svchost.Hostname, serviceID string) (*url.URL, error) {
service, err := c.services.DiscoverServiceURL(ctx, host, serviceID)
if err != nil {
return nil, &ServiceUnreachableError{err}
}
@@ -96,7 +96,7 @@ func (c *Client) ModuleVersions(ctx context.Context, module *regsrc.Module) (*re
return nil, err
}
service, err := c.Discover(host, modulesServiceID)
service, err := c.Discover(ctx, host, modulesServiceID)
if err != nil {
return nil, err
}
@@ -116,7 +116,7 @@ func (c *Client) ModuleVersions(ctx context.Context, module *regsrc.Module) (*re
}
req = req.WithContext(ctx)
c.addRequestCreds(host, req.Request)
c.addRequestCreds(ctx, host, req.Request)
req.Header.Set(xTerraformVersion, tfVersion)
resp, err := c.client.Do(req)
@@ -150,8 +150,8 @@ func (c *Client) ModuleVersions(ctx context.Context, module *regsrc.Module) (*re
return &versions, nil
}
func (c *Client) addRequestCreds(host svchost.Hostname, req *http.Request) {
creds, err := c.services.CredentialsForHost(host)
func (c *Client) addRequestCreds(ctx context.Context, host svchost.Hostname, req *http.Request) {
creds, err := c.services.CredentialsForHost(ctx, host)
if err != nil {
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err)
return
@@ -179,7 +179,7 @@ func (c *Client) ModuleLocation(ctx context.Context, module *regsrc.Module, vers
return "", err
}
service, err := c.Discover(host, modulesServiceID)
service, err := c.Discover(ctx, host, modulesServiceID)
if err != nil {
return "", err
}
@@ -204,7 +204,7 @@ func (c *Client) ModuleLocation(ctx context.Context, module *regsrc.Module, vers
req = req.WithContext(ctx)
c.addRequestCreds(host, req.Request)
c.addRequestCreds(ctx, host, req.Request)
req.Header.Set(xTerraformVersion, tfVersion)
resp, err := c.client.Do(req)

View File

@@ -18,13 +18,12 @@ import (
"github.com/hashicorp/go-retryablehttp"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/opentofu/internal/registry/regsrc"
"github.com/opentofu/opentofu/internal/registry/response"
"github.com/opentofu/opentofu/internal/registry/test"
tfversion "github.com/opentofu/opentofu/version"
)
func TestLookupModuleVersions(t *testing.T) {
@@ -150,8 +149,9 @@ func TestAccLookupModuleVersions(t *testing.T) {
if os.Getenv("TF_ACC") == "" {
t.Skip()
}
regDisco := disco.New()
regDisco.SetUserAgent(httpclient.OpenTofuUserAgent(tfversion.String()))
regDisco := disco.New(
disco.WithHTTPClient(httpclient.New(t.Context())),
)
// test with and without a hostname
for _, src := range []string{

View File

@@ -8,7 +8,8 @@ package registry
import (
"fmt"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/registry/regsrc"
)

View File

@@ -9,7 +9,7 @@ import (
"regexp"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
)
var (

View File

@@ -11,7 +11,8 @@ import (
"regexp"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/opentofu/svchost"
"github.com/opentofu/opentofu/internal/addrs"
)

View File

@@ -10,13 +10,12 @@ import (
"regexp"
"strings"
svchost "github.com/hashicorp/terraform-svchost"
"github.com/hashicorp/terraform-svchost/auth"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/opentofu/opentofu/internal/httpclient"
"github.com/opentofu/svchost"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/svchost/svcauth"
"github.com/opentofu/opentofu/internal/registry/regsrc"
"github.com/opentofu/opentofu/internal/registry/response"
tfversion "github.com/opentofu/opentofu/version"
)
// Disco return a *disco.Disco mapping registry.opentofu.org, localhost,
@@ -28,8 +27,10 @@ func Disco(s *httptest.Server) *disco.Disco {
"modules.v1": fmt.Sprintf("%s/v1/modules", s.URL),
"providers.v1": fmt.Sprintf("%s/v1/providers", s.URL),
}
d := disco.NewWithCredentialsSource(credsSrc)
d.SetUserAgent(httpclient.OpenTofuUserAgent(tfversion.String()))
d := disco.New(
disco.WithCredentials(credsSrc),
disco.WithHTTPClient(s.Client()),
)
d.ForceHostServices(svchost.Hostname("registry.opentofu.org"), services)
d.ForceHostServices(svchost.Hostname("localhost"), services)
@@ -58,8 +59,8 @@ const (
var (
regHost = svchost.Hostname(regsrc.PublicRegistryHost.Normalized())
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
regHost: {"token": testCred},
credsSrc = svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
regHost: svcauth.HostCredentialsToken(testCred),
})
)

View File

@@ -12,6 +12,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
regaddr "github.com/opentofu/registry-address/v2"
"github.com/zclconf/go-cty/cty"
"github.com/opentofu/opentofu/internal/addrs"
@@ -20,7 +21,6 @@ import (
"github.com/opentofu/opentofu/internal/states/statefile"
"github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/opentofu/opentofu/version"
tfaddr "github.com/opentofu/registry-address"
)
func TestState_impl(t *testing.T) {
@@ -103,7 +103,7 @@ func TestStatePersist(t *testing.T) {
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: tfaddr.Provider{Namespace: "local"},
Provider: regaddr.Provider{Namespace: "local"},
},
addrs.NoKey,
)

View File

@@ -9,8 +9,9 @@ import (
"os"
"github.com/hashicorp/hcl/v2"
tfaddr "github.com/opentofu/registry-address"
regaddr "github.com/opentofu/registry-address/v2"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/getproviders"
"github.com/opentofu/opentofu/internal/states"
@@ -66,8 +67,8 @@ func MigrateStateProviderAddresses(config *configs.Config, state *states.State)
for _, module := range stateCopy.Modules {
for _, resource := range module.Resources {
_, referencedInConfig := providers[resource.ProviderConfig.Provider]
if resource.ProviderConfig.Provider.Hostname == "registry.terraform.io" && !referencedInConfig {
resource.ProviderConfig.Provider.Hostname = tfaddr.DefaultProviderRegistryHost
if resource.ProviderConfig.Provider.Hostname == regaddr.TransitionalDefaultProviderRegistryHost && !referencedInConfig {
resource.ProviderConfig.Provider.Hostname = addrs.DefaultProviderRegistryHost
}
}
}