-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
Lock module versions, like providers, in .terraform.lock.hcl #31301
Comments
Thanks for sharing this use-case, @jcarlson. I was initially tempted to merge this into #31134, because the challenges I described in a comment there are also why we didn't end up managing to include module dependencies in the lock file as we'd originally hoped, but on reflection it seems like the two use-cases are separate even though the underlying problems are not and so I'll just create a link between them with this comment instead. Using exact version constraints is indeed our recommended approach for now, but I think we would like to find a way to introduce more centralized management of module dependencies at some point, just that we need to find a design that is in some sense backward compatible with existing configurations due to the Terraform v1.0 Compatibility Promises. Thanks again! |
It feels like all the contracts are in place today to accommodate this. The existing provider "registry.terraform.io/hashicorp/aws" {
version = "3.74.3"
constraints = ">= 3.0.0, < 4.0.0"
hashes = [
"a hash of the provider binary",
]
}
module "my.module.address" {
version = "1.2.0"
constraints = "~> 1.2.0"
hashes = [
"a hash of the module source code"
]
} I'm not at all familiar with the internal working of Terraform, so pardon any ignorance here, but assuming such a feature were added to a future version of Terraform, say 1.4 or 1.5, this doesn't feel like it would be cause for significant backwards-compatibility problems. Older versions of Terraform would simply not respect locked versions of modules, much the same way Terraform 0.13 does not lock providers, but 0.14 does. |
What I mean to say is: in what way does adding module locking to Terraform 1.x violate any compatibility promise? Does the compatibility promise mean that a configuration written for Terraform 1.8 should work with Terraform 1.2? |
Hi @jcarlson, In the past requests similar to this one have been framed as wanting to lock modules on a per-source basis, rather than on a per-call basis. In other words, the label on the I'm sorry for reading a requirement into your use-case that wasn't actually there. However, given that we've heard various different desires around this over the years I think whatever we do the first step will be to research and find out what the broad needs are. We're currently in some sense "spoiled" by each call being totally independent of the others and so we have the ultimate flexibility to upgrade each call separately, but what some see as a benefit others see as the burden of having to update many separate configurations each time they want to adopt a new version of a module. A solution here should, I think, ideally aim to help those with both perspectives, or at least enable building tools that could work alongside Terraform for coordinating upgrades (such as dependabot). While I would agree that what you described would not be a breaking change on its face, it also isn't necessarily a design that meets all of the requirements. I cannot say for sure because I don't have all of the requirements to hand, but a design effort in this area will hopefully collect some other perspectives and help us figure out what the best compromise is. (When we introduced the provider dependency lock file in the first place we did have some folks who reacted as if we had made a breaking change, because they had got accustomed to a workflow where providers were not locked. We do need to be cognizant of that sort of reaction here too, but I expect that as with the dependency lock file that could come in the form of documenting a way that people can bypass the locking of modules if they wish, rather than being constrained to not do it at all. We'll see!) |
Thanks @apparentlymart. Yes just to add clarity for my use case, I would expect that a single root configuration could call multiple instances of a module and specify different version constraints for each independently. e.g.: module "foo" {
source = "app.registry.io/my-org/my-module/aws"
version = "~> 1.2"
}
module "bar" {
source = "app.registry.io/my-org/my-module/aws"
version = "~> 2.0"
feature_opt_in = true
} In this example, modules Perhaps in this example, module |
I solved this using separated git repositories for versioned modules. Module releases are tagged, so I can stick to different versions of one module. It looks like this:
|
@redzioch unfortunately, using Git tags as a module source does not allow for semantic version ranges to be expressed. For example, you can’t set a version constraint of This also requires that for nested modules, you must update each intermediate module and create a release. This is not ideal for a root configuration that depends on module A, which in turn depends on module B. Updating module B would require touching code in modules B, A and the root. While you could maintain Git tags corresponding to v1.3.0, v1.3 and v1, and update the latter two over time, this doesn’t achieve the goal here which is to include a lock file for modules, so that two separate machines running |
@apparentlymart Most of the information that would be required for this "lock" file appears to be present in Terraform today in versions going back to at least 0.13.7 (I did not check older) in the project's
module "some-module" {
source = "app.terraform.io/my-org/my-module/aws"
version = "~> 1.2"
}
module "nested" {
source = "app.terraform.io/my-org/nested/aws"
version = "~> 3.1"
}
{
"Modules": [
{
"Key": "some-module",
"Source": "app.terraform.io/my-org/my-module/aws",
"Version": "1.2.3",
"Dir": ".terraform/modules/some-module"
},
{
"Key": "some-module.nested",
"Source": "app.terraform.io/my-org/nested/aws",
"Version": "3.3.0",
"Dir": ".terraform/modules/some-module.nested"
}
]
} While it would be nice to have Terraform support this natively, if this |
Hi @jcarlson! What you have there is the modules analog to what Terraform did for providers before we introduced the dependency lock file: there was a hidden, implementation-detail-only JSON file under What isn't clear at this time is what exactly the lock file should be tracking in order to meet the use-cases people want it to meet. One answer would be to copy the existing lock file exactly, identifying each However, in the past other feedback has suggested that people want to lock modules on a per-source-address basis instead, which might lead to a formulation more like the following: module "app.terraform.io/my-org/my-module/aws" {
version = "1.2.3"
hashes = [
# ...
]
} The idea in this case then would be that any There are some other variations too, such as treating the source address and the version number together as the lookup key and associating a set of hashes with each, so that there can still be multiple versions of the same module used in the same configuration but we can still share the hashes whenever two have the same version selected. In order to move forward here we need to complete some product research to learn which of these is the best fit for the requirements. Our current structure where the manifest is only a temporary file can get away without this prior engineering because we are free to change it at any time and have |
@apparentlymart well said, thank you for the detailed explanation! I may still pursue a mechanism to consume that Ultimately, what we're hoping to get out of this lock file is a way to detect, based on metadata, which of our many, many configurations need to be applied, because a dependency (either direct or transitive) has been updated. We have, for a while, been using Git-based module source addresses in both our root configurations as well as our in-house modules, so any changes to dependencies could be identified explicitly through changes to source code. Now, however, we've switched to a module registry with version constraints, which means a configuration could have pending changes even though no code has changed. So in summary, the three things we want to solve for are:
I'd be happy to help on this effort in anyway that I can; hopefully, providing some concrete uses cases is a good start. |
As an initial solution I'd like the lock file to capture the hash of modules and error if the hash doesn't match when running a command. I have some opinions on other parts of this discussion but for now this would solve a supply chain concern with the current implementation (e.g. re-tagging a module to provide a malicious payload). I'm not sure if it's related but adding a new module "my_module" {
source = "https://mysite.io/modules/my-module-v1.0.0.tgz"
version_match = "(?:v)(\d+\.\d+\d+)(?:\.tgz$)" |
@redzioch Curious to know how to approach managing module version in case of multiple environments using same SSH Git URL format. |
The problem with this is that if you use a module that itself uses a module with a version constraint instead of an exact version, there is no way to lock the version. So you end up needing to have exact versions in all modules as well. And that in turn means if you make a minor bug fix, you have to propagate the version increase up through multiple dependencies. Ideally, if I had a scenario where my root module A depends on module B and module B depends on module C, I could release a new version of module C, and then just update the lock file for module A to get the new version of C, without having to release a new version of module B. |
We are currently using a monorepo for our Terraform modules (200+ and growing), and we are using git URLs with specific tags in the projects that consume the modules. We are on our way to split that monorepo into smaller repos and use a private Terraform registry to publish the modules. And the anticipated problem with that approach is exactly as @jcarlson describes: we will either use version ranges and lose control over updates, or we will have to use specific versions and painstakingly propagate any changes up the dependency graph. Neither option is ideal. Our use case for version locking is also exactly as @jcarlson describes: we need to allow locking each module path separately, simply for the reason that we are dealing with some long-living projects (3+ years), and some instances of the modules have to be locked to older versions because newer versions may trigger resource recreation. |
@sergei-ivanov We have just ran into this same limitation. Our use case is very similar - we have a private module for provisioning our kubernetes cluster, setting up firewalls, etc. Then we have one repository for each environment (production, staging, testing, etc.) - As we are currently using the module like this:
If someone changes the core module, all of those projects suddenly have drift - which is very unexpected to me. |
I am resolving my modules via a JFrog terraform module registry. However, I consider JFrog as an untrusted entity, so I would like to check at least the checksum for my modules as it is checked for providers. Do we have any development on going? This issue is getting referenced but didnt see any progress. As some of logic is already implemented for providers, how much work would it be for modules as well? |
My team also would like to express interest in having support for exact module version in I am in support of a solution which allows locking on a per-module instantiation basis, IE taking into account the modules name when resolving versions. We are on terraform version |
Hi @apparentlymart, Any progress on this topic? Regarding this:
|
Any news? |
Current Terraform Version
Use-cases
In order to ensure that the versions of modules selected by
terraform init
is repeatable across multiple machines and multiple environments, which may runapply
at different times, it would be useful to include module versions in the lock file.Attempted Solutions
An obvious solution would be to specify exact versions for modules and update as necessary. However, we have switched hosting our in-house modules to a private Terraform Registry in order to take advantage of semantic versioning requirements, such as
~> 1.2
for a module.We do have a few nested modules where for example the root depends on module A @ 1.2.0, which depends on module B @ 2.4.0. If module B needs to have a security fix released in 2.4.1, we would then have to issue a release to module A @ 1.2.1, and finally update the root configuration, which is time consuming and not ideal.
Proposal
Dependency managers for most other programming languages such as Ruby and Node already handle this sort of transitive dependency management, so it makes sense for Terraform's dependency management to do this as well.
The specific version of dependent and transient modules resolved by
terraform init
should be recorded to a lock file included in version control in order to improve repeatability across machines, environments and time.Module versions can be upgraded with
terraform init -upgrade
similar to how providers can be upgraded.References
The text was updated successfully, but these errors were encountered: