From a7faacc027aa4475ca44501082ca9c63393482af Mon Sep 17 00:00:00 2001 From: Sergei Zharinov <zharinov@users.noreply.github.com> Date: Thu, 2 Nov 2023 09:46:16 -0300 Subject: [PATCH] fix(terraform): Reduce constraints changes in lockfiles (#25430) Co-authored-by: Rhys Arkins <rhys@arkins.net> --- .../manager/terraform/lockfile/index.spec.ts | 179 ++++++++++++++++++ .../manager/terraform/lockfile/index.ts | 59 +++++- 2 files changed, 235 insertions(+), 3 deletions(-) diff --git a/lib/modules/manager/terraform/lockfile/index.spec.ts b/lib/modules/manager/terraform/lockfile/index.spec.ts index 669754fb2e..9db80efa40 100644 --- a/lib/modules/manager/terraform/lockfile/index.spec.ts +++ b/lib/modules/manager/terraform/lockfile/index.spec.ts @@ -910,4 +910,183 @@ describe('modules/manager/terraform/lockfile/index', () => { }); expect(result).toBeNull(); }); + + it('preserves constraints when current value and new value are same', async () => { + fs.readLocalFile.mockResolvedValueOnce(codeBlock` + provider "registry.terraform.io/hashicorp/aws" { + version = "3.0.0" + constraints = "~> 3.0.0" + hashes = [ + "aaa", + "bbb", + "ccc", + ] + } + `); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('.terraform.lock.hcl'); + + mockHash.mockResolvedValueOnce([ + 'h1:lDsKRxDRXPEzA4AxkK4t+lJd3IQIP2UoaplJGjQSp2s=', + 'h1:6zB2hX7YIOW26OrKsLJn0uLMnjqbPNxcz9RhlWEuuSY=', + ]); + + const result = await updateArtifacts({ + packageFileName: 'main.tf', + updatedDeps: [ + { + depName: 'aws', + depType: 'provider', + packageName: 'hashicorp/aws', + registryUrls: ['https://registry.example.com'], + newVersion: '3.36.1', + currentValue: '~> 3.36', + newValue: '~> 3.36', + }, + ], + newPackageFileContent: '', + config, + }); + + expect(result).toEqual([ + { + file: { + contents: codeBlock` + provider "registry.terraform.io/hashicorp/aws" { + version = "3.36.1" + constraints = "~> 3.0.0" + hashes = [ + "h1:lDsKRxDRXPEzA4AxkK4t+lJd3IQIP2UoaplJGjQSp2s=", + "h1:6zB2hX7YIOW26OrKsLJn0uLMnjqbPNxcz9RhlWEuuSY=", + ] + } + `, + path: '.terraform.lock.hcl', + type: 'addition', + }, + }, + ]); + + expect(mockHash.mock.calls).toEqual([ + ['https://registry.example.com', 'hashicorp/aws', '3.36.1'], + ]); + }); + + it('replaces current value to new version within a constraint', async () => { + fs.readLocalFile.mockResolvedValueOnce(codeBlock` + provider "registry.terraform.io/hashicorp/aws" { + version = "3.0.0" + constraints = "~> 3.0.0" + hashes = [ + "aaa", + "bbb", + "ccc", + ] + } + `); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('.terraform.lock.hcl'); + + mockHash.mockResolvedValueOnce([ + 'h1:lDsKRxDRXPEzA4AxkK4t+lJd3IQIP2UoaplJGjQSp2s=', + 'h1:6zB2hX7YIOW26OrKsLJn0uLMnjqbPNxcz9RhlWEuuSY=', + ]); + + const result = await updateArtifacts({ + packageFileName: 'main.tf', + updatedDeps: [ + { + depName: 'aws', + depType: 'provider', + packageName: 'hashicorp/aws', + registryUrls: ['https://registry.example.com'], + newVersion: '3.37.0', + currentValue: '~> 3.0.0', + newValue: '~> 3.37.0', + }, + ], + newPackageFileContent: '', + config, + }); + + expect(result).toEqual([ + { + file: { + contents: codeBlock` + provider "registry.terraform.io/hashicorp/aws" { + version = "3.37.0" + constraints = "~> 3.37.0" + hashes = [ + "h1:lDsKRxDRXPEzA4AxkK4t+lJd3IQIP2UoaplJGjQSp2s=", + "h1:6zB2hX7YIOW26OrKsLJn0uLMnjqbPNxcz9RhlWEuuSY=", + ] + } + `, + path: '.terraform.lock.hcl', + type: 'addition', + }, + }, + ]); + + expect(mockHash.mock.calls).toEqual([ + ['https://registry.example.com', 'hashicorp/aws', '3.37.0'], + ]); + }); + + it('replaces current version to new version within a constraint', async () => { + fs.readLocalFile.mockResolvedValueOnce(codeBlock` + provider "registry.terraform.io/hashicorp/aws" { + version = "3.0.0" + constraints = "~> 3.0.0" + hashes = [ + "aaa", + "bbb", + "ccc", + ] + } + `); + fs.findLocalSiblingOrParent.mockResolvedValueOnce('.terraform.lock.hcl'); + + mockHash.mockResolvedValueOnce([ + 'h1:lDsKRxDRXPEzA4AxkK4t+lJd3IQIP2UoaplJGjQSp2s=', + 'h1:6zB2hX7YIOW26OrKsLJn0uLMnjqbPNxcz9RhlWEuuSY=', + ]); + + const result = await updateArtifacts({ + packageFileName: 'main.tf', + updatedDeps: [ + { + depName: 'aws', + depType: 'provider', + packageName: 'hashicorp/aws', + registryUrls: ['https://registry.example.com'], + newVersion: '3.37.0', + currentVersion: '3.0.0', + }, + ], + newPackageFileContent: '', + config, + }); + + expect(result).toEqual([ + { + file: { + contents: codeBlock` + provider "registry.terraform.io/hashicorp/aws" { + version = "3.37.0" + constraints = "~> 3.37.0" + hashes = [ + "h1:lDsKRxDRXPEzA4AxkK4t+lJd3IQIP2UoaplJGjQSp2s=", + "h1:6zB2hX7YIOW26OrKsLJn0uLMnjqbPNxcz9RhlWEuuSY=", + ] + } + `, + path: '.terraform.lock.hcl', + type: 'addition', + }, + }, + ]); + + expect(mockHash.mock.calls).toEqual([ + ['https://registry.example.com', 'hashicorp/aws', '3.37.0'], + ]); + }); }); diff --git a/lib/modules/manager/terraform/lockfile/index.ts b/lib/modules/manager/terraform/lockfile/index.ts index 58c72065e2..287df2bff9 100644 --- a/lib/modules/manager/terraform/lockfile/index.ts +++ b/lib/modules/manager/terraform/lockfile/index.ts @@ -4,7 +4,11 @@ import * as p from '../../../../util/promises'; import { GetPkgReleasesConfig, getPkgReleases } from '../../../datasource'; import { TerraformProviderDatasource } from '../../../datasource/terraform-provider'; import { get as getVersioning } from '../../../versioning'; -import type { UpdateArtifact, UpdateArtifactsResult } from '../../types'; +import type { + UpdateArtifact, + UpdateArtifactsResult, + Upgrade, +} from '../../types'; import { massageProviderLookupName } from '../util'; import { TerraformProviderHash } from './hash'; import type { ProviderLock, ProviderLockUpdate } from './types'; @@ -61,6 +65,55 @@ async function updateAllLocks( return updates.filter(is.truthy); } +function getNewConstraint( + dep: Upgrade<Record<string, unknown>>, + oldConstraint: string | undefined +): string | undefined { + const { currentValue, currentVersion, newValue, newVersion, packageName } = + dep; + + if (oldConstraint && currentValue && newValue && currentValue === newValue) { + logger.debug( + `Leaving constraints "${oldConstraint}" unchanged for "${packageName}" as current and new values are the same` + ); + return oldConstraint; + } + + if ( + oldConstraint && + currentValue && + newValue && + oldConstraint.includes(currentValue) + ) { + logger.debug( + `Updating constraint "${oldConstraint}" to replace "${currentValue}" with "${newValue}" for "${packageName}"` + ); + return oldConstraint.replace(currentValue, newValue); + } + + if ( + oldConstraint && + currentVersion && + newVersion && + oldConstraint.includes(currentVersion) + ) { + logger.debug( + `Updating constraint "${oldConstraint}" to replace "${currentVersion}" with "${newVersion}" for "${packageName}"` + ); + return oldConstraint.replace(currentVersion, newVersion); + } + + if (isPinnedVersion(newValue)) { + logger.debug(`Pinning constraint for "${packageName}" to "${newVersion}"`); + return newVersion; + } + + logger.debug( + `Could not detect constraint to update for "${packageName}" so setting to newValue "${newValue}"` + ); + return newValue; +} + export async function updateArtifacts({ packageFileName, updatedDeps, @@ -99,12 +152,11 @@ export async function updateArtifacts({ ); for (const dep of providerDeps) { massageProviderLookupName(dep); - const { registryUrls, newVersion, newValue, packageName } = dep; + const { registryUrls, newVersion, packageName } = dep; const registryUrl = registryUrls ? registryUrls[0] : TerraformProviderDatasource.defaultRegistryUrls[0]; - const newConstraint = isPinnedVersion(newValue) ? newVersion : newValue; const updateLock = locks.find( (value) => value.packageName === packageName ); @@ -112,6 +164,7 @@ export async function updateArtifacts({ if (!updateLock) { continue; } + const newConstraint = getNewConstraint(dep, updateLock.constraints); const update: ProviderLockUpdate = { // TODO #22198 newVersion: newVersion!, -- GitLab