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(config): custom status checks #26047

Merged
merged 21 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/usage/configuration-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -3564,6 +3564,23 @@ Configure this to `true` if you wish to get one PR for every separate major vers
e.g. if you are on webpack@v1 currently then default behavior is a PR for upgrading to webpack@v3 and not for webpack@v2.
If this setting is true then you would get one PR for webpack@v2 and one for webpack@v3.

## statusCheckNames

This feature allows users to customize the context of the staus checks added by Renovate to the update branches.

1. You can modify existing status checks, but adding entirely new ones is not supported
2. Setting the value to `null` or an empty string, will effectively disable/skip that particular status check
3. This option is mergeable, meaning you only have to specify the status checks you want to modify
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved

```json
rarkins marked this conversation as resolved.
Show resolved Hide resolved
{
"statusCheckNames": {
"minimumReleaseAge": "custom/stability-days",
"mergeConfidence": "custom/merge-confidence-level"
}
}
```

## stopUpdatingLabel

This feature only works on supported platforms, check the table above.
Expand Down
12 changes: 12 additions & 0 deletions lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,18 @@ const options: RenovateOptions[] = [
type: 'string',
},
},
{
name: 'statusCheckNames',
description: 'Custom strings to use as status check names',
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
type: 'object',
mergeable: true,
rarkins marked this conversation as resolved.
Show resolved Hide resolved
default: {
minimumReleaseAge: 'renovate/stability-days',
mergeConfidence: 'renovate/merge-confidence',
configValidation: 'renovate/config-validation',
artifactError: 'renovate/artifacts',
rarkins marked this conversation as resolved.
Show resolved Hide resolved
},
},
{
name: 'extends',
description: 'Configuration presets to use or extend.',
Expand Down
2 changes: 2 additions & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ export interface RenovateConfig

checkedBranches?: string[];
customizeDashboard?: Record<string, string>;

statusCheckNames?: Record<string, string>;
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
}

export interface CustomDatasourceConfig {
Expand Down
22 changes: 22 additions & 0 deletions lib/config/validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@ describe('config/validation', () => {
]);
});

it('validates invalid statusCheckNames', async () => {
const config = {
statusCheckNames: {
randomKey: '',
mergeConfidence: 10,
configValidation: '',
artifactError: null,
},
} as any;
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
const { errors } = await configValidation.validateConfig(config);
expect(errors).toMatchObject([
{
message:
'Invalid `statusCheckNames.mergeConfidence` configuration: status check is not a string',
},
{
message:
'Invalid `statusCheckNames.statusCheckNames.randomKey` configuration: key is not allowed',
},
]);
});

it('catches invalid customDatasources record type', async () => {
const config = {
customDatasources: {
Expand Down
26 changes: 26 additions & 0 deletions lib/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,32 @@ export async function validateConfig(
message: `Invalid \`${currentPath}.${key}.${res}\` configuration: value is not a string`,
});
}
} else if (key === 'statusCheckNames') {
const allowedStatusStrings = [
'minimumReleaseAge',
'artifactError',
'mergeConfidence',
'configValidation',
];
for (const [statusCheckKey, statusCheckValue] of Object.entries(
val,
)) {
if (!allowedStatusStrings.includes(statusCheckKey)) {
errors.push({
topic: 'Configuration Error',
message: `Invalid \`${currentPath}.${key}.${statusCheckKey}\` configuration: key is not allowed`,
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
});
}
if (
!(is.string(statusCheckValue) || is.null_(statusCheckValue))
) {
errors.push({
topic: 'Configuration Error',
message: `Invalid \`${currentPath}.${statusCheckKey}\` configuration: status check is not a string`,
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
});
continue;
}
}
} else if (key === 'customDatasources') {
const allowedKeys = [
'description',
Expand Down
3 changes: 3 additions & 0 deletions lib/workers/repository/reconfigure/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ describe('workers/repository/reconfigure/index', () => {
const config: RenovateConfig = {
branchPrefix: 'prefix/',
baseBranch: 'base',
statusCheckNames: {
configValidation: 'renovate/config-validation',
},
};

beforeEach(() => {
Expand Down
77 changes: 44 additions & 33 deletions lib/workers/repository/reconfigure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,32 @@
setReconfigureBranchCache,
} from './reconfigure-cache';

async function setBranchStatus(
branchName: string,
description: string,
state: string,
context: string | undefined,
): Promise<void> {
if (!is.nonEmptyString(context)) {
return;

Check warning on line 26 in lib/workers/repository/reconfigure/index.ts

View check run for this annotation

Codecov / codecov/patch

lib/workers/repository/reconfigure/index.ts#L26

Added line #L26 was not covered by tests
}

await platform.setBranchStatus({
branchName,
context,
description,
state: state as any,
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
});
}

export function getReconfigureBranchName(prefix: string): string {
return `${prefix}reconfigure`;
}
export async function validateReconfigureBranch(
config: RenovateConfig,
): Promise<void> {
logger.debug('validateReconfigureBranch()');
const context = `renovate/config-validation`;
const context = config.statusCheckNames?.configValidation;

const branchName = getReconfigureBranchName(config.branchPrefix!);
const branchExists = await scm.branchExists(branchName);
Expand All @@ -48,14 +66,17 @@
return;
}

const validationStatus = await platform.getBranchStatusCheck(
branchName,
'renovate/config-validation',
);
// if old status check is present skip validation
if (is.nonEmptyString(validationStatus)) {
logger.debug('Skipping validation check as status check already exists');
return;
if (context) {
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
const validationStatus = await platform.getBranchStatusCheck(
branchName,
context,
);

// if old status check is present skip validation
if (is.nonEmptyString(validationStatus)) {
logger.debug('Skipping validation check as status check already exists');
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
return;
}
}

try {
Expand All @@ -70,12 +91,12 @@

if (!is.nonEmptyString(configFileName)) {
logger.warn('No config file found in reconfigure branch');
await platform.setBranchStatus({
await setBranchStatus(
branchName,
'Validation Failed - No config file found',
'red',
context,
description: 'Validation Failed - No config file found',
state: 'red',
});
);
setReconfigureBranchCache(branchSha, false);
await scm.checkoutBranch(config.defaultBranch!);
return;
Expand All @@ -90,12 +111,12 @@

if (!is.nonEmptyString(configFileRaw)) {
logger.warn('Empty or invalid config file');
await platform.setBranchStatus({
await setBranchStatus(
branchName,
'Validation Failed - Empty/Invalid config file',
'red',
context,
description: 'Validation Failed - Empty/Invalid config file',
state: 'red',
});
);
setReconfigureBranchCache(branchSha, false);
await scm.checkoutBranch(config.baseBranch!);
return;
Expand All @@ -110,12 +131,12 @@
}
} catch (err) {
logger.error({ err }, 'Error while parsing config file');
await platform.setBranchStatus({
await setBranchStatus(
branchName,
'Validation Failed - Unparsable config file',
'red',
context,
description: 'Validation Failed - Unparsable config file',
state: 'red',
});
);
setReconfigureBranchCache(branchSha, false);
await scm.checkoutBranch(config.baseBranch!);
return;
Expand Down Expand Up @@ -150,24 +171,14 @@
content: body,
});
}
await platform.setBranchStatus({
branchName,
context,
description: 'Validation Failed',
state: 'red',
});
await setBranchStatus(branchName, 'Validation Failed', 'red', context);
setReconfigureBranchCache(branchSha, false);
await scm.checkoutBranch(config.baseBranch!);
return;
}

// passing check
await platform.setBranchStatus({
branchName,
context,
description: 'Validation Successful',
state: 'green',
});
await setBranchStatus(branchName, 'Validation Successful', 'green', context);

setReconfigureBranchCache(branchSha, true);
await scm.checkoutBranch(config.baseBranch!);
Expand Down
3 changes: 3 additions & 0 deletions lib/workers/repository/update/branch/artifacts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe('workers/repository/update/branch/artifacts', () => {
branchName: 'renovate/pin',
upgrades: [],
artifactErrors: [{ lockFile: 'some' }],
statusCheckNames: {
artifactError: 'renovate/artifact',
},
} satisfies BranchConfig;
});

Expand Down
8 changes: 6 additions & 2 deletions lib/workers/repository/update/branch/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import is from '@sindresorhus/is';
import { GlobalConfig } from '../../../../config/global';
import { logger } from '../../../../logger';
import { platform } from '../../../../modules/platform';
Expand All @@ -6,12 +7,15 @@
export async function setArtifactErrorStatus(
config: BranchConfig,
): Promise<void> {
if (!config.artifactErrors?.length) {
if (
!config.artifactErrors?.length ||
!is.nonEmptyString(config.statusCheckNames?.artifactError)
) {
// no errors
return;
}

const context = `renovate/artifacts`;
const context = config.statusCheckNames!.artifactError;

Check failure on line 18 in lib/workers/repository/update/branch/artifacts.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

This assertion is unnecessary since it does not change the type of the expression.
const description = 'Artifact file update failure';
const state = 'red';
const existingState = await platform.getBranchStatusCheck(
Expand Down
6 changes: 6 additions & 0 deletions lib/workers/repository/update/branch/status-checks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe('workers/repository/update/branch/status-checks', () => {
beforeEach(() => {
config = partial<StabilityConfig>({
branchName: 'renovate/some-branch',
statusCheckNames: {
minimumReleaseAge: 'renovate/stability-days',
},
});
});

Expand Down Expand Up @@ -53,6 +56,9 @@ describe('workers/repository/update/branch/status-checks', () => {
beforeEach(() => {
config = {
branchName: 'renovate/some-branch',
statusCheckNames: {
mergeConfidence: 'renovate/merge-confidence',
},
};
});

Expand Down
12 changes: 9 additions & 3 deletions lib/workers/repository/update/branch/status-checks.ts
RahulGautamSingh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import is from '@sindresorhus/is';
import type { RenovateConfig } from '../../../../config/types';
import { logger } from '../../../../logger';
import { platform } from '../../../../modules/platform';
Expand Down Expand Up @@ -59,10 +60,14 @@
}

export async function setStability(config: StabilityConfig): Promise<void> {
if (!config.stabilityStatus) {
if (
!config.stabilityStatus ||
!is.nonEmptyString(config.statusCheckNames?.minimumReleaseAge)
) {
return;
}
const context = `renovate/stability-days`;

const context = config.statusCheckNames!.minimumReleaseAge;

Check failure on line 70 in lib/workers/repository/update/branch/status-checks.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

This assertion is unnecessary since it does not change the type of the expression.
const description =
config.stabilityStatus === 'green'
? 'Updates have met minimum release age requirement'
Expand All @@ -85,12 +90,13 @@
if (
!config.branchName ||
!config.confidenceStatus ||
!is.nonEmptyString(config.statusCheckNames?.mergeConfidence) ||
(config.minimumConfidence &&
!isActiveConfidenceLevel(config.minimumConfidence))
) {
return;
}
const context = `renovate/merge-confidence`;
const context = config.statusCheckNames!.mergeConfidence;

Check failure on line 99 in lib/workers/repository/update/branch/status-checks.ts

View workflow job for this annotation

GitHub Actions / lint-eslint

This assertion is unnecessary since it does not change the type of the expression.
const description =
config.confidenceStatus === 'green'
? 'Updates have met Merge Confidence requirement'
Expand Down