Skip to content

Commit

Permalink
feat: create issues when the release is failing (#349)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Issues will be created or are commented by default . Set the `failComment` or `failTitle` option to `false` to disable this behavior.

Co-authored-by: Jonas Schubert <jonas.schubert@siemens.com>
Co-authored-by: Florian Greinacher <florian@greinacher.de>
  • Loading branch information
3 people committed Apr 9, 2022
1 parent 467a9d4 commit 0ad008e
Show file tree
Hide file tree
Showing 12 changed files with 688 additions and 78 deletions.
46 changes: 34 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
[![Build Status](https://github.com/semantic-release/gitlab/workflows/Test/badge.svg)](https://github.com/semantic-release/gitlab/actions?query=workflow%3ATest+branch%3Amaster) [![npm latest version](https://img.shields.io/npm/v/@semantic-release/gitlab/latest.svg)](https://www.npmjs.com/package/@semantic-release/gitlab)
[![npm next version](https://img.shields.io/npm/v/@semantic-release/gitlab/next.svg)](https://www.npmjs.com/package/@semantic-release/gitlab)

| Step | Description |
|--------------------|-----------------------------------------------------------------------------------------------------------------------|
| `verifyConditions` | Verify the presence and the validity of the authentication (set via [environment variables](#environment-variables)). |
| `publish` | Publish a [GitLab release](https://docs.gitlab.com/ee/user/project/releases/). |
| `success` | Add a comment to each GitLab Issue or Merge Request resolved by the release. |
| Step | Description |
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| `verifyConditions` | Verify the presence and the validity of the authentication (set via [environment variables](#environment-variables)). |
| `publish` | Publish a [GitLab release](https://docs.gitlab.com/ee/user/project/releases/). |
| `success` | Add a comment to each GitLab Issue or Merge Request resolved by the release. |
| `fail` | Open or update a [GitLab Issue](https://docs.gitlab.com/ee/user/project/issues/) with information about the errors that caused the release to fail. |

## Install

Expand Down Expand Up @@ -70,13 +71,17 @@ If your GitLab instance is exposed via plain HTTP (not recommended!) use `HTTP_P

### Options

| Option | Description | Default |
|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `gitlabUrl` | The GitLab endpoint. | `GL_URL` or `GITLAB_URL` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `https://gitlab.com`. |
| `gitlabApiPathPrefix` | The GitLab API prefix. | `GL_PREFIX` or `GITLAB_PREFIX` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `/api/v4`. |
| `assets` | An array of files to upload to the release. See [assets](#assets). | - |
| `milestones` | An array of milestone titles to associate to the release. See [GitLab Release API](https://docs.gitlab.com/ee/api/releases/#create-a-release). | - |
| `successComment` | The comment to add to each Issue and Merge Request resolved by the release. Set to false to disable commenting. See [successComment](#successComment). | :tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitLab release](<gitlab_release_url>) |
| Option | Description | Default |
|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `gitlabUrl` | The GitLab endpoint. | `GL_URL` or `GITLAB_URL` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `https://gitlab.com`. |
| `gitlabApiPathPrefix` | The GitLab API prefix. | `GL_PREFIX` or `GITLAB_PREFIX` environment variable or CI provided environment variables if running on [GitLab CI/CD](https://docs.gitlab.com/ee/ci) or `/api/v4`. |
| `assets` | An array of files to upload to the release. See [assets](#assets). | - |
| `milestones` | An array of milestone titles to associate to the release. See [GitLab Release API](https://docs.gitlab.com/ee/api/releases/#create-a-release). | - |
| `successComment` | The comment to add to each Issue and Merge Request resolved by the release. Set to false to disable commenting. See [successComment](#successComment). | :tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nThe release is available on [GitLab release](<gitlab_release_url>) |
| `failComment` | The content of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. See [failComment](#failcomment). | Friendly message with links to **semantic-release** documentation and support, with the list of errors that caused the release to fail. |
| `failTitle` | The title of the issue created when a release fails. Set to `false` to disable opening an issue when a release fails. | `The automated release is failing 🚨` |
| `labels` | The [labels](https://docs.gitlab.com/ee/user/project/labels.html#labels) to add to the issue created when a release fails. Set to `false` to not add any label. | `semantic-release` |
| `assignee` | The [assignee](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#assignee) to add to the issue created when a release fails. | - |

#### assets

Expand Down Expand Up @@ -127,6 +132,23 @@ The message for the issue comments is generated with [Lodash template](https://l
| `mergeRequest` | A [GitLab API Issue object](https://docs.gitlab.com/ee/api/issues.html#single-issue) the comment will be posted to, or `false` when commenting Merge Requests.
| `issue` | A [GitHub API Merge Request object](https://docs.gitlab.com/ee/api/merge_requests.html#get-single-mr) the comment will be posted to, or `false` when commenting Issues.

#### failComment

The message for the issue content is generated with [Lodash template](https://lodash.com/docs#template). The following variables are available:

| Parameter | Description |
|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `branch` | The branch from which the release had failed. |
| `errors` | An `Array` of [SemanticReleaseError](https://github.com/semantic-release/error). Each error has the `message`, `code`, `pluginName` and `details` properties.<br>`pluginName` contains the package name of the plugin that threw the error.<br>`details` contains a information about the error formatted in markdown. |

##### failComment example

The `failComment` `This release from branch ${branch.name} had failed due to the following errors:\n- ${errors.map(err => err.message).join('\\n- ')}` will generate the comment:

> This release from branch master had failed due to the following errors:
> - Error message 1
> - Error message 2
## Compatibility

The latest version of this plugin is compatible with all currently-supported versions of GitLab, [which is the current major version and previous two major versions](https://about.gitlab.com/support/statement-of-support.html#version-support). This plugin is not guaranteed to work with unsupported versions of GitLab.
Expand Down
12 changes: 11 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const verifyGitLab = require('./lib/verify');
const publishGitLab = require('./lib/publish');
const successGitLab = require('./lib/success');
const failGitLab = require('./lib/fail');

let verified;

Expand All @@ -29,4 +30,13 @@ async function success(pluginConfig, context) {
return successGitLab(pluginConfig, context);
}

module.exports = {verifyConditions, publish, success};
async function fail(pluginConfig, context) {
if (!verified) {
await verifyGitLab(pluginConfig, context);
verified = true;
}

return failGitLab(pluginConfig, context);
}

module.exports = {verifyConditions, publish, success, fail};
4 changes: 3 additions & 1 deletion lib/definitions/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const HOME_URL = 'https://github.com/semantic-release/semantic-release';

const RELEASE_NAME = 'GitLab release';

module.exports = {RELEASE_NAME};
module.exports = {HOME_URL, RELEASE_NAME};
24 changes: 24 additions & 0 deletions lib/definitions/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@ module.exports = {
)}) must be an \`Array\` of \`Strings\` or \`Objects\` with a \`path\` property.
Your configuration for the \`assets\` option is \`${stringify(assets)}\`.`,
}),
EINVALIDFAILTITLE: ({failTitle}) => ({
message: 'Invalid `failTitle` option.',
details: `The [failTitle option](${linkify('README.md#failtitle')}) if defined, must be a non empty \`String\`.
Your configuration for the \`failTitle\` option is \`${stringify(failTitle)}\`.`,
}),
EINVALIDFAILCOMMENT: ({failComment}) => ({
message: 'Invalid `failComment` option.',
details: `The [failComment option](${linkify('README.md#failcomment')}) if defined, must be a non empty \`String\`.
Your configuration for the \`failComment\` option is \`${stringify(failComment)}\`.`,
}),
EINVALIDLABELS: ({labels}) => ({
message: 'Invalid `labels` option.',
details: `The [labels option](${linkify('README.md#labels')}) if defined, must be a non empty \`String\`.
Your configuration for the \`labels\` option is \`${stringify(labels)}\`.`,
}),
EINVALIDASSIGNEE: ({assignee}) => ({
message: 'Invalid `assignee` option.',
details: `The [assignee option](${linkify('README.md#assignee')}) if defined, must be a non empty \`String\`.
Your configuration for the \`assignee\` option is \`${stringify(assignee)}\`.`,
}),
EINVALIDGITLABURL: () => ({
message: 'The git repository URL is not a valid GitLab URL.',
details: `The **semantic-release** \`repositoryUrl\` option must a valid GitLab URL with the format \`<GitLab_URL>/<repoId>.git\`.
Expand Down
62 changes: 62 additions & 0 deletions lib/fail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const {template} = require('lodash');
const urlJoin = require('url-join');
const got = require('got');
const debug = require('debug')('semantic-release:gitlab');
const resolveConfig = require('./resolve-config');
const getRepoId = require('./get-repo-id');
const getFailComment = require('./get-fail-comment');

module.exports = async (pluginConfig, context) => {
const {
options: {repositoryUrl},
branch,
errors,
logger,
} = context;
const {gitlabToken, gitlabUrl, gitlabApiUrl, failComment, failTitle, labels, assignee} = resolveConfig(
pluginConfig,
context
);
const repoId = getRepoId(context, gitlabUrl, repositoryUrl);
const encodedRepoId = encodeURIComponent(repoId);
const apiOptions = {headers: {'PRIVATE-TOKEN': gitlabToken}};

if (failComment === false || failTitle === false) {
logger.log('Skip issue creation.');
} else {
const encodedFailTitle = encodeURIComponent(failTitle);
const description = failComment ? template(failComment)({branch, errors}) : getFailComment(branch, errors);

const issuesEndpoint = urlJoin(gitlabApiUrl, `/projects/${repoId}/issues`);
const openFailTitleIssueEndpoint = urlJoin(issuesEndpoint, `?state=opened&search=${encodedFailTitle}`);

const openFailTitleIssues = await got(openFailTitleIssueEndpoint, {...apiOptions}).json();
const existingIssue = openFailTitleIssues.find(openFailTitleIssue => openFailTitleIssue.title === failTitle);

if (existingIssue) {
debug('comment on issue: %O', existingIssue);

const issueNotesEndpoint = urlJoin(
gitlabApiUrl,
`/projects/${existingIssue.project_id}/issues/${existingIssue.iid}/notes`
);
await got.post(issueNotesEndpoint, {
...apiOptions,
json: {body: description},
});

const {id, web_url} = existingIssue;
logger.log('Commented on issue #%d: %s.', id, web_url);
} else {
const newIssue = {id: encodedRepoId, description, labels, title: failTitle, assignee_id: assignee};
debug('create issue: %O', newIssue);

/* eslint camelcase: off */
const {id, web_url} = await got.post(issuesEndpoint, {
...apiOptions,
json: newIssue,
});
logger.log('Created issue #%d: %s.', id, web_url);
}
}
};
46 changes: 46 additions & 0 deletions lib/get-fail-comment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const {HOME_URL} = require('./definitions/constants');

const FAQ_URL = `${HOME_URL}/blob/master/docs/support/FAQ.md`;
const GET_HELP_URL = `${HOME_URL}#get-help`;
const USAGE_DOC_URL = `${HOME_URL}/blob/master/docs/usage/README.md`;
const NEW_ISSUE_URL = `${HOME_URL}/issues/new`;

const formatError = error => `### ${error.message}
${error.details ||
`Unfortunately this error doesn't have any additional information.${
error.pluginName
? ` Feel free to kindly ask the author of the \`${error.pluginName}\` plugin to add more helpful information.`
: ''
}`}`;

module.exports = (branch, errors) => `## :rotating_light: The automated release from the \`${
branch.name
}\` branch failed. :rotating_light:
I recommend you give this issue a high priority, so other packages depending on you can benefit from your bug fixes and new features again.
You can find below the list of errors reported by **semantic-release**. Each one of them has to be resolved in order to automatically publish your package. I'm sure you can fix this 💪.
Errors are usually caused by a misconfiguration or an authentication problem. With each error reported below you will find explanation and guidance to help you to resolve it.
Once all the errors are resolved, **semantic-release** will release your package the next time you push a commit to the \`${
branch.name
}\` branch. You can also manually restart the failed CI job that runs **semantic-release**.
If you are not sure how to resolve this, here are some links that can help you:
- [Usage documentation](${USAGE_DOC_URL})
- [Frequently Asked Questions](${FAQ_URL})
- [Support channels](${GET_HELP_URL})
If those don't help, or if this issue is reporting something you think isn't right, you can always ask the humans behind **[semantic-release](${NEW_ISSUE_URL})**.
---
${errors.map(error => formatError(error)).join('\n\n---\n\n')}
---
Good luck with your project ✨
Your **[semantic-release](${HOME_URL})** bot :package: :rocket:`;
3 changes: 2 additions & 1 deletion lib/get-success-comment.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const HOME_URL = 'https://github.com/semantic-release/semantic-release';
const {HOME_URL} = require('./definitions/constants');

const linkify = releaseInfo =>
`${releaseInfo.url ? `[${releaseInfo.name}](${releaseInfo.url})` : `\`${releaseInfo.name}\``}`;

Expand Down
6 changes: 5 additions & 1 deletion lib/resolve-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const urlJoin = require('url-join');
const {HttpProxyAgent, HttpsProxyAgent} = require('hpagent');

module.exports = (
{gitlabUrl, gitlabApiPathPrefix, assets, milestones, successComment},
{gitlabUrl, gitlabApiPathPrefix, assets, milestones, successComment, failTitle, failComment, labels, assignee},
{
envCi: {service} = {},
env: {
Expand Down Expand Up @@ -45,6 +45,10 @@ module.exports = (
milestones: milestones ? castArray(milestones) : milestones,
successComment,
proxy: getProxyConfiguration(defaultedGitlabUrl, HTTP_PROXY, HTTPS_PROXY),
failTitle: isNil(failTitle) ? 'The automated release is failing 🚨' : failTitle,
failComment,
labels: isNil(labels) ? 'semantic-release' : labels === false ? false : labels,
assignee,
};
};

Expand Down

0 comments on commit 0ad008e

Please sign in to comment.