commit 2fc95f817492a51b0f6edce00f0f6f7fb5e38441 Author: Sergey Paramoshkin Date: Sun Dec 7 11:10:46 2025 +0300 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5626675 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Gradle +.gradle/ +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +# IntelliJ IDEA +.idea/ +*.iws +*.iml +*.ipr +out/ + +# VS Code +.vscode/ + +# OS +.DS_Store +Thumbs.db diff --git a/.teamcity/examples/ExamplePhpProject.kt b/.teamcity/examples/ExamplePhpProject.kt new file mode 100644 index 0000000..7b7e82a --- /dev/null +++ b/.teamcity/examples/ExamplePhpProject.kt @@ -0,0 +1,71 @@ +package examples + +import templates.PhpDockerBuildTemplate + +/** + * Example: How to use PhpDockerBuildTemplate in your project + * + * In your project's .teamcity/settings.kts: + * + * ```kotlin + * import jetbrains.buildServer.configs.kotlin.* + * import templates.PhpDockerBuildTemplate + * + * version = "2024.07" + * + * project { + * buildType(Build) + * } + * + * object Build : PhpDockerBuildTemplate({ + * backendDir = "backend" + * enableCs = true + * enableAnalyse = true + * composerAuth = """ + * { + * "http-basic": { + * "nexus.dot-dot.ru": { + * "username": "%env.NEXUS_USER%", + * "password": "%env.NEXUS_PASSWORD%" + * } + * } + * } + * """.trimIndent() + * }) + * ``` + */ +object ExamplePhpBuild : PhpDockerBuildTemplate({ + // Registry settings + registry = "registry.dot-dot.ru" + dockerRegistryId = "PROJECT_EXT_2" + + // PHP settings + backendDir = "backend" + enableCs = true + enableAnalyse = true + + // Composer auth for private packages + composerAuth = """ + { + "http-basic": { + "nexus.dot-dot.ru": { + "username": "ddadmin", + "password": "secret" + } + } + } + """.trimIndent() + + // Docker settings + dockerfile = "Dockerfile" + tag = "%teamcity.build.branch%" + nexusHost = "nexus.dot-dot.ru" + nexusIp = "192.168.100.110" + + // Gitea settings + giteaUrl = "https://gitea.dot-dot.ru/api/v1" + giteaTokenId = "credentialsJSON:gitea-token" + + // Triggers + enableVcsTrigger = true +}) diff --git a/.teamcity/examples/ExampleSimpleDocker.kt b/.teamcity/examples/ExampleSimpleDocker.kt new file mode 100644 index 0000000..1210fa9 --- /dev/null +++ b/.teamcity/examples/ExampleSimpleDocker.kt @@ -0,0 +1,19 @@ +package examples + +import templates.DockerBuildTemplate + +/** + * Example: Simple Docker build without PHP-specific features + * + * Use this for non-PHP projects that just need Docker build + push + */ +object ExampleSimpleBuild : DockerBuildTemplate({ + registry = "registry.dot-dot.ru" + dockerfile = "Dockerfile" + tag = "%teamcity.build.branch%" + buildArgs = "--pull" + extraHosts = listOf("nexus.dot-dot.ru:192.168.100.110") + dockerRegistryId = "PROJECT_EXT_2" + giteaUrl = "https://gitea.dot-dot.ru/api/v1" + enableVcsTrigger = true +}) diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml new file mode 100644 index 0000000..9eb7b91 --- /dev/null +++ b/.teamcity/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + ru.dot-dot.ci + ci-templates + 1.0-SNAPSHOT + + + 1.9.10 + 2024.07 + + + + + jetbrains-all + https://download.jetbrains.com/teamcity-repository + + + mavenCentral + https://repo1.maven.org/maven2/ + + + + + + jetbrains-all + https://download.jetbrains.com/teamcity-repository + + + + + + org.jetbrains.teamcity + configs-dsl-kotlin-latest + ${teamcity.dsl.version} + compile + + + org.jetbrains.teamcity + configs-dsl-kotlin-plugins-latest + 1.0-SNAPSHOT + pom + compile + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-script-runtime + ${kotlin.version} + + + + + . + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + + + org.jetbrains.teamcity + teamcity-configs-maven-plugin + ${teamcity.dsl.version} + + kotlin + target/generated-configs + + + + + diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts new file mode 100644 index 0000000..5929a04 --- /dev/null +++ b/.teamcity/settings.kts @@ -0,0 +1,7 @@ +import jetbrains.buildServer.configs.kotlin.* + +version = "2024.07" + +project { + description = "CI Templates - reusable TeamCity DSL configurations" +} diff --git a/.teamcity/templates/DockerBuildTemplate.kt b/.teamcity/templates/DockerBuildTemplate.kt new file mode 100644 index 0000000..122fe0c --- /dev/null +++ b/.teamcity/templates/DockerBuildTemplate.kt @@ -0,0 +1,134 @@ +package templates + +import jetbrains.buildServer.configs.kotlin.* +import jetbrains.buildServer.configs.kotlin.buildFeatures.commitStatusPublisher +import jetbrains.buildServer.configs.kotlin.buildFeatures.dockerRegistryConnections +import jetbrains.buildServer.configs.kotlin.buildSteps.DockerCommandStep +import jetbrains.buildServer.configs.kotlin.buildSteps.dockerCommand +import jetbrains.buildServer.configs.kotlin.triggers.vcs + +/** + * Configuration for Docker build template + */ +open class DockerBuildConfig { + /** Docker registry URL (without protocol) */ + var registry: String = "registry.dot-dot.ru" + + /** Path to Dockerfile relative to repo root */ + var dockerfile: String = "Dockerfile" + + /** Image tag expression, supports TeamCity parameters */ + var tag: String = "%teamcity.build.branch%" + + /** Additional docker build arguments */ + var buildArgs: String = "--pull" + + /** Docker registry connection ID in TeamCity */ + var dockerRegistryId: String = "PROJECT_EXT_2" + + /** Extra hosts for docker build (host:ip format) */ + var extraHosts: List = listOf() + + /** Enable VCS trigger */ + var enableVcsTrigger: Boolean = true + + /** Branch filter for VCS trigger (e.g., "+:*" for all branches, "+:refs/tags/*" for tags) */ + var branchFilter: String = "" + + /** Enable trigger on tags */ + var triggerOnTags: Boolean = false + + /** Gitea API URL for commit status */ + var giteaUrl: String = "https://gitea.dot-dot.ru/api/v1" + + /** Gitea token credential ID */ + var giteaTokenId: String = "credentialsJSON:gitea-token" +} + +/** + * Template for building and pushing Docker images + * + * Usage: + * ``` + * object Build : DockerBuildTemplate({ + * registry = "registry.dot-dot.ru" + * dockerfile = "Dockerfile" + * extraHosts = listOf("nexus.dot-dot.ru:192.168.100.110") + * }) + * ``` + */ +open class DockerBuildTemplate( + configure: DockerBuildConfig.() -> Unit = {} +) : BuildType() { + + protected val config = DockerBuildConfig().apply(configure) + + init { + name = "build" + allowExternalStatus = true + + steps { + dockerCommand { + name = "build image" + id = "build_image" + commandType = build { + source = file { + path = config.dockerfile + } + platform = DockerCommandStep.ImagePlatform.Linux + namesAndTags = "${config.registry}/%env.TEAMCITY_PROJECT_NAME%:${config.tag}" + commandArgs = buildDockerArgs() + } + } + dockerCommand { + name = "push to registry" + id = "push_to_registry" + commandType = push { + namesAndTags = "${config.registry}/%env.TEAMCITY_PROJECT_NAME%:${config.tag}" + } + } + } + + if (config.enableVcsTrigger) { + triggers { + vcs { + // Build branch filter + val filters = mutableListOf() + if (config.branchFilter.isNotEmpty()) { + filters.add(config.branchFilter) + } + if (config.triggerOnTags) { + filters.add("+:refs/tags/*") + } + if (filters.isNotEmpty()) { + branchFilter = filters.joinToString("\n") + } + } + } + } + + features { + dockerRegistryConnections { + loginToRegistry = on { + dockerRegistryId = config.dockerRegistryId + } + } + commitStatusPublisher { + publisher = github { + githubUrl = config.giteaUrl + authType = personalToken { + token = config.giteaTokenId + } + } + } + } + } + + private fun buildDockerArgs(): String { + val args = mutableListOf(config.buildArgs) + config.extraHosts.forEach { host -> + args.add("--add-host=$host") + } + return args.joinToString(" ") + } +} diff --git a/.teamcity/templates/PhpDockerBuildTemplate.kt b/.teamcity/templates/PhpDockerBuildTemplate.kt new file mode 100644 index 0000000..c9f1c27 --- /dev/null +++ b/.teamcity/templates/PhpDockerBuildTemplate.kt @@ -0,0 +1,169 @@ +package templates + +import jetbrains.buildServer.configs.kotlin.* +import jetbrains.buildServer.configs.kotlin.buildFeatures.commitStatusPublisher +import jetbrains.buildServer.configs.kotlin.buildFeatures.dockerRegistryConnections +import jetbrains.buildServer.configs.kotlin.buildSteps.DockerCommandStep +import jetbrains.buildServer.configs.kotlin.buildSteps.dockerCommand +import jetbrains.buildServer.configs.kotlin.buildSteps.script +import jetbrains.buildServer.configs.kotlin.triggers.vcs + +/** + * Configuration for PHP Docker build template + */ +open class PhpDockerBuildConfig : DockerBuildConfig() { + /** Path to backend directory */ + var backendDir: String = "backend" + + /** CI image for running linters/analysis */ + var ciImage: String? = null + + /** Enable composer code style check */ + var enableCs: Boolean = false + + /** Enable composer static analysis */ + var enableAnalyse: Boolean = false + + /** Composer auth.json content (for private packages) */ + var composerAuth: String? = null + + /** Nexus host for private packages */ + var nexusHost: String = "nexus.dot-dot.ru" + + /** Nexus IP for /etc/hosts */ + var nexusIp: String = "192.168.100.110" +} + +/** + * Template for PHP projects with Docker build + * + * Usage: + * ``` + * object Build : PhpDockerBuildTemplate({ + * backendDir = "backend" + * enableCs = true + * enableAnalyse = true + * composerAuth = """{"http-basic": {"nexus.dot-dot.ru": {"username": "user", "password": "pass"}}}""" + * extraHosts = listOf("nexus.dot-dot.ru:192.168.100.110") + * }) + * ``` + */ +open class PhpDockerBuildTemplate( + configure: PhpDockerBuildConfig.() -> Unit = {} +) : BuildType() { + + protected val phpConfig = PhpDockerBuildConfig().apply(configure) + + init { + name = "build" + allowExternalStatus = true + + steps { + // Create auth.json if configured + phpConfig.composerAuth?.let { auth -> + script { + name = "create auth.json" + id = "create_auth_json" + scriptContent = """ + echo '$auth' > ${phpConfig.backendDir}/auth.json + """.trimIndent() + } + } + + // CI image for linting + val ciImg = phpConfig.ciImage ?: "${phpConfig.registry}/%env.TEAMCITY_PROJECT_NAME%-ci:latest" + val dockerRunArgs = buildDockerRunArgs() + + // Code style check + if (phpConfig.enableCs) { + dockerCommand { + name = "composer cs" + id = "composer_cs" + commandType = other { + subCommand = "run" + commandArgs = """ + $dockerRunArgs + -v %system.teamcity.build.workingDir%/${phpConfig.backendDir}:/application + -w /application $ciImg /usr/local/bin/composer cs + """.trimIndent() + } + } + } + + // Static analysis + if (phpConfig.enableAnalyse) { + dockerCommand { + name = "composer analyse" + id = "composer_analyse" + commandType = other { + subCommand = "run" + commandArgs = """ + $dockerRunArgs + -v %system.teamcity.build.workingDir%/${phpConfig.backendDir}:/application + -w /application $ciImg /usr/local/bin/composer analyse-ci + """.trimIndent() + } + } + } + + // Build image + dockerCommand { + name = "build image" + id = "build_image" + commandType = build { + source = file { + path = phpConfig.dockerfile + } + platform = DockerCommandStep.ImagePlatform.Linux + namesAndTags = "${phpConfig.registry}/%env.TEAMCITY_PROJECT_NAME%:${phpConfig.tag}" + commandArgs = buildDockerBuildArgs() + } + } + + // Push to registry + dockerCommand { + name = "push to registry" + id = "push_to_registry" + commandType = push { + namesAndTags = "${phpConfig.registry}/%env.TEAMCITY_PROJECT_NAME%:${phpConfig.tag}" + } + } + } + + if (phpConfig.enableVcsTrigger) { + triggers { + vcs {} + } + } + + features { + dockerRegistryConnections { + loginToRegistry = on { + dockerRegistryId = phpConfig.dockerRegistryId + } + } + commitStatusPublisher { + publisher = github { + githubUrl = phpConfig.giteaUrl + authType = personalToken { + token = phpConfig.giteaTokenId + } + } + } + } + } + + private fun buildDockerRunArgs(): String { + val args = mutableListOf("--add-host=${phpConfig.nexusHost}:${phpConfig.nexusIp}", "-i", "--rm") + return args.joinToString(" ") + } + + private fun buildDockerBuildArgs(): String { + val args = mutableListOf(phpConfig.buildArgs) + args.add("--add-host=${phpConfig.nexusHost}:${phpConfig.nexusIp}") + phpConfig.extraHosts.forEach { host -> + args.add("--add-host=$host") + } + return args.joinToString(" ") + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..02e3b69 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# CI Templates + +Переиспользуемые TeamCity DSL шаблоны для проектов dot-dot.ru. + +## Подключение к проекту + +### 1. Добавить зависимость в pom.xml вашего проекта + +```xml + + ru.dot-dot.ci + ci-templates + 1.0-SNAPSHOT + +``` + +### 2. Настроить Versioned Settings в TeamCity + +В настройках проекта TeamCity → Versioned Settings: +- Synchronization enabled: **Yes** +- Project settings VCS root: выбрать VCS root вашего проекта +- Settings format: **Kotlin** +- Allow editing project settings via UI: по желанию + +Добавить дополнительный VCS root для ci-templates: +- VCS URL: `git@gitea.dot-dot.ru:dot-dot/ci-templates.git` +- Checkout rules: `+:.teamcity => .teamcity/ci-templates` + +## Доступные шаблоны + +### DockerBuildTemplate + +Базовый шаблон для сборки и публикации Docker образов. + +```kotlin +import templates.DockerBuildTemplate + +object Build : DockerBuildTemplate({ + registry = "registry.dot-dot.ru" + dockerfile = "Dockerfile" + tag = "%teamcity.build.branch%" + extraHosts = listOf("nexus.dot-dot.ru:192.168.100.110") +}) +``` + +### PhpDockerBuildTemplate + +Шаблон для PHP проектов с поддержкой Composer, линтеров и анализаторов. + +```kotlin +import templates.PhpDockerBuildTemplate + +object Build : PhpDockerBuildTemplate({ + backendDir = "backend" + enableCs = true + enableAnalyse = true + composerAuth = """{"http-basic": {"nexus.dot-dot.ru": {"username": "%env.NEXUS_USER%", "password": "%env.NEXUS_PASSWORD%"}}}""" +}) +``` + +## Параметры + +### DockerBuildConfig + +| Параметр | По умолчанию | Описание | +|----------|--------------|----------| +| registry | registry.dot-dot.ru | Docker registry URL | +| dockerfile | Dockerfile | Путь к Dockerfile | +| tag | %teamcity.build.branch% | Тег образа | +| buildArgs | --pull | Аргументы docker build | +| dockerRegistryId | PROJECT_EXT_2 | ID подключения к registry в TeamCity | +| extraHosts | [] | Дополнительные хосты (host:ip) | +| enableVcsTrigger | true | Включить VCS триггер | +| giteaUrl | https://gitea.dot-dot.ru/api/v1 | URL Gitea API | +| giteaTokenId | credentialsJSON:gitea-token | ID токена Gitea | + +### PhpDockerBuildConfig (наследует DockerBuildConfig) + +| Параметр | По умолчанию | Описание | +|----------|--------------|----------| +| backendDir | backend | Директория с PHP кодом | +| ciImage | null | CI образ для линтеров (по умолчанию {project}-ci:latest) | +| enableCs | false | Включить проверку code style | +| enableAnalyse | false | Включить статический анализ | +| composerAuth | null | Содержимое auth.json | +| nexusHost | nexus.dot-dot.ru | Хост Nexus | +| nexusIp | 192.168.100.110 | IP Nexus для /etc/hosts |