Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(managers/ocb): add new manager for OpenTelemetryCollectorBuilder #26509

Merged
14 changes: 13 additions & 1 deletion docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,19 @@ 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.
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.

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.
secustor marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
2 changes: 2 additions & 0 deletions lib/modules/manager/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
74 changes: 74 additions & 0 deletions lib/modules/manager/ocb/extract.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
78 changes: 78 additions & 0 deletions lib/modules/manager/ocb/extract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
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.trace({ packageFile, error }, 'Failed to parse OCB file as YAML');
secustor marked this conversation as resolved.
Show resolved Hide resolved
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;
}
13 changes: 13 additions & 0 deletions lib/modules/manager/ocb/index.ts
Original file line number Diff line number Diff line change
@@ -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: [],
};
21 changes: 21 additions & 0 deletions lib/modules/manager/ocb/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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).

The `ocb` manager has no `fileMatch` default patterns, so it won't match any files until you configure it with a pattern.
secustor marked this conversation as resolved.
Show resolved Hide resolved

```json title="If your builder files are named like foo-builder.yml or builder.yaml"
viceice marked this conversation as resolved.
Show resolved Hide resolved
{
"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` |
22 changes: 22 additions & 0 deletions lib/modules/manager/ocb/schema.ts
Original file line number Diff line number Diff line change
@@ -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>;
76 changes: 76 additions & 0 deletions lib/modules/manager/ocb/update.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
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',
true as any,
secustor marked this conversation as resolved.
Show resolved Hide resolved
);
expect(bumpedContent).toEqual(content);
});
});
});
47 changes: 47 additions & 0 deletions lib/modules/manager/ocb/update.ts
Original file line number Diff line number Diff line change
@@ -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 };
}