Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve discovery and loading of credentials from Terraform config files #360

Merged
merged 9 commits into from
Sep 10, 2021

Conversation

nfagerlund
Copy link
Member

@nfagerlund nfagerlund commented Sep 4, 2021

Description

This provider is somewhat special, because the API token it uses is also used
by Terraform CLI itself. We can give a nicer experience on the local CLI by
auto-discovering Terraform's copy of the token (if one isn't provided in the
provider config or the TFE_TOKEN environment variable).

However, we weren't quite matching Terraform's credential-finding behavior:

  • We were respecting the TERRAFORM_CONFIG environment variable for the main
    config file location, but not TF_CLI_CONFIG_FILE. (Terraform will use
    either, but prefers the latter since it matches the other environment variable
    names it uses.)
  • We weren't ever consulting the credentials file
    (~/.terraform.d/credentials.tfrc.json, on unix), which is written to by
    terraform login and/or credential helper scripts. The HCL library
    understands the credentials JSON, so you could work around this by pointing
    TERRAFORM_CONFIG at the credentials file, but if there was anything else
    important in the config file you also had to set TF_CLI_CONFIG_FILE so that
    Terraform (but not the provider) could still find it.

This commit fixes both of those: we'll accept a non-default config file location
via either environment variable (preferring TF_CLI_CONFIG_FILE), and we'll
also consult the credentials file if present.

If both sources include a token for the desired hostname, the provider will
follow Terraform's behavior and prefer the token from the CLI config file.

Testing plan

  1. Create a basic config that configures the tfe provider and maybe manages one resource. In the provider config, provide a hostname (if you aren't using app.terraform.io) but not a token.
  2. Ensure that your environment doesn't have any values set for TFE_TOKEN, TF_CLI_CONFIG_FILE, or TERRAFORM_CONFIG.
  3. Ensure that neither your ~/.terraformrc or ~/.terraform.d/credentials.tfrc.json files include a token for the configured TFE host, to start out with.
  4. Then:

Note: for the environment variable columns, "x" means "unset"; read any other value as "points to a loadable .terraformrc config file containing _____ for the configured hostname".

.terraformrc credentials json TF_CLI_ CONFIG_FILE TERRAFORM_ CONFIG Outcome
valid token no token x x Works (finds default config file)
no token valid token x x Works (finds credentials file, merges it with config file)
valid token bad token x x Works (config file beats credentials file)
no token no token valid token x Works (new env var works)
no token no token x valid token Works (old env var works)
bad token no token valid token x Works (env var beats config file)
no token bad token valid token x Works (env var beats credentials file)
no token no token valid token bad token Works (new env var beats old)
valid token no token no token x Fails (env var replaces config file instead of merging with it)
no token valid token no token x Works (credentials file is still merged with config file, even if config file is non-default)

Output from acceptance tests

See CI output in the checks below.

This provider is somewhat special, because the API token it uses is also used
by Terraform CLI itself. We can give a nicer experience on the local CLI by
auto-discovering Terraform's copy of the token (if one isn't provided in the
provider config or the `TFE_TOKEN` environment variable).

However, we weren't quite matching Terraform's credential-finding behavior:

- We were respecting the `TERRAFORM_CONFIG` environment variable for the main
  config file location, but not `TF_CLI_CONFIG_FILE`. (Terraform will use
  either, but prefers the latter since it matches the other environment variable
  names it uses.)
- We weren't ever consulting the credentials file
  (`~/.terraform.d/credentials.tfrc.json`, on unix), which is written to by
  `terraform login` and/or credential helper scripts. The HCL library
  understands the credentials JSON, so you could work around this by pointing
  `TERRAFORM_CONFIG` at the credentials file, but if there was anything else
  important in the config file you also had to set `TF_CLI_CONFIG_FILE` so that
  Terraform (but not the provider) could still find it.

This commit fixes both of those: we'll accept a non-default config file location
via either environment variable (preferring `TF_CLI_CONFIG_FILE`), and we'll
_also_ consult the credentials file if present.

If both sources include a token for the desired hostname, the provider will
follow Terraform's behavior and prefer the token from the CLI config file.
@nfagerlund nfagerlund requested a review from a team as a code owner September 4, 2021 00:45
Copy link
Member

@chrisarcand chrisarcand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM, though admittedly I don't have the time to properly go through the testing plan at the moment. Really really great PR!

tfe/provider.go Outdated

// There might be credentials in the main CLI config file (manually entered)
// AND/OR the credentials file (auto-configured by terraform login or a
// credentials helper), so we need to consult both. As per the behavior of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work with a credentials helper, no?

FWIW we could support them, the logic is here: https://github.com/hashicorp/terraform-svchost/blob/f050f53b97348772833a8f7fc1b297ec71f9fc7c/auth/helper_program.go

But this PR is already 💯 without all that, for sure 👍🏻 I'd just amend this comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! I thought credentials helpers used the credentials file as a cache, though now I've forgotten where I would have gotten that idea.

I'll just amend the comment to only refer to login. 👍🏼

tfe/provider.go Outdated Show resolved Hide resolved
Copy link
Contributor

@omarismail omarismail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionally this is great! My main concern is just adding tests.

I put an example here:

c23bbb5

tfe/provider.go Outdated
configFilePath := os.Getenv("TF_CLI_CONFIG_FILE")
if configFilePath == "" {
configFilePath = os.Getenv("TERRAFORM_CONFIG")
}
Copy link
Contributor

@omarismail omarismail Sep 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about refactoring this a tad bit for readability? I added an example here c23bbb5

The way the previous implementation combined the configs was
throwing away the Hosts member from the one Config that might have
anything in it. This makes the combining behavior a bit more explicit,
and also pulls out some wordy conditionals into a separate function per
Omar's suggestion.
More info about what this undocumented feature is used for over at
hashicorp/terraform#28309
hasCredentials := "test-fixtures/env/home"
noCredentials := "test-fixtures/env/empty"
terraformrc := "test-fixtures/env/terraformrc"
noTerraformrc := "test-fixtures/env/nothing"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know i'm the one who made this directory path, so I blame my older self 😆 . I think if I was looking at the directory path, i'd be confused what "env" referred to. Could we change these paths to be test-fixtures/tf-env-files/*

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And nit comment, could we be more specific for the empty and nothing? So like test-fixtures/tf-env-files/no-terraformrc and test-fixtures/tf-env-files/no-credentials

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏼 !

I went with test-fixtures/cli-config-files/* for the path name.

noCredentials := "test-fixtures/env/empty"
terraformrc := "test-fixtures/env/terraformrc"
noTerraformrc := "test-fixtures/env/nothing"
fromTerraformrc := "something.atlasv1.prod_rc_file"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit as well, s/fromTerraformrc/tokenFromTerraformrc

Copy link
Contributor

@omarismail omarismail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor nits, but overall amazing job! :shipit: once tests pass

@nfagerlund nfagerlund merged commit 2a079b9 into main Sep 10, 2021
@nfagerlund nfagerlund deleted the nf/sep21-core-credentials-file branch September 10, 2021 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants