hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.43k stars 9.51k forks source link

when requesting an exact prerelease version, the = operator cannot be used #32356

Closed bowtie-ltsa closed 1 year ago

bowtie-ltsa commented 1 year ago

Terraform Version

Terraform v1.3.6
on linux_amd64

Terraform Configuration Files

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

provider "aws" {
  alias  = "global_region"
  region = "us-east-1"
}

module "tf_next" {
  source  = "milliHQ/next-js/aws"
  version = "=1.0.0-canary.2"

  providers = {
    aws.global_region = aws.global_region
  }
}

Debug Output

https://gist.github.com/bowtie-ltsa/cb1ef0658cea1433e90531ab0472c066

Expected Behavior

the requested version should have been downloaded, according to my understanding of the docs

https://developer.hashicorp.com/terraform/language/expressions/version-constraints#version-constraint-behavior

image

Actual Behavior

actual behavior

Error: Unresolvable module version constraint

workaround:

to work-around this problem, you can remove the exact version operator (=) from the version attribute string --

this fails:

module "tf_next" {
  source  = "milliHQ/next-js/aws"
  version = "=1.0.0-canary.2"
  ...
}

this works:

module "tf_next" {
  source  = "milliHQ/next-js/aws"
  version = "1.0.0-canary.2"
  ...
}

proposed fix

change this line? https://github.com/hashicorp/terraform/blob/main/internal/initwd/module_install.go#L390 Because it is looking for an exact string match, ignoring the "=" operator that may be the first character of the version constraint. Right?

Steps to Reproduce

👉 just run terraform init with the above terraform, but to be more precise:

curl -o tf.zip https://releases.hashicorp.com/terraform/1.3.6/terraform_1.3.6_linux_amd64.zip
unzip tf.zip
chmod +x terraform

mkdir ex1
cd ex1

cat <<EOF > main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = "us-west-2"
}

provider "aws" {
  alias  = "global_region"
  region = "us-east-1"
}

module "tf_next" {
  source  = "milliHQ/next-js/aws"
  version = "=1.0.0-canary.2"

  providers = {
    aws.global_region = aws.global_region
  }
}
EOF

../terraform init

Additional Context

none. I reproduced this in an isolated simple container.

References

No response

liamcervante commented 1 year ago

Hi @bowtie-ltsa, thanks for reporting this! It does look like the docs and the actual functionality disagree. I'm not sure if this is because the docs are wrong or if the functionality is wrong. I'll look into this further and try and decide which.

apparentlymart commented 1 year ago

It does indeed seem like the implementation here is lacking a case to handle the use of the = operator explicitly. If we are going to add support for that then we should be sure to support it both with and without a space after it, because both are valid and equivalent version constraint syntax.

There are a couple different ways we could make this more general within the API of the underlying version constraints package:

  1. This code could dig around inside the IntersectionSpec that represents the version constraints, because getproviders.VersionConstraints is just an alias to that type. I guess the rule would be to check if there's exactly one selection spec, its operator is OpEqual, and its boundary is equal to the selected version.
  2. The code could use the top-level versions package's MeetingConstraints function and then test whether the requested version is in the resulting version set. MeetingConstraints has its own rule that prerelease versions are only available when selected exactly, so our caller could potentially just ask this constraints set about all of the versions and let the versions package be the one to filter out the prerelease versions when it's appropriate to do so.

I think the reason for this quirk is that a while back now we adopted a different library for wrangling version and version constraint strings, because the hashicorp/go-version one tends to generate poor error messages for invalid input and doesn't expose enough information for our installers to do their work. The provider installer uses the new library exclusively, but it's been longer since we did any significant work on the module installer and so it's still largely using hashicorp/go-version to do its work. However, this logic is mixing a version value using the old library with a version constraint using the new library, and so it's relying on string comparison to make this decision rather than using the MeetingConstraints API which already knows how to implement this properly.

The documentation about version constraints is trying to discuss both provider version constraints and module version constraints together, and is therefore assuming that they both behave the same way. I believe the quoted sentence is true for provider version constraints because they exclusively use the new versions library, but the module version constraint handling is currently a bit of a hack and so it's not implementing the behavior quite as specified.

I think option 2 above is the better choice if it's practical to use it, because it matches how the provider installer does similar things. The current mixing of the two different libraries for version wrangling might complicate things however. I would recommend against a broad rework of how the module installer deals with versions just to fix this bug -- I think better to amortize that risk with other possible modernizations of the module installer -- but perhaps there's a more localized way to deal with it.

github-actions[bot] commented 1 year ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.