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,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)}"
}
}