Compare commits

..

5 Commits

Author SHA1 Message Date
b517d6bccd BM5 2024-09-12 14:54:27 +03:00
3692781bb6 BM4 2024-09-12 12:43:48 +03:00
8ddeceb0f9 BM3 2024-09-12 11:03:42 +03:00
dfd04e1bf4 BM2 2024-09-11 18:42:29 +03:00
71a35f7805 BM1 2024-09-11 16:30:33 +03:00
2 changed files with 452 additions and 125 deletions

456
Jenkinsfile vendored
View File

@ -1,15 +1,463 @@
defaults = [
action_type: ['print_branches'],
action_help: '',
version: '0.0.0',
protect_branch: true
]
if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME.startsWith('hotfix/v')) {
defaults.branch_type = 'hotfix'
}
if (env.BRANCH_NAME == 'develop' || env.BRANCH_NAME.startsWith('release/v')) {
defaults.branch_type = 'release'
}
if (env.BRANCH_NAME == 'master' || env.BRANCH_NAME == 'develop') {
defaults.action_type.add('start')
defaults.action_type.add('protect')
defaults.action_type.add('unprotect')
}
if (env.BRANCH_NAME ==~ /^(hotfix|release)\/v.+/) {
defaults.action_type.add('merge')
defaults.action_type.add('finish')
defaults.action_type.add('rename')
defaults.action_type.add('delete')
defaults.action_type.add('protect')
defaults.action_type.add('unprotect')
}
pipeline { pipeline {
agent { agent {
label 'master' label 'branch_manager'
}
environment {
GIT_SERVER = 'git.onlyoffice.com'
GIT_OWNER = 'heatray'
GITEA_TOKEN = credentials('gitea-token')
TELEGRAM_TOKEN = credentials('telegram-bot-token')
}
options {
disableConcurrentBuilds()
} }
parameters { 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) [' + defaults.branch_type + ']',
defaultValue: defaults.version
)
booleanParam (
name: 'protect_branch',
description: 'Protect branch (for start & rename 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 { stages {
stage('Build') { stage('Branch Manager') {
steps { steps {
echo params.test script {
currentBuild.displayName += ' - ' + params.action_type
if (params.action_type == 'start')
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 == '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) {
if (repo != 'documents-pipeline') {
sAction = protectBranch(repo, branch)
} else {
sAction = false
}
status.secondary = (sAction) ? 'lock' : 'none'
}
}
} else if (params.action_type == '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 == '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) {
if (repo != 'documents-pipeline') {
sAction = deleteBranch(repo, branch)
} else {
sAction = false
}
status.secondary = (sAction) ? 'delete' : 'none'
}
}
}
}
} else if (params.action_type == '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) {
if (repo != 'documents-pipeline') {
sAction = protectBranch(repo, branch)
} else {
sAction = false
}
status.secondary = (sAction) ? 'lock' : 'none'
}
}
}
}
}
} else if (params.action_type == '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 != 'documents-pipeline') {
pAction = deleteBranch(repo, branch)
status.primary = (pAction) ? 'success' : 'failure'
}
}
}
}
} else if (params.action_type == 'protect') {
stats.repos.each { repo, status ->
pAction = protectBranch(repo, branch)
status.primary = (pAction) ? 'success' : 'failure'
}
} else if (params.action_type == 'unprotect') {
stats.repos.each { repo, status ->
pAction = unprotectBranch(repo, branch)
status.primary = (pAction) ? 'success' : 'failure'
}
}
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 ['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:\$GIT_OWNER/${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:\$GIT_OWNER/${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 \$GIT_OWNER/${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: """
HTTP_CODE=\$(curl -s -X 'GET' \
'https://'"\$GIT_SERVER"'/api/v1/repos/'"\$GIT_OWNER"'/${repo}/branches' \
-H 'Authorization: token '"\$GITEA_TOKEN" \
-w '%{http_code}' \
-o output.json)
test \$HTTP_CODE -eq 200
jq -r '.[] | [.name, .protected] | @tsv' output.json
""",
returnStatus: true
) == 0
}
def protectBranch(String repo, String branch) {
return sh (
label: "${repo}: protect ${branch}",
script: """
HTTP_CODE=\$(echo '{
"branch_name": "${branch}",
"enable_push": true,
"enable_push_whitelist": true,
"push_whitelist_usernames": [
"heatray"
]
}' | \
curl -s -X 'POST' \
'https://'"\$GIT_SERVER"'/api/v1/repos/'"\$GIT_OWNER"'/${repo}/branch_protections' \
-H 'Authorization: token '"\$GITEA_TOKEN" \
-H 'Content-Type: application/json' \
-w '%{http_code}' \
-o output.json \
-d @-)
jq '.' output.json
test \$HTTP_CODE -eq 201
""",
returnStatus: true
) == 0
}
def unprotectBranch(String repo, String branch) {
String branchUrl = URLEncoder.encode(branch)
return sh (
label: "${repo}: unprotect ${branch}",
script: """
HTTP_CODE=\$(curl -s -X 'DELETE' \
'https://'"\$GIT_SERVER"'/api/v1/repos/'"\$GIT_OWNER"'/${repo}/branch_protections/${branchUrl}' \
-H 'Authorization: token '"\$GITEA_TOKEN" \
-w '%{http_code}' \
-o output.json)
jq '.' output.json
test \$HTTP_CODE -eq 204 || test \$HTTP_CODE -eq 404
""",
returnStatus: true
) == 0
}
def sendNotification() {
String text = ''
switch(params.action_type) {
case 'start':
text = "Branch `${stats.branch}` created from `${stats.baseBranches[0]}`"
break
case 'merge':
text = "Branch `${stats.branch}` merged into `${stats.baseBranches[0]}`"
break
case 'finish':
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}/${env.GIT_OWNER}/${repo})"
}
echo text
}

View File

@ -1,121 +0,0 @@
pipeline {
agent {
label 'branch_manager'
}
environment {
GIT_SERVER = 'git.onlyoffice.com'
GIT_OWNER = 'ONLYOFFICE'
GITEA_TOKEN = credentials('gitea-token')
}
options {
disableConcurrentBuilds()
}
stages {
stage('Branch Manager') {
steps {
script {
stats = [repos: [:]]
String branch = env.BRANCH_NAME
ArrayList baseBranches = []
getRepos().each {
stats.repos.put(it, [:])
}
Boolean pAction
Boolean sAction
// printBranches
// stats.repos.each { repo, status ->
// pAction = printBranches(repo)
// status.primary = (pAction) ? 'success' : 'failure'
// }
//printBranchProtection
stats.repos.each { repo, status ->
pAction = printBranchProtection(repo)
status.primary = (pAction) ? 'success' : 'failure'
}
stats.putAll([
branch: branch,
baseBranches: baseBranches,
success: stats.repos.findAll { repo, status ->
status.primary in ['skip', 'success']
}.size(),
total: stats.repos.size()
])
println stats
String text = ''
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 += ''
}
text += " ${repo}"
}
echo text
}
}
}
}
}
def getRepos() {
return [
'build_tools',
'core',
'desktop-apps',
'desktop-sdk',
'document-builder-package',
'document-server-package',
'documents-pipeline',
'onlyoffice',
'sdkjs',
'sdkjs-forms',
'sdkjs-ooxml',
'server',
'server-license',
'server-lockstorage',
'web-apps',
'web-apps-mobile',
'Docker-DocumentServer',
'DocumentBuilder'
]
}
def printBranches(String repo) {
return sh (
label: "${repo}: branches list",
script: """
HTTP_CODE=\$(curl -s -X 'GET' \
https://\$GIT_SERVER/api/v1/repos/\$GIT_OWNER/${repo}/branches \
-H 'Authorization: token '\$GITEA_TOKEN \
-w %{http_code} \
-o output.json)
test \$HTTP_CODE -eq 200
jq -r '.[] | [.name, .protected] | @tsv' output.json
""",
returnStatus: true
) == 0
}
def printBranchProtection(String repo) {
return sh (
label: "${repo}: branch protection list",
script: """
HTTP_CODE=\$(curl -s -X 'GET' \
https://\$GIT_SERVER/api/v1/repos/\$GIT_OWNER/${repo}/branch_protections \
-H 'Authorization: token '\$GITEA_TOKEN \
-w %{http_code} \
-o output.json)
test \$HTTP_CODE -eq 200
jq -r '.[] | [.branch_name] | @tsv' output.json
""",
returnStatus: true
) == 0
}