From 71a35f780557d24dc6fc87ca524fb56692626877 Mon Sep 17 00:00:00 2001 From: Semyon Bezrukov Date: Wed, 11 Sep 2024 16:30:33 +0300 Subject: [PATCH] BM1 --- Jenkinsfile | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 425 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index cd99f86..1e2992d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,15 +1,436 @@ +defaults = [ + action_type: ['print_branches'], + action_help: '', + version: '0.0.0', + protect_branch: true +] + +if (env.BRANCH_NAME == 'master') { + defaults.action_type.add('start_hotfix') + defaults.branch_type = 'hotfix' +} +if (env.BRANCH_NAME == 'develop') { + defaults.action_type.add('start_release') + defaults.branch_type = 'release' +} +if (env.BRANCH_NAME ==~ /^(hotfix)\/.+/) { + defaults.action_type.addAll(['merge_hotfix', 'finish_hotfix', 'rename_hotfix', 'delete_hotfix', 'unprotect_hotfix']) + defaults.branch_type = 'hotfix' +} +if (env.BRANCH_NAME ==~ /^(release)\/.+/) { + defaults.action_type.addAll(['merge_release', 'finish_release', 'rename_release', 'delete_release', 'unprotect_release']) + defaults.branch_type = 'release' +} + pipeline { agent { - label 'master' + label 'branch_manager' + } + environment { + GIT_SERVER = 'git.onlyoffice.com' + GITEA_TOKEN = credentials('gitea-token') + GITHUB_TOKEN = credentials('github-token') + TELEGRAM_TOKEN = credentials('telegram-bot-token') + } + options { + disableConcurrentBuilds() } parameters { - string name: 'test', defaultValue: 'test', description: 'test' + booleanParam ( + name: 'wipe', + description: 'Wipe out current workspace', + defaultValue: false + ) + choice ( + name: 'action_type', + description: "Action type", + choices: defaults.action_type + ) + string ( + name: 'version', + description: 'Release version (for start only)', + defaultValue: defaults.version + ) + booleanParam ( + name: 'protect_branch', + description: 'Protect branch (for start only)', + defaultValue: defaults.protect_branch + ) + string ( + name: 'extra_branch', + description: 'Extra branch (for finish only)', + defaultValue: '' + ) + booleanParam ( + name: 'notify', + description: 'Telegram notification', + defaultValue: true + ) } stages { - stage('Build') { + stage('Branch Manager') { steps { - echo params.test + script { + currentBuild.displayName += ' - ' + params.action_type + if (params.action_type in ['start_hotfix', 'start_release']) + currentBuild.displayName += ' ' + params.version + + if (params.wipe) { + deleteDir() + checkout scm + } + + stats = [repos: [:]] + String branch = env.BRANCH_NAME + ArrayList baseBranches = [] + getRepos().each { + stats.repos.put(it, [:]) + } + Boolean pAction + Boolean sAction + + if (params.action_type == 'print_branches') { + + stats.repos.each { repo, status -> + pAction = printBranches(repo) + status.primary = (pAction) ? 'success' : 'failure' + } + + } else if params.action_type.startsWith('start') { + + branch = defaults.branch_type + '/v' + params.version + baseBranches = [env.BRANCH_NAME] + + stats.repos.each { repo, status -> + if (checkRemoteBranch(repo, branch)) { + echo "${repo}: Branch already ${branch} exists." + status.primary = 'skip' + } else { + dir ('repos/' + repo) { + checkoutRepo(repo) + if (!checkRemoteBranch(repo, 'develop') + && !createBranch(repo, 'develop', 'master')) + error("Can't create develop branch.") + + pAction = createBranch(repo, branch, baseBranches[0]) + status.primary = (pAction) ? 'success' : 'failure' + } + } + + if (params.protect_branch) { + sAction = protectBranch(repo, branch) + status.secondary = (sAction) ? 'lock' : '' + } + } + + } else if params.action_type.startsWith('merge') { + + baseBranches = ['master'] + + stats.repos.each { repo, status -> + if (!checkRemoteBranch(repo, branch)) { + echo "${repo}: Branch doesn't ${branch} exist." + status.primary = 'skip' + } else { + dir ('repos/' + repo) { + checkoutRepo(repo) + pAction = mergeBranch(repo, branch, baseBranches) + status.primary = (pAction) ? 'success' : 'failure' + } + } + } + + } else if params.action_type.startsWith('finish') { + + baseBranches = ['master', 'develop'] + if (!params.extra_branch.isEmpty()) + baseBranches.add(params.extra_branch) + + stats.repos.each { repo, status -> + if (!checkRemoteBranch(repo, branch)) { + echo "${repo}: Branch doesn't ${branch} exist." + status.primary = 'skip' + } else { + dir ('repos/' + repo) { + checkoutRepo(repo) + unprotectBranch(repo, branch) + pAction = mergeBranch(repo, branch, baseBranches) + status.primary = (pAction) ? 'success' : 'failure' + if (pAction && !repo.contains('documents-pipeline')) { + sAction = deleteBranch(repo, branch) + status.secondary = (sAction) ? 'delete' : '' + } + } + } + } + + } else if params.action_type.startsWith('rename') { + + branch = defaults.branch_type + '/v' + params.version + baseBranches = [env.BRANCH_NAME] + + stats.repos.each { repo, status -> + if (checkRemoteBranch(repo, branch)) { + echo "${repo}: Branch already ${branch} exists." + status.primary = 'skip' + } else { + dir ('repos/' + repo) { + checkoutRepo(repo, env.BRANCH_NAME) + pAction = createBranch(repo, branch, env.BRANCH_NAME) + status.primary = (pAction) ? 'success' : 'failure' + if (pAction) { + unprotectBranch(repo, env.BRANCH_NAME) + deleteBranch(repo, env.BRANCH_NAME) + if (params.protect_branch) { + sAction = protectBranch(repo, branch) + status.secondary = (sAction) ? 'lock' : '' + } + } + } + } + } + + } else if params.action_type.startsWith('delete') { + + stats.repos.each { repo, status -> + if (!checkRemoteBranch(repo, branch)) { + echo "${repo}: Branch doesn't ${branch} exist." + status.primary = 'skip' + } else { + dir ('repos/' + repo) { + checkoutRepo(repo, branch) + unprotectBranch(repo, branch) + if (!repo.contains('documents-pipeline')) { + pAction = deleteBranch(repo, branch) + status.primary = (pAction) ? 'success' : 'failure' + } + } + } + } + + } else if params.action_type.startsWith('protect') { + + stats.repos.each { repo, status -> + pAction = protectBranch(repo, branch) + status.primary = (pAction) ? 'success' : 'failure' + status.secondary = 'none' + } + + } else if params.action_type.startsWith('unprotect') { + + stats.repos.each { repo, status -> + pAction = unprotectBranch(repo, branch) + status.primary = (pAction) ? 'success' : 'failure' + status.secondary = 'none' + } + + } + + stats.putAll([ + branch: branch, + baseBranches: baseBranches, + success: stats.repos.findAll { repo, status -> + status.primary in ['skip', 'success'] + }.size(), + total: stats.repos.size() + ]) + println stats + + } + } + } + } + post { + success { + script { + if (stats.success == 0) + currentBuild.result = 'FAILURE' + else if (stats.success != stats.total) + currentBuild.result = 'UNSTABLE' + else if (stats.success == stats.total) + currentBuild.result = 'SUCCESS' + + sendNotification() } } } } + +def getRepos() { + return ['heatray/foo'] +} + +def checkoutRepo(String repo, String branch = 'master') { + sh ( + label: "${repo}: checkout", + script: """ + if [ "\$(GIT_DIR=.git git rev-parse --is-inside-work-tree)" = 'true' ]; then + git fetch --all --prune + git switch -f ${branch} + git reset --hard origin/${branch} + git clean -df + else + rm -rfv ./* + git clone -b ${branch} git@\$GIT_SERVER:${repo}.git . + fi + git branch -vv + """ + ) +} + +def checkRemoteBranch(String repo, String branch = 'master') { + return sh ( + label: "${repo}: check branch ${branch}", + script: "git ls-remote --exit-code git@\$GIT_SERVER:${repo}.git ${branch}", + returnStatus: true + ) == 0 +} + +def createBranch(String repo, String branch, String baseBranch) { + return sh ( + label: "${repo}: start ${branch}", + script: """ + git switch ${baseBranch} + git reset --hard origin/${baseBranch} + git checkout -B ${branch} + git push origin ${branch} + git branch -vv + echo "Branch created." + """, + returnStatus: true + ) == 0 +} + +def mergeBranch(String repo, String branch, ArrayList baseBranches) { + return sh ( + label: "${repo}: merge ${branch} into ${baseBranches.join(' ')}", + script: """#!/bin/bash -xe + git switch ${branch} + git reset --hard origin/${branch} + base_branches=(${baseBranches.join(' ')}) + merged=0 + rev_branch=\$(git rev-parse @) + for base in "\${base_branches[@]}"; do + git switch \$base || (((++merged)) && continue) + git reset --hard origin/\$base + rev_base=\$(git rev-parse @) + if [[ \$rev_branch == \$rev_base ]]; then + ((++merged)) + echo "No new commits." + continue + fi + # gh pr create --repo ${repo} --base \$base --head ${branch} \ + # --title "Merge branch ${branch} into \$base" --fill || \ + # true + if ! git merge ${branch} --no-edit --no-ff \ + -m "Merge branch ${branch} into \$base"; then + git merge --abort + continue + fi + git push origin \$base + ((++merged)) + done + git branch -vv + if [[ \$merged -ne \${#base_branches[@]} ]]; then + echo "Not fully merged." + exit 2 + fi + echo "Branch merged." + """, + returnStatus: true + ) == 0 +} + +def deleteBranch(String repo, String branch) { + return sh ( + label: "${repo}: delete ${branch}", + script: """ + git switch -f master + git branch -D ${branch} + git push --delete origin ${branch} + echo "Branch deleted." + """, + returnStatus: true + ) == 0 +} + +def printBranches(String repo) { + return sh ( + label: "${repo}: branches list", + script: """ + curl -X 'GET' \ + 'https://\$GIT_SERVER/api/v1/repos/${repo}/branches' \ + -H 'accept: application/json' \ + -H 'Authorization: \$GITEA_TOKEN' | \ + jq -r '.[] | [.name, .protected] | @tsv' + """, + returnStatus: true + ) == 0 +} + +def protectBranch(String repo, String branch) { + return sh ( + label: "${repo}: protect ${branch}", + script: """ + echo '{ + "branch_name": "master" + }' | \ + curl -X 'POST' \ + 'https://\$GIT_SERVER/api/v1/repos/${repo}/branch_protections?token=\$GITEA_TOKEN' \ + -H 'accept: application/json' \ + -H 'Authorization: \$GITEA_TOKEN' \ + -H 'Content-Type: application/json' \ + -d - + """, + returnStatus: true + ) == 0 +} + +def unprotectBranch(String repo, String branch) { + return sh ( + label: "${repo}: unprotect ${branch}", + script: """ + curl -X 'DELETE' \ + 'https://\$GIT_SERVER/api/v1/repos/${repo}/branch_protections/${branch}?token=\$GITEA_TOKEN' \ + -H 'accept: application/json' \ + -H 'Authorization: \$GITEA_TOKEN' + """ + returnStatus: true + ) == 0 +} + +def sendNotification() { + String text = '' + switch(params.action_type) { + case ['start_hotfix', 'start_release']: + text = "Branch `${stats.branch}` created from `${stats.baseBranches[0]}`" + break + case ['merge_hotfix', 'merge_release']: + text = "Branch `${stats.branch}` merged into `${stats.baseBranches[0]}`" + break + case ['finish_hotfix', 'finish_release']: + text = "Branch `${stats.branch}` merged into " + text += stats.baseBranches.collect({"`$it`"}).join(', ') + break + default: text = 'Stats' + } + text += " \\[${stats.success}/${stats.total}]" + stats.repos.each { repo, status -> + text += '\n' + switch(status.primary) { + case 'skip': text += '🔘'; break + case 'success': text += '☑️'; break + case 'failure': text += '🚫'; break + default: text += '➖' + } + switch(status.secondary) { + case 'lock': text += '🔒'; break + case 'delete': text += '♻️'; break + case 'none': text += ''; break + default: text += '➖' + } + text += " [${repo}](https://${env.GIT_SERVER}/${repo})" + } + + echo text +}