diff --git a/internal/client/client.go b/internal/client/client.go index 7d4a0373a..0e2357c86 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -74,6 +74,22 @@ func getTokenFromCreds(services *disco.Disco, hostname svchost.Hostname) string return "" } +// TFE Client along with other necessary information for the provider to run it +type ProviderClient struct { + TfeClient *tfe.Client + tokenSource tokenSource +} + +// Using presence of TFC_AGENT_VERSION to determine if this provider is running on HCP Terraform / enterprise +func providerRunningInCloud() bool { + _, envVariablePresent := os.LookupEnv("TFC_AGENT_VERSION") + return envVariablePresent +} + +func (pc *ProviderClient) SendAuthenticationWarning() bool { + return pc.tokenSource == credentialFiles && providerRunningInCloud() +} + // GetClient encapsulates the logic for configuring a go-tfe client instance for // the provider, including fallback to values from environment variables. This // is useful because we're muxing multiple provider servers together and each @@ -81,7 +97,7 @@ func getTokenFromCreds(services *disco.Disco, hostname svchost.Hostname) string // // Internally, this function caches configured clients using the specified // parameters -func GetClient(tfeHost, token string, insecure bool) (*tfe.Client, error) { +func GetClient(tfeHost, token string, insecure bool) (*ProviderClient, error) { config, err := configure(tfeHost, token, insecure) if err != nil { return nil, err @@ -93,7 +109,7 @@ func GetClient(tfeHost, token string, insecure bool) (*tfe.Client, error) { // Try to retrieve the client from cache cached := clientCache.GetByConfig(config) if cached != nil { - return cached, nil + return &ProviderClient{cached, config.tokenSource}, nil } // Discover the Terraform Enterprise address. @@ -157,7 +173,7 @@ func GetClient(tfeHost, token string, insecure bool) (*tfe.Client, error) { client.RetryServerErrors(true) clientCache.Set(client, config) - return client, nil + return &ProviderClient{client, config.tokenSource}, nil } // CheckConstraints checks service version constrains against our own diff --git a/internal/client/client_test.go b/internal/client/client_test.go index 3cc4afcd1..bbef2f654 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -138,7 +138,8 @@ credentials "%s" { t.Setenv(k, v) } // Must always skip SSL verification for this test server - client, err := GetClient(c.hostname, c.token, true) + providerClient, err := GetClient(c.hostname, c.token, true) + client := providerClient.TfeClient if c.expectMissingAuth { if !errors.Is(err, ErrMissingAuthToken) { t.Errorf("Expected ErrMissingAuthToken, got %v", err) diff --git a/internal/client/config.go b/internal/client/config.go index e4e879361..c0237c7d4 100644 --- a/internal/client/config.go +++ b/internal/client/config.go @@ -41,13 +41,22 @@ type ConfigHost struct { Services map[string]interface{} `hcl:"services"` } -// ClientConfiguration is the refined information needed to configure a tfe.Client +type tokenSource int + +const ( + providerArgument tokenSource = iota + environmentVariable + credentialFiles +) + +// ClientConfiguration is the refined information needed to configureClient a tfe.Client type ClientConfiguration struct { - Services *disco.Disco - HTTPClient *http.Client - TFEHost svchost.Hostname - Token string - Insecure bool + Services *disco.Disco + HTTPClient *http.Client + TFEHost svchost.Hostname + Token string + tokenSource tokenSource + Insecure bool } // Key returns a string that is comparable to other ClientConfiguration values @@ -232,11 +241,15 @@ func configure(tfeHost, token string, insecure bool) (*ClientConfiguration, erro // If a token wasn't set in the provider configuration block, try and fetch it // from the environment or from Terraform's CLI configuration or configured credential helper. + + tokenSource := providerArgument if token == "" { if os.Getenv("TFE_TOKEN") != "" { token = getTokenFromEnv() + tokenSource = environmentVariable } else { token = getTokenFromCreds(services, hostname) + tokenSource = credentialFiles } } @@ -246,10 +259,11 @@ func configure(tfeHost, token string, insecure bool) (*ClientConfiguration, erro } return &ClientConfiguration{ - Services: services, - HTTPClient: httpClient, - TFEHost: hostname, - Token: token, - Insecure: insecure, + Services: services, + HTTPClient: httpClient, + TFEHost: hostname, + Token: token, + tokenSource: tokenSource, + Insecure: insecure, }, nil } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 6637a0abd..1c6f0405a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -153,19 +153,31 @@ func configure() schema.ConfigureContextFunc { providerOrganization = os.Getenv("TFE_ORGANIZATION") } - tfeClient, err := configureClient(rd) + providerClient, err := configureClient(rd) if err != nil { return nil, diag.FromErr(err) } + var diagnosticWarnings diag.Diagnostics = nil + + if providerClient.SendAuthenticationWarning() { + diagnosticWarnings = diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Authentication with configuration files is invalid for TFE Provider running on HCP Terraform or Terraform Enterprise", + Detail: "Use a TFE_TOKEN variable in the workspace or the token argument for the provider. This authentication method will be deprecated in a future version.", + }, + } + } + return ConfiguredClient{ - tfeClient, + providerClient.TfeClient, providerOrganization, - }, nil + }, diagnosticWarnings } } -func configureClient(d *schema.ResourceData) (*tfe.Client, error) { +func configureClient(d *schema.ResourceData) (*client.ProviderClient, error) { hostname := d.Get("hostname").(string) token := d.Get("token").(string) insecure := d.Get("ssl_skip_verify").(bool) diff --git a/internal/provider/provider_next.go b/internal/provider/provider_next.go index 0102d5d4d..80fbad19d 100644 --- a/internal/provider/provider_next.go +++ b/internal/provider/provider_next.go @@ -111,15 +111,19 @@ func (p *frameworkProvider) Configure(ctx context.Context, req provider.Configur } } - tfeClient, err := client.GetClient(data.Hostname.ValueString(), data.Token.ValueString(), data.SSLSkipVerify.ValueBool()) + providerClient, err := client.GetClient(data.Hostname.ValueString(), data.Token.ValueString(), data.SSLSkipVerify.ValueBool()) if err != nil { res.Diagnostics.AddError("Failed to initialize HTTP client", err.Error()) return } + if providerClient.SendAuthenticationWarning() { + res.Diagnostics.AddWarning("Authentication with configuration files is invalid for TFE Provider running on HCP Terraform or Terraform Enterprise", "Use a TFE_TOKEN variable in the workspace or the token argument for the provider. This authentication method will be deprecated in a future version.") + } + configuredClient := ConfiguredClient{ - Client: tfeClient, + Client: providerClient.TfeClient, Organization: data.Organization.ValueString(), } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index 1025e9951..9b3791a75 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -150,11 +150,11 @@ func getClientUsingEnv() (*tfe.Client, error) { } token := os.Getenv("TFE_TOKEN") - tfeClient, err := client.GetClient(hostname, token, defaultSSLSkipVerify) + providerClient, err := client.GetClient(hostname, token, defaultSSLSkipVerify) if err != nil { return nil, fmt.Errorf("Error getting client: %w", err) } - return tfeClient, nil + return providerClient.TfeClient, nil } func TestProvider(t *testing.T) {