From bbedb2d7379bff9b222fde7ac298e7156648f5ac Mon Sep 17 00:00:00 2001 From: Pavel Busko <busko.pavel@gmail.com> Date: Thu, 21 Nov 2024 13:11:58 +0100 Subject: [PATCH] feat(manager): Cloud Native Buildpacks project descriptor manager (#30799) Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> Co-authored-by: Michael Kriese <michael.kriese@visualon.de> Co-authored-by: loewenstein-sap <jan.von.loewenstein@sap.com> --- lib/config/options/index.ts | 1 + lib/modules/manager/api.ts | 2 + .../manager/buildpacks/extract.spec.ts | 111 ++++++++++++++++++ lib/modules/manager/buildpacks/extract.ts | 103 ++++++++++++++++ lib/modules/manager/buildpacks/index.ts | 12 ++ lib/modules/manager/buildpacks/readme.md | 26 ++++ lib/modules/manager/buildpacks/schema.ts | 25 ++++ 7 files changed, 280 insertions(+) create mode 100644 lib/modules/manager/buildpacks/extract.spec.ts create mode 100644 lib/modules/manager/buildpacks/extract.ts create mode 100644 lib/modules/manager/buildpacks/index.ts create mode 100644 lib/modules/manager/buildpacks/readme.md create mode 100644 lib/modules/manager/buildpacks/schema.ts diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts index c91af4a604..0575942785 100644 --- a/lib/config/options/index.ts +++ b/lib/config/options/index.ts @@ -1135,6 +1135,7 @@ const options: RenovateOptions[] = [ supportedManagers: [ 'ansible', 'bitbucket-pipelines', + 'buildpacks', 'crossplane', 'devcontainer', 'docker-compose', diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index 466ce2f0f6..e8522f7332 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -12,6 +12,7 @@ import * as bicep from './bicep'; import * as bitbucketPipelines from './bitbucket-pipelines'; import * as bitrise from './bitrise'; import * as buildkite from './buildkite'; +import * as buildpacks from './buildpacks'; import * as bun from './bun'; import * as bunVersion from './bun-version'; import * as bundler from './bundler'; @@ -117,6 +118,7 @@ api.set('bicep', bicep); api.set('bitbucket-pipelines', bitbucketPipelines); api.set('bitrise', bitrise); api.set('buildkite', buildkite); +api.set('buildpacks', buildpacks); api.set('bun', bun); api.set('bun-version', bunVersion); api.set('bundler', bundler); diff --git a/lib/modules/manager/buildpacks/extract.spec.ts b/lib/modules/manager/buildpacks/extract.spec.ts new file mode 100644 index 0000000000..dedcefcbd4 --- /dev/null +++ b/lib/modules/manager/buildpacks/extract.spec.ts @@ -0,0 +1,111 @@ +import { codeBlock } from 'common-tags'; + +import { extractPackageFile } from '.'; + +describe('modules/manager/buildpacks/extract', () => { + describe('extractPackageFile()', () => { + it('returns null for invalid files', () => { + expect(extractPackageFile('not a project toml', '', {})).toBeNull(); + }); + + it('returns null for empty package.toml', () => { + const res = extractPackageFile( + '[_]\nschema-version = "0.2"', + 'project.toml', + {}, + ); + expect(res).toBeNull(); + }); + + it('extracts builder and buildpack images', () => { + const res = extractPackageFile( + codeBlock` +[_] +schema-version = "0.2" + +[io.buildpacks] +builder = "registry.corp/builder/noble:1.1.1" + +[[io.buildpacks.group]] +uri = "docker://buildpacks/java:2.2.2" + +[[io.buildpacks.group]] +uri = "buildpacks/nodejs:3.3.3" + +[[io.buildpacks.group]] +uri = "example/foo@1.0.0" + +[[io.buildpacks.group]] +uri = "example/registry-cnb" + +[[io.buildpacks.group]] +uri = "urn:cnb:registry:example/foo@1.0.0" + +[[io.buildpacks.group]] +uri = "some-bp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + +[[io.buildpacks.group]] +uri = "cnbs/some-bp:some-tag@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + +[[io.buildpacks.group]] +uri = "from=builder:foobar" + +[[io.buildpacks.group]] +uri = "file://local.oci" + +[[io.buildpacks.group]] +uri = "foo://fake.oci"`, + 'project.toml', + {}, + ); + expect(res?.deps).toEqual([ + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + commitMessageTopic: 'builder {{depName}}', + currentValue: '1.1.1', + datasource: 'docker', + depName: 'registry.corp/builder/noble', + replaceString: 'registry.corp/builder/noble:1.1.1', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentValue: '2.2.2', + datasource: 'docker', + depName: 'buildpacks/java', + replaceString: 'buildpacks/java:2.2.2', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentValue: '3.3.3', + datasource: 'docker', + depName: 'buildpacks/nodejs', + replaceString: 'buildpacks/nodejs:3.3.3', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: + 'sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + datasource: 'docker', + depName: 'some-bp', + replaceString: + 'some-bp@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }, + { + autoReplaceStringTemplate: + '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', + currentDigest: + 'sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + currentValue: 'some-tag', + datasource: 'docker', + depName: 'cnbs/some-bp', + replaceString: + 'cnbs/some-bp:some-tag@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + }, + ]); + }); + }); +}); diff --git a/lib/modules/manager/buildpacks/extract.ts b/lib/modules/manager/buildpacks/extract.ts new file mode 100644 index 0000000000..c00d4a4c68 --- /dev/null +++ b/lib/modules/manager/buildpacks/extract.ts @@ -0,0 +1,103 @@ +import is from '@sindresorhus/is'; +import { logger } from '../../../logger'; +import { regEx } from '../../../util/regex'; +import { getDep } from '../dockerfile/extract'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; +import { type ProjectDescriptor, ProjectDescriptorToml } from './schema'; + +const dockerPrefix = regEx(/^docker:\/?\//); +const dockerRef = regEx( + /^((?:[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)*)(?::\d{2,5}\/)?)?[a-z\d]+((\.|_|__|-+)[a-z\d]+)*(\/[a-z\d]+((\.|_|__|-+)[a-z\d]+)*)*(?::(\w[\w.-]{0,127})(?:@sha256:[A-Fa-f\d]{32,})?|@sha256:[A-Fa-f\d]{32,})$/, +); + +function isDockerRef(ref: string): boolean { + if (ref.startsWith('docker:/') || dockerRef.test(ref)) { + return true; + } + return false; +} + +function parseProjectToml( + content: string, + packageFile: string, +): ProjectDescriptor | null { + const res = ProjectDescriptorToml.safeParse(content); + if (res.success) { + return res.data; + } + + logger.debug( + { packageFile, err: res.error }, + 'Failed to parse buildpacks project descriptor TOML', + ); + + return null; +} + +export function extractPackageFile( + content: string, + packageFile: string, + config: ExtractConfig, +): PackageFileContent | null { + const deps: PackageDependency[] = []; + + const descriptor = parseProjectToml(content, packageFile); + if (!descriptor) { + return null; + } + + if ( + descriptor.io?.buildpacks?.builder && + isDockerRef(descriptor.io.buildpacks.builder) + ) { + const dep = getDep( + descriptor.io.buildpacks.builder.replace(dockerPrefix, ''), + true, + config.registryAliases, + ); + logger.trace( + { + depName: dep.depName, + currentValue: dep.currentValue, + currentDigest: dep.currentDigest, + }, + 'Cloud Native Buildpacks builder', + ); + + deps.push({ ...dep, commitMessageTopic: 'builder {{depName}}' }); + } + + if ( + descriptor.io?.buildpacks?.group && + is.array(descriptor.io.buildpacks.group) + ) { + for (const group of descriptor.io.buildpacks.group) { + if (group.uri && isDockerRef(group.uri)) { + const dep = getDep( + group.uri.replace(dockerPrefix, ''), + true, + config.registryAliases, + ); + logger.trace( + { + depName: dep.depName, + currentValue: dep.currentValue, + currentDigest: dep.currentDigest, + }, + 'Cloud Native Buildpack', + ); + + deps.push(dep); + } + } + } + + if (!deps.length) { + return null; + } + return { deps }; +} diff --git a/lib/modules/manager/buildpacks/index.ts b/lib/modules/manager/buildpacks/index.ts new file mode 100644 index 0000000000..53a3dcfeb4 --- /dev/null +++ b/lib/modules/manager/buildpacks/index.ts @@ -0,0 +1,12 @@ +import type { Category } from '../../../constants'; +import { DockerDatasource } from '../../datasource/docker'; +export { extractPackageFile } from './extract'; + +export const defaultConfig = { + commitMessageTopic: 'buildpack {{depName}}', + fileMatch: ['(^|/)project\\.toml$'], + pinDigests: false, +}; + +export const categories: Category[] = ['docker']; +export const supportedDatasources = [DockerDatasource.id]; diff --git a/lib/modules/manager/buildpacks/readme.md b/lib/modules/manager/buildpacks/readme.md new file mode 100644 index 0000000000..d2f7b6c613 --- /dev/null +++ b/lib/modules/manager/buildpacks/readme.md @@ -0,0 +1,26 @@ +The `buildpacks` manager updates Cloud Native Buildpacks project descriptors in `project.toml` files. +A `project.toml` file can reference builder / buildpack images by URIs. +Renovate can update a `project.toml` file if: + +- It can find the file +- The file follows the [project descriptor specifications](https://github.com/buildpacks/spec/blob/main/extensions/project-descriptor.md) +- The buildpack `uri` is an OCI image reference (references to a local file or buildpack registry are ignored) + +If you use buildpacks in the `io.buildpacks.group` array, then you _must_ configure the Docker reference (`uri`) for Renovate to work. + +```toml title="Example of a project.toml file with Docker reference URIs" +[_] +schema-version = "0.2" + +[io.buildpacks] +builder = "registry.corp/builder/noble:1.1.1" + +[[io.buildpacks.group]] +uri = "docker://buildpacks/java:2.2.2" + +[[io.buildpacks.group]] +uri = "buildpacks/nodejs:3.3.3" + +[[io.buildpacks.group]] +uri = "file://local.oci" # will be ignored +``` diff --git a/lib/modules/manager/buildpacks/schema.ts b/lib/modules/manager/buildpacks/schema.ts new file mode 100644 index 0000000000..dc90371ca4 --- /dev/null +++ b/lib/modules/manager/buildpacks/schema.ts @@ -0,0 +1,25 @@ +import { z } from 'zod'; +import { Toml } from '../../../util/schema-utils'; + +const BuildpackGroup = z.object({ + uri: z.string().optional(), +}); + +const IoBuildpacks = z.object({ + builder: z.string().optional(), + group: z.array(BuildpackGroup).optional(), +}); + +export const ProjectDescriptor = z.object({ + _: z.object({ + 'schema-version': z.string(), + }), + io: z + .object({ + buildpacks: IoBuildpacks.optional(), + }) + .optional(), +}); + +export type ProjectDescriptor = z.infer<typeof ProjectDescriptor>; +export const ProjectDescriptorToml = Toml.pipe(ProjectDescriptor); -- GitLab