diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index 84b74fe6a2159e944540f5cfe1f287ed91701795..0ae05622fc90595b0a24b287433f674f5e1a78dd 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -67,6 +67,7 @@ import * as rubyVersion from './ruby-version'; import * as sbt from './sbt'; import * as setupCfg from './setup-cfg'; import * as swift from './swift'; +import * as tekton from './tekton'; import * as terraform from './terraform'; import * as terraformVersion from './terraform-version'; import * as terragrunt from './terragrunt'; @@ -148,6 +149,7 @@ api.set('ruby-version', rubyVersion); api.set('sbt', sbt); api.set('setup-cfg', setupCfg); api.set('swift', swift); +api.set('tekton', tekton); api.set('terraform', terraform); api.set('terraform-version', terraformVersion); api.set('terragrunt', terragrunt); diff --git a/lib/modules/manager/tekton/__fixtures__/multi-doc.yaml b/lib/modules/manager/tekton/__fixtures__/multi-doc.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c39ce14263d1392cd17ee741e4de3d60a5112c3d --- /dev/null +++ b/lib/modules/manager/tekton/__fixtures__/multi-doc.yaml @@ -0,0 +1,111 @@ +--- +kind: Pipeline +spec: + tasks: + - taskRef: + bundle: gcr.io/tekton-releases/catalog/upstream/pipeline:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b +--- +kind: Pipeline +spec: + tasks: + - taskRef: + resolver: bundles + resource: + - name: bundle + value: gcr.io/tekton-releases/catalog/upstream/pipeline-resolver:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b +--- +kind: List +items: +- kind: Pipeline + spec: + tasks: + - taskRef: + bundle: >- + gcr.io/tekton-releases/catalog/upstream/list-pipeline +- kind: PipelineRun + spec: + pipelineRef: + bundle: >- + gcr.io/tekton-releases/catalog/upstream/list-pipeline-run +- kind: TaskRun + spec: + taskRef: + bundle: >- + gcr.io/tekton-releases/catalog/upstream/list-task-run +--- +kind: TriggerTemplate +spec: + resourcetemplates: + - kind: TaskRun + spec: + taskRef: + bundle: gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run + - kind: TaskRun + spec: + taskRef: + resolver: bundles + resource: + - name: bundle + value: gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run-resolver + - kind: PipelineRun + spec: + pipelineRef: + bundle: gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run + - kind: PipelineRun + spec: + pipelineRef: + resolver: bundles + resource: + - name: bundle + value: gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run-resolver +--- +kind: TaskRun +spec: + taskRef: + resolver: bundles + resource: + - name: bundle + value: gcr.io/tekton-releases/catalog/upstream/task-run +--- +kind: TaskRun +spec: + taskRef: + resolver: bundles + resource: + - name: bundle + value: gcr.io/tekton-releases/catalog/upstream/task-run-resolver +--- +kind: PipelineRun +spec: + pipelineRef: + bundle: gcr.io/tekton-releases/catalog/upstream/pipeline-run +--- +kind: PipelineRun +spec: + pipelineRef: + resolver: bundles + resource: + - name: bundle + value: gcr.io/tekton-releases/catalog/upstream/pipeline-run-resolver +--- +kind: Pipeline +spec: + tasks: + - taskRef: + bundle: true +--- +kind: Pipeline +spec: + tasks: + - taskRef: + bundle: "" +--- +kind: Pipeline +spec: + tasks: + - taskRef: + resolver: bundles + resource: + - name: bundle + value: "" +--- diff --git a/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap b/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..1e7d50094e9902bd4cadc6f6c3b9120c56bd8a3a --- /dev/null +++ b/lib/modules/manager/tekton/__snapshots__/extract.spec.ts.snap @@ -0,0 +1,137 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`modules/manager/tekton/extract extractPackageFile() extracts deps from a file 1`] = ` +{ + "deps": [ + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": "sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "currentValue": "1.0", + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": "sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + "currentValue": "1.0", + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-resolver:1.0@sha256:01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/list-pipeline", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/list-pipeline", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/list-pipeline-run", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/list-pipeline-run", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/list-task-run", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/list-task-run", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run-resolver", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-task-run-resolver", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run-resolver", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/trigger-template-pipeline-run-resolver", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/task-run", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/task-run", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/task-run-resolver", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/task-run-resolver", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-run", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-run", + }, + { + "autoReplaceStringTemplate": "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}", + "currentDigest": undefined, + "currentValue": undefined, + "datasource": "docker", + "depName": "gcr.io/tekton-releases/catalog/upstream/pipeline-run-resolver", + "depType": "tekton-bundle", + "replaceString": "gcr.io/tekton-releases/catalog/upstream/pipeline-run-resolver", + }, + { + "depType": "tekton-bundle", + "skipReason": "invalid-value", + }, + { + "depType": "tekton-bundle", + "skipReason": "invalid-value", + }, + { + "depType": "tekton-bundle", + "skipReason": "invalid-value", + }, + ], +} +`; diff --git a/lib/modules/manager/tekton/extract.spec.ts b/lib/modules/manager/tekton/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..b522c8eed7194155264d51288918856ef880a1bd --- /dev/null +++ b/lib/modules/manager/tekton/extract.spec.ts @@ -0,0 +1,35 @@ +import { Fixtures } from '../../../../test/fixtures'; +import { extractPackageFile } from '.'; + +describe('modules/manager/tekton/extract', () => { + describe('extractPackageFile()', () => { + it('extracts deps from a file', () => { + const result = extractPackageFile( + Fixtures.get('multi-doc.yaml'), + 'test-file.yaml' + ); + expect(result).toMatchSnapshot(); + expect(result?.deps).toHaveLength(16); + }); + + it('ignores file without any deps', () => { + expect(extractPackageFile('foo: bar', 'test-file.yaml')).toBeNull(); + }); + + it('ignores invalid YAML', () => { + expect( + extractPackageFile( + ` + --- + bundle: registry.com/repo + `, + 'test-file.yaml' + ) + ).toBeNull(); + }); + + it('ignores empty file', () => { + expect(extractPackageFile('', 'test-file.yaml')).toBeNull(); + }); + }); +}); diff --git a/lib/modules/manager/tekton/extract.ts b/lib/modules/manager/tekton/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..27832cff23f28d21175e0ef884ac0e6fa7b5f171 --- /dev/null +++ b/lib/modules/manager/tekton/extract.ts @@ -0,0 +1,95 @@ +import is from '@sindresorhus/is'; +import { loadAll } from 'js-yaml'; +import { logger } from '../../../logger'; +import { getDep } from '../dockerfile/extract'; +import type { PackageDependency, PackageFile } from '../types'; +import type { TektonBundle, TektonResource } from './types'; + +export function extractPackageFile( + content: string, + fileName: string +): PackageFile | null { + logger.trace('tekton.extractPackageFile()'); + const deps: PackageDependency[] = []; + let docs: TektonResource[]; + try { + docs = loadAll(content) as TektonResource[]; + } catch (err) { + logger.debug( + { err, fileName }, + 'Failed to parse YAML resource as a Tekton resource' + ); + return null; + } + for (const doc of docs) { + deps.push(...getDeps(doc)); + } + if (!deps.length) { + return null; + } + return { deps }; +} + +function getDeps(doc: TektonResource): PackageDependency[] { + const deps: PackageDependency[] = []; + if (is.falsy(doc)) { + return deps; + } + + // Handle TaskRun resource + addDep(doc.spec?.taskRef, deps); + + // Handle PipelineRun resource + addDep(doc.spec?.pipelineRef, deps); + + // Handle Pipeline resource + for (const task of doc.spec?.tasks ?? []) { + addDep(task.taskRef, deps); + } + + // Handle TriggerTemplate resource + for (const resource of doc.spec?.resourcetemplates ?? []) { + addDep(resource?.spec?.taskRef, deps); + addDep(resource?.spec?.pipelineRef, deps); + } + + // Handle list of TektonResources + for (const item of doc.items ?? []) { + deps.push(...getDeps(item)); + } + + return deps; +} + +function addDep(ref: TektonBundle, deps: PackageDependency[]): void { + if (is.falsy(ref)) { + return; + } + let imageRef: string | undefined; + // Find a bundle reference from the Bundle resolver + if (ref.resolver === 'bundles') { + for (const field of ref.resource ?? []) { + if (field.name === 'bundle') { + imageRef = field.value; + break; + } + } + } + + if (is.nullOrUndefined(imageRef)) { + // Fallback to older style bundle reference + imageRef = ref.bundle; + } + + const dep = getDep(imageRef); + dep.depType = 'tekton-bundle'; + logger.trace( + { + depName: dep.depName, + currentValue: dep.currentValue, + currentDigest: dep.currentDigest, + }, + 'Tekton bundle dependency found' + ); + deps.push(dep); +} diff --git a/lib/modules/manager/tekton/index.ts b/lib/modules/manager/tekton/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d68cf8e0703bb8fda4375fc008fcef7521ee9f1c --- /dev/null +++ b/lib/modules/manager/tekton/index.ts @@ -0,0 +1,10 @@ +import { DockerDatasource } from '../../datasource/docker'; +import { extractPackageFile } from './extract'; + +export const defaultConfig = { + fileMatch: [], +}; + +export const supportedDatasources = [DockerDatasource.id]; + +export { extractPackageFile }; diff --git a/lib/modules/manager/tekton/readme.md b/lib/modules/manager/tekton/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..b036d9503447b2387b0114be03e2999be477a1e8 --- /dev/null +++ b/lib/modules/manager/tekton/readme.md @@ -0,0 +1,34 @@ +[Tekton](https://tekton.dev/) is an open-source cloud native CICD (Continuous Integration and Continuous Delivery/Deployment) solution. + +It uses Tasks to capture specifics commands to be executed, and Pipelines to combine different Tasks in order to achieve a goal, e.g. build a container image. +Tasks and Pipelines are defined as Kubernetes custom resources. + +Its [documentation](https://tekton.dev/docs/) is a great resource to learn more about the overall concepts and how to start using it. + +There are different ways to distribute Task and Pipeline definitions. +They can be created directly as a Kubernetes resource with standard tools like `kubectl`. +They can also reside outside the Kubernetes cluster and fetched by Tekton itself when needed. +This second approach relies on Tekton resource references rather than the resource definition. +The `tekton` manager focuses on providing updates to Tekton resource references. + +Currently, the manager only supports references that are [Bundles](https://tekton.dev/docs/pipelines/tekton-bundle-contracts/). +See the [tektoncd/resolution](https://github.com/tektoncd/resolution) project for the different kinds of Tekton references. + +There are two ways to use a Tekton Bundle reference. +The first is via the previously mentioned [tektoncd/resolution](https://github.com/tektoncd/resolution) project, the second is via the `taskRun.spec.taskRef.bundle` and the `pipelineRun.spec.pipelineRef.bundle` attributes. +This manager supports both. + +The `tekton` manager does not have a default `fileMatch` pattern. +It won't match any files until it is configured with a pattern. +This is to avoid unexpected issues with unrelated YAML files since there is no well-established file name pattern for [Tekton](https://tekton.dev/) resources. +As an example, the following config will match all the YAML files in a repository: + +```json +{ + "tekton": { + "fileMatch": ["\\.yaml$", "\\.yml$"] + } +} +``` + +See the [versioning](https://docs.renovatebot.com/modules/versioning/) documentation for details on the existing versioning rules and possible alterations. diff --git a/lib/modules/manager/tekton/types.ts b/lib/modules/manager/tekton/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cc689dc8f1d77b8645970af52ef8f882bac4840 --- /dev/null +++ b/lib/modules/manager/tekton/types.ts @@ -0,0 +1,26 @@ +export interface TektonResource { + spec: TektonResourceSpec; + items: TektonResource[]; +} + +interface TektonResourceSpec { + // TaskRun + taskRef: TektonBundle; + // PipelineRun + pipelineRef: TektonBundle; + // Pipeline + tasks: TektonResourceSpec[]; + // TriggerTemplate + resourcetemplates: TektonResource[]; +} + +export interface TektonBundle { + bundle: string; + resolver: string; + resource: TektonBundleResourceField[]; +} + +interface TektonBundleResourceField { + name: string; + value: string; +}