diff --git a/docs/usage/configuration-options.md b/docs/usage/configuration-options.md index 7e099f851b683df956ac4ab741f12c461c067a97..bb4a74e298a9ad481f574ecbb0e3cbe5685d345c 100644 --- a/docs/usage/configuration-options.md +++ b/docs/usage/configuration-options.md @@ -410,9 +410,21 @@ This is an advance field and it's recommend you seek a config review before appl ## bumpVersion -Currently this setting supports `helmv3`, `npm`, `nuget`, `maven`, `pep621`, `poetry` and `sbt` only, so raise a feature request if you have a use for it with other package managers. -Its purpose is if you want Renovate to update the `version` field within your package file any time it updates dependencies within. -Usually this is for automatic release purposes, so that you don't need to add another step after Renovate before you can release a new version. +Currently, this config option only works with these managers: + +- `helmv3` +- `npm` +- `nuget` +- `maven` +- `ocb` +- `pep621` +- `poetry` +- `sbt` + +Raise a feature request if you want to use this config option with other package managers. + +Use `bumpVersion` if you want Renovate to update the `version` field in your package file when it updates the dependencies in that file. +This can be handy when you have automated your package's release, as you you don't need extra steps after the Renovate upgrade, you can just release a new version. Configure this value to `"prerelease"`, `"patch"`, `"minor"` or `"major"` to have Renovate update the version in your edited package file. e.g. if you wish Renovate to always increase the target `package.json` version with a patch update, configure this to `"patch"`. diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts index ba08a398818dbd3114ff486de2b3b95d18777abd..6f618abaee49d2954d1fb72792ad42a2c96bca10 100644 --- a/lib/modules/manager/api.ts +++ b/lib/modules/manager/api.ts @@ -61,6 +61,7 @@ import * as nodenv from './nodenv'; import * as npm from './npm'; import * as nuget from './nuget'; import * as nvm from './nvm'; +import * as ocb from './ocb'; import * as osgi from './osgi'; import * as pep621 from './pep621'; import * as pipCompile from './pip-compile'; @@ -153,6 +154,7 @@ api.set('nodenv', nodenv); api.set('npm', npm); api.set('nuget', nuget); api.set('nvm', nvm); +api.set('ocb', ocb); api.set('osgi', osgi); api.set('pep621', pep621); api.set('pip-compile', pipCompile); diff --git a/lib/modules/manager/ocb/extract.spec.ts b/lib/modules/manager/ocb/extract.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..99fd13ec2a1f71dc622b3ce01d3090d6ddea374b --- /dev/null +++ b/lib/modules/manager/ocb/extract.spec.ts @@ -0,0 +1,74 @@ +import { codeBlock } from 'common-tags'; +import { extractPackageFile } from '.'; + +describe('modules/manager/ocb/extract', () => { + describe('extractPackageFile', () => { + it('run successfully with full example', () => { + const content = codeBlock` + dist: + name: otelcol-custom + description: Local OpenTelemetry Collector binary + module: github.com/open-telemetry/opentelemetry-collector + otelcol_version: 0.40.0 + version: 1.0.0 + output_path: /tmp/dist + exporters: + - gomod: github.com/open-telemetry/opentelemetry-collector-contrib/exporter/alibabacloudlogserviceexporter v0.86.0 + - gomod: go.opentelemetry.io/collector/exporter/debugexporter v0.86.0 + + receivers: + - gomod: go.opentelemetry.io/collector/receiver/otlpreceiver v0.86.0 + + processors: + - gomod: go.opentelemetry.io/collector/processor/batchprocessor v0.86.0 + `; + const result = extractPackageFile(content, 'builder-config.yaml'); + expect(result?.deps).toEqual([ + { + currentValue: '0.40.0', + datasource: 'go', + depName: 'github.com/open-telemetry/opentelemetry-collector', + depType: 'collector', + extractVersion: '^v(?<version>\\S+)', + }, + { + currentValue: 'v0.86.0', + datasource: 'go', + depName: + 'github.com/open-telemetry/opentelemetry-collector-contrib/exporter/alibabacloudlogserviceexporter', + depType: 'exports', + }, + { + currentValue: 'v0.86.0', + datasource: 'go', + depName: 'go.opentelemetry.io/collector/exporter/debugexporter', + depType: 'exports', + }, + { + currentValue: 'v0.86.0', + datasource: 'go', + depName: 'go.opentelemetry.io/collector/processor/batchprocessor', + depType: 'processors', + }, + ]); + }); + + it('return null for unknown content', () => { + expect(extractPackageFile('foo', 'bar.yaml')).toBeNull(); + }); + + it('return null for content which is not YAML', () => { + expect( + extractPackageFile( + codeBlock` + myObject: + aString: value + --- + foo: bar + `, + 'bar.yaml', + ), + ).toBeNull(); + }); + }); +}); diff --git a/lib/modules/manager/ocb/extract.ts b/lib/modules/manager/ocb/extract.ts new file mode 100644 index 0000000000000000000000000000000000000000..99416851bce64fcdfb594cb6f26a2d407b124221 --- /dev/null +++ b/lib/modules/manager/ocb/extract.ts @@ -0,0 +1,81 @@ +import is from '@sindresorhus/is'; +import { logger } from '../../../logger'; +import { regEx } from '../../../util/regex'; +import { parseSingleYaml } from '../../../util/yaml'; +import { GoDatasource } from '../../datasource/go'; +import type { + ExtractConfig, + PackageDependency, + PackageFileContent, +} from '../types'; +import { type Module, type OCBConfig, OCBConfigSchema } from './schema'; + +export function extractPackageFile( + content: string, + packageFile: string, + _config?: ExtractConfig, +): PackageFileContent | null { + let definition: OCBConfig | null = null; + try { + const yaml = parseSingleYaml(content); + const parsed = OCBConfigSchema.safeParse(yaml); + if (!parsed.success) { + logger.trace( + { packageFile, error: parsed.error }, + 'Failed to parse OCB schema', + ); + return null; + } + + definition = parsed.data; + } catch (error) { + logger.debug( + { packageFile, error }, + 'OCB manager failed to parse file as YAML', + ); + return null; + } + + const deps: PackageDependency[] = []; + if (definition.dist.module && definition.dist.otelcol_version) { + deps.push({ + datasource: GoDatasource.id, + depType: 'collector', + depName: definition.dist.module, + currentValue: definition.dist.otelcol_version, + extractVersion: '^v(?<version>\\S+)', + }); + } + + deps.push(...processModule(definition.connectors, 'connectors')); + deps.push(...processModule(definition.exporters, 'exports')); + deps.push(...processModule(definition.extension, 'extensions')); + deps.push(...processModule(definition.processors, 'processors')); + + return { + packageFileVersion: definition.dist.version, + deps, + }; +} + +export function processModule( + module: Module, + depType: string, +): PackageDependency[] { + const deps: PackageDependency[] = []; + if (is.nullOrUndefined(module)) { + return deps; + } + + for (const element of module) { + const [depName, currentValue] = element.gomod.trim().split(regEx(/\s+/)); + deps.push({ + datasource: GoDatasource.id, + depType, + depName, + currentValue, + }); + } + + return deps; +} diff --git a/lib/modules/manager/ocb/index.ts b/lib/modules/manager/ocb/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..013a7f62965a023bfe26ba54ee42e59640089d33 --- /dev/null +++ b/lib/modules/manager/ocb/index.ts @@ -0,0 +1,13 @@ +import type { Category } from '../../../constants'; +import { GoDatasource } from '../../datasource/go'; + +export { extractPackageFile } from './extract'; +export { bumpPackageVersion } from './update'; + +export const supportedDatasources = [GoDatasource.id]; + +export const categories: Category[] = ['golang']; + +export const defaultConfig = { + fileMatch: [], +}; diff --git a/lib/modules/manager/ocb/readme.md b/lib/modules/manager/ocb/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..2ca2119d6aedd08e756ed2679ea1a99106e30ba3 --- /dev/null +++ b/lib/modules/manager/ocb/readme.md @@ -0,0 +1,23 @@ +Renovate uses this manager to update dependencies defined in the build definitions for the [OpenTelemetry Collector Builder (ocb)](https://github.com/open-telemetry/opentelemetry-collector/tree/main/cmd/builder). + +By default, the `ocb` manager has no `fileMatch` patterns. +This means you must set a `fileMatch` pattern for the `ocb` manager, in order for Renovate to update your `ocb` files. +Here's a configuration example: + +```json title="If your builder files are named like foo-builder.yml or builder.yaml" +{ + "ocb": { + "fileMatch": ["builder.ya?ml$"] + } +} +``` + +Supported dependencies and their respective `depType`s are: + +| Name | depType | +| -------------- | ------------ | +| base collector | `collector` | +| connectors | `connectors` | +| exports | `exports` | +| extensions | `extensions` | +| processors | `processors` | diff --git a/lib/modules/manager/ocb/schema.ts b/lib/modules/manager/ocb/schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b8bd927b469a5e48a1306a1195e342f4b51f3c6 --- /dev/null +++ b/lib/modules/manager/ocb/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod'; + +const Entry = z.object({ + gomod: z.string(), +}); + +const ModuleSchema = z.array(Entry).optional(); +export type Module = z.infer<typeof ModuleSchema>; + +export const OCBConfigSchema = z.object({ + dist: z.object({ + otelcol_version: z.string().optional(), + module: z.string().optional(), + version: z.string().optional(), + }), + extension: ModuleSchema, + exporters: ModuleSchema, + receivers: ModuleSchema, + processors: ModuleSchema, + connectors: ModuleSchema, +}); +export type OCBConfig = z.infer<typeof OCBConfigSchema>; diff --git a/lib/modules/manager/ocb/update.spec.ts b/lib/modules/manager/ocb/update.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc1e8cda5f9463d1126dc81a90af795ca71f72a7 --- /dev/null +++ b/lib/modules/manager/ocb/update.spec.ts @@ -0,0 +1,77 @@ +import { codeBlock } from 'common-tags'; +import { bumpPackageVersion } from '.'; + +describe('modules/manager/ocb/update', () => { + describe('bumpPackageVersion()', () => { + it('increments with all fields', () => { + const content = codeBlock` + dist: + name: otelcol-custom + description: Local OpenTelemetry Collector binary + module: github.com/open-telemetry/opentelemetry-collector + otelcol_version: 0.40.0 + version: 1.0.0 + output_path: /tmp/dist + `; + const expected = content.replace('1.0.0', '1.0.1'); + + const { bumpedContent } = bumpPackageVersion(content, '1.0.0', 'patch'); + expect(bumpedContent).toEqual(expected); + }); + + it('increments with double quotes', () => { + const content = codeBlock` + dist: + version: "1.0.0" + `; + const expected = content.replace('1.0.0', '1.0.1'); + + const { bumpedContent } = bumpPackageVersion(content, '1.0.0', 'patch'); + expect(bumpedContent).toEqual(expected); + }); + + it('increments with single quotes', () => { + const content = codeBlock` + dist: + version: '1.0.0' + `; + const expected = content.replace('1.0.0', '1.0.1'); + + const { bumpedContent } = bumpPackageVersion(content, '1.0.0', 'patch'); + expect(bumpedContent).toEqual(expected); + }); + + it('no ops', () => { + const content = codeBlock` + dist: + version: '0.0.2' + `; + const { bumpedContent } = bumpPackageVersion(content, '0.0.1', 'patch'); + expect(bumpedContent).toEqual(content); + }); + + it('updates', () => { + const content = codeBlock` + dist: + version: '0.0.2' + `; + const { bumpedContent } = bumpPackageVersion(content, '0.0.1', 'minor'); + const expected = content.replace('0.0.2', '0.1.0'); + expect(bumpedContent).toEqual(expected); + }); + + it('returns content if bumping errors', () => { + const content = codeBlock` + dist: + version: '1.0.0' + `; + const { bumpedContent } = bumpPackageVersion( + content, + '0.0.2', + // @ts-expect-error supplying a wrong parameter to trigger an exception + true, + ); + expect(bumpedContent).toEqual(content); + }); + }); +}); diff --git a/lib/modules/manager/ocb/update.ts b/lib/modules/manager/ocb/update.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e893c699b0f031d37d4fffaee38b4617b5d05bc --- /dev/null +++ b/lib/modules/manager/ocb/update.ts @@ -0,0 +1,47 @@ +import { type ReleaseType, inc } from 'semver'; +import { logger } from '../../../logger'; +import { regEx } from '../../../util/regex'; +import type { BumpPackageVersionResult } from '../types'; + +export function bumpPackageVersion( + content: string, + currentValue: string, + bumpVersion: ReleaseType, +): BumpPackageVersionResult { + logger.debug( + { bumpVersion, currentValue }, + 'Checking if we should bump OCB version', + ); + + let bumpedContent = content; + try { + const newProjectVersion = inc(currentValue, bumpVersion); + if (!newProjectVersion) { + throw new Error('semver inc failed'); + } + + logger.debug(`newProjectVersion: ${newProjectVersion}`); + bumpedContent = content.replace( + regEx(/\b(?<version>version:\s+["']?)(?<currentValue>[^'"\s]*)/), + `$<version>${newProjectVersion}`, + ); + + if (bumpedContent === content) { + logger.debug('Version was already bumped'); + } else { + logger.debug('Bumped OCB version'); + } + } catch (err) { + logger.warn( + { + content, + currentValue, + bumpVersion, + manager: 'ocb', + }, + 'Failed to bumpVersion', + ); + } + + return { bumpedContent }; +}