From c82c38b498f1124b7ed7240ed0277e0ac40f0bf0 Mon Sep 17 00:00:00 2001
From: Adam Setch <adam.setch@outlook.com>
Date: Wed, 22 Feb 2023 01:41:56 -0500
Subject: [PATCH] fix: auto-replace for pinDigest without currentDigest or
 shortDigest (#20317)

---
 .../update/branch/auto-replace.spec.ts        | 247 +++++++++++++++++-
 .../repository/update/branch/auto-replace.ts  |  18 +-
 2 files changed, 262 insertions(+), 3 deletions(-)

diff --git a/lib/workers/repository/update/branch/auto-replace.spec.ts b/lib/workers/repository/update/branch/auto-replace.spec.ts
index 4a1abc13f0..e5082c25bc 100644
--- a/lib/workers/repository/update/branch/auto-replace.spec.ts
+++ b/lib/workers/repository/update/branch/auto-replace.spec.ts
@@ -909,7 +909,7 @@ describe('workers/repository/update/branch/auto-replace', () => {
       const tf = codeBlock`
         module "vpc" {
           source  = "terraform-aws-modules/vpc/aws"
-         version = "3.14.2"
+          version = "3.14.2"
         }
       `;
       upgrade.manager = 'terraform';
@@ -1038,5 +1038,250 @@ describe('workers/repository/update/branch/auto-replace', () => {
         `
       );
     });
+
+    it('docker: updates with pinDigest enabled but no currentDigest value', async () => {
+      const dockerfile = codeBlock`
+        FROM ubuntu:18.04
+      `;
+      upgrade.manager = 'dockerfile';
+      upgrade.depName = 'ubuntu';
+      upgrade.currentValue = '18.04';
+      upgrade.currentDigest = undefined;
+      upgrade.depIndex = 0;
+      upgrade.pinDigests = true;
+      upgrade.updateType = 'replacement';
+      upgrade.replaceString = 'ubuntu:18.04';
+      upgrade.newName = 'alpine';
+      upgrade.newValue = '3.16';
+      upgrade.newDigest = 'sha256:p0o9i8u7z6t5r4e3w2q1';
+      upgrade.packageFile = 'Dockerfile';
+      const res = await doAutoReplace(upgrade, dockerfile, reuseExistingBranch);
+      expect(res).toBe(
+        codeBlock`
+          FROM alpine:3.16
+        `
+      );
+    });
+
+    it('docker: updates with pinDigest enabled and a currentDigest value', async () => {
+      const dockerfile = codeBlock`
+        FROM ubuntu:18.04@sha256:q1w2e3r4t5z6u7i8o9p0
+      `;
+      upgrade.manager = 'dockerfile';
+      upgrade.depName = 'ubuntu';
+      upgrade.currentValue = '18.04';
+      upgrade.currentDigest = 'sha256:q1w2e3r4t5z6u7i8o9p0';
+      upgrade.depIndex = 0;
+      upgrade.pinDigests = true;
+      upgrade.updateType = 'replacement';
+      upgrade.replaceString = 'ubuntu:18.04@sha256:q1w2e3r4t5z6u7i8o9p0';
+      upgrade.newName = 'alpine';
+      upgrade.newValue = '3.16';
+      upgrade.newDigest = 'sha256:p0o9i8u7z6t5r4e3w2q1';
+      upgrade.packageFile = 'Dockerfile';
+      const res = await doAutoReplace(upgrade, dockerfile, reuseExistingBranch);
+      expect(res).toBe(
+        codeBlock`
+          FROM alpine:3.16@sha256:p0o9i8u7z6t5r4e3w2q1
+        `
+      );
+    });
+
+    it('regex: updates with pinDigest enabled but no currentDigest value', async () => {
+      const yml = 'image: "some.url.com/my-repository:1.0"';
+      upgrade.manager = 'regex';
+      upgrade.pinDigests = true;
+      upgrade.depName = 'some.url.com/my-repository';
+      upgrade.currentValue = '1.0';
+      upgrade.currentDigest = undefined;
+      upgrade.depIndex = 0;
+      upgrade.replaceString = 'image: "some.url.com/my-repository:1.0"';
+      upgrade.packageFile = 'k8s/base/defaults.yaml';
+      upgrade.newName = 'some.other.url.com/some-new-repo';
+      upgrade.newValue = '3.16';
+      upgrade.newDigest = 'sha256:p0o9i8u7z6t5r4e3w2q1';
+      upgrade.matchStrings = [
+        'image:\\s*?\\\'?\\"?(?<depName>[^:\\\'\\"]+):(?<currentValue>[^@\\\'\\"]+)@?(?<currentDigest>[^\\s\\\'\\"]+)?\\"?\\\'?\\s*',
+      ];
+      const res = await doAutoReplace(upgrade, yml, reuseExistingBranch);
+      expect(res).toBe('image: "some.other.url.com/some-new-repo:3.16"');
+    });
+
+    it('regex: updates with pinDigest enabled and a currentDigest value', async () => {
+      const yml =
+        'image: "some.url.com/my-repository:1.0@sha256:q1w2e3r4t5z6u7i8o9p0"';
+      upgrade.manager = 'regex';
+      upgrade.pinDigests = true;
+      upgrade.depName = 'some.url.com/my-repository';
+      upgrade.currentValue = '1.0';
+      upgrade.currentDigest = 'sha256:q1w2e3r4t5z6u7i8o9p0';
+      upgrade.depIndex = 0;
+      upgrade.replaceString =
+        'image: "some.url.com/my-repository:1.0@sha256:q1w2e3r4t5z6u7i8o9p0"';
+      upgrade.packageFile = 'k8s/base/defaults.yaml';
+      upgrade.newName = 'some.other.url.com/some-new-repo';
+      upgrade.newValue = '3.16';
+      upgrade.newDigest = 'sha256:p0o9i8u7z6t5r4e3w2q1';
+      upgrade.matchStrings = [
+        'image:\\s*[\\\'\\"]?(?<depName>[^:]+):(?<currentValue>[^@]+)?@?(?<currentDigest>[^\\s\\\'\\"]+)?[\\\'\\"]?\\s*',
+      ];
+      const res = await doAutoReplace(upgrade, yml, reuseExistingBranch);
+      expect(res).toBe(
+        'image: "some.other.url.com/some-new-repo:3.16@sha256:p0o9i8u7z6t5r4e3w2q1"'
+      );
+    });
+
+    it('github-actions: update with newValue only', async () => {
+      const githubAction = codeBlock`
+        jobs:
+          build:
+            runs-on: ubuntu-latest
+            steps:
+              - uses: actions/checkout@v1.0.0
+      `;
+      upgrade.manager = 'github-actions';
+      upgrade.autoReplaceStringTemplate =
+        '{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}';
+      upgrade.depName = 'actions/checkout';
+      upgrade.currentValue = 'v1.0.0';
+      upgrade.currentDigest = undefined;
+      upgrade.currentDigestShort = undefined;
+      upgrade.depIndex = 0;
+      upgrade.pinDigests = true;
+      upgrade.updateType = 'replacement';
+      upgrade.replaceString = 'actions/checkout@v1.0.0';
+      upgrade.newValue = 'v2.0.0';
+      upgrade.newDigest = undefined;
+      upgrade.packageFile = 'workflows/build.yml';
+      const res = await doAutoReplace(
+        upgrade,
+        githubAction,
+        reuseExistingBranch
+      );
+      expect(res).toBe(
+        codeBlock`
+          jobs:
+            build:
+              runs-on: ubuntu-latest
+              steps:
+                - uses: actions/checkout@v2.0.0
+        `
+      );
+    });
+
+    it('github-actions: update with newValue and newDigest', async () => {
+      const githubAction = codeBlock`
+        jobs:
+          build:
+            runs-on: ubuntu-latest
+            steps:
+              - uses: actions/checkout@v1.0.0
+      `;
+      upgrade.manager = 'github-actions';
+      upgrade.autoReplaceStringTemplate =
+        '{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}';
+      upgrade.depName = 'actions/checkout';
+      upgrade.currentValue = 'v1.0.0';
+      upgrade.currentDigest = undefined;
+      upgrade.currentDigestShort = undefined;
+      upgrade.depIndex = 0;
+      upgrade.pinDigests = true;
+      upgrade.updateType = 'replacement';
+      upgrade.replaceString = 'actions/checkout@v1.0.0';
+      upgrade.newValue = 'v2.0.0';
+      upgrade.newDigest = '1cf887';
+      upgrade.packageFile = 'workflows/build.yml';
+      const res = await doAutoReplace(
+        upgrade,
+        githubAction,
+        reuseExistingBranch
+      );
+      expect(res).toBe(
+        codeBlock`
+          jobs:
+            build:
+              runs-on: ubuntu-latest
+              steps:
+                - uses: actions/checkout@1cf887 # v2.0.0
+        `
+      );
+    });
+
+    it('github-actions: updates with pinDigest enabled but no currentDigest value', async () => {
+      const githubAction = codeBlock`
+        jobs:
+          build:
+            runs-on: ubuntu-latest
+            steps:
+              - uses: actions/checkout@v1.0.0
+      `;
+      upgrade.manager = 'github-actions';
+      upgrade.autoReplaceStringTemplate =
+        '{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}';
+      upgrade.depName = 'actions/checkout';
+      upgrade.currentValue = 'v1.0.0';
+      upgrade.currentDigest = undefined;
+      upgrade.currentDigestShort = undefined;
+      upgrade.depIndex = 0;
+      upgrade.pinDigests = true;
+      upgrade.updateType = 'replacement';
+      upgrade.replaceString = 'actions/checkout@v1.0.0';
+      upgrade.newName = 'some-other-action/checkout';
+      upgrade.newValue = 'v2.0.0';
+      upgrade.newDigest = '1cf887';
+      upgrade.packageFile = 'workflows/build.yml';
+      const res = await doAutoReplace(
+        upgrade,
+        githubAction,
+        reuseExistingBranch
+      );
+      expect(res).toBe(
+        codeBlock`
+          jobs:
+            build:
+              runs-on: ubuntu-latest
+              steps:
+                - uses: some-other-action/checkout@v2.0.0
+        `
+      );
+    });
+
+    it('github-actions: updates with pinDigest enabled and a currentDigest value', async () => {
+      const githubAction = codeBlock`
+        jobs:
+          build:
+            runs-on: ubuntu-latest
+            steps:
+              - uses: actions/checkout@2485f4 # tag=v1.0.0
+      `;
+      upgrade.manager = 'github-actions';
+      upgrade.autoReplaceStringTemplate =
+        '{{depName}}@{{#if newDigest}}{{newDigest}}{{#if newValue}} # {{newValue}}{{/if}}{{/if}}{{#unless newDigest}}{{newValue}}{{/unless}}';
+      upgrade.depName = 'actions/checkout';
+      upgrade.currentValue = 'v1.0.0';
+      upgrade.currentDigestShort = '2485f4';
+      upgrade.depIndex = 0;
+      upgrade.pinDigests = true;
+      upgrade.updateType = 'replacement';
+      upgrade.replaceString = 'actions/checkout@2485f4 # tag=v1.0.0';
+      upgrade.newName = 'some-other-action/checkout';
+      upgrade.newValue = 'v2.0.0';
+      upgrade.newDigest = '1cf887';
+      upgrade.packageFile = 'workflow.yml';
+      const res = await doAutoReplace(
+        upgrade,
+        githubAction,
+        reuseExistingBranch
+      );
+      expect(res).toBe(
+        codeBlock`
+          jobs:
+            build:
+              runs-on: ubuntu-latest
+              steps:
+                - uses: some-other-action/checkout@1cf887 # tag=v2.0.0
+        `
+      );
+    });
   });
 });
diff --git a/lib/workers/repository/update/branch/auto-replace.ts b/lib/workers/repository/update/branch/auto-replace.ts
index 1b905801ed..2843f15c1d 100644
--- a/lib/workers/repository/update/branch/auto-replace.ts
+++ b/lib/workers/repository/update/branch/auto-replace.ts
@@ -41,6 +41,7 @@ export async function confirmIfDepUpdated(
   } catch (err) /* istanbul ignore next */ {
     logger.debug({ manager, packageFile, err }, 'Failed to parse newContent');
   }
+
   if (!newUpgrade!) {
     logger.debug(`No newUpgrade in ${packageFile!}`);
     return false;
@@ -61,6 +62,7 @@ export async function confirmIfDepUpdated(
     );
     return false;
   }
+
   if (newValue && newUpgrade.currentValue !== newValue) {
     logger.debug(
       {
@@ -73,15 +75,21 @@ export async function confirmIfDepUpdated(
     );
     return false;
   }
+
   if (!newDigest) {
     return true;
   }
   if (newUpgrade.currentDigest === newDigest) {
     return true;
   }
-  if (!currentDigest && !pinDigests) {
-    return true;
+  if (!currentDigest) {
+    if (!pinDigests) {
+      return true;
+    } else if (newDigest) {
+      return true;
+    }
   }
+
   // istanbul ignore next
   return false;
 }
@@ -147,6 +155,7 @@ export async function doAutoReplace(
     currentValue,
     newValue,
     currentDigest,
+    currentDigestShort,
     newDigest,
     autoReplaceStringTemplate,
   } = upgrade;
@@ -203,6 +212,11 @@ export async function doAutoReplace(
           regEx(escapeRegExp(currentDigest), 'g'),
           newDigest
         );
+      } else if (currentDigestShort && newDigest) {
+        newString = newString.replace(
+          regEx(escapeRegExp(currentDigestShort), 'g'),
+          newDigest
+        );
       }
     }
     if (!firstUpdate && (await confirmIfDepUpdated(upgrade, existingContent))) {
-- 
GitLab