From c14e30a6760e6003da4aed9ca814b88b2d074a72 Mon Sep 17 00:00:00 2001
From: Adam Setch <adam.setch@outlook.com>
Date: Wed, 26 Jun 2024 20:25:42 -0400
Subject: [PATCH] feat(manager): runtime version (#29745)

Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
---
 lib/modules/manager/api.ts                    |  2 ++
 .../manager/runtime-version/extract.spec.ts   | 21 ++++++++++++++++
 .../manager/runtime-version/extract.ts        | 24 +++++++++++++++++++
 lib/modules/manager/runtime-version/index.ts  | 15 ++++++++++++
 lib/modules/manager/runtime-version/readme.md |  8 +++++++
 5 files changed, 70 insertions(+)
 create mode 100644 lib/modules/manager/runtime-version/extract.spec.ts
 create mode 100644 lib/modules/manager/runtime-version/extract.ts
 create mode 100644 lib/modules/manager/runtime-version/index.ts
 create mode 100644 lib/modules/manager/runtime-version/readme.md

diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index 66cac2fdf8..9616fd86cb 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -75,6 +75,7 @@ import * as pub from './pub';
 import * as puppet from './puppet';
 import * as pyenv from './pyenv';
 import * as rubyVersion from './ruby-version';
+import * as runtimeVersion from './runtime-version';
 import * as sbt from './sbt';
 import * as scalafmt from './scalafmt';
 import * as setupCfg from './setup-cfg';
@@ -171,6 +172,7 @@ api.set('pub', pub);
 api.set('puppet', puppet);
 api.set('pyenv', pyenv);
 api.set('ruby-version', rubyVersion);
+api.set('runtime-version', runtimeVersion);
 api.set('sbt', sbt);
 api.set('scalafmt', scalafmt);
 api.set('setup-cfg', setupCfg);
diff --git a/lib/modules/manager/runtime-version/extract.spec.ts b/lib/modules/manager/runtime-version/extract.spec.ts
new file mode 100644
index 0000000000..04fc7ea9b7
--- /dev/null
+++ b/lib/modules/manager/runtime-version/extract.spec.ts
@@ -0,0 +1,21 @@
+import { extractPackageFile } from '.';
+
+describe('modules/manager/runtime-version/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('returns a result - python', () => {
+      const res = extractPackageFile('python-3.12.4');
+      expect(res?.deps).toEqual([
+        {
+          depName: 'python',
+          currentValue: '3.12.4',
+          datasource: 'docker',
+        },
+      ]);
+    });
+
+    it('returns no result', () => {
+      const res = extractPackageFile('3.12.4');
+      expect(res).toBeNull();
+    });
+  });
+});
diff --git a/lib/modules/manager/runtime-version/extract.ts b/lib/modules/manager/runtime-version/extract.ts
new file mode 100644
index 0000000000..05200be976
--- /dev/null
+++ b/lib/modules/manager/runtime-version/extract.ts
@@ -0,0 +1,24 @@
+import { regEx } from '../../../util/regex';
+import { DockerDatasource } from '../../datasource/docker';
+import type { PackageDependency, PackageFileContent } from '../types';
+
+export const pythonRuntimeRegex = regEx(
+  '^python-(?<version>\\d+\\.\\d+\\.\\d+)$',
+);
+
+export function extractPackageFile(content: string): PackageFileContent | null {
+  const regexResult = pythonRuntimeRegex.exec(content);
+  const runtimeVersion = regexResult?.groups?.version;
+
+  if (runtimeVersion) {
+    const dep: PackageDependency = {
+      depName: 'python',
+      currentValue: runtimeVersion,
+      datasource: DockerDatasource.id,
+    };
+
+    return { deps: [dep] };
+  }
+
+  return null;
+}
diff --git a/lib/modules/manager/runtime-version/index.ts b/lib/modules/manager/runtime-version/index.ts
new file mode 100644
index 0000000000..5ee0f32b50
--- /dev/null
+++ b/lib/modules/manager/runtime-version/index.ts
@@ -0,0 +1,15 @@
+import type { Category } from '../../../constants';
+import { DockerDatasource } from '../../datasource/docker';
+
+export { extractPackageFile } from './extract';
+
+export const displayName = 'Runtime Version';
+
+export const defaultConfig = {
+  fileMatch: ['(^|/)runtime.txt$'],
+  pinDigests: false,
+};
+
+export const categories: Category[] = ['python'];
+
+export const supportedDatasources = [DockerDatasource.id];
diff --git a/lib/modules/manager/runtime-version/readme.md b/lib/modules/manager/runtime-version/readme.md
new file mode 100644
index 0000000000..0325f30783
--- /dev/null
+++ b/lib/modules/manager/runtime-version/readme.md
@@ -0,0 +1,8 @@
+Keep `runtime.txt` files updated.
+
+Currently supports `Python` runtime updates, commonly used by platforms such as
+
+- [Heroku](https://devcenter.heroku.com/articles/python-runtimes)
+- [CloudFoundry](https://docs.cloudfoundry.org/buildpacks/python/index.html)
+- [Koyeb](https://www.koyeb.com/docs/build-and-deploy/build-from-git/python)
+- and more
-- 
GitLab