implemented issueBranchName
nclazz/gitea-bot/pipeline/head This commit looks good
Details
nclazz/gitea-bot/pipeline/head This commit looks good
Details
parent
79e604f832
commit
f133114bcf
|
@ -4,6 +4,7 @@ pipeline {
|
||||||
environment {
|
environment {
|
||||||
NEXUS = credentials('jenkins_nexus')
|
NEXUS = credentials('jenkins_nexus')
|
||||||
NCLAZZ = credentials('nclazz_api_token')
|
NCLAZZ = credentials('nclazz_api_token')
|
||||||
|
GITEA_TOKEN = credentials('jenkins_gitea_token')
|
||||||
|
|
||||||
DOCKER_REGISTRY = "docker.nclazz.de"
|
DOCKER_REGISTRY = "docker.nclazz.de"
|
||||||
DOCKER_GROUP = 'nclazz-bots'
|
DOCKER_GROUP = 'nclazz-bots'
|
||||||
|
@ -46,7 +47,9 @@ pipeline {
|
||||||
env: [
|
env: [
|
||||||
PORT: 7007,
|
PORT: 7007,
|
||||||
VERSION: env.DOCKER_VERSION,
|
VERSION: env.DOCKER_VERSION,
|
||||||
AUTH_TOKEN: env.NCLAZZ
|
AUTH_TOKEN: env.NCLAZZ,
|
||||||
|
GITEA_TOKEN: env.GITEA_TOKEN,
|
||||||
|
GITEA_BASE_URL: 'https://git.l--n.de/api/v1'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
exposeService(
|
exposeService(
|
||||||
|
|
20
README.md
20
README.md
|
@ -1,4 +1,22 @@
|
||||||
Gitea Bot
|
Gitea Bot
|
||||||
============================
|
============================
|
||||||
|
|
||||||
This bot/server manages issues in gitea repositories.
|
This bot/server manages issues in gitea repositories.
|
||||||
|
|
||||||
|
## Webhooks
|
||||||
|
|
||||||
|
The bot provides some useful webhooks. Webhooks can either be enabled opt-in by
|
||||||
|
listing the required webhooks in the query-parameter `hooks` as a comma separated list
|
||||||
|
or enable all hooks by leaving the hooks parameter empty.
|
||||||
|
|
||||||
|
Send gitea events to `https://gitea-bot.nclazz.de/webhook`
|
||||||
|
|
||||||
|
### issueBranchName
|
||||||
|
|
||||||
|
Updates the branch name in the issues body. The name is created from the title,
|
||||||
|
issue number and optionally a `bug` label, resulting in the following format:
|
||||||
|
|
||||||
|
> `feature/{issue}-{title}`
|
||||||
|
|
||||||
|
the prefix is either`feature` or `bugfix`. Whitespaces in the title are replaced by
|
||||||
|
`-`.
|
|
@ -9,6 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.27.2",
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.0",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"express": "^4.18.1"
|
"express": "^4.18.1"
|
||||||
|
@ -33,6 +34,22 @@
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/axios/-/axios-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.14.9",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/body-parser": {
|
"node_modules/body-parser": {
|
||||||
"version": "1.20.0",
|
"version": "1.20.0",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/body-parser/-/body-parser-1.20.0.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/body-parser/-/body-parser-1.20.0.tgz",
|
||||||
|
@ -79,6 +96,18 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
@ -124,6 +153,15 @@
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/depd/-/depd-2.0.0.tgz",
|
||||||
|
@ -242,6 +280,40 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.1",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||||
|
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/forwarded": {
|
"node_modules/forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
|
@ -672,6 +744,20 @@
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/array-flatten/-/array-flatten-1.1.1.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||||
},
|
},
|
||||||
|
"asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/axios/-/axios-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.9",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
"version": "1.20.0",
|
"version": "1.20.0",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/body-parser/-/body-parser-1.20.0.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/body-parser/-/body-parser-1.20.0.tgz",
|
||||||
|
@ -705,6 +791,14 @@
|
||||||
"get-intrinsic": "^1.0.2"
|
"get-intrinsic": "^1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"requires": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"content-disposition": {
|
"content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
|
@ -736,6 +830,11 @@
|
||||||
"ms": "2.0.0"
|
"ms": "2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||||
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/depd/-/depd-2.0.0.tgz",
|
||||||
|
@ -823,6 +922,21 @@
|
||||||
"unpipe": "~1.0.0"
|
"unpipe": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"follow-redirects": {
|
||||||
|
"version": "1.15.1",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/follow-redirects/-/follow-redirects-1.15.1.tgz",
|
||||||
|
"integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA=="
|
||||||
|
},
|
||||||
|
"form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"forwarded": {
|
"forwarded": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://nexus.nclazz.de/repository/npm_public/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://nexus.nclazz.de/repository/npm_public/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"author": "Niclas Thobaben",
|
"author": "Niclas Thobaben",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.27.2",
|
||||||
"body-parser": "^1.20.0",
|
"body-parser": "^1.20.0",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.1",
|
||||||
"express": "^4.18.1"
|
"express": "^4.18.1"
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
const axios = require('axios')
|
||||||
|
const api = {}
|
||||||
|
|
||||||
|
const BASE_URL = process.env.GITEA_BASE_URL || 'https://git.l--n.de/api/v1'
|
||||||
|
const TOKEN = process.env.GITEA_TOKEN
|
||||||
|
|
||||||
|
console.log(`Use gitea api @ ${BASE_URL}`)
|
||||||
|
|
||||||
|
api.post = (path, payload) => {
|
||||||
|
const url = `${BASE_URL}${path}`
|
||||||
|
|
||||||
|
return axios.post(
|
||||||
|
url,
|
||||||
|
payload,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${TOKEN}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
api.patch = (path, payload) => {
|
||||||
|
const url = `${BASE_URL}${path}`
|
||||||
|
|
||||||
|
return axios.patch(
|
||||||
|
url,
|
||||||
|
payload,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `token ${TOKEN}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = api
|
|
@ -0,0 +1,17 @@
|
||||||
|
const fs = require('fs')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
const hooks = {}
|
||||||
|
fs.readdirSync(__dirname).forEach(file => {
|
||||||
|
if(file === 'index.js') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const name = file.replace(/\.[^/.]+$/, '')
|
||||||
|
const hook = require(path.join(__dirname, file))
|
||||||
|
hook.name = name
|
||||||
|
hooks[name] = hook
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Loaded available Hooks [${Object.keys(hooks)}]`)
|
||||||
|
|
||||||
|
module.exports = hooks
|
|
@ -0,0 +1,37 @@
|
||||||
|
const gitea = require('../gitea-api')
|
||||||
|
|
||||||
|
const log = (msg) => {
|
||||||
|
console.log('[issueBranchName]', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
exec: (req) => {
|
||||||
|
if(req.headers['x-gitea-event'] !== 'issues') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const { issue, repository } = req.body
|
||||||
|
const isBug = issue.labels.length && !!issue.labels.find(label => label.name === 'bug')
|
||||||
|
const prefix = isBug ? 'bugfix' : 'feature'
|
||||||
|
const branch = issue.title.replace(/[^a-z0-9\s]/gi, '').replaceAll(' ', '-')
|
||||||
|
const branchName = `${prefix}/${issue.number}-${branch}`
|
||||||
|
|
||||||
|
log(`Created branch name ${branchName} in ${repository.full_name}`)
|
||||||
|
|
||||||
|
issue.body = issue.body || ''
|
||||||
|
|
||||||
|
let body
|
||||||
|
if(issue.body.includes('Branchname')) {
|
||||||
|
body = issue.body.replaceAll(/\*\*Branchname\*\*: <code>.+<\/code>/g, `**Branchname**: <code>${branchName}</code>`)
|
||||||
|
}else {
|
||||||
|
body = `${issue.body}<br>**Branchname**: <code>${branchName}</code>`
|
||||||
|
}
|
||||||
|
|
||||||
|
body = body.replaceAll('\n', '\\n')
|
||||||
|
|
||||||
|
const path = `/repos/${repository.full_name}/issues/${issue.number}`
|
||||||
|
return gitea.patch(path, { body })
|
||||||
|
.then(() => {})
|
||||||
|
.catch(reason => reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
const log = (msg) => {
|
||||||
|
console.log('[issueBranchRef]', msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
exec: (req) => {
|
||||||
|
if(req.headers['x-gitea-event'] !== 'issues') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log('issue branch ref')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const bodyParser = require('body-parser')
|
const bodyParser = require('body-parser')
|
||||||
|
const hooks = require('./hooks')
|
||||||
|
|
||||||
console.log('Initialize express server')
|
console.log('Initialize express server')
|
||||||
|
|
||||||
|
@ -9,5 +10,36 @@ app.use(bodyParser.json())
|
||||||
|
|
||||||
const port = process.env.SERVER_PORT || 8080
|
const port = process.env.SERVER_PORT || 8080
|
||||||
|
|
||||||
|
app.post('/webhook', async (req, res) => {
|
||||||
|
const selectedHooks = []
|
||||||
|
if(req.query['hooks']) {
|
||||||
|
req.query.hooks.split(',').forEach(hookName => {
|
||||||
|
const hook = hooks[hookName.trim()]
|
||||||
|
if(!hook) {
|
||||||
|
console.warn(`Hook ${hookName} does not exist! from=${req.ip}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedHooks.push(hook)
|
||||||
|
})
|
||||||
|
}else {
|
||||||
|
selectedHooks.push(...Object.values(hooks))
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = {}
|
||||||
|
const promises = selectedHooks.map(async hook => {
|
||||||
|
console.log(`Execute hook ${hook.name} from=${req.ip}`)
|
||||||
|
const error = await hook.exec(req)
|
||||||
|
if(error) {
|
||||||
|
errors[hook.name] = error
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await Promise.all(promises)
|
||||||
|
if(Object.keys(errors).length) {
|
||||||
|
res.status(400).json(errors)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.status(200).send()
|
||||||
|
})
|
||||||
|
|
||||||
app.listen(port)
|
app.listen(port)
|
||||||
console.log(`Started express server on port ${port}`)
|
console.log(`Started express server on port ${port}`)
|
||||||
|
|
Loading…
Reference in New Issue