initial commit

This commit is contained in:
2025-03-04 15:35:36 +03:00
commit 7f3a76c984
28 changed files with 1385 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
package script
import util.CIProperties
import util.DockerImageNames
import util.DockerTags
import util.Dockerfiles
/**
* Скрип для деплоя докер образа на сервер посредством обновления docker-compose файла и его перезапуска.
*/
@GrabResolver(name='gitea', root='https://git.tswf.io/api/packages/public-repos/maven')
@Grapes([
@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)
])
import util.GitTags
import util.GlobalProperties
import util.ScriptLog
GlobalProperties.loadGlobalProperties()
println """
#############################################################################
DEPLOY via docker-compose script
#############################################################################
"""
def deployGitTags = GitTags.getDeployTags()
if (deployGitTags.isNullOrEmpty()) {
ScriptLog.printf "There is no tags to remote restart."
System.exit(1)
}
def deployDockerfiles = Dockerfiles.getDeployDockerfiles()
def dockerComposeBaseCommand = CIProperties.findProperty("deploy.docker-compose.base-command")
.orElse("docker compose")
def dockerComposeDirectory = CIProperties.getProperty("deploy.docker-compose.dir")
def dockerComposeFileName = CIProperties.findProperty("deploy.docker-compose.filename")
.orElse("docker-compose.yml")
def sshDeployProfile = CIProperties.findProperty("deploy.ssh.profile")
.orElse("deploy")
if (deployGitTags.size() > 1) {
throw new IllegalStateException("Can not deploy more than one git tag via docker-compose. Found multiple deploy tags: ${deployGitTags}")
}
def deployGitTag = deployGitTags.first()
ScriptLog.printf "Starting, total ${deployDockerfiles.size()} dockerfiles marked to be deployed... (tag: ${deployGitTag}, dockerfiles: ${deployDockerfiles})"
for (def dockerfileName : deployDockerfiles) {
ScriptLog.printf "Deploying image for dockerfile named '${dockerfileName}' via ssh profile ${sshDeployProfile}"
def dockerImageToUpdateTagBaseName = DockerImageNames.getImageName(dockerfileName)
ScriptLog.printf "Docker image ${dockerImageToUpdateTagBaseName} base name will be used for dockerfile named '${dockerfileName}'"
def dockerReleaseTagToSetup = DockerTags.getDockerTagPostfixForDockerfile(
GitTags.sanitizeTagFromPrefixes(deployGitTag, GitTags.deployPrefixes)
)
ScriptLog.printf "Updating docker-compose image tag to '${dockerReleaseTagToSetup}' in service '${dockerImageToUpdateTagBaseName}' in file '${dockerComposeFileName}' located in directory '${dockerComposeDirectory}'..."
sh """ ssh -tt ${sshDeployProfile} "\\
cd '${dockerComposeDirectory}' && \\
sed -i 's|image: ${dockerImageToUpdateTagBaseName}:.*|image: ${dockerImageToUpdateTagBaseName}:${dockerReleaseTagToSetup}|g' '${dockerComposeFileName}'\\
"
"""
ScriptLog.printf "Service tag updated, restarting compose..."
sh """ ssh -tt ${sshDeployProfile} "\\
cd '${dockerComposeDirectory}' && \\
${dockerComposeBaseCommand} -f '${dockerComposeFileName}' up -d \\
"
"""
ScriptLog.printf "Deploying of docker tag '${dockerReleaseTagToSetup}' successfully completed!"
}

View File

@@ -0,0 +1,69 @@
package script
/**
* Скрипт для начальной конфигурации SSH профиля для деплоя
* Создает профиль для подключения к стенду, а также настраивает пользователя для этих целей.
*/
@GrabResolver(name='gitea', root='https://git.tswf.io/api/packages/public-repos/maven')
@Grapes([
@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)
])
import util.GlobalProperties
import util.ScriptLog
println """
#############################################################################
SSH Profile Setup Script
#############################################################################
"""
GlobalProperties.loadGlobalProperties()
ScriptLog.printf "Creating .ssh directory..."
def sshDir = new File("${System.getProperty("user.home")}/.ssh")
sshDir.mkdirsUnsafe()
// Записываем приватный ключ
ScriptLog.printf "Writing ssh private key..."
def sshPrivateKey = sshDir.subFile("id_rsa")
//TODO: может быть тут посолить?
def sshPrivateKeyBase64Content = System.getGlobalProperty("ci.deploy.ssh.profile.private-key-base64").removeAll(' ')
def sshPrivateKeyDecodedContent = Base64.decoder.decode(sshPrivateKeyBase64Content)
sshPrivateKey.bytes = sshPrivateKeyDecodedContent
sh 'chmod 400 ~/.ssh/id_rsa'
// Записываем ssh config
ScriptLog.printf "Writing ssh config..."
def sshProfileName = System.getGlobalProperty("ci.deploy.ssh.profile")
def sshHost = System.getGlobalProperty("ci.deploy.ssh.host")
def sshPort = System.getGlobalProperty("ci.deploy.ssh.port")
def sshUsername = System.getGlobalProperty("ci.deploy.ssh.username")
def sshConfigFile = sshDir.subFile("config")
sshConfigFile.text = """
Host ${sshProfileName}
HostName ${sshHost}
User ${sshUsername}
Port ${sshPort}
""".stripIndent(false)
// Выполняем тестовое подключение к SSH, чтобы убедиться в корректности настройки.
ScriptLog.printf "Validating ssh configuration..."
sh "ssh ${sshProfileName} -o StrictHostKeyChecking=no -tt 'echo Testing connection!'"
ScriptLog.printf "SSH profile configured successfully!"

View File

@@ -0,0 +1,39 @@
package script
import util.Dockerfiles
import util.DockerBuildCommandFactory
import util.GlobalProperties
import util.ScriptLog
/**
* Скрипт для тестового прогона сборки докер. Просто проверить, что собирается.
*/
@GrabResolver(name='gitea', root='https://git.tswf.io/api/packages/public-repos/maven')
@Grapes([
@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)
])
_
println """
#############################################################################
Build Docker Image Script
#############################################################################
"""
GlobalProperties.loadGlobalProperties()
// Проверяем, что установлен docker
if (System.findExecutablesInPath(['docker']).isEmpty())
throw new FileNotFoundException("Can't find installed docker-compose at that system!")
ScriptLog.printf "Building docker image without tags..."
for (def dockerfileName : Dockerfiles.getPresetDockerfiles()) {
def dockerCommand = DockerBuildCommandFactory.getBuildCommand(dockerfileName)
println "Using dockerfile '${dockerfileName}', full command: '$dockerCommand'"
sh dockerCommand
}
ScriptLog.printf "Docker image successfully built!"

View File

@@ -0,0 +1,143 @@
package script
/**
*
* Скрипт для создания или обновления релиза в gitea для всех релизных тегов текущего коммита.
* Ищет описание релиза в специальной папке, после чего добавляет его к релизу, если находит.
*
* Собирает докер образ, после чего из него копирует указанные как артефакты файлы и прикрепляет к релизу.
*
* Для работы требуется токен API Gitea и список нужных файлов, либо флаг того, что список может быть пустым.
*/
@GrabResolver(name='gitea', root='https://git.tswf.io/api/packages/public-repos/maven')
@Grapes([
@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)
])
import io.tswf.gitea.parser.repository.GiteaRepositoryMetadataParser
import io.tswf.groovy.bettergroovy.scripting.v2.git.Git
import io.tswf.groovy.bettergroovy.scripting.v2.gitea.Gitea
import util.CIProperties
import util.ReleaseArtifactNames
import util.Dockerfiles
import util.DockerBuildCommandFactory
import util.GitTags
import util.GlobalProperties
import util.ReleaseArtifacts
import util.ReleaseDescriptionReader
import util.ScriptLog
println """
#############################################################################
Publish Release Artifacts Script
#############################################################################
"""
GlobalProperties.loadGlobalProperties()
def dockerFiles = Dockerfiles.getReleaseDockerfiles()
def isAnyArtifactRequiredForRelease = CIProperties.findProperty("gitea.release.artifacts.is-required")
.map { it.toBoolean() }
.orElse(true)
def releaseTags = GitTags.getReleaseTags()
if (releaseTags.isNullOrEmpty()) {
ScriptLog.printf "There is no release tags to publish!"
System.exit(1)
}
def giteaHost = CIProperties.findProperty("gitea.origin") // В рамках обратной совместимости
.orElseGet {
CIProperties.getProperty("gitea.host") // Актуальное название
}
def giteaToken = CIProperties.getProperty("gitea.token")
def giteaRepositoryParser = new GiteaRepositoryMetadataParser()
def giteaRepositoryMetadata = giteaRepositoryParser.parseRepositoryMetadataFromOrigin(giteaHost)
def artifactLocalFiles = new ArrayList<File>()
for (def dockerfileName : dockerFiles) {
ScriptLog.printf "Collecting artifacts from dockerfile named '${dockerfileName}'"
// Собираем докер образ и поднимаем контейнер, чтобы далее извлечь из него нужные артефакты.
def temporaryDockerImage = Random.randomString(40)
def temporaryDockerContainerName = "build_in_docker_${Random.randomString(25)}"
// Подчищаем за собою
addShutdownHook {
println "Removing temporary docker container '${temporaryDockerContainerName}' and image '${temporaryDockerImage}'"
sh "docker rm -f ${temporaryDockerContainerName}"
sh "docker image rm -f ${temporaryDockerImage}"
}
def temporaryBuildDir = new File("./gitea-release-publication/tmp/build/${Random.randomString(10)}/")
temporaryBuildDir.mkdirsUnsafe()
temporaryBuildDir.deleteOnExit()
def dockerBuildCommand = DockerBuildCommandFactory.getBuildCommand(dockerfileName, "-t ${temporaryDockerImage}")
sh dockerBuildCommand
sh "docker create -it --name ${temporaryDockerContainerName} ${temporaryDockerImage} sh"
def artifactsToPublish = ReleaseArtifacts.getArtifacts(dockerfileName)
if (artifactsToPublish.isEmpty()) {
ScriptLog.printf "There is no artifacts was configured for dockerfile named '${dockerfileName}'"
} else {
ScriptLog.printf "Copying artifacts for dockerfile named '${dockerfileName}': ${artifactsToPublish.join('\n')}"
}
// Копируем все артефакты для публикации из временного докер контейнера
artifactsToPublish.stream()
.map { artifactToPublishAbsolutePath ->
def artifactSimpleName = artifactToPublishAbsolutePath.split("/").last()
def artifactLocalFile = temporaryBuildDir.subFile(artifactSimpleName)
sh "docker cp ${temporaryDockerContainerName}:${artifactToPublishAbsolutePath} ${artifactLocalFile.path}"
artifactLocalFile
}
// Переименовываем файл, если это настроено
.map { artifactFile ->
ReleaseArtifactNames.getNewArtifactName(artifactFile, dockerfileName)
.map { newName ->
def newFile = artifactFile.parentFile.subFile(newName)
ScriptLog.printf "Renaming artifact '${artifactFile.absoluteCanonicalFile.path}' to '${newName}' for dockerfile named '${dockerfileName}'"
artifactFile.renameTo(newFile)
newFile
}
.orElse(artifactFile)
}
.forEach {
artifactLocalFiles << it
}
}
ScriptLog.printf "Total ${artifactLocalFiles.size()} artifacts collected: ${artifactLocalFiles}"
if (artifactLocalFiles.isEmpty() && isAnyArtifactRequiredForRelease) {
ScriptLog.printf "Release artifacts required, but no one found!"
System.exit(1)
}
def giteaRepository = Gitea.getCIRepositoryUtilityV1(giteaToken, giteaRepositoryMetadata)
// Создаем релизы в гитее
releaseTags.forEach { releaseTag ->
def releaseName = GitTags.sanitizeTagFromPrefixes(releaseTag, GitTags.getReleasePrefixes())
def releaseCommitSha = System.findGlobalProperty("ci.gitea.release.commit.short-sha")
.orElse(Git.commitShortSha)
def releaseDescription = ReleaseDescriptionReader.readDescription(releaseName)
.orElse("")
giteaRepository.createOrUpdateRelease(releaseTag, releaseCommitSha, releaseName, releaseDescription, artifactLocalFiles)
}

View File

@@ -0,0 +1,74 @@
package script
import util.DockerImageNames
/**
* Скрипт для публикации докер образа при сборке.
*/
@GrabResolver(name='gitea', root='https://git.tswf.io/api/packages/public-repos/maven')
@Grapes([
@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)
])
import util.DockerLogin
import util.DockerBuildCommandFactory
import util.Dockerfiles
import util.GitTags
import util.GlobalProperties
import util.ScriptLog
println """
#############################################################################
Push Docker Images Script
#############################################################################
"""
GlobalProperties.loadGlobalProperties()
def gitReleaseTags = GitTags.getDeployTags()
if (gitReleaseTags.isNullOrEmpty()) {
ScriptLog.printf "There is no release tags found!"
System.exit(1)
}
else
ScriptLog.printf "Found git release tags ${gitReleaseTags}"
ScriptLog.printf "Starting docker publishing..."
DockerLogin.perform()
def dockerfilesToPush = System.findGlobalPropertyList("ci.docker.image.push.files").orElse([])
gitReleaseTags.each { gitTag ->
Dockerfiles.getPresetDockerfiles().each { dockerfileName ->
if (!dockerfilesToPush.isEmpty() && dockerfileName !in dockerfilesToPush) {
ScriptLog.printf "Skipping push of dockerfile '${dockerfileName}' because it is not in push list: ${dockerfilesToPush}"
return
}
ScriptLog.printf "Building docker tag: '${gitTag}' with dockerfile named '${dockerfileName}'"
def dockerImageBaseName = DockerImageNames.getImageName(dockerfileName)
// Для докер-тегов образов используем гит-теги с отброшенным префиксом
def imageBaseTag = gitTag.replace(GitTags.getPrefix(), "")
def imageTag = DockerImageNames.getImageTag(imageBaseTag, dockerfileName)
def imageFullName = "${dockerImageBaseName}:${imageTag}"
ScriptLog.printf "Full docker image name will be '${imageFullName}'"
def dockerBuildCommand = DockerBuildCommandFactory.getBuildCommand(dockerfileName, "-t ${imageFullName}")
ScriptLog.printf "Executing: '${dockerBuildCommand}'"
sh dockerBuildCommand
sh "docker push '${imageFullName}'"
ScriptLog.printf "Docker tag ${imageTag} build and push completed with full image name: '${imageFullName}'"
}
}
ScriptLog.printf "Publishing done!"

View File

@@ -0,0 +1,25 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
class CIProperties {
public static final String CI_PROPERTIES_PREFIX = "ci"
static String getProperty(String propertyName) {
return findProperty(propertyName).orElseThrow { new IllegalStateException("Required CI property named '${propertyName}' was not found!") }
}
static Optional<String> findProperty(String propertyName) {
return System.findGlobalProperty("${CI_PROPERTIES_PREFIX}.${propertyName}")
}
static List<String> getListProperty(String propertyName) {
return findListProperty(propertyName).orElseThrow { new IllegalStateException("Required CI property named '${propertyName}' was not found!") }
}
static Optional<List<String>> findListProperty(String propertyName) {
return System.findGlobalPropertyList("${CI_PROPERTIES_PREFIX}.${propertyName}")
}
}

View File

@@ -0,0 +1,26 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
class DockerBuildCommandFactory {
static String getBuildAdditionalArgs(String dockerfileName) {
def propertyNames = [
"docker.build.${dockerfileName.toLowerCase()}.additional-args",
"docker.build.additional-args"
]
return propertyNames.stream()
.map { CIProperties.findProperty("ci.docker.build.additional-args").orNull() }
.filter { it != null }
.findFirst()
.orElse("")
}
static String getBuildCommand(String dockerfileName, String additionalArgs = "", String context = ".") {
def dockerCommand = "docker build ${context} -f '${dockerfileName}' ${additionalArgs} ${getBuildAdditionalArgs(dockerfileName)}"
return dockerCommand
}
}

View File

@@ -0,0 +1,29 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
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) {
def propertyNames = [
"docker.image.${dockerfileName.toLowerCase()}.name",
"docker.image.base.name"
]
return propertyNames.stream()
.map { it.toString() }
.map {
CIProperties.findProperty(it).orNull()
}
.filter { it != null }
.findFirst()
.orElseThrow { new NoSuchElementException("There is no one property set: ${propertyNames}") }
}
}

View File

@@ -0,0 +1,29 @@
package util
import groovy.transform.CompileStatic
import groovy.transform.Memoized
@CompileStatic
class DockerLogin {
static void perform() {
loginDockerInternal()
}
@Memoized
private static Object loginDockerInternal() {
// Проверяем на всякий случай, что докер вообще установлен
if (System.findExecutablesInPath(['docker']).isEmpty())
throw new FileNotFoundException("Can't find installed docker-compose at that system!")
// Логинимся в Registry
ScriptLog.printf "Performing login to registry..."
def registryName = System.getGlobalProperty("ci.docker.registry")
def registryUser = System.getGlobalProperty("ci.docker.registry.username")
def registryPassword = System.getGlobalProperty("ci.docker.registry.password")
sh "docker login $registryName -u $registryUser -p $registryPassword"
ScriptLog.printf "Login into docker registry '${registryName}' successful!"
}
}

View File

@@ -0,0 +1,17 @@
package util;
import groovy.transform.CompileStatic
@CompileStatic
class DockerTags {
static String getDockerTagForDockerfile(String dockerfileName, String baseTag) {
return "${baseTag}${getDockerTagPostfixForDockerfile(dockerfileName)}"
}
static String getDockerTagPostfixForDockerfile(String dockerfileName) {
return CIProperties.findProperty("docker.tag.${dockerfileName}.prefix")
.orElse("")
}
}

View File

@@ -0,0 +1,38 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
class Dockerfiles {
static List<String> getPresetDockerfiles() {
def preset = CIProperties.findProperty("docker.files.preset")
.orElse("default")
def presetDockerfiles = CIProperties.findListProperty("docker.files.${preset}")
.orElse([])
if (presetDockerfiles.isEmpty())
return CIProperties.findListProperty("docker.files")
.orElse(["Dockerfile"])
else
return presetDockerfiles
}
static List<String> getReleaseDockerfiles() {
return CIProperties.findListProperty("release.artifacts.dockerfiles")
.orElseGet {
CIProperties.findListProperty("gitea.release.artifacts.dockerfiles") // Обратная совместимость
.orElseGet {
getPresetDockerfiles()
}
}
}
static List<String> getDeployDockerfiles() {
return CIProperties.findListProperty("deploy.dockerfile.names")
.orElseGet {
getPresetDockerfiles()
}
}
}

View File

@@ -0,0 +1,44 @@
package util
import groovy.transform.CompileStatic
import io.tswf.groovy.bettergroovy.scripting.v2.git.Git
@CompileStatic
class GitTags {
public static final String DEFAULT_RELEASE_TAG_PREFIX = "release-"
public static final String DEFAULT_DEPLOY_TAG_PREFIX = DEFAULT_RELEASE_TAG_PREFIX
static Set<String> getDeployPrefixes() {
return CIProperties.findListProperty("git.tag.deploy.prefixes")
.orElse([DEFAULT_DEPLOY_TAG_PREFIX])
.toSet()
}
static Set<String> getReleasePrefixes() {
return CIProperties.findListProperty("git.tag.release.prefixes")
.orElse([DEFAULT_RELEASE_TAG_PREFIX])
.toSet()
}
static List<String> getTagsWithPrefix(Collection<String> prefixes) {
def matchingTags = Git.tags.stream()
.filter {tag -> prefixes.any { tag.startsWith(it) }}
.toList()
return matchingTags
}
static List<String> getReleaseTags() {
def releasePrefixes = getReleasePrefixes()
return getTagsWithPrefix(releasePrefixes)
}
static List<String> getDeployTags() {
def deployPrefixes = getDeployPrefixes()
return getTagsWithPrefix(deployPrefixes)
}
static String sanitizeTagFromPrefixes(String tag, Collection<String> prefixes) {
return tag.removeAll(prefixes)
}
}

View File

@@ -0,0 +1,39 @@
package util
import groovy.transform.Memoized
class GlobalProperties {
@Memoized
static loadGlobalProperties() {
def propertiesFiles = System.findGlobalPropertyList("${CIProperties.CI_PROPERTIES_PREFIX}.properties.file.locations")
.orElse([".ci/ci.properties"])
.map { new File(it) }
if (propertiesFiles.isNotNullOrEmpty()) {
propertiesFiles.forEach { propertiesFile ->
if (propertiesFile.exists()) {
loadPropertiesFile(propertiesFile)
} else {
ScriptLog.printf "Skipping non-existing properties file '${propertiesFile.absoluteCanonicalFile.path}'!"
}
}
}
}
static void loadPropertiesFile(File propertiesFile) {
if (propertiesFile == null) {
return
}
if (!propertiesFile.exists()) {
throw new IOException("Could not find properties file '${propertiesFile}' (with absolute path: '${propertiesFile.absoluteCanonicalFile.path}')!")
}
if (propertiesFile.isDirectory()) {
throw new IOException("Directory found instead properties file '${propertiesFile}' (with absolute path: '${propertiesFile.absoluteCanonicalFile.path}')!")
}
System.registerGlobalConfiguration(propertiesFile)
}
}

View File

@@ -0,0 +1,67 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
class ReleaseArtifactNames {
static List<ArtifactNameResolver> artifactFileNameResolvers = new ArrayList<>()
static Optional<String> getNewArtifactName(File file, String dockerfileName) {
def newArtifactName = artifactFileNameResolvers.stream()
.map { it.resolveArtifactNewName(file, dockerfileName) }
.filter { it != null }
.firstOrNull()
return Optional.ofNullable(newArtifactName)
}
static void registerArtifactResolver(ArtifactNameResolver resolver) {
artifactFileNameResolvers << resolver
}
static {
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("release.artifact.${dockerfile}.${replacePathInvalidChars(file.absoluteCanonicalFile.path)}.name").orNull()
}
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("release.artifact.${dockerfile}.${file.name}.name").orNull()
}
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("release.artifact.${replacePathInvalidChars(file.absoluteCanonicalFile.path)}.name").orNull()
}
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("release.artifact.${file.name}.name").orNull()
}
// В рамках обратной совместимости
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("gitea.release.artifact.${dockerfile}.${replacePathInvalidChars(file.absoluteCanonicalFile.path)}.name").orNull()
}
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("gitea.release.artifact.${dockerfile}.${file.name}.name").orNull()
}
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("gitea.release.artifact.${replacePathInvalidChars(file.absoluteCanonicalFile.path)}.name").orNull()
}
registerArtifactResolver { file, dockerfile ->
CIProperties.findProperty("gitea.release.artifact.${file.name}.name").orNull()
}
}
static String replacePathInvalidChars(String fileFullName) {
return fileFullName
.replace('\\', '_')
.replace('/', '_')
}
static interface ArtifactNameResolver {
String resolveArtifactNewName(File artifactFile, String dockerfileName)
}
}

View File

@@ -0,0 +1,23 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
class ReleaseArtifacts {
static List<String> getArtifacts(String dockerFile) {
def propertyNames = [
"gitea.release.${dockerFile}.artifacts", // В рамках обратной совместимости
"gitea.release.artifacts", // В рамках обратной совместимости
"release.artifacts.${dockerFile}.grab",
"release.artifacts.grab",
]
return propertyNames.stream()
.map { it.toString() }
.map { CIProperties.findListProperty(it).orNull() }
.filter { it != null }
.map { (List<String>) it }
.findFirst().orElse([])
}
}

View File

@@ -0,0 +1,27 @@
package util
import groovy.transform.CompileStatic
@CompileStatic
class ReleaseDescriptionReader {
static Optional<String> readDescription(String releaseName) {
def releaseDescriptionsDir = CIProperties.findProperty("release.descriptions.dir")
.orElse(".releases/changelog/")
.with {
new File(it)
}
def releaseDescriptionFile = releaseDescriptionsDir.subFile("${releaseName}-changelog.md")
if (releaseDescriptionFile.exists()) {
def releaseAbsPath = releaseDescriptionFile.absoluteCanonicalFile.path
ScriptLog.printf "Found description file for '$releaseName' release in '$releaseAbsPath'! It will be used for release creation."
return Optional.of(releaseDescriptionFile.getText("UTF-8"))
} else {
ScriptLog.printf "Release description file not found in '${releaseDescriptionFile.absoluteCanonicalFile.path}', falling back to default value..."
}
return Optional.empty()
}
}

View File

@@ -0,0 +1,8 @@
package util
class ScriptLog {
static void printf(Object text, Object... args) {
def callerClassName = new Throwable().getStackTrace()[1].getClassName().split("\\.").last()
println "[${callerClassName}]: ${String.format(String.valueOf(text), args)}"
}
}