Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,30 @@ 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
// one needs an identically configured client.
//
// 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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
36 changes: 25 additions & 11 deletions internal/client/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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
}
20 changes: 16 additions & 4 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 6 additions & 2 deletions internal/provider/provider_next.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}

Expand Down
4 changes: 2 additions & 2 deletions internal/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down