mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
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:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
22
go.mod
@@ -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
45
go.sum
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
20
internal/command/cliconfig/svcauthconfig/doc.go
Normal file
20
internal/command/cliconfig/svcauthconfig/doc.go
Normal 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
|
||||
153
internal/command/cliconfig/svcauthconfig/helper_program.go
Normal file
153
internal/command/cliconfig/svcauthconfig/helper_program.go
Normal 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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
})
|
||||
}
|
||||
1
internal/command/cliconfig/svcauthconfig/testdata/helperprog/.gitignore
vendored
Normal file
1
internal/command/cliconfig/svcauthconfig/testdata/helperprog/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
main
|
||||
72
internal/command/cliconfig/svcauthconfig/testdata/helperprog/main.go
vendored
Normal file
72
internal/command/cliconfig/svcauthconfig/testdata/helperprog/main.go
vendored
Normal 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)
|
||||
}
|
||||
7
internal/command/cliconfig/svcauthconfig/testdata/helperprog/test-helper
vendored
Executable file
7
internal/command/cliconfig/svcauthconfig/testdata/helperprog/test-helper
vendored
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
cd "$( dirname "${BASH_SOURCE[0]}" )"
|
||||
[ -x main ] || go build -o main .
|
||||
exec ./main "$@"
|
||||
57
internal/command/cliconfig/svcauthconfig/testing.go
Normal file
57
internal/command/cliconfig/svcauthconfig/testing.go
Normal 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)
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/opentofu/svchost"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/opentofu/svchost"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/opentofu/svchost"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/opentofu/svchost"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/opentofu/svchost"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -11,7 +11,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
svchost "github.com/hashicorp/terraform-svchost"
|
||||
"github.com/opentofu/svchost"
|
||||
|
||||
"github.com/opentofu/opentofu/internal/addrs"
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user