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-plugin"
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"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/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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/command"
|
"github.com/opentofu/opentofu/internal/command"
|
||||||
@@ -486,7 +486,7 @@ func makeShutdownCh() <-chan struct{} {
|
|||||||
return resultCh
|
return resultCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func credentialsSource(config *cliconfig.Config) (auth.CredentialsSource, error) {
|
func credentialsSource(config *cliconfig.Config) (svcauth.CredentialsSource, error) {
|
||||||
helperPlugins := pluginDiscovery.FindPlugins("credentials", globalPluginDirs())
|
helperPlugins := pluginDiscovery.FindPlugins("credentials", globalPluginDirs())
|
||||||
return config.CredentialsSource(helperPlugins)
|
return config.CredentialsSource(helperPlugins)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/apparentlymart/go-userdirs/userdirs"
|
"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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"github.com/hashicorp/terraform-svchost/auth"
|
"github.com/opentofu/svchost/disco"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/svcauth"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
"github.com/opentofu/opentofu/internal/httpclient"
|
||||||
"github.com/opentofu/opentofu/internal/logging"
|
"github.com/opentofu/opentofu/internal/logging"
|
||||||
"github.com/opentofu/opentofu/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -43,27 +43,15 @@ const (
|
|||||||
// object should obtain authentication credentials for service discovery
|
// object should obtain authentication credentials for service discovery
|
||||||
// requests. Passing a nil credSrc is acceptable and means that all discovery
|
// requests. Passing a nil credSrc is acceptable and means that all discovery
|
||||||
// requests are to be made anonymously.
|
// requests are to be made anonymously.
|
||||||
func newServiceDiscovery(_ context.Context, credSrc auth.CredentialsSource) *disco.Disco {
|
func newServiceDiscovery(ctx context.Context, credSrc svcauth.CredentialsSource) *disco.Disco {
|
||||||
services := disco.NewWithCredentialsSource(credSrc)
|
|
||||||
services.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
|
|
||||||
|
|
||||||
// For historical reasons, the registry request retry policy also applies
|
// For historical reasons, the registry request retry policy also applies
|
||||||
// to all service discovery requests, which we implement by using transport
|
// to all service discovery requests, which we implement by using transport
|
||||||
// from a HTTP client that is configured for registry client use.
|
// from a HTTP httpClient that is configured for registry httpClient use.
|
||||||
//
|
registryHTTPClient := newRegistryHTTPClient(ctx)
|
||||||
// TEMP: The disco.Disco API isn't yet set up to pass through
|
services := disco.New(
|
||||||
// context.Context, so we're intentionally ignoring the passed-in ctx
|
disco.WithHTTPClient(registryHTTPClient.HTTPClient),
|
||||||
// here to prevent the created client from having OpenTelemetry
|
disco.WithCredentials(credSrc),
|
||||||
// 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
|
|
||||||
|
|
||||||
return services
|
return services
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
22
go.mod
22
go.mod
@@ -58,7 +58,6 @@ require (
|
|||||||
github.com/hashicorp/hcl v1.0.0
|
github.com/hashicorp/hcl v1.0.0
|
||||||
github.com/hashicorp/hcl/v2 v2.20.1
|
github.com/hashicorp/hcl/v2 v2.20.1
|
||||||
github.com/hashicorp/jsonapi v1.3.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/jmespath/go-jmespath v0.4.0
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||||
github.com/lib/pq v1.10.3
|
github.com/lib/pq v1.10.3
|
||||||
@@ -78,7 +77,8 @@ require (
|
|||||||
github.com/openbao/openbao/api/v2 v2.1.0
|
github.com/openbao/openbao/api/v2 v2.1.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.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/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/posener/complete v1.2.3
|
github.com/posener/complete v1.2.3
|
||||||
@@ -99,14 +99,14 @@ require (
|
|||||||
go.opentelemetry.io/otel/sdk v1.35.0
|
go.opentelemetry.io/otel/sdk v1.35.0
|
||||||
go.opentelemetry.io/otel/trace v1.35.0
|
go.opentelemetry.io/otel/trace v1.35.0
|
||||||
go.uber.org/mock v0.4.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/exp v0.0.0-20230905200255-921286631fa9
|
||||||
golang.org/x/mod v0.21.0
|
golang.org/x/mod v0.21.0
|
||||||
golang.org/x/net v0.36.0
|
golang.org/x/net v0.40.0
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sys v0.30.0
|
golang.org/x/sys v0.33.0
|
||||||
golang.org/x/term v0.29.0
|
golang.org/x/term v0.32.0
|
||||||
golang.org/x/text v0.22.0
|
golang.org/x/text v0.25.0
|
||||||
golang.org/x/tools v0.25.0
|
golang.org/x/tools v0.25.0
|
||||||
google.golang.org/api v0.155.0
|
google.golang.org/api v0.155.0
|
||||||
google.golang.org/grpc v1.62.1
|
google.golang.org/grpc v1.62.1
|
||||||
@@ -122,8 +122,7 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.112.0 // indirect
|
cloud.google.com/go v0.112.0 // indirect
|
||||||
cloud.google.com/go/compute v1.23.3 // indirect
|
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
|
||||||
cloud.google.com/go/iam v1.1.5 // indirect
|
cloud.google.com/go/iam v1.1.5 // indirect
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
|
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // 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/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.0.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/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
|
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 v0.0.0-20240123012728-ef4313101c80 // indirect
|
||||||
google.golang.org/genproto/googleapis/api 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
|
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.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.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.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU=
|
||||||
cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
|
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||||
cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
|
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||||
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/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I=
|
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/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=
|
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/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 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
|
||||||
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
|
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/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/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=
|
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/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 h1:QHUIrylb/q3pQdQcnAr74cGWsXS1lmA8GqP+RWFMK6U=
|
||||||
github.com/opentofu/hcl/v2 v2.20.2-0.20250121132637-504036cd70e7/go.mod h1:k+HgkLpoWu9OS81sy4j1XKDXaWm/rLysG33v5ibdDnc=
|
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/v2 v2.0.0-20250611143131-d0a99bd8acdd h1:YAAnzmyOoMvm5SuGXL4hhlfBgqz92XDfORGPV3kmQFc=
|
||||||
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/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 h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU=
|
||||||
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk=
|
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=
|
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.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.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.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
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-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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
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.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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||||
golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I=
|
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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/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.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
|
||||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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-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-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/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.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.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.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.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.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/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.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/appengine v1.6.6/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.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-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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
package addrs
|
package addrs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
tfaddr "github.com/opentofu/registry-address"
|
regaddr "github.com/opentofu/registry-address/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A ModulePackage represents a physical location where OpenTofu can retrieve
|
// 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
|
// registry in order to find a real module package address. These being
|
||||||
// distinct is intended to help future maintainers more easily follow the
|
// 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.
|
// 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"
|
"strings"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/getmodules"
|
"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
|
// 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
|
// combination of a ModuleSourceRegistry and a module version number into
|
||||||
// a concrete ModuleSourceRemote that OpenTofu will then download and
|
// a concrete ModuleSourceRemote that OpenTofu will then download and
|
||||||
// install.
|
// install.
|
||||||
type ModuleSourceRegistry tfaddr.Module
|
type ModuleSourceRegistry regaddr.Module
|
||||||
|
|
||||||
// DefaultModuleRegistryHost is the hostname used for registry-based module
|
// DefaultModuleRegistryHost is the hostname used for registry-based module
|
||||||
// source addresses that do not have an explicit hostname.
|
// 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
|
// ParseModuleSourceRegistry is a variant of ParseModuleSource which only
|
||||||
// accepts module registry addresses, and will reject any other address type.
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseModuleSource(t *testing.T) {
|
func TestParseModuleSource(t *testing.T) {
|
||||||
|
|||||||
@@ -7,23 +7,32 @@ package addrs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hashicorp/hcl/v2"
|
"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"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
tfaddr "github.com/opentofu/registry-address"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider encapsulates a single provider type. In the future this will be
|
// Provider encapsulates a single provider type. In the future this will be
|
||||||
// extended to include additional fields including Namespace and SourceHost
|
// 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
|
// DefaultProviderRegistryHost is the hostname used for provider addresses that do
|
||||||
// not have an explicit hostname.
|
// not have an explicit hostname.
|
||||||
const DefaultProviderRegistryHost = tfaddr.DefaultProviderRegistryHost
|
const DefaultProviderRegistryHost = regaddr.DefaultProviderRegistryHost
|
||||||
|
|
||||||
// BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
|
// BuiltInProviderHost is the pseudo-hostname used for the "built-in" provider
|
||||||
// namespace. Built-in provider addresses must also have their namespace set
|
// namespace. Built-in provider addresses must also have their namespace set
|
||||||
// to BuiltInProviderNamespace in order to be considered as built-in.
|
// 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"
|
// BuiltInProviderNamespace is the provider namespace used for "built-in"
|
||||||
// providers. Built-in provider addresses must also have their hostname
|
// 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
|
// 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
|
// 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.
|
// 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
|
// LegacyProviderNamespace is the special string used in the Namespace field
|
||||||
// of type Provider to mark a legacy provider address. This special namespace
|
// 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
|
// 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
|
// DefaultRegistryHost because that host owns the mapping from legacy name to
|
||||||
// FQN.
|
// FQN.
|
||||||
const LegacyProviderNamespace = tfaddr.LegacyProviderNamespace
|
const LegacyProviderNamespace = regaddr.LegacyProviderNamespace
|
||||||
|
|
||||||
func IsDefaultProvider(addr Provider) bool {
|
func IsDefaultProvider(addr Provider) bool {
|
||||||
return addr.Hostname == DefaultProviderRegistryHost && addr.Namespace == "hashicorp"
|
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
|
// When accepting namespace or type values from outside the program, use
|
||||||
// ParseProviderPart first to check that the given value is valid.
|
// ParseProviderPart first to check that the given value is valid.
|
||||||
func NewProvider(hostname svchost.Hostname, namespace, typeName string) Provider {
|
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
|
// 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,
|
// NewDefaultProvider returns the default address of a HashiCorp-maintained,
|
||||||
// Registry-hosted provider.
|
// Registry-hosted provider.
|
||||||
func NewDefaultProvider(name string) Provider {
|
func NewDefaultProvider(name string) Provider {
|
||||||
return tfaddr.Provider{
|
return regaddr.Provider{
|
||||||
Type: MustParseProviderPart(name),
|
Type: MustParseProviderPart(name),
|
||||||
Namespace: "hashicorp",
|
Namespace: "hashicorp",
|
||||||
Hostname: DefaultProviderRegistryHost,
|
Hostname: DefaultProviderRegistryHost,
|
||||||
@@ -97,7 +106,7 @@ func NewDefaultProvider(name string) Provider {
|
|||||||
// NewBuiltInProvider returns the address of a "built-in" provider. See
|
// NewBuiltInProvider returns the address of a "built-in" provider. See
|
||||||
// the docs for Provider.IsBuiltIn for more information.
|
// the docs for Provider.IsBuiltIn for more information.
|
||||||
func NewBuiltInProvider(name string) Provider {
|
func NewBuiltInProvider(name string) Provider {
|
||||||
return tfaddr.Provider{
|
return regaddr.Provider{
|
||||||
Type: MustParseProviderPart(name),
|
Type: MustParseProviderPart(name),
|
||||||
Namespace: BuiltInProviderNamespace,
|
Namespace: BuiltInProviderNamespace,
|
||||||
Hostname: BuiltInProviderHost,
|
Hostname: BuiltInProviderHost,
|
||||||
@@ -127,11 +136,11 @@ func NewLegacyProvider(name string) Provider {
|
|||||||
// - name
|
// - name
|
||||||
// - namespace/name
|
// - namespace/name
|
||||||
// - hostname/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
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
ret, err := tfaddr.ParseProviderSource(str)
|
ret, err := regaddr.ParseProviderSource(str)
|
||||||
if pe, ok := err.(*tfaddr.ParserError); ok {
|
if pe, ok := err.(*regaddr.ParserError); ok {
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: pe.Summary,
|
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
|
// 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.
|
// subsequent call, in which case the result will be identical.
|
||||||
func ParseProviderPart(given string) (string, error) {
|
func ParseProviderPart(given string) (string, error) {
|
||||||
return tfaddr.ParseProviderPart(given)
|
return regaddr.ParseProviderPart(given)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustParseProviderPart is a wrapper around ParseProviderPart that panics if
|
// MustParseProviderPart is a wrapper around ParseProviderPart that panics if
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestProviderString(t *testing.T) {
|
func TestProviderString(t *testing.T) {
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
|
||||||
"github.com/mitchellh/go-homedir"
|
"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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/command/clistate"
|
"github.com/opentofu/opentofu/internal/command/clistate"
|
||||||
"github.com/opentofu/opentofu/internal/command/views"
|
"github.com/opentofu/opentofu/internal/command/views"
|
||||||
@@ -31,7 +33,6 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/states/statemgr"
|
"github.com/opentofu/opentofu/internal/states/statemgr"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/opentofu/opentofu/internal/tofu"
|
"github.com/opentofu/opentofu/internal/tofu"
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultStateName is the name of the default, initial state that every
|
// DefaultStateName is the name of the default, initial state that every
|
||||||
|
|||||||
@@ -10,12 +10,10 @@ package init
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/disco"
|
||||||
"github.com/opentofu/opentofu/internal/backend"
|
|
||||||
"github.com/opentofu/opentofu/internal/encryption"
|
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/opentofu/opentofu/internal/backend"
|
||||||
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
|
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
|
||||||
backendRemote "github.com/opentofu/opentofu/internal/backend/remote"
|
backendRemote "github.com/opentofu/opentofu/internal/backend/remote"
|
||||||
backendAzure "github.com/opentofu/opentofu/internal/backend/remote-state/azure"
|
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"
|
backendPg "github.com/opentofu/opentofu/internal/backend/remote-state/pg"
|
||||||
backendS3 "github.com/opentofu/opentofu/internal/backend/remote-state/s3"
|
backendS3 "github.com/opentofu/opentofu/internal/backend/remote-state/s3"
|
||||||
backendCloud "github.com/opentofu/opentofu/internal/cloud"
|
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
|
// backends is the list of available backends. This is a global variable
|
||||||
|
|||||||
@@ -19,13 +19,15 @@ import (
|
|||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
version "github.com/hashicorp/go-version"
|
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/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"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"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/backend"
|
"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/configs/configschema"
|
||||||
"github.com/opentofu/opentofu/internal/encryption"
|
"github.com/opentofu/opentofu/internal/encryption"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
"github.com/opentofu/opentofu/internal/httpclient"
|
||||||
@@ -35,8 +37,6 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/opentofu/opentofu/internal/tofu"
|
"github.com/opentofu/opentofu/internal/tofu"
|
||||||
tfversion "github.com/opentofu/opentofu/version"
|
tfversion "github.com/opentofu/opentofu/version"
|
||||||
|
|
||||||
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
// Discover the service URL for this host to confirm that it provides
|
||||||
// a remote backend API and to get the version constraints.
|
// 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.
|
// Historical note: in OpenTofu's predecessor project there was an
|
||||||
if constraints != nil {
|
// extra step here of checking some metadata returned by the remote
|
||||||
diags = diags.Append(b.checkConstraints(constraints))
|
// API describing which versions of the predecessor's CLI it considers
|
||||||
if diags.HasErrors() {
|
// itself to be compatible with. Since OpenTofu's version numbers have
|
||||||
return diags
|
// 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
|
// When we don't have any constraints errors, also check for discovery
|
||||||
// errors before we continue.
|
// 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.
|
// 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)
|
hostname, err := svchost.ForComparison(b.hostname)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
service, err := host.ServiceURL(serviceID)
|
service, err := host.ServiceURL(serviceID)
|
||||||
// Return the error, unless its a disco.ErrVersionNotSupported error.
|
// Return the error, unless its a disco.ErrVersionNotSupported error.
|
||||||
if _, ok := err.(*disco.ErrVersionNotSupported); !ok && err != nil {
|
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
|
return service, nil
|
||||||
// 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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// token returns the token for this host as configured in the credentials
|
// 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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
creds, err := b.services.CredentialsForHost(hostname)
|
creds, err := b.services.CredentialsForHost(context.TODO(), hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", b.hostname, err)
|
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", b.hostname, err)
|
||||||
return "", nil
|
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 creds.Token(), nil
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -14,15 +14,14 @@ import (
|
|||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/backend"
|
"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/encryption"
|
||||||
"github.com/opentofu/opentofu/internal/states/statemgr"
|
"github.com/opentofu/opentofu/internal/states/statemgr"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
tfversion "github.com/opentofu/opentofu/version"
|
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) {
|
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) {
|
func TestRemote_localBackend(t *testing.T) {
|
||||||
b, bCleanup := testBackendDefault(t)
|
b, bCleanup := testBackendDefault(t)
|
||||||
defer bCleanup()
|
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) {
|
func TestRemote_StateMgr_versionCheck(t *testing.T) {
|
||||||
b, bCleanup := testBackendDefault(t)
|
b, bCleanup := testBackendDefault(t)
|
||||||
defer bCleanup()
|
defer bCleanup()
|
||||||
|
|||||||
@@ -16,24 +16,22 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
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/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"
|
"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/cloud"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
"github.com/opentofu/opentofu/internal/configs"
|
||||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||||
"github.com/opentofu/opentofu/internal/encryption"
|
"github.com/opentofu/opentofu/internal/encryption"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/states/remote"
|
"github.com/opentofu/opentofu/internal/states/remote"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/opentofu/opentofu/internal/tofu"
|
"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 (
|
const (
|
||||||
@@ -42,8 +40,8 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
mockedBackendHost = "app.example.com"
|
mockedBackendHost = "app.example.com"
|
||||||
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
credsSrc = svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
|
||||||
svchost.Hostname(mockedBackendHost): {"token": testCred},
|
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 {
|
func testInput(t *testing.T, answers map[string]string) *mockInput {
|
||||||
|
t.Helper()
|
||||||
return &mockInput{answers: answers}
|
return &mockInput{answers: answers}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBackendDefault(t *testing.T) (*Remote, func()) {
|
func testBackendDefault(t *testing.T) (*Remote, func()) {
|
||||||
|
t.Helper()
|
||||||
obj := cty.ObjectVal(map[string]cty.Value{
|
obj := cty.ObjectVal(map[string]cty.Value{
|
||||||
"hostname": cty.StringVal(mockedBackendHost),
|
"hostname": cty.StringVal(mockedBackendHost),
|
||||||
"organization": cty.StringVal("hashicorp"),
|
"organization": cty.StringVal("hashicorp"),
|
||||||
@@ -98,6 +98,7 @@ func testBackendNoDefault(t *testing.T) (*Remote, func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testBackendNoOperations(t *testing.T) (*Remote, func()) {
|
func testBackendNoOperations(t *testing.T) (*Remote, func()) {
|
||||||
|
t.Helper()
|
||||||
obj := cty.ObjectVal(map[string]cty.Value{
|
obj := cty.ObjectVal(map[string]cty.Value{
|
||||||
"hostname": cty.StringVal(mockedBackendHost),
|
"hostname": cty.StringVal(mockedBackendHost),
|
||||||
"organization": cty.StringVal("no-operations"),
|
"organization": cty.StringVal("no-operations"),
|
||||||
@@ -111,6 +112,7 @@ func testBackendNoOperations(t *testing.T) (*Remote, func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testRemoteClient(t *testing.T) remote.Client {
|
func testRemoteClient(t *testing.T) remote.Client {
|
||||||
|
t.Helper()
|
||||||
b, bCleanup := testBackendDefault(t)
|
b, bCleanup := testBackendDefault(t)
|
||||||
defer bCleanup()
|
defer bCleanup()
|
||||||
|
|
||||||
@@ -123,6 +125,8 @@ func testRemoteClient(t *testing.T) remote.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
|
func testBackend(t *testing.T, obj cty.Value) (*Remote, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
s := testServer(t)
|
s := testServer(t)
|
||||||
b := New(testDisco(s), encryption.StateEncryptionDisabled())
|
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 {
|
func testLocalBackend(t *testing.T, remote *Remote) backend.Enhanced {
|
||||||
|
t.Helper()
|
||||||
b := backendLocal.NewWithBackend(remote, nil)
|
b := backendLocal.NewWithBackend(remote, nil)
|
||||||
|
|
||||||
// Add a test provider to the local backend.
|
// 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.
|
// testServer returns a *httptest.Server used for local testing.
|
||||||
func testServer(t *testing.T) *httptest.Server {
|
func testServer(t *testing.T) *httptest.Server {
|
||||||
|
t.Helper()
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
// Respond to service discovery calls.
|
// 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),
|
"tfe.v2.1": fmt.Sprintf("%s/api/v2/", s.URL),
|
||||||
"versions.v1": fmt.Sprintf("%s/v1/versions/", s.URL),
|
"versions.v1": fmt.Sprintf("%s/v1/versions/", s.URL),
|
||||||
}
|
}
|
||||||
d := disco.NewWithCredentialsSource(credsSrc)
|
d := disco.New(
|
||||||
d.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
|
disco.WithCredentials(credsSrc),
|
||||||
|
disco.WithHTTPClient(s.Client()),
|
||||||
|
)
|
||||||
|
|
||||||
d.ForceHostServices(svchost.Hostname(mockedBackendHost), services)
|
d.ForceHostServices(svchost.Hostname(mockedBackendHost), services)
|
||||||
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ import (
|
|||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
version "github.com/hashicorp/go-version"
|
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/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"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"
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
|
||||||
@@ -490,7 +491,7 @@ func (b *Cloud) discover() (*url.URL, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
host, err := b.services.Discover(hostname)
|
host, err := b.services.Discover(context.TODO(), hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var serviceDiscoErr *disco.ErrServiceDiscoveryNetworkRequest
|
var serviceDiscoErr *disco.ErrServiceDiscoveryNetworkRequest
|
||||||
|
|
||||||
@@ -520,12 +521,24 @@ func (b *Cloud) cliConfigToken() (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
creds, err := b.services.CredentialsForHost(hostname)
|
creds, err := b.services.CredentialsForHost(context.TODO(), hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", b.hostname, err)
|
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", b.hostname, err)
|
||||||
return "", nil
|
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 creds.Token(), nil
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
|
|||||||
@@ -21,26 +21,23 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
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/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"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"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/backend"
|
"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"
|
||||||
"github.com/opentofu/opentofu/internal/configs/configschema"
|
"github.com/opentofu/opentofu/internal/configs/configschema"
|
||||||
"github.com/opentofu/opentofu/internal/encryption"
|
"github.com/opentofu/opentofu/internal/encryption"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
|
||||||
"github.com/opentofu/opentofu/internal/providers"
|
"github.com/opentofu/opentofu/internal/providers"
|
||||||
"github.com/opentofu/opentofu/internal/states"
|
"github.com/opentofu/opentofu/internal/states"
|
||||||
"github.com/opentofu/opentofu/internal/states/statefile"
|
"github.com/opentofu/opentofu/internal/states/statefile"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
"github.com/opentofu/opentofu/internal/tofu"
|
"github.com/opentofu/opentofu/internal/tofu"
|
||||||
"github.com/opentofu/opentofu/version"
|
|
||||||
|
|
||||||
backendLocal "github.com/opentofu/opentofu/internal/backend/local"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -49,8 +46,8 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
tfeHost = "app.terraform.io"
|
tfeHost = "app.terraform.io"
|
||||||
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
credsSrc = svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
|
||||||
svchost.Hostname(tfeHost): {"token": testCred},
|
svchost.Hostname(tfeHost): svcauth.HostCredentialsToken("testCred"),
|
||||||
})
|
})
|
||||||
testBackendSingleWorkspaceName = "app-prod"
|
testBackendSingleWorkspaceName = "app-prod"
|
||||||
defaultTFCPing = map[string]func(http.ResponseWriter, *http.Request){
|
defaultTFCPing = map[string]func(http.ResponseWriter, *http.Request){
|
||||||
@@ -598,8 +595,10 @@ func testDisco(s *httptest.Server) *disco.Disco {
|
|||||||
services := map[string]interface{}{
|
services := map[string]interface{}{
|
||||||
"tfe.v2": fmt.Sprintf("%s/api/v2/", s.URL),
|
"tfe.v2": fmt.Sprintf("%s/api/v2/", s.URL),
|
||||||
}
|
}
|
||||||
d := disco.NewWithCredentialsSource(credsSrc)
|
d := disco.New(
|
||||||
d.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
|
disco.WithCredentials(credsSrc),
|
||||||
|
disco.WithHTTPClient(s.Client()),
|
||||||
|
)
|
||||||
|
|
||||||
d.ForceHostServices(svchost.Hostname(tfeHost), services)
|
d.ForceHostServices(svchost.Hostname(tfeHost), services)
|
||||||
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package cliconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@@ -14,12 +15,12 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/opentofu/svchost"
|
||||||
|
"github.com/opentofu/svchost/svcauth"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
|
||||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
|
"github.com/opentofu/opentofu/internal/configs/hcl2shim"
|
||||||
pluginDiscovery "github.com/opentofu/opentofu/internal/plugin/discovery"
|
pluginDiscovery "github.com/opentofu/opentofu/internal/plugin/discovery"
|
||||||
"github.com/opentofu/opentofu/internal/replacefile"
|
"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)
|
return nil, fmt.Errorf("can't locate credentials file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var helper svcauth.CredentialsSource
|
var helper svcauth.CredentialsStore
|
||||||
var helperType string
|
var helperType string
|
||||||
for givenType, givenConfig := range c.CredentialsHelpers {
|
for givenType, givenConfig := range c.CredentialsHelpers {
|
||||||
available := helperPlugins.WithName(givenType)
|
available := helperPlugins.WithName(givenType)
|
||||||
@@ -58,8 +59,8 @@ func (c *Config) CredentialsSource(helperPlugins pluginDiscovery.PluginMetaSet)
|
|||||||
|
|
||||||
selected := available.Newest()
|
selected := available.Newest()
|
||||||
|
|
||||||
helperSource := svcauth.HelperProgramCredentialsSource(selected.Path, givenConfig.Args...)
|
helperSource := svcauthconfig.NewHelperProgramCredentialsStore(selected.Path, givenConfig.Args...)
|
||||||
helper = svcauth.CachingCredentialsSource(helperSource) // cached because external operation may be slow/expensive
|
helper = svcauth.CachingCredentialsStore(helperSource)
|
||||||
helperType = givenType
|
helperType = givenType
|
||||||
|
|
||||||
// There should only be zero or one "credentials_helper" blocks. We
|
// 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
|
// credentialsSource is an internal factory for the credentials source which
|
||||||
// allows overriding the credentials file path, which allows setting it to
|
// allows overriding the credentials file path, which allows setting it to
|
||||||
// a temporary file location when testing.
|
// 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{}
|
configured := map[svchost.Hostname]cty.Value{}
|
||||||
for userHost, creds := range c.Credentials {
|
for userHost, creds := range c.Credentials {
|
||||||
host, err := svchost.ForComparison(userHost)
|
host, err := svchost.ForComparison(userHost)
|
||||||
@@ -221,7 +222,7 @@ type CredentialsSource struct {
|
|||||||
// hostnames not explicitly represented in "configured". Any writes to
|
// hostnames not explicitly represented in "configured". Any writes to
|
||||||
// the credentials store will also be sent to a configured helper instead
|
// the credentials store will also be sent to a configured helper instead
|
||||||
// of the credentials.tfrc.json file.
|
// of the credentials.tfrc.json file.
|
||||||
helper svcauth.CredentialsSource
|
helper svcauth.CredentialsStore
|
||||||
|
|
||||||
// helperType is the name of the type of credentials helper that is
|
// helperType is the name of the type of credentials helper that is
|
||||||
// referenced in "helper", or the empty string if "helper" is nil.
|
// referenced in "helper", or the empty string if "helper" is nil.
|
||||||
@@ -231,7 +232,7 @@ type CredentialsSource struct {
|
|||||||
// Assertion that credentialsSource implements CredentialsSource
|
// Assertion that credentialsSource implements CredentialsSource
|
||||||
var _ svcauth.CredentialsSource = (*CredentialsSource)(nil)
|
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
|
// The first order of precedence for credentials is a host-specific environment variable
|
||||||
if envCreds := hostCredentialsFromEnv(host); envCreds != nil {
|
if envCreds := hostCredentialsFromEnv(host); envCreds != nil {
|
||||||
return 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
|
// Then, any credentials block present in the CLI config
|
||||||
v, ok := s.configured[host]
|
v, ok := s.configured[host]
|
||||||
if ok {
|
if ok {
|
||||||
return svcauth.HostCredentialsFromObject(v), nil
|
return svcauthconfig.HostCredentialsFromObject(v), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// And finally, the credentials helper
|
// And finally, the credentials helper
|
||||||
if s.helper != nil {
|
if s.helper != nil {
|
||||||
return s.helper.ForHost(host)
|
return s.helper.ForHost(ctx, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CredentialsSource) StoreForHost(host svchost.Hostname, credentials svcauth.HostCredentialsWritable) error {
|
func (s *CredentialsSource) StoreForHost(ctx context.Context, host svchost.Hostname, credentials svcauth.NewHostCredentials) error {
|
||||||
return s.updateHostCredentials(host, credentials)
|
return s.updateHostCredentials(ctx, host, credentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *CredentialsSource) ForgetForHost(host svchost.Hostname) error {
|
func (s *CredentialsSource) ForgetForHost(ctx context.Context, host svchost.Hostname) error {
|
||||||
return s.updateHostCredentials(host, nil)
|
return s.updateHostCredentials(ctx, host, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HostCredentialsLocation returns a value indicating what type of storage is
|
// HostCredentialsLocation returns a value indicating what type of storage is
|
||||||
@@ -297,7 +298,7 @@ func (s *CredentialsSource) CredentialsHelperType() string {
|
|||||||
return s.helperType
|
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 {
|
switch loc := s.HostCredentialsLocation(host); loc {
|
||||||
case CredentialsInOtherFile:
|
case CredentialsInOtherFile:
|
||||||
return ErrUnwritableHostCredentials(host)
|
return ErrUnwritableHostCredentials(host)
|
||||||
@@ -311,16 +312,16 @@ func (s *CredentialsSource) updateHostCredentials(host svchost.Hostname, new svc
|
|||||||
case CredentialsViaHelper:
|
case CredentialsViaHelper:
|
||||||
// Delegate entirely to the helper, then.
|
// Delegate entirely to the helper, then.
|
||||||
if new == nil {
|
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:
|
default:
|
||||||
// Should never happen because the above cases are exhaustive
|
// Should never happen because the above cases are exhaustive
|
||||||
return fmt.Errorf("invalid credentials location %#v", loc)
|
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,
|
// This function updates the local credentials file in particular,
|
||||||
// regardless of whether a credentials helper is active. It should be
|
// regardless of whether a credentials helper is active. It should be
|
||||||
// called only indirectly via updateHostCredentials.
|
// called only indirectly via updateHostCredentials.
|
||||||
|
|||||||
@@ -6,15 +6,18 @@
|
|||||||
package cliconfig
|
package cliconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/opentofu/svchost"
|
||||||
|
"github.com/opentofu/svchost/svcauth"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
|
||||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCredentialsForHost(t *testing.T) {
|
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
|
// a credentials helper program, since we're only testing the logic
|
||||||
// for choosing when to delegate to the helper here. The logic for
|
// for choosing when to delegate to the helper here. The logic for
|
||||||
// interacting with a helper program is tested in the svcauth package.
|
// interacting with a helper program is tested in the svcauth package.
|
||||||
helper: svcauth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
helper: readOnlyCredentialsStore{
|
||||||
"from-helper.example.com": {
|
svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
|
||||||
"token": "from-helper",
|
"from-helper.example.com": svcauth.HostCredentialsToken("from-helper"),
|
||||||
},
|
|
||||||
|
|
||||||
// This should be shadowed by the "configured" entry with the same
|
// This should be shadowed by the "configured" entry with the same
|
||||||
// hostname above.
|
// hostname above.
|
||||||
"configured.example.com": {
|
"configured.example.com": svcauth.HostCredentialsToken("incorrectly-from-helper"),
|
||||||
"token": "incorrectly-from-helper",
|
}),
|
||||||
},
|
},
|
||||||
}),
|
|
||||||
helperType: "fake",
|
helperType: "fake",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("configured", func(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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -71,7 +72,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("from helper", func(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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -80,7 +81,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("not available", func(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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -94,7 +95,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
expectedToken := "configured-by-env"
|
expectedToken := "configured-by-env"
|
||||||
t.Setenv(envName, expectedToken)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
}
|
}
|
||||||
@@ -103,7 +104,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Fatal("no credentials found")
|
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)
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -115,7 +116,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Setenv(envName, expectedToken)
|
t.Setenv(envName, expectedToken)
|
||||||
|
|
||||||
hostname, _ := svchost.ForComparison("env.ドメイン名例.com")
|
hostname, _ := svchost.ForComparison("env.ドメイン名例.com")
|
||||||
creds, err := credSrc.ForHost(hostname)
|
creds, err := credSrc.ForHost(t.Context(), hostname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
@@ -125,7 +126,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Fatal("no credentials found")
|
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)
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -137,7 +138,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Setenv(envName, expectedToken)
|
t.Setenv(envName, expectedToken)
|
||||||
|
|
||||||
hostname, _ := svchost.ForComparison("env.café.fr")
|
hostname, _ := svchost.ForComparison("env.café.fr")
|
||||||
creds, err := credSrc.ForHost(hostname)
|
creds, err := credSrc.ForHost(t.Context(), hostname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
@@ -147,7 +148,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Fatal("no credentials found")
|
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)
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -159,7 +160,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Setenv(envName, expectedToken)
|
t.Setenv(envName, expectedToken)
|
||||||
|
|
||||||
hostname, _ := svchost.ForComparison("configured.example.com")
|
hostname, _ := svchost.ForComparison("configured.example.com")
|
||||||
creds, err := credSrc.ForHost(hostname)
|
creds, err := credSrc.ForHost(t.Context(), hostname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
@@ -169,7 +170,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Fatal("no credentials found")
|
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)
|
t.Errorf("wrong result\ngot: %s\nwant: %s", got, expectedToken)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -181,7 +182,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Setenv(envName, expectedToken)
|
t.Setenv(envName, expectedToken)
|
||||||
|
|
||||||
hostname, _ := svchost.ForComparison("configureduppercase.example.com")
|
hostname, _ := svchost.ForComparison("configureduppercase.example.com")
|
||||||
creds, err := credSrc.ForHost(hostname)
|
creds, err := credSrc.ForHost(t.Context(), hostname)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %s", err)
|
t.Fatalf("unexpected error: %s", err)
|
||||||
@@ -191,7 +192,7 @@ func TestCredentialsForHost(t *testing.T) {
|
|||||||
t.Fatal("no credentials found")
|
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)
|
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.
|
// Otherwise downstream tests might fail in confusing ways.
|
||||||
{
|
{
|
||||||
err := credSrc.StoreForHost(
|
err := credSrc.StoreForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("manually-configured.example.com"),
|
svchost.Hostname("manually-configured.example.com"),
|
||||||
svcauth.HostCredentialsToken("not-manually-configured"),
|
svcauth.HostCredentialsToken("not-manually-configured"),
|
||||||
)
|
)
|
||||||
@@ -251,6 +253,7 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
err := credSrc.ForgetForHost(
|
err := credSrc.ForgetForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("manually-configured.example.com"),
|
svchost.Hostname("manually-configured.example.com"),
|
||||||
)
|
)
|
||||||
if err == nil {
|
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
|
// We don't have a credentials file at all yet, so this first call
|
||||||
// must create it.
|
// must create it.
|
||||||
err := credSrc.StoreForHost(
|
err := credSrc.StoreForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("stored-locally.example.com"),
|
svchost.Hostname("stored-locally.example.com"),
|
||||||
svcauth.HostCredentialsToken("stored-locally"),
|
svcauth.HostCredentialsToken("stored-locally"),
|
||||||
)
|
)
|
||||||
@@ -271,7 +275,7 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error storing locally: %s", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to read back stored-locally credentials: %s", err)
|
t.Fatalf("failed to read back stored-locally credentials: %s", err)
|
||||||
}
|
}
|
||||||
@@ -303,6 +307,7 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
)
|
)
|
||||||
{
|
{
|
||||||
err := credSrc.StoreForHost(
|
err := credSrc.StoreForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("manually-configured.example.com"),
|
svchost.Hostname("manually-configured.example.com"),
|
||||||
svcauth.HostCredentialsToken("not-manually-configured"),
|
svcauth.HostCredentialsToken("not-manually-configured"),
|
||||||
)
|
)
|
||||||
@@ -312,6 +317,7 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
err := credSrc.StoreForHost(
|
err := credSrc.StoreForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("stored-in-helper.example.com"),
|
svchost.Hostname("stored-in-helper.example.com"),
|
||||||
svcauth.HostCredentialsToken("stored-in-helper"),
|
svcauth.HostCredentialsToken("stored-in-helper"),
|
||||||
)
|
)
|
||||||
@@ -319,7 +325,7 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error storing in helper: %s", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to read back stored-in-helper credentials: %s", err)
|
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
|
// Because stored-locally is already in the credentials file, a new
|
||||||
// store should be sent there rather than to the credentials helper.
|
// store should be sent there rather than to the credentials helper.
|
||||||
err := credSrc.StoreForHost(
|
err := credSrc.StoreForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("stored-locally.example.com"),
|
svchost.Hostname("stored-locally.example.com"),
|
||||||
svcauth.HostCredentialsToken("stored-locally-again"),
|
svcauth.HostCredentialsToken("stored-locally-again"),
|
||||||
)
|
)
|
||||||
@@ -348,7 +355,7 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error storing locally again: %s", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to read back stored-locally credentials: %s", err)
|
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
|
// Forgetting a host already in the credentials file should remove it
|
||||||
// from the credentials file, not from the helper.
|
// from the credentials file, not from the helper.
|
||||||
err := credSrc.ForgetForHost(
|
err := credSrc.ForgetForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("stored-locally.example.com"),
|
svchost.Hostname("stored-locally.example.com"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error forgetting locally: %s", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to read back stored-locally credentials: %s", err)
|
t.Fatalf("failed to read back stored-locally credentials: %s", err)
|
||||||
}
|
}
|
||||||
@@ -385,13 +393,14 @@ func TestCredentialsStoreForget(t *testing.T) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
err := credSrc.ForgetForHost(
|
err := credSrc.ForgetForHost(
|
||||||
|
t.Context(),
|
||||||
svchost.Hostname("stored-in-helper.example.com"),
|
svchost.Hostname("stored-in-helper.example.com"),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error forgetting in helper: %s", err)
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("failed to read back stored-in-helper credentials: %s", err)
|
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
|
// Assertion that mockCredentialsHelper implements svcauth.CredentialsSource
|
||||||
var _ svcauth.CredentialsSource = (*mockCredentialsHelper)(nil)
|
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]
|
v, ok := s.current[hostname]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
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{
|
s.log = append(s.log, mockCredentialsHelperChange{
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
Action: "store",
|
Action: "store",
|
||||||
@@ -451,7 +460,7 @@ func (s *mockCredentialsHelper) StoreForHost(hostname svchost.Hostname, new svca
|
|||||||
return nil
|
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{
|
s.log = append(s.log, mockCredentialsHelperChange{
|
||||||
Host: hostname,
|
Host: hostname,
|
||||||
Action: "forget",
|
Action: "forget",
|
||||||
@@ -459,3 +468,29 @@ func (s *mockCredentialsHelper) ForgetForHost(hostname svchost.Hostname) error {
|
|||||||
delete(s.current, hostname)
|
delete(s.current, hostname)
|
||||||
return nil
|
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"
|
hcltoken "github.com/hashicorp/hcl/hcl/token"
|
||||||
hcl2 "github.com/hashicorp/hcl/v2"
|
hcl2 "github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"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"
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
"github.com/zclconf/go-cty/cty/convert"
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/getproviders"
|
"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"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/disco"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
otelAttr "go.opentelemetry.io/otel/attribute"
|
otelAttr "go.opentelemetry.io/otel/attribute"
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
tfe "github.com/hashicorp/go-tfe"
|
tfe "github.com/hashicorp/go-tfe"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
"github.com/opentofu/svchost/disco"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/svcauth"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
"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.)
|
// working as expected. (Perhaps the normalization is part of the cause.)
|
||||||
dispHostname := hostname.ForDisplay()
|
dispHostname := hostname.ForDisplay()
|
||||||
|
|
||||||
host, err := c.Services.Discover(hostname)
|
host, err := c.Services.Discover(ctx, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
@@ -220,7 +220,7 @@ func (c *LoginCommand) Run(args []string) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
err = creds.StoreForHost(hostname, token)
|
err = creds.StoreForHost(ctx, hostname, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
|
|||||||
@@ -13,16 +13,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/opentofu/svchost"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost/disco"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
"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"
|
oauthserver "github.com/opentofu/opentofu/internal/command/testdata/login-oauth-server"
|
||||||
tfeserver "github.com/opentofu/opentofu/internal/command/testdata/login-tfe-server"
|
tfeserver "github.com/opentofu/opentofu/internal/command/testdata/login-tfe-server"
|
||||||
"github.com/opentofu/opentofu/internal/command/webbrowser"
|
"github.com/opentofu/opentofu/internal/command/webbrowser"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
"github.com/opentofu/opentofu/internal/httpclient"
|
||||||
"github.com/opentofu/opentofu/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogin(t *testing.T) {
|
func TestLogin(t *testing.T) {
|
||||||
@@ -57,8 +56,10 @@ func TestLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
creds := cliconfig.EmptyCredentialsSourceForTests(filepath.Join(workDir, "credentials.tfrc.json"))
|
creds := cliconfig.EmptyCredentialsSourceForTests(filepath.Join(workDir, "credentials.tfrc.json"))
|
||||||
svcs := disco.NewWithCredentialsSource(creds)
|
svcs := disco.New(
|
||||||
svcs.SetUserAgent(httpclient.OpenTofuUserAgent(version.String()))
|
disco.WithCredentials(creds),
|
||||||
|
disco.WithHTTPClient(httpclient.New(t.Context())),
|
||||||
|
)
|
||||||
|
|
||||||
svcs.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{
|
svcs.ForceHostServices(svchost.Hostname("example.com"), map[string]interface{}{
|
||||||
"login.v1": map[string]interface{}{
|
"login.v1": map[string]interface{}{
|
||||||
@@ -135,11 +136,11 @@ func TestLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
credsSrc := c.Services.CredentialsSource()
|
credsSrc := c.Services.CredentialsSource()
|
||||||
creds, err := credsSrc.ForHost(svchost.Hostname(tfeHost))
|
creds, err := credsSrc.ForHost(t.Context(), svchost.Hostname(tfeHost))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to retrieve credentials: %s", err)
|
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)
|
t.Errorf("wrong token %q; want %q", got, want)
|
||||||
}
|
}
|
||||||
if got, want := ui.OutputWriter.String(), "Welcome to the cloud backend!"; !strings.Contains(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()
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to retrieve credentials: %s", err)
|
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)
|
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) {
|
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")
|
client, _ := host.ServiceOAuthClient("login.v1")
|
||||||
if len(client.Scopes) != 0 {
|
if len(client.Scopes) != 0 {
|
||||||
t.Errorf("unexpected scopes %q; expected none", client.Scopes)
|
t.Errorf("unexpected scopes %q; expected none", client.Scopes)
|
||||||
@@ -191,13 +192,13 @@ func TestLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
credsSrc := c.Services.CredentialsSource()
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to retrieve credentials: %s", err)
|
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)
|
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) {
|
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")
|
client, _ := host.ServiceOAuthClient("login.v1")
|
||||||
|
|
||||||
expectedScopes := [2]string{"app1.full_access", "app2.read_only"}
|
expectedScopes := [2]string{"app1.full_access", "app2.read_only"}
|
||||||
@@ -234,11 +235,11 @@ func TestLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
credsSrc := c.Services.CredentialsSource()
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to retrieve credentials: %s", err)
|
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)
|
t.Errorf("wrong token %q; want %q", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,12 +260,12 @@ func TestLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
credsSrc := c.Services.CredentialsSource()
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to retrieve credentials: %s", err)
|
t.Errorf("failed to retrieve credentials: %s", err)
|
||||||
}
|
}
|
||||||
if creds != nil {
|
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))
|
}, true))
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
"github.com/opentofu/opentofu/internal/command/cliconfig"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
@@ -24,6 +24,8 @@ type LogoutCommand struct {
|
|||||||
|
|
||||||
// Run implements cli.Command.
|
// Run implements cli.Command.
|
||||||
func (c *LogoutCommand) Run(args []string) int {
|
func (c *LogoutCommand) Run(args []string) int {
|
||||||
|
ctx := c.CommandContext()
|
||||||
|
|
||||||
args = c.Meta.process(args)
|
args = c.Meta.process(args)
|
||||||
cmdFlags := c.Meta.defaultFlagSet("logout")
|
cmdFlags := c.Meta.defaultFlagSet("logout")
|
||||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
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))
|
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 {
|
if err != nil {
|
||||||
diags = diags.Append(tfdiags.Sourceless(
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
tfdiags.Error,
|
tfdiags.Error,
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mitchellh/cli"
|
"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"
|
||||||
|
"github.com/opentofu/opentofu/internal/command/cliconfig/svcauthconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogout(t *testing.T) {
|
func TestLogout(t *testing.T) {
|
||||||
@@ -26,8 +27,10 @@ func TestLogout(t *testing.T) {
|
|||||||
|
|
||||||
c := &LogoutCommand{
|
c := &LogoutCommand{
|
||||||
Meta: Meta{
|
Meta: Meta{
|
||||||
Ui: ui,
|
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 {
|
for _, tc := range testCases {
|
||||||
host := svchost.Hostname(tc.hostname)
|
host := svchost.Hostname(tc.hostname)
|
||||||
token := svcauth.HostCredentialsToken("some-token")
|
token := svcauth.HostCredentialsToken("some-token")
|
||||||
err := credsSrc.StoreForHost(host, token)
|
err := credsSrc.StoreForHost(t.Context(), host, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error storing credentials: %s", err)
|
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())
|
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 {
|
if err != nil {
|
||||||
t.Errorf("failed to retrieve credentials: %s", err)
|
t.Errorf("failed to retrieve credentials: %s", err)
|
||||||
}
|
}
|
||||||
if tc.shouldRemove {
|
if tc.shouldRemove {
|
||||||
if creds != nil {
|
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 {
|
} 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)
|
t.Errorf("wrong token %q; want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
|
"github.com/opentofu/svchost/disco"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/backend"
|
"github.com/opentofu/opentofu/internal/backend"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
|
|
||||||
version "github.com/hashicorp/go-version"
|
version "github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/depsfile"
|
"github.com/opentofu/opentofu/internal/depsfile"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
svchost "github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/getproviders"
|
"github.com/opentofu/opentofu/internal/getproviders"
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import (
|
|||||||
|
|
||||||
"github.com/apparentlymart/go-versions/versions"
|
"github.com/apparentlymart/go-versions/versions"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
"github.com/opentofu/svchost/svcauth"
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"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
|
// It might return an error if the mirror base URL is invalid, or if the
|
||||||
// credentials lookup itself fails.
|
// 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()
|
hostname, err := s.mirrorHost()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid provider mirror base URL %s: %w", s.baseURL.String(), err)
|
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 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.
|
// 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 = req.WithContext(ctx)
|
||||||
req.Request.Header.Set(terraformVersionHeader, version.String())
|
req.Request.Header.Set(terraformVersionHeader, version.String())
|
||||||
creds, err := s.mirrorHostCredentials()
|
creds, err := s.mirrorHostCredentials(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, endpointURL, fmt.Errorf("failed to determine request credentials: %w", err)
|
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/google/go-cmp/cmp"
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
"github.com/opentofu/svchost/svcauth"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
@@ -33,10 +33,8 @@ func TestHTTPMirrorSource(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("httptest.NewTLSServer returned a server with an invalid URL")
|
t.Fatalf("httptest.NewTLSServer returned a server with an invalid URL")
|
||||||
}
|
}
|
||||||
creds := svcauth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
creds := svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
|
||||||
svchost.Hostname(baseURL.Host): {
|
svchost.Hostname(baseURL.Host): svcauth.HostCredentialsToken("placeholder-token"),
|
||||||
"token": "placeholder-token",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
retryHTTPClient := retryablehttp.NewClient()
|
retryHTTPClient := retryablehttp.NewClient()
|
||||||
retryHTTPClient.HTTPClient = httpClient
|
retryHTTPClient.HTTPClient = httpClient
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
|
||||||
ociDigest "github.com/opencontainers/go-digest"
|
ociDigest "github.com/opencontainers/go-digest"
|
||||||
ociSpecs "github.com/opencontainers/image-spec/specs-go"
|
ociSpecs "github.com/opencontainers/image-spec/specs-go"
|
||||||
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/opentofu/svchost"
|
||||||
orasContent "oras.land/oras-go/v2/content"
|
orasContent "oras.land/oras-go/v2/content"
|
||||||
orasOCI "oras.land/oras-go/v2/content/oci"
|
orasOCI "oras.land/oras-go/v2/content/oci"
|
||||||
orasErrors "oras.land/oras-go/v2/errdef"
|
orasErrors "oras.land/oras-go/v2/errdef"
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
svcauth "github.com/hashicorp/terraform-svchost/auth"
|
"github.com/opentofu/svchost/svcauth"
|
||||||
otelAttr "go.opentelemetry.io/otel/attribute"
|
otelAttr "go.opentelemetry.io/otel/attribute"
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|||||||
@@ -16,8 +16,9 @@ import (
|
|||||||
|
|
||||||
"github.com/apparentlymart/go-versions/versions"
|
"github.com/apparentlymart/go-versions/versions"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
disco "github.com/hashicorp/terraform-svchost/disco"
|
disco "github.com/opentofu/svchost/disco"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
disco "github.com/hashicorp/terraform-svchost/disco"
|
disco "github.com/opentofu/svchost/disco"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
"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) {
|
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 {
|
if err != nil {
|
||||||
return nil, ErrHostUnreachable{
|
return nil, ErrHostUnreachable{
|
||||||
Hostname: hostname,
|
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.
|
// 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 {
|
if err != nil {
|
||||||
// This indicates that a credentials helper failed, which means we
|
// This indicates that a credentials helper failed, which means we
|
||||||
// can't do anything better than just pass through the helper's
|
// can't do anything better than just pass through the helper's
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
tfaddr "github.com/opentofu/registry-address"
|
|
||||||
|
|
||||||
"github.com/apparentlymart/go-versions/versions"
|
"github.com/apparentlymart/go-versions/versions"
|
||||||
"github.com/google/go-cmp/cmp"
|
"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"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
@@ -142,7 +141,7 @@ func TestSourcePackageMeta(t *testing.T) {
|
|||||||
[]SigningKey{
|
[]SigningKey{
|
||||||
{ASCIIArmor: TestingPublicKey},
|
{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/go-test/deep"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
version "github.com/hashicorp/go-version"
|
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/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/configs"
|
"github.com/opentofu/opentofu/internal/configs"
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ import (
|
|||||||
"github.com/apparentlymart/go-versions/versions/constraints"
|
"github.com/apparentlymart/go-versions/versions/constraints"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/disco"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
"github.com/opentofu/opentofu/internal/depsfile"
|
"github.com/opentofu/opentofu/internal/depsfile"
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/disco"
|
||||||
otelAttr "go.opentelemetry.io/otel/attribute"
|
otelAttr "go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"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.
|
// Discover queries the host, and returns the url for the registry.
|
||||||
func (c *Client) Discover(host svchost.Hostname, serviceID string) (*url.URL, error) {
|
func (c *Client) Discover(ctx context.Context, host svchost.Hostname, serviceID string) (*url.URL, error) {
|
||||||
service, err := c.services.DiscoverServiceURL(host, serviceID)
|
service, err := c.services.DiscoverServiceURL(ctx, host, serviceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &ServiceUnreachableError{err}
|
return nil, &ServiceUnreachableError{err}
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func (c *Client) ModuleVersions(ctx context.Context, module *regsrc.Module) (*re
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
service, err := c.Discover(host, modulesServiceID)
|
service, err := c.Discover(ctx, host, modulesServiceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ func (c *Client) ModuleVersions(ctx context.Context, module *regsrc.Module) (*re
|
|||||||
}
|
}
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
c.addRequestCreds(host, req.Request)
|
c.addRequestCreds(ctx, host, req.Request)
|
||||||
req.Header.Set(xTerraformVersion, tfVersion)
|
req.Header.Set(xTerraformVersion, tfVersion)
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
@@ -150,8 +150,8 @@ func (c *Client) ModuleVersions(ctx context.Context, module *regsrc.Module) (*re
|
|||||||
return &versions, nil
|
return &versions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) addRequestCreds(host svchost.Hostname, req *http.Request) {
|
func (c *Client) addRequestCreds(ctx context.Context, host svchost.Hostname, req *http.Request) {
|
||||||
creds, err := c.services.CredentialsForHost(host)
|
creds, err := c.services.CredentialsForHost(ctx, host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err)
|
log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err)
|
||||||
return
|
return
|
||||||
@@ -179,7 +179,7 @@ func (c *Client) ModuleLocation(ctx context.Context, module *regsrc.Module, vers
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
service, err := c.Discover(host, modulesServiceID)
|
service, err := c.Discover(ctx, host, modulesServiceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ func (c *Client) ModuleLocation(ctx context.Context, module *regsrc.Module, vers
|
|||||||
|
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
c.addRequestCreds(host, req.Request)
|
c.addRequestCreds(ctx, host, req.Request)
|
||||||
req.Header.Set(xTerraformVersion, tfVersion)
|
req.Header.Set(xTerraformVersion, tfVersion)
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
|
|||||||
@@ -18,13 +18,12 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-retryablehttp"
|
"github.com/hashicorp/go-retryablehttp"
|
||||||
version "github.com/hashicorp/go-version"
|
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/httpclient"
|
||||||
"github.com/opentofu/opentofu/internal/registry/regsrc"
|
"github.com/opentofu/opentofu/internal/registry/regsrc"
|
||||||
"github.com/opentofu/opentofu/internal/registry/response"
|
"github.com/opentofu/opentofu/internal/registry/response"
|
||||||
"github.com/opentofu/opentofu/internal/registry/test"
|
"github.com/opentofu/opentofu/internal/registry/test"
|
||||||
tfversion "github.com/opentofu/opentofu/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLookupModuleVersions(t *testing.T) {
|
func TestLookupModuleVersions(t *testing.T) {
|
||||||
@@ -150,8 +149,9 @@ func TestAccLookupModuleVersions(t *testing.T) {
|
|||||||
if os.Getenv("TF_ACC") == "" {
|
if os.Getenv("TF_ACC") == "" {
|
||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
regDisco := disco.New()
|
regDisco := disco.New(
|
||||||
regDisco.SetUserAgent(httpclient.OpenTofuUserAgent(tfversion.String()))
|
disco.WithHTTPClient(httpclient.New(t.Context())),
|
||||||
|
)
|
||||||
|
|
||||||
// test with and without a hostname
|
// test with and without a hostname
|
||||||
for _, src := range []string{
|
for _, src := range []string{
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ package registry
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/disco"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/registry/regsrc"
|
"github.com/opentofu/opentofu/internal/registry/regsrc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
svchost "github.com/hashicorp/terraform-svchost"
|
"github.com/opentofu/svchost"
|
||||||
"github.com/hashicorp/terraform-svchost/auth"
|
"github.com/opentofu/svchost/disco"
|
||||||
"github.com/hashicorp/terraform-svchost/disco"
|
"github.com/opentofu/svchost/svcauth"
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
|
||||||
"github.com/opentofu/opentofu/internal/registry/regsrc"
|
"github.com/opentofu/opentofu/internal/registry/regsrc"
|
||||||
"github.com/opentofu/opentofu/internal/registry/response"
|
"github.com/opentofu/opentofu/internal/registry/response"
|
||||||
tfversion "github.com/opentofu/opentofu/version"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Disco return a *disco.Disco mapping registry.opentofu.org, localhost,
|
// 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),
|
"modules.v1": fmt.Sprintf("%s/v1/modules", s.URL),
|
||||||
"providers.v1": fmt.Sprintf("%s/v1/providers", s.URL),
|
"providers.v1": fmt.Sprintf("%s/v1/providers", s.URL),
|
||||||
}
|
}
|
||||||
d := disco.NewWithCredentialsSource(credsSrc)
|
d := disco.New(
|
||||||
d.SetUserAgent(httpclient.OpenTofuUserAgent(tfversion.String()))
|
disco.WithCredentials(credsSrc),
|
||||||
|
disco.WithHTTPClient(s.Client()),
|
||||||
|
)
|
||||||
|
|
||||||
d.ForceHostServices(svchost.Hostname("registry.opentofu.org"), services)
|
d.ForceHostServices(svchost.Hostname("registry.opentofu.org"), services)
|
||||||
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
d.ForceHostServices(svchost.Hostname("localhost"), services)
|
||||||
@@ -58,8 +59,8 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
regHost = svchost.Hostname(regsrc.PublicRegistryHost.Normalized())
|
regHost = svchost.Hostname(regsrc.PublicRegistryHost.Normalized())
|
||||||
credsSrc = auth.StaticCredentialsSource(map[svchost.Hostname]map[string]interface{}{
|
credsSrc = svcauth.StaticCredentialsSource(map[svchost.Hostname]svcauth.HostCredentials{
|
||||||
regHost: {"token": testCred},
|
regHost: svcauth.HostCredentialsToken(testCred),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
|
regaddr "github.com/opentofu/registry-address/v2"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/addrs"
|
"github.com/opentofu/opentofu/internal/addrs"
|
||||||
@@ -20,7 +21,6 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/states/statefile"
|
"github.com/opentofu/opentofu/internal/states/statefile"
|
||||||
"github.com/opentofu/opentofu/internal/states/statemgr"
|
"github.com/opentofu/opentofu/internal/states/statemgr"
|
||||||
"github.com/opentofu/opentofu/version"
|
"github.com/opentofu/opentofu/version"
|
||||||
tfaddr "github.com/opentofu/registry-address"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestState_impl(t *testing.T) {
|
func TestState_impl(t *testing.T) {
|
||||||
@@ -103,7 +103,7 @@ func TestStatePersist(t *testing.T) {
|
|||||||
Status: states.ObjectReady,
|
Status: states.ObjectReady,
|
||||||
},
|
},
|
||||||
addrs.AbsProviderConfig{
|
addrs.AbsProviderConfig{
|
||||||
Provider: tfaddr.Provider{Namespace: "local"},
|
Provider: regaddr.Provider{Namespace: "local"},
|
||||||
},
|
},
|
||||||
addrs.NoKey,
|
addrs.NoKey,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"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/configs"
|
||||||
"github.com/opentofu/opentofu/internal/getproviders"
|
"github.com/opentofu/opentofu/internal/getproviders"
|
||||||
"github.com/opentofu/opentofu/internal/states"
|
"github.com/opentofu/opentofu/internal/states"
|
||||||
@@ -66,8 +67,8 @@ func MigrateStateProviderAddresses(config *configs.Config, state *states.State)
|
|||||||
for _, module := range stateCopy.Modules {
|
for _, module := range stateCopy.Modules {
|
||||||
for _, resource := range module.Resources {
|
for _, resource := range module.Resources {
|
||||||
_, referencedInConfig := providers[resource.ProviderConfig.Provider]
|
_, referencedInConfig := providers[resource.ProviderConfig.Provider]
|
||||||
if resource.ProviderConfig.Provider.Hostname == "registry.terraform.io" && !referencedInConfig {
|
if resource.ProviderConfig.Provider.Hostname == regaddr.TransitionalDefaultProviderRegistryHost && !referencedInConfig {
|
||||||
resource.ProviderConfig.Provider.Hostname = tfaddr.DefaultProviderRegistryHost
|
resource.ProviderConfig.Provider.Hostname = addrs.DefaultProviderRegistryHost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user