DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Enterprise AI Trend Report: Gain insights on ethical AI, MLOps, generative AI, large language models, and much more.

2024 Cloud survey: Share your insights on microservices, containers, K8s, CI/CD, and DevOps (+ enter a $750 raffle!) for our Trend Reports.

PostgreSQL: Learn about the open-source RDBMS' advanced capabilities, core components, common commands and functions, and general DBA tasks.

AI Automation Essentials. Check out the latest Refcard on all things AI automation, including model training, data security, and more.

Related

  • Integrating Java and npm Builds Using Gradle - Groovy or Kotlin DSL
  • How to Practice TDD With Kotlin?
  • An Explanation of Jenkins Architecture
  • Four Common CI/CD Pipeline Vulnerabilities

Trending

  • How to Submit a Post to DZone
  • API Appliance for Extreme Agility and Simplicity
  • Some Thoughts on Bad Programming Practices
  • DZone's Article Submission Guidelines
  1. DZone
  2. Testing, Deployment, and Maintenance
  3. Deployment
  4. Automated CI/CD of Multiple Projects Using TeamCity’s Kotlin DSL

Automated CI/CD of Multiple Projects Using TeamCity’s Kotlin DSL

Using a programming language to define build configuration allows for a dramatic reduction in manual configuration settings tweaking.

By 
Ilya Kaznacheev user avatar
Ilya Kaznacheev
·
Updated May. 11, 22 · Tutorial
Like (5)
Save
Tweet
Share
14.5K Views

Join the DZone community and get the full member experience.

Join For Free

In a previous article, I described a way to organize low-latency products as multiple code bases which are bound together with a Maven Bill of Materials (BOM). Understandably, this requires setting up continuous integration and deployment for a large number of similar projects. Maintaining such a setup manually in the face of change while ensuring its consistency will take a lot of effort.  In this article, I will describe how the team at Chronicle Software has tackled these issues in different projects by writing code that does this for us, in the form of Kotlin DSL for TeamCity.

This guide will show how to configure the same set of CI/CD builds for multiple Maven project repositories of similar layouts programmatically, following the DRY (don’t repeat yourself) principle. Following it will require a base knowledge of git, Maven, and TeamCity but would not require knowledge of the Kotlin language, since all of the displayed code is self-explanatory.

Once you configure versioned settings for a TeamCity project, it will push a skeleton Maven configuration project to a repository of choice, which we will be working with.

Configuration as Code

First, we introduce our own product management code into configuration scripts by adding the dependency to .teamcity/pom.xml. If you need any specific dependency settings file used during dependency resolving, such as specifying credentials for internal Maven repositories, upload them to the TeamCity <Root project>’s Maven Settings tab under the name mavenSettingsDsl.xml.

XML
 
<dependency>
  <groupId>software.chronicle</groupId>
  <artifactId>release-automation</artifactId>
  <version>1.0.21</version>
</dependency>


Then we start working on .teamcity/setting.kts Kotlin script file, adding all of our products as TeamCity projects in a data-driven fashion based on our existing Products enum:

Kotlin
 
version = "2021.2"

project {
   description = "Chronicle Software’s Low Latency Microservice Products"
   template(GitHubTriggerNotify)

   subProjects(*subProjectsFromReleaseAutomation())
}

fun subProjectsFromReleaseAutomation(): Array<Project> {
   val projects = ArrayList<Project>()

   for (product in Product.values()) {
       projects.add(Project {
           id = RelativeId(product.name)

           name = product.name
           description = product.gitUrl().replace("git@github.com:",
                             "https://github.com/").replace(".git", "")


Continuing the same script, every product will have its GitHub repository set up:

Kotlin
 
           vcsRoot(GitVcsRoot {
               id = RelativeId(product.toString() + "_GitHub")

               name = product.name + " GitHub Repository"

               url = product.gitUrl()
               branch = "refs/heads/main"
               branchSpec = "+:refs/heads/(*)"

               authMethod = uploadedKey {
                   uploadedKey = "teamcity-kotlin-dsl"
               }
           })


After that, a snapshot deploying build is configured for every product, to be triggered when a commit is pushed to main branch and deploy the SNAPSHOT artifacts to our Maven repository:

Kotlin
 
           buildType {
               id = RelativeId(product.name + "_DeploySnapshot")
               name = "Deploy Snapshot"

               description = product.name + " SNAPSHOT artifacts deployment"

               vcs {
                   root(RelativeId(product.toString() + "_GitHub"))
               }
              
               triggers {
                   vcs {
                       id = "TriggerOnMain"
                       branchFilter = "+:<default>"
                   }
               }

               steps {
                   maven {
                       id = "CleanDeploy"
                       name = "clean deploy"
                       goals = "clean deploy"
                       userSettingsSelection = "kotlinDslBuildSettings.xml"
                       jvmArgs = "-ea"
                       jdkHome = "%env.JDK_1_8_x64%"
                   }
               }
           }


Secondly, a GitHub pull request testing build is added based on a template defined elsewhere, then returning the resulting TeamCity projects list, with a repository and two builds each:

Kotlin
 
           buildType {
               templates(RelativeId("GitHubTriggerNotify"))
               id = RelativeId(product.name + "_PullRequest")
               name = product.name + " Pull Request"

               description = product.name + " regular build for testing a Pull Request"

               vcs {
                   root(RelativeId(product.toString() + "_GitHub"))
               }
           }
       })
   }

   return projects.toTypedArray()
}


In a separate file, .teamcity/GitHubTriggerNotify.kt, a template is defined which will run tests and report results to GitHub pull request branches:

Kotlin
 
object GitHubTriggerNotify : Template({
   RelativeId("GitHubTriggerNotify")
   name = "Template for pull request builds"
   description = "Triggered by branch changes, posts results to GitHub"

   triggers {
       vcs {
           id = "TriggerAll"
           branchFilter = """
                   +:*
                   -:<default>
               """.trimIndent()
       }
   }

   failureConditions {
       executionTimeoutMin = 60
   }

   features {
       commitStatusPublisher {
           id = "NotifyGitHub"
           publisher = github {
               githubUrl = "https://api.github.com"
               authType = personalToken {
                   token = DslContext.getParameter("GitHub-PR-notification-API-token")
               }
           }
           param("github_oauth_user", "our-team-city")
       }
   }
})


SSH keys and API tokens should be specified separately, which is not shown in this guide.

Conclusion

The demonstrated approach allows us to not only keep our TeamCity configuration in a versioned and audited state, but it also dramatically reduces the line count of configuration scripts as we are iterating our products and defining multiple build configurations per product without repeating the specifics, while also making sure that all builds adhere to exactly the same build conditions and stages. It makes sense to disable any manual configuration for the project and only rely on explicit, thought-out configuration code changes.

Resources

TeamCity On-Premises Kotlin DSL documentation

Continuous Integration/Deployment Domain-Specific Language Kotlin (programming language) TeamCity

Opinions expressed by DZone contributors are their own.

Related

  • Integrating Java and npm Builds Using Gradle - Groovy or Kotlin DSL
  • How to Practice TDD With Kotlin?
  • An Explanation of Jenkins Architecture
  • Four Common CI/CD Pipeline Vulnerabilities

Partner Resources


Comments

ABOUT US

  • About DZone
  • Send feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: