feature: names refactoring, Jenkinsfile edit, example edit, bug fixes

This commit is contained in:
amorozov 2025-03-05 01:36:24 +03:00
parent 7f3a76c984
commit 1b34bd5df1
11 changed files with 128 additions and 67 deletions

View File

@ -16,6 +16,8 @@ pipeline {
steps { steps {
script { script {
env.CI_PROPERTIES_FILE_LOCATIONS = ".ci/ci.properties" env.CI_PROPERTIES_FILE_LOCATIONS = ".ci/ci.properties"
env.CI_DOCKER_REGISTRY_USERNAME = env.GITEA_USER
env.CI_DOCKER_REGISTRY_PASSWORD = env.GITEA_TOKEN
} }
} }
} }
@ -35,44 +37,46 @@ pipeline {
stage('Build: Regular') { stage('Build: Regular') {
steps { steps {
// TODO: Костыль. Надо скрипты адаптировать под сборку "На каждый коммит" и "Для релизов" // TODO: Костыль. Надо скрипты адаптировать под сборку "На каждый коммит" и "Для релизов"
runGroovy 'docker_build' runGroovy 'build_docker'
} }
} }
// Дополнительная сборка для релизов // Дополнительная сборка для релизов
stage('Build: Release Binaries And Deploy Image') { stage('Build: Release Binaries And Deploy Image') {
when { when {
tag "release-*" anyOf {
tag "release-*"
tag "binaries-*"
}
} }
steps { steps {
script { script {
// Пример смены набора докерфайлов, которые используются по-умолчанию
env.CI_DOCKER_FILES_PRESET="release" env.CI_DOCKER_FILES_PRESET="release"
} }
runGroovy 'docker_build' runGroovy 'release_docker_gitea_publish'
} }
} }
// Пушим собранный образ в Docker Registry // Пушим собранный образ в Docker Registry
stage('Publish: Publish a docker image') { stage('Publish: Release docker image') {
when { when {
tag "release-*" anyOf {
tag "release-*"
tag "docker-*"
}
} }
steps { steps {
// Переопределяем параметры скрипта из переменных окружения сборщика runGroovy 'release_docker_build_push'
script {
env.CI_DOCKER_REGISTRY_USERNAME = env.GITEA_USER
env.CI_DOCKER_REGISTRY_PASSWORD = env.GITEA_TOKEN
}
runGroovy 'docker_push'
} }
} }
// Создаем в Gitea релиз с бинарями // Создаем в Gitea релиз с бинарями
stage('Publish: Create gitea release') { stage('Publish: Create gitea release') {
when { when {
tag "release-*" anyOf {
tag "release-*"
tag "binaries-*"
}
} }
steps { steps {
// Переопределяем параметры скрипта из переменных окружения сборщика // Переопределяем параметры скрипта из переменных окружения сборщика
@ -83,32 +87,24 @@ pipeline {
env.CI_GITEA_TOKEN = env.GITEA_TOKEN env.CI_GITEA_TOKEN = env.GITEA_TOKEN
} }
runGroovy 'docker_gitea_release_publish' runGroovy 'release_gitea_artifacts_publish_docker'
}
}
// Добавляем SSH профиль для последующего деплоя и проверяем его
stage('Prepare: Configuring SSH profile') {
when {
tag "release-*"
}
steps {
// Переопределяем параметры скрипта из переменных окружения сборщика
script {
env.CI_DEPLOY_SSH_PROFILE_PRIVATE_KEY_BASE64 = env.SSH_KEY_BASE64
}
runGroovy 'deploy_ssh_profile_setup'
} }
} }
// Подключаемся к серверу по SSH, обновляем тэг образа и перезапускаем // Подключаемся к серверу по SSH, обновляем тэг образа и перезапускаем
stage('Deploy: Update PROD docker-compose environment tag') { stage('Deploy: Update PROD docker-compose environment tag') {
when { when {
tag "release-*" anyOf {
tag "release-*"
tag "deploy-*"
}
} }
steps { steps {
runGroovy 'deploy_compose_via_ssh' script {
env.CI_DEPLOY_SSH_PROFILE_PRIVATE_KEY_BASE64 = env.SSH_KEY_BASE64
}
runGroovy 'deploy_ssh_profile_setup'
runGroovy 'deploy_docker_build_push_update_compose_via_ssh'
} }
} }
} }

View File

@ -1,6 +1,6 @@
# Git # Git
ci.git.tag.deploy.prefixes=release-, deploy- ci.git.tag.deploy.prefixes=deploy-, release-
ci.git.tag.release.prefixes=release- ci.git.tag.release.prefixes=release-, binaries-, docker-
# Docker # Docker
ci.docker.files.default=Deploy.Dockerfile ci.docker.files.default=Deploy.Dockerfile
@ -31,5 +31,5 @@
## Releases ## Releases
ci.release.artifacts.dockerfiles=Binaries.Extra.Dockerfile ci.release.artifacts.dockerfiles=Binaries.Extra.Dockerfile
ci.release.artifacts.is-required=true ci.release.artifacts.is-required=true
ci.release.artifacts.binaries.libc.dockerfile.grab=/build/app.linux-386 ci.release.artifacts.binaries.extra.dockerfile.grab=/build/app.linux-386
ci.release.artifact.app.linux-386.name=my-application-name.linux-386 ci.release.artifact.app.linux-386.name=my-application-name.linux-386

View File

@ -32,7 +32,7 @@ ScriptLog.printf "Building docker image without tags..."
for (def dockerfileName : Dockerfiles.getPresetDockerfiles()) { for (def dockerfileName : Dockerfiles.getPresetDockerfiles()) {
def dockerCommand = DockerBuildCommandFactory.getBuildCommand(dockerfileName) def dockerCommand = DockerBuildCommandFactory.getBuildCommand(dockerfileName)
println "Using dockerfile '${dockerfileName}', full command: '$dockerCommand'" ScriptLog.printf "Building dockerfile '${dockerfileName}', full command: '$dockerCommand'"
sh dockerCommand sh dockerCommand
} }

View File

@ -1,7 +1,9 @@
package script package script
import util.CIProperties import util.CIProperties
import util.DockerBuildCommandFactory
import util.DockerImageNames import util.DockerImageNames
import util.DockerLogin
import util.DockerTags import util.DockerTags
import util.Dockerfiles import util.Dockerfiles
@ -36,15 +38,15 @@ if (deployGitTags.isNullOrEmpty()) {
def deployDockerfiles = Dockerfiles.getDeployDockerfiles() def deployDockerfiles = Dockerfiles.getDeployDockerfiles()
def dockerComposeBaseCommand = CIProperties.findProperty("deploy.docker-compose.base-command") def dockerComposeBaseCommand = CIProperties.findProperty("deploy.docker-compose.base-command")
.orElse("docker compose") .orElse("docker compose")
def dockerComposeDirectory = CIProperties.getProperty("deploy.docker-compose.dir") def dockerComposeDirectory = CIProperties.getProperty("deploy.docker-compose.dir")
def dockerComposeFileName = CIProperties.findProperty("deploy.docker-compose.filename") def dockerComposeFileName = CIProperties.findProperty("deploy.docker-compose.filename")
.orElse("docker-compose.yml") .orElse("docker-compose.yml")
def sshDeployProfile = CIProperties.findProperty("deploy.ssh.profile") def sshDeployProfile = CIProperties.findProperty("deploy.ssh.profile")
.orElse("deploy") .orElse("deploy")
if (deployGitTags.size() > 1) { if (deployGitTags.size() > 1) {
throw new IllegalStateException("Can not deploy more than one git tag via docker-compose. Found multiple deploy tags: ${deployGitTags}") throw new IllegalStateException("Can not deploy more than one git tag via docker-compose. Found multiple deploy tags: ${deployGitTags}")
@ -54,6 +56,8 @@ def deployGitTag = deployGitTags.first()
ScriptLog.printf "Starting, total ${deployDockerfiles.size()} dockerfiles marked to be deployed... (tag: ${deployGitTag}, dockerfiles: ${deployDockerfiles})" ScriptLog.printf "Starting, total ${deployDockerfiles.size()} dockerfiles marked to be deployed... (tag: ${deployGitTag}, dockerfiles: ${deployDockerfiles})"
DockerLogin.perform()
for (def dockerfileName : deployDockerfiles) { for (def dockerfileName : deployDockerfiles) {
ScriptLog.printf "Deploying image for dockerfile named '${dockerfileName}' via ssh profile ${sshDeployProfile}" ScriptLog.printf "Deploying image for dockerfile named '${dockerfileName}' via ssh profile ${sshDeployProfile}"
@ -61,25 +65,52 @@ for (def dockerfileName : deployDockerfiles) {
ScriptLog.printf "Docker image ${dockerImageToUpdateTagBaseName} base name will be used for dockerfile named '${dockerfileName}'" ScriptLog.printf "Docker image ${dockerImageToUpdateTagBaseName} base name will be used for dockerfile named '${dockerfileName}'"
def dockerReleaseTagToSetup = DockerTags.getDockerTagPostfixForDockerfile( def dockerReleaseTagToSetup = DockerTags.getDockerTagForDockerfile(
GitTags.sanitizeTagFromPrefixes(deployGitTag, GitTags.deployPrefixes) dockerfileName,
GitTags.sanitizeTagFromPrefixes(deployGitTag, GitTags.deployPrefixes)
) )
def dockerReleaseImageToSetup = "${dockerImageToUpdateTagBaseName}:${dockerReleaseTagToSetup}"
def dockerImageBuildCommand = DockerBuildCommandFactory.getBuildCommand(dockerfileName, "-t ${dockerReleaseImageToSetup}")
ScriptLog.printf "Rebuilding docker image '${dockerReleaseImageToSetup}' before deploy by command '${dockerImageBuildCommand}'..."
sh dockerImageBuildCommand
ScriptLog.printf "Pushing docker image '${dockerReleaseImageToSetup}' before deploy..."
sh "docker push ${dockerReleaseImageToSetup}"
ScriptLog.printf "Updating docker-compose image tag to '${dockerReleaseTagToSetup}' in service '${dockerImageToUpdateTagBaseName}' in file '${dockerComposeFileName}' located in directory '${dockerComposeDirectory}'..." ScriptLog.printf "Updating docker-compose image tag to '${dockerReleaseTagToSetup}' in service '${dockerImageToUpdateTagBaseName}' in file '${dockerComposeFileName}' located in directory '${dockerComposeDirectory}'..."
sh """ ssh -tt ${sshDeployProfile} "\\ if (dockerReleaseTagToSetup.isNullOrBlank()) {
cd '${dockerComposeDirectory}' && \\ throw new IllegalStateException("Build tag was not resolved (empty or null '${dockerReleaseTagToSetup}')")
sed -i 's|image: ${dockerImageToUpdateTagBaseName}:.*|image: ${dockerImageToUpdateTagBaseName}:${dockerReleaseTagToSetup}|g' '${dockerComposeFileName}'\\ }
"
"""
ScriptLog.printf "Service tag updated, restarting compose..." def dockerComposeRollbackFileName = "${dockerComposeFileName}.backup.${System.currentTimeMillis()}.yml"
sh """ ssh -tt ${sshDeployProfile} "\\ try {
cd '${dockerComposeDirectory}' && \\ sh """ ssh -tt ${sshDeployProfile} "\\
${dockerComposeBaseCommand} -f '${dockerComposeFileName}' up -d \\ cd '${dockerComposeDirectory}' && \\
" cp ${dockerComposeFileName} ${dockerComposeRollbackFileName} && \\
""" sed -i 's|image: ${dockerImageToUpdateTagBaseName}:.*|image: ${dockerReleaseImageToSetup}|g' '${dockerComposeFileName}'\\ &&
${dockerComposeBaseCommand} -f '${dockerComposeFileName}' up -d \\
"
"""
} catch (Throwable e) {
ScriptLog.printf("Exception occurred while updating docker-compose tag on deploy, rolling back")
e.printStackTrace()
sh """ ssh -tt ${sshDeployProfile} "\\
cd '${dockerComposeDirectory}' && \\
rm ${dockerComposeFileName} && \\
cp ${dockerComposeRollbackFileName} ${dockerComposeFileName} \\
"
"""
ScriptLog.printf("Application restored from backup")
throw e
}
ScriptLog.printf "Deploying of docker tag '${dockerReleaseTagToSetup}' successfully completed!" ScriptLog.printf "Deploying of docker tag '${dockerReleaseTagToSetup}' successfully completed!"
} }

View File

@ -1,7 +1,9 @@
package script package script
import util.CIProperties
import util.DockerImageNames import util.DockerImageNames
/** /**
* Скрипт для публикации докер образа при сборке. * Скрипт для публикации докер образа при сборке.
*/ */
@ -11,8 +13,10 @@ import util.DockerImageNames
@Grab(group='io.tswf.groovy.better-groovy', module='better-groovy-scripting-shell', version='2.0.2-SNAPSHOT', changing = true), @Grab(group='io.tswf.groovy.better-groovy', module='better-groovy-scripting-shell', version='2.0.2-SNAPSHOT', changing = true),
@Grab(group='io.tswf.groovy.better-groovy', module='better-groovy-scripting-gitea', version='2.0.2-SNAPSHOT', changing = true) @Grab(group='io.tswf.groovy.better-groovy', module='better-groovy-scripting-gitea', version='2.0.2-SNAPSHOT', changing = true)
]) ])
@groovy.transform.CompileStatic
import util.DockerLogin import util.DockerLogin
import util.DockerBuildCommandFactory import util.DockerBuildCommandFactory
import util.DockerTags
import util.Dockerfiles import util.Dockerfiles
import util.GitTags import util.GitTags
import util.GlobalProperties import util.GlobalProperties
@ -39,7 +43,7 @@ ScriptLog.printf "Starting docker publishing..."
DockerLogin.perform() DockerLogin.perform()
def dockerfilesToPush = System.findGlobalPropertyList("ci.docker.image.push.files").orElse([]) def dockerfilesToPush = CIProperties.findListProperty("docker.image.push.files").orElse([])
gitReleaseTags.each { gitTag -> gitReleaseTags.each { gitTag ->
Dockerfiles.getPresetDockerfiles().each { dockerfileName -> Dockerfiles.getPresetDockerfiles().each { dockerfileName ->
@ -53,8 +57,12 @@ gitReleaseTags.each { gitTag ->
def dockerImageBaseName = DockerImageNames.getImageName(dockerfileName) def dockerImageBaseName = DockerImageNames.getImageName(dockerfileName)
// Для докер-тегов образов используем гит-теги с отброшенным префиксом // Для докер-тегов образов используем гит-теги с отброшенным префиксом
def imageBaseTag = gitTag.replace(GitTags.getPrefix(), "") def imageBaseTag = GitTags.sanitizeTagFromPrefixes(gitTag, GitTags.getReleasePrefixes())
def imageTag = DockerImageNames.getImageTag(imageBaseTag, dockerfileName) def imageTag = DockerTags.getDockerTagForDockerfile(imageBaseTag, dockerfileName)
if (imageTag.isNullOrBlank()) {
throw new IllegalStateException("Build tag was not resolved (empty or null '${imageTag}')")
}
def imageFullName = "${dockerImageBaseName}:${imageTag}" def imageFullName = "${dockerImageBaseName}:${imageTag}"

View File

@ -12,7 +12,7 @@ class DockerBuildCommandFactory {
] ]
return propertyNames.stream() return propertyNames.stream()
.map { CIProperties.findProperty("ci.docker.build.additional-args").orNull() } .map { CIProperties.findProperty(it).orNull() }
.filter { it != null } .filter { it != null }
.findFirst() .findFirst()
.orElse("") .orElse("")

View File

@ -5,11 +5,6 @@ import groovy.transform.CompileStatic
@CompileStatic @CompileStatic
class DockerImageNames { class DockerImageNames {
static String getImageTag(String baseName, String dockerfile) {
def imageTagPostfix = CIProperties.findProperty("docker.image.tag.${dockerfile.toLowerCase()}.postfix").orElse("")
return "${baseName}${imageTagPostfix}"
}
static String getImageName(String dockerfileName) { static String getImageName(String dockerfileName) {
def propertyNames = [ def propertyNames = [
"docker.image.${dockerfileName.toLowerCase()}.name", "docker.image.${dockerfileName.toLowerCase()}.name",

View File

@ -18,9 +18,20 @@ class DockerLogin {
// Логинимся в Registry // Логинимся в Registry
ScriptLog.printf "Performing login to registry..." ScriptLog.printf "Performing login to registry..."
def registryName = System.getGlobalProperty("ci.docker.registry")
def registryUser = System.getGlobalProperty("ci.docker.registry.username") def registryName = CIProperties.getProperty("docker.registry")
def registryPassword = System.getGlobalProperty("ci.docker.registry.password") def registryUser = CIProperties.getProperty("docker.registry.username")
def registryPassword = CIProperties.getProperty("docker.registry.password")
if (registryName.isNullOrBlank()) {
throw new IllegalStateException("Docker registry name not set")
}
if (registryUser.isNullOrBlank()) {
throw new IllegalStateException("Docker registry user not set")
}
if (registryPassword.isNullOrBlank()) {
throw new IllegalStateException("Docker registry password not set")
}
sh "docker login $registryName -u $registryUser -p $registryPassword" sh "docker login $registryName -u $registryUser -p $registryPassword"

View File

@ -38,7 +38,16 @@ class GitTags {
return getTagsWithPrefix(deployPrefixes) return getTagsWithPrefix(deployPrefixes)
} }
static String sanitizeTagFromPrefixes(String tag, Collection<String> prefixes) { static List<String> getAllPrefixes() {
def result = new ArrayList<String>()
result.addAll(getReleasePrefixes())
result.addAll(getDeployPrefixes())
return result
}
static String sanitizeTagFromPrefixes(String tag, Collection<String> prefixes = getAllPrefixes()) {
return tag.removeAll(prefixes) return tag.removeAll(prefixes)
} }
} }

View File

@ -1,8 +1,19 @@
package util package util
import groovy.transform.CompileStatic
@CompileStatic
class ScriptLog { class ScriptLog {
static void printf(Object text, Object... args) { static void printf(Object text, Object... args) {
def callerClassName = new Throwable().getStackTrace()[1].getClassName().split("\\.").last() def scriptPrefix = new Throwable().getStackTrace()
println "[${callerClassName}]: ${String.format(String.valueOf(text), args)}" .stream()
.map { it.className }
.filter { it != null }
.map { it.split("\\.").last() }
.filter { it.isNotNullOrBlank() && it.charAt(0).isLowerCase() }
.toList()
.lastOrNull() ?: "ci script"
println "[${scriptPrefix}]: ${String.format(String.valueOf(text), args)}"
} }
} }