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

terraform providers lock command don't honour the .terraformrc file #35106

Open
carlitos081 opened this issue May 1, 2024 · 4 comments
Open
Labels
enhancement new new issue not yet triaged

Comments

@carlitos081
Copy link

Terraform Version

Terraform v1.7.4
on darwin_amd64


### Terraform Configuration Files

My `providers.tf`
```terraform
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "3.61.0"
    }
  }
}

my .terraformrc

provider_installation {
    direct {
        exclude = ["registry.terraform.io/*/*"]
    }
    network_mirror {
        url = "https://artifactory.my-company.com/artifactory/api/terraform/terraform-virtual-dev/providers/"
    }
}

Debug Output

https://gist.github.com/carlitos081/1744aa46e0d73b56aa3e6c52a292bfbf
https://gist.github.com/carlitos081/8ea2d68ed52c4a4f08efa14e99946ef3

Expected Behavior

When I run terraform init I can see that the provider is downloaded from my network_mirror https://artifactory.my-company.com/artifactory/api/terraform/terraform-virtual-dev/providers/ see the first gist

When I run the terraform providers lock -platform=linux_amd64 it will go directly to https://registry.terraform.io/v1/providers/hashicorp/aws/versions instead of the mirror defined in .terraformrc

Actual Behavior

both terraform init and terraform providers lock -platform=linux_amd64 must honour the .terraformrc

Steps to Reproduce

  1. Create a providers.tf file as posted above
  2. export TF_LOG=trace
  3. Create the .terraformrc file as posted above and change to your own network_mirror
  4. run terraform init you will see it is honouring the network_mirror
  5. Run terraform providers lock -platform=linux_amd64 you will see it is to the public mirror https://registry.terraform.io/ instead

Additional Context

No response

References

No response

@carlitos081 carlitos081 added bug new new issue not yet triaged labels May 1, 2024
@carlitos081 carlitos081 changed the title terraform providers lock command ignore .terraformrc file terraform providers lock command don't honour the .terraformrc file May 1, 2024
@jbardin
Copy link
Member

jbardin commented May 1, 2024

Hi @carlitos081,

Thanks for filing the issue. Does the resulting lock file work as expected when you specify the mirror using the -net-mirror flag? In order to get all the published signature information, Terraform will by default contact the provider's origin registry.

@apparentlymart
Copy link
Member

apparentlymart commented May 1, 2024

Indeed, the original idea of this command was that it would intentionally ignore the provider installation configuration so that you can generate a dependency lock file containing the official release checksums and then use it to validate that your mirror was constructed correctly. This command is an "escape hatch" alternative because those who are using mirrors otherwise don't get the default behavior of terraform init checking directly against the upstream releases.

The -fs-mirror and -net-mirror options let you override that if you intend to treat the packages in your mirror as the authoritative "correct" checksums to use, but that's not the default because the primary use-case for filesystem and network mirrors is for them to contain exact copies of the official provider releases, and so the default assumes that you'd want to be able to verify that the mirror is correct.

Although I don't think this is a bug (Terraform behaved as it was designed to behave), it does seem to me that it could be reasonable for this command to offer a shorthand option -- mutually-exclusive with the existing fs-mirror/net-mirror options -- that just means "use the same installation methods that terraform init would have used", for situations where the author wants to treat their configured mirrors as the authority and just wants to use this command for its ability to calculate checksums for multiple platforms at once.

@mmxmb
Copy link

mmxmb commented May 1, 2024

I want to chime in on this issue since I looked into it in the past. It seems like not honoring Terraform CLI Config file is an explicit choice in the design of this particular command based on this comment:

// Unlike other commands, this command ignores the installation methods
// selected in the CLI configuration and instead chooses an installation
// method based on CLI options.
//
// This is so that folks who use a local mirror for everyday use can
// use this command to populate their lock files from upstream so
// subsequent "terraform init" calls can then verify the local mirror
// against the upstream checksums.
var source getproviders.Source
switch {
case fsMirrorDir != "":
source = getproviders.NewFilesystemMirrorSource(fsMirrorDir)
case netMirrorURL != "":
u, err := url.Parse(netMirrorURL)
if err != nil || u.Scheme != "https" {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid network mirror URL",
"The -net-mirror option requires a valid https: URL as the mirror base URL.",
))
c.showDiagnostics(diags)
return 1
}
source = getproviders.NewHTTPMirrorSource(u, c.Services.CredentialsSource())
default:
// With no special options we consult upstream registries directly,
// because that gives us the most information to produce as complete
// and portable as possible a lock entry.
source = getproviders.NewRegistrySource(c.Services)
}

Specifying the mirror URL explicitly using -net-mirror flag would probably solve the problem of the original author of this issue. However, is there a practical solution for when multiple installation methods are required? Here's a CLI config similar to what I use:

provider_installation {
    direct {
        exclude = ["registry.terraform.io/*/*", "some-provider-available-only-via-fs-mirror.corp.net/*/*"]
    }
    network_mirror {
        url = "https://artifactory.my-company.com/artifactory/api/terraform/terraform-virtual-dev/providers/"
    }
    filesystem_mirror {
        path = "/usr/share/terraform/plugins"
    }
}

Let's say I have a config that requires two providers: one can be installed from a filesystem mirror and the other one from a network mirror. Here is what I discovered about providers lock from experimenting with a config like this:

  • terraform providers lock - without any additional flag will fail since it assumes that all providers need to be installed from an official registry.
  • terraform providers lock -fs-mirror=/usr/share/terraform/plugins - this fails because Terraform now tries to install all providers from the filesystem mirror, but only one of the providers is available at that mirror.
  • terraform providers lock -fs-mirror=/usr/share/terraform/plugins some-provider-available-only-via-fs-mirror.corp.net/some/provider - will populate the lockfile for the provider that needs to be installed from the filesystem mirror.

After the last command, I will need to run terraform providers lock -network-mirror and explicitly list every single provider that needs to be installed from network mirror. If I don't explicitly list every single provider, it will try to install some-provider-available-only-via-fs-mirror.corp.net/some/provider which is not available via network mirror.

The fact that the command needs to run several times (once for each installation method) and that providers need to be specified explicitly is super impractical. Adding proper support for Terraform CLI config similar to every other command would make things a lot easier.

@apparentlymart
Copy link
Member

apparentlymart commented May 1, 2024

I don't intend the following as an argument for or against changing anything here. Just sharing some historical context about the design intent of these features, so that we can hopefully then talk about either ways in which the design assumptions were incorrect or how we could extend the functionality to support new use-cases that it wasn't originally intended for.

The main thing to keep in mind about the current design is that the word "mirror" is meant to imply "exact copies of packages from the official source", and so the design is shaped by that assumption. The original intended user of filesystem or network mirrors has the following setup:

  • In development environments there is no custom provider installation configuration at all. Configuration authors install packages directly from their upstreams, and check the generated lock file into version control.
  • In the production environment changes are made using centralised automation and that automation has a restrictive network connection that either has no internet connection at all (if managing systems accessible on the LAN) or has its connectivity limited to the extent that origin registries are not reachable.

In this "happy path", the mirror naturally gets checked against the signed checksums that the developers fetched during their work. If the mirror has a missing or modified package then it will be detected by terraform init due to mismatch with the lock file.

The terraform providers lock command is a pragmatic slight modification to the above model: sometimes even the development environments are using the internal mirrors, perhaps as a way to achieve development/production parity. In that case the developers don't naturally obtain the official checksums as part of their work, so instead they run terraform providers lock to build the lock file with the correct checksums and then the terraform init checks that the mirror is correct in the same way as it would in production. This allows problems with the mirror to be detected during the development phase rather than only in production.


This issue is implying a third situation that our mirror features were not designed to support: there are some provider packages that exist only in a mirror, and have no upstream origin registry and so therefore no "official" checksums (in the sense that Terraform thinks of "official", where the origin registry is the authority for its own namespace).

In that case (as described in the comment above) the terraform providers lock command fails trying to contact an origin registry that doesn't exist. It thinks its job is to find the official package checksums for the provider, but there are no such checksums to find and so it fails.

To make this work with today's Terraform, the provider packages would need to be published both in a provider registry and in the provider mirror, so that Terraform can verify that the mirror and the registry match. But if the mirror and the registry are both run by the same entity then that is essentially redundant: there is no separate "upstream authority" to check against.

The idea I offered in my earlier comment would work for organisations that wish to use their mirror as the authoritative source for all providers, and perhaps that compromise is sufficient.

A more general answer would be to allow some way to explicitly configure that a particular mirror is the authority for a specific set of providers and that there is not any upstream origin registry that the mirror is subordinate to. For example:

provider_installation {
  network_mirror {
    url = "https://tf.example.com/providers/"

    # This mirror is authoritative for
    # any providers whose hostname
    # is tf.example.com. There is no
    # upstream registry to compare
    # against.
    authority_for = ["tf.example.com/*/*"]

    # Since there is no include/exclude here
    # Terraform will also prefer to use this
    # mirror for installing any other providers
    # too, but will treat their origin registries
    # as authoritative for official checksums.
  }
}

In this hypothetical I'm imagining authority_for as a stronger version of include which essentially tells Terraform to treat the mirror as the origin registry for any matching provider addresses. Terraform would never try to use tf.example.com as a provider registry directly, even when using terraform providers lock. It would instead use the mirror to obtain any information that an origin registry would normally be considered the authority for, such as the "official checksums".

I don't know if this flexibility is actually needed. It might be sufficient to assume that if someone wants to treat their mirror as authoritative for anything then they want to treat it as the authority for everything, in which case I think my earlier idea of a new option on the lock command would be sufficient and considerably more straightforward.

@crw crw added enhancement and removed bug labels May 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement new new issue not yet triaged
Projects
None yet
Development

No branches or pull requests

5 participants