From ce1be2c5a192fba1e9226768a0a932aa86c57752 Mon Sep 17 00:00:00 2001
From: RahulGautamSingh <rahultesnik@gmail.com>
Date: Wed, 26 Jul 2023 00:03:41 +0545
Subject: [PATCH] fix(versioning/hashicorp): allow `!=` in ranges, constraints
 (#23492)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
---
 lib/modules/versioning/common.ts              | 17 ++++++++++
 .../versioning/hashicorp/index.spec.ts        | 23 +++++++------
 lib/modules/versioning/hashicorp/index.ts     | 32 +++++++++++++++++--
 3 files changed, 60 insertions(+), 12 deletions(-)

diff --git a/lib/modules/versioning/common.ts b/lib/modules/versioning/common.ts
index 8378db6e24..2416258339 100644
--- a/lib/modules/versioning/common.ts
+++ b/lib/modules/versioning/common.ts
@@ -1,3 +1,4 @@
+import { regEx } from '../../util/regex';
 import type { VersioningApi, VersioningApiConstructor } from './types';
 
 export function isVersioningApiConstructor(
@@ -5,3 +6,19 @@ export function isVersioningApiConstructor(
 ): obj is VersioningApiConstructor {
   return typeof obj === 'function';
 }
+
+export function getExcludedVersions(range: string): string[] {
+  return range
+    .split(',')
+    .map((v) => v.trim())
+    .filter((version) => regEx(/^!=/).test(version))
+    .map((version) => version.replace('!=', '').trim());
+}
+
+export function getFilteredRange(range: string): string {
+  return range
+    .split(',')
+    .map((v) => v.trim())
+    .filter((version) => !regEx(/^!=/).test(version))
+    .join(',');
+}
diff --git a/lib/modules/versioning/hashicorp/index.spec.ts b/lib/modules/versioning/hashicorp/index.spec.ts
index 03d77c4a47..2fc235a241 100644
--- a/lib/modules/versioning/hashicorp/index.spec.ts
+++ b/lib/modules/versioning/hashicorp/index.spec.ts
@@ -2,9 +2,11 @@ import { api as semver } from '.';
 
 describe('modules/versioning/hashicorp/index', () => {
   it.each`
-    version    | range         | expected
-    ${'4.2.0'} | ${'~> 4.0'}   | ${true}
-    ${'4.2.0'} | ${'~> 4.0.0'} | ${false}
+    version    | range                 | expected
+    ${'4.2.0'} | ${'~> 4.0'}           | ${true}
+    ${'4.2.0'} | ${'~> 4.0.0'}         | ${false}
+    ${'4.2.0'} | ${'~> 4.0, != 4.2.0'} | ${false}
+    ${'4.2.6'} | ${'~> 4.0, != 4.2.0'} | ${true}
   `(
     'matches("$version", "$range") === $expected',
     ({ version, range, expected }) => {
@@ -13,9 +15,10 @@ describe('modules/versioning/hashicorp/index', () => {
   );
 
   it.each`
-    versions                                         | range         | expected
-    ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'~> 4.0'}   | ${'4.2.0'}
-    ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'~> 4.0.0'} | ${'4.0.0'}
+    versions                                         | range                 | expected
+    ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'~> 4.0'}           | ${'4.2.0'}
+    ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'~> 4.0.0'}         | ${'4.0.0'}
+    ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'!=4.2.0, > 4.0.0'} | ${'5.0.0'}
   `(
     'getSatisfyingVersion($versions, "$range") === $expected',
     ({ versions, range, expected }) => {
@@ -54,9 +57,11 @@ describe('modules/versioning/hashicorp/index', () => {
   );
 
   it.each`
-    versions                                | range         | expected
-    ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'~> 4.0'}   | ${'4.2.0'}
-    ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'~> 4.0.0'} | ${null}
+    versions                                | range                 | expected
+    ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'~> 4.0'}           | ${'4.2.0'}
+    ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'~> 4.0.0'}         | ${null}
+    ${['0.4.0', '0.5.0', '4.2.0', '5.0.0']} | ${'~> 4.0, != 4.2.0'} | ${null}
+    ${['0.4.0', '0.5.0', '4.2.0', '4.1.0']} | ${'~> 4.0, != 4.2.0'} | ${'4.1.0'}
   `(
     'minSatisfyingVersion($versions, "$range") === $expected',
     ({ versions, range, expected }) => {
diff --git a/lib/modules/versioning/hashicorp/index.ts b/lib/modules/versioning/hashicorp/index.ts
index 807636782c..0e8a2ad938 100644
--- a/lib/modules/versioning/hashicorp/index.ts
+++ b/lib/modules/versioning/hashicorp/index.ts
@@ -1,5 +1,6 @@
 import { logger } from '../../../logger';
 import type { RangeStrategy } from '../../../types/versioning';
+import { getExcludedVersions, getFilteredRange } from '../common';
 import { api as npm } from '../npm';
 import type { NewValueConfig, VersioningApi } from '../types';
 import { hashicorp2npm, npm2hashicorp } from './convertor';
@@ -34,21 +35,46 @@ export function isValid(input: string): boolean {
 }
 
 function matches(version: string, range: string): boolean {
-  return isValid(range) && npm.matches(version, hashicorp2npm(range));
+  const excludedVersions = getExcludedVersions(range);
+  if (excludedVersions.includes(version)) {
+    return false;
+  }
+
+  const filteredRange = getFilteredRange(range);
+  return (
+    isValid(filteredRange) && npm.matches(version, hashicorp2npm(filteredRange))
+  );
 }
 
 function getSatisfyingVersion(
   versions: string[],
   range: string
 ): string | null {
-  return npm.getSatisfyingVersion(versions, hashicorp2npm(range));
+  const excludedVersions = getExcludedVersions(range);
+  const filteredRange = getFilteredRange(range);
+  const filteredVersions = versions.filter(
+    (version) => !excludedVersions.includes(version)
+  );
+
+  return npm.getSatisfyingVersion(
+    filteredVersions,
+    hashicorp2npm(filteredRange)
+  );
 }
 
 function minSatisfyingVersion(
   versions: string[],
   range: string
 ): string | null {
-  return npm.minSatisfyingVersion(versions, hashicorp2npm(range));
+  const excludedVersions = getExcludedVersions(range);
+  const filteredRange = getFilteredRange(range);
+  const filteredVersions = versions.filter(
+    (version) => !excludedVersions.includes(version)
+  );
+  return npm.minSatisfyingVersion(
+    filteredVersions,
+    hashicorp2npm(filteredRange)
+  );
 }
 
 function getNewValue({
-- 
GitLab