All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.octopusden.octopus.automation.teamcity.TeamcityCreateBuildChainCommand.kt Maven / Gradle / Ivy

There is a newer version: 1.0.17
Show newest version
package org.octopusden.octopus.automation.teamcity

import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.requireObject
import com.github.ajalt.clikt.parameters.options.check
import com.github.ajalt.clikt.parameters.options.convert
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.required
import org.octopusden.octopus.components.registry.core.dto.BuildSystem
import org.octopusden.octopus.components.registry.core.dto.DetailedComponent
import org.octopusden.octopus.components.registry.core.dto.RepositoryType
import org.octopusden.octopus.components.registry.core.exceptions.NotFoundException
import org.octopusden.octopus.infrastructure.teamcity.client.getProject
import org.octopusden.octopus.components.registry.client.impl.ClassicComponentsRegistryServiceClient
import org.octopusden.octopus.components.registry.client.impl.ClassicComponentsRegistryServiceClientUrlProvider
import org.octopusden.octopus.infrastructure.teamcity.client.TeamcityClient
import org.octopusden.octopus.infrastructure.teamcity.client.ConfigurationType
import org.octopusden.octopus.infrastructure.teamcity.client.TeamcityVCSType
import org.octopusden.octopus.infrastructure.teamcity.client.disableBuildStep
import org.octopusden.octopus.infrastructure.teamcity.client.getBuildSteps
import org.octopusden.octopus.infrastructure.teamcity.client.createBuildTypeVcsRootEntry
import org.octopusden.octopus.infrastructure.teamcity.client.createSnapshotDependency
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityBuildType
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityCreateBuildType
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityCreateProject
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityCreateVcsRoot
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityCreateVcsRootEntry
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityLinkBuildType
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityLinkProject
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityLinkVcsRoot
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityProject
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityProperties
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityProperty
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcitySnapshotDependency
import org.octopusden.octopus.infrastructure.teamcity.client.dto.TeamcityVcsRoot
import org.slf4j.Logger

class TeamcityCreateBuildChainCommand : CliktCommand(name = COMMAND) {
    private val context by requireObject>()

    private val parentProjectId by option(PARENT, help = "Teamcity parent project Id").convert { it.trim() }.required()
        .check("$PARENT is empty") { it.isNotEmpty() }

    private val componentName by option(COMPONENT, help = "Component registry name").convert { it.trim() }.required()
        .check("$COMPONENT is empty") { it.isNotEmpty() }

    private val minorVersion by option(VERSION, help = "Minor version").convert { it.trim() }.required()
        .check("$VERSION is empty") { it.isNotEmpty() }

    private val componentsRegistryUrl by option(CR, help = "Components Registry service Url").required()
        .check("$CR is empty") { it.isNotEmpty() }

    private val client by lazy { context[TeamcityCommand.CLIENT] as TeamcityClient }
    private val log by lazy { context[TeamcityCommand.LOG] as Logger }

    override fun run() {
        log.info("Create build chain")
        val parentProject = client.getProject(parentProjectId)
        val componentsRegistryClient = ClassicComponentsRegistryServiceClient(
            object : ClassicComponentsRegistryServiceClientUrlProvider {
                override fun getApiUrl(): String {
                    return componentsRegistryUrl
                }
            }
        )
        val detailedComponent = componentsRegistryClient.getDetailedComponent(componentName, minorVersion)
        createBuildChain(parentProject, detailedComponent)
    }

    private fun createBuildChain(
        parentProject: TeamcityProject,
        component: DetailedComponent
    ) {
        val project = client.createProject(
            TeamcityCreateProject(name = componentName, parentProject = TeamcityLinkProject(id = parentProject.id))
        )
        val vcsRootId = createVcsRoot(project.id, component)?.id
        var counter = 0
        val compileConfig = createBuildConf(
            when (component.buildSystem) {
                BuildSystem.MAVEN -> TEMPLATE_MAVEN_COMPILE
                BuildSystem.GRADLE -> TEMPLATE_GRADLE_COMPILE
                BuildSystem.PROVIDED -> TEMPLATE_GRADLE_COMPILE
                else -> throw NotFoundException("Unsupported build system: ${component.buildSystem?.name}")
            },
            "[${++counter}.0] Compile & UT [AUTO]",
            project.id
        )
        attachVcsRootToBuildType(compileConfig.id, vcsRootId)
        val defaultJDKVersion = client.getParameter(ConfigurationType.PROJECT, parentProjectId, "JDK_VERSION")
        component.buildParameters?.javaVersion?.takeIf { it != defaultJDKVersion }?.let { projectJDKVersion ->
            setBuildTypeParameter(compileConfig.id, "JDK_VERSION", projectJDKVersion)
        }
        val releaseConfig = if (component.distribution?.explicit == true && component.distribution?.external == true) {
            val rcConfig = createBuildConf(
                TEMPLATE_RC,
                "[${++counter}.0] Release Candidate [Manual]",
                project.id
            )
            attachVcsRootToBuildType(rcConfig.id, vcsRootId)

            val checklistConfig = createBuildConf(
                TEMPLATE_CHECKLIST,
                "[${++counter}.0] Release Checklist Validation [MANUAL]",
                project.id
            )
            attachVcsRootToBuildType(checklistConfig.id, vcsRootId)

            val releaseConfig = createBuildConf(
                TEMPLATE_RELEASE,
                "[${++counter}.0] Release [Manual]",
                project.id
            )
            attachVcsRootToBuildType(releaseConfig.id, vcsRootId)

            addSnapshotDependency(rcConfig, compileConfig)
            addSnapshotDependency(checklistConfig, rcConfig)
            addSnapshotDependency(releaseConfig, rcConfig)

            setBuildTypeParameter(rcConfig.id, "BUILD_VERSION", "%dep.${compileConfig.id}.BUILD_VERSION%")
            setBuildTypeParameter(checklistConfig.id, "BUILD_VERSION", "%dep.${compileConfig.id}.BUILD_VERSION%")
            releaseConfig
        } else {
            val releaseConfig = createBuildConf(
                TEMPLATE_RELEASE,
                "[${++counter}.0] Release [Manual]",
                project.id
            )
            attachVcsRootToBuildType(releaseConfig.id, vcsRootId)
            addSnapshotDependency(releaseConfig, compileConfig)
            releaseConfig
        }
        disableBuildStep(releaseConfig.id, "IncrementTeamCityBuildConfigurationParameter")
        if (component.buildSystem == BuildSystem.GRADLE) {
            disableBuildStep(releaseConfig.id, "Deploy to Share")
        }
        setBuildTypeParameter(releaseConfig.id, "BUILD_VERSION", "%dep.${compileConfig.id}.BUILD_VERSION%")
        setBuildTypeParameter(
            releaseConfig.id,
            "STAGING_REPOSITORY_ID",
            "%dep.${compileConfig.id}.STAGING_REPOSITORY_ID%"
        )
        setProjectParameter(project.id, "COMPONENT_NAME", componentName)
        setProjectParameter(project.id, "RELENG_SKIP", "false")
        setProjectParameter(project.id, "PROJECT_VERSION", minorVersion)
    }

    private fun attachVcsRootToBuildType(buildTypeId: String, vcsRootId: String?) =
        vcsRootId?.let {
            client.createBuildTypeVcsRootEntry(
                buildTypeId,
                TeamcityCreateVcsRootEntry(
                    id = vcsRootId,
                    vcsRoot = TeamcityLinkVcsRoot(vcsRootId)
                )
            )
        } ?: log.info("Skip attach vcs root to {}", buildTypeId)

    private fun createBuildConf(templateId: String, name: String, projectId: String) =
        client.createBuildType(
            TeamcityCreateBuildType(
                template = TeamcityLinkBuildType(id = templateId),
                name = name,
                project = TeamcityLinkProject(id = projectId),
            )
        )

    private fun addSnapshotDependency(buildType: TeamcityBuildType, sourceBuildType: TeamcityBuildType) {
        client.createSnapshotDependency(
            buildType.id,
            TeamcitySnapshotDependency(
                id = sourceBuildType.name,
                type = "snapshot_dependency",
                properties = TeamcityProperties(
                    listOf(
                        TeamcityProperty("run-build-if-dependency-failed", "MAKE_FAILED_TO_START"),
                        TeamcityProperty("run-build-if-dependency-failed-to-start", "MAKE_FAILED_TO_START"),
                        TeamcityProperty("run-build-on-the-same-agent", "false"),
                        TeamcityProperty("take-started-build-with-same-revisions", "true"),
                        TeamcityProperty("take-successful-builds-only", "true"),
                    )
                ),
                sourceBuildType = TeamcityLinkBuildType(sourceBuildType.id)
            )
        )
    }

    private fun setBuildTypeParameter(buildTypeId: String, name: String, value: String) =
        client.setParameter(ConfigurationType.BUILD_TYPE, buildTypeId, name, value).also {
            log.info("Set parameter $name value $value for build configuration with id $buildTypeId")
        }

    private fun setProjectParameter(projectId: String, name: String, value: String) =
        client.setParameter(ConfigurationType.PROJECT, projectId, name, value).also {
            log.info("Set parameter $name value $value for project with id $projectId")
        }

    private fun disableBuildStep(buildTypeId: String, stepNameOrType: String, disable: Boolean = true) {
        client.getBuildSteps(buildTypeId)
            .steps.find { step -> step.name == stepNameOrType || step.type == stepNameOrType }
            ?.let { step -> client.disableBuildStep(buildTypeId, step.id, disable) }
            ?: log.warn("Skip disable build step '{}' not found for build type {}", stepNameOrType, buildTypeId)
    }

    private fun createVcsRoot(projectId: String, component: DetailedComponent): TeamcityVcsRoot? {
        return component.vcsSettings?.versionControlSystemRoots?.firstOrNull()?.let { vcsRootData ->
            val vcsRootName = "${projectId}_VCS_ROOT"
            when (vcsRootData.type) {
                RepositoryType.GIT -> client.createVcsRoot(
                    TeamcityCreateVcsRoot(
                        name = vcsRootName,
                        vcsName = TeamcityVCSType.GIT.value,
                        projectLocator = projectId,
                        TeamcityProperties(
                            listOf(
                                TeamcityProperty("url", vcsRootData.vcsPath),
                                TeamcityProperty("branch", "master"),
                                TeamcityProperty("authMethod", "PRIVATE_KEY_DEFAULT"),
                                TeamcityProperty("userForTags", "tcagent"),
                                TeamcityProperty("username", "git"),
                                TeamcityProperty("ignoreKnownHosts", "true"),
                            )
                        )
                    )
                )
                else -> throw NotFoundException("Unsupported vcs type: ${vcsRootData.type}")
            }
        }
    }

    companion object {
        const val COMMAND = "create-build-chain"
        const val PARENT = "--parent-project-id"
        const val COMPONENT = "--component"
        const val VERSION = "--minor-version"
        const val CR = "--registry-url"

        const val TEMPLATE_GRADLE_COMPILE = "CDCompileUTGradle"
        const val TEMPLATE_MAVEN_COMPILE = "CDCompileUTMaven"
        const val TEMPLATE_RC = "CdReleaseCandidateNew"
        const val TEMPLATE_CHECKLIST = "CdReleaeChecklistValidation"
        const val TEMPLATE_RELEASE = "CDRelease"
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy