rodm / gradle-teamcity-plugin

Gradle plugin for developing TeamCity plugins
Apache License 2.0
47 stars 15 forks source link
gradle-plugin teamcity

= Gradle TeamCity plugin :plugin-id: io.github.rodm.teamcity-server :uri-teamcity-sdk-docs: https://plugins.jetbrains.com/docs/teamcity :uri-teamcity-environment: {uri-teamcity-sdk-docs}/development-environment.html :uri-server-descriptor: {uri-teamcity-sdk-docs}/plugins-packaging.html#Plugin+Descriptor :uri-agent-descriptor: {uri-teamcity-sdk-docs}/plugins-packaging.html#plugin-descriptor-1 :uri-jetbrains-plugin-portal: https://plugins.jetbrains.com/teamcity :uri-jetbrains-hub-token: https://www.jetbrains.com/help/hub/Manage-Permanent-Tokens.html :uri-jetbrains-teamcity-releases: https://www.jetbrains.com/teamcity/download/other.html :uri-github-project: https://github.com/rodm/gradle-teamcity-plugin :uri-github-actions: {uri-github-project}/actions :uri-github-status: {uri-github-actions}/workflows/build.yml/badge.svg :uri-shields-gradle-portal: https://img.shields.io/gradle-plugin-portal/v :uri-gradle-plugin-portal: https://plugins.gradle.org :uri-gradle-plugin: {uri-gradle-plugin-portal}/plugin/{plugin-id} :uri-gradle-plugin-version: {uri-shields-gradle-portal}/{plugin-id} :uri-docker-hub: https://hub.docker.com :uri-docker-teamcity-server: {uri-docker-hub}/r/jetbrains/teamcity-server :uri-docker-teamcity-agent: {uri-docker-hub}/r/jetbrains/teamcity-agent :uri-github-example-project: https://github.com/rodm/teamcity-docker-multi-node :default-api-version: 9.0 :example-api-version: 2018.1

The plugin provides support for {uri-teamcity-sdk-docs}[developing TeamCity plugins], creating agent and server side archives, publishing them to {uri-jetbrains-plugin-portal}[plugins portal], downloading and installing a TeamCity server and tasks to deploy, start and stop the server and agent.

image:{uri-github-status}?branch=main["Build Status", link="{uri-github-actions}"] image:{uri-gradle-plugin-version}?label=Gradle%20Plugin%20Portal[Gradle Plugin Portal, link="{uri-gradle-plugin}"]

== Supported Gradle and Java versions

|=== |Plugin version |Gradle version |Java version |Documentation |v1.5 |7.0+ |Java 8 & later |{uri-github-project}/blob/v1.5/README.adoc#using-the-plugin[README] |v1.4 |6.0+ |Java 8 & later |{uri-github-project}/blob/v1.4/README.adoc#using-the-plugin[README] |v1.3 |5.0+ |Java 8 & later |{uri-github-project}/blob/v1.3/README.adoc#using-the-plugin[README] |v1.2 |4.0+ |Java 7 & later |{uri-github-project}/blob/v1.2.2/README.adoc#using-the-plugin[README] |v1.1 |3.0+ |Java 7 & 8 |{uri-github-project}/blob/v1.1/README.adoc#using-the-plugin[README] |v1.0 |2.8+ |Java 7 & 8 |{uri-github-project}/blob/v1.0/README.adoc#using-the-plugin[README] |===

== Building the plugin

To build the plugin and run the unit tests run

./gradlew -s check

To run the functional tests run

./gradlew -s functionalTest

To test the samples run

./gradlew -s samplesTest

== Using the plugin

=== Applying the plugins to a build

The plugin archive published to the {uri-gradle-plugin-portal}[Gradle Plugin Portal] contains five plugins:

Refer to the Gradle Plugin Portal for instructions on how to apply the {uri-gradle-plugin-portal}/plugin/io.github.rodm.teamcity-server[server], {uri-gradle-plugin-portal}/plugin/io.github.rodm.teamcity-agent[agent], {uri-gradle-plugin-portal}/plugin/io.github.rodm.teamcity-common[common], {uri-gradle-plugin-portal}/plugin/io.github.rodm.teamcity-base[base] and {uri-gradle-plugin-portal}/plugin/io.github.rodm.teamcity-environments[environments] plugins.

=== Configurations

The plugins add the following configurations.

=== Extension Properties

The following properties are defined in the teamcity configuration block. The version property controls the version of the TeamCity API dependencies added to the provided configuration.

=== TeamCity Base Plugin

Applying the base plugin allows the extension properties version, allowSnapshotVersions, validateBeanDefinition and defaultRepositories to be inherited by sub-projects applying the other plugins.

==== Example

Plugin descriptor defined in the build script.

[source,groovy] [subs="attributes"]

teamcity {
    version = '2021.2-SNAPSHOT'
    allowSnapshotVersions = true            // allow snapshot versions
    validateBeanDefinition = "fail"         // fail build on any plugin validation errors
    defaultRepositories = true              // use default repositories to resolve dependencies
}

=== TeamCity Server Plugin

The plugin when applied with the Java Plugin, adds the JetBrains Maven repository and adds the TeamCity server-api, server-web-api and tests-support dependencies to the provided and testImplementation configurations. If the Java Plugin is not applied the plugin provides only the tasks to package and publish the server side plugin archive if a plugin descriptor is defined.

The server plugin can be combined with the agent plugin in the same project but not with the Java Plugin.

==== Configuration

The following properties can be defined in the server configuration block.

The plugin descriptor properties shown in the examples below and described in the TeamCity documentation for {uri-server-descriptor}[Server-Side Plugin Descriptor]

==== Tasks

The plugin enhances the jar task to perform validation of the bean definition file and outputs a warning if there are no beans defined or if a class is missing from the jar file.

==== Examples

Plugin descriptor defined in the build script.

[source,groovy] [subs="attributes"]

teamcity {
    // Use TeamCity {example-api-version} API
    version = '{example-api-version}'

    // Plugin descriptor
    server {
        descriptor {
            // required properties
            name = project.name
            displayName = 'TeamCity Plugin'
            version = project.version
            vendorName = 'vendor name'

            // optional properties
            description = 'Example TeamCity plugin'
            downloadUrl = 'download url'
            email = 'me@example.com'
            vendorUrl = 'vendor url'
            vendorLogo = 'vendor logo'

            // deployment properties
            useSeparateClassloader = true
            allowRuntimeReload = true
            nodeResponsibilitiesAware = true

            // requirements properties
            minimumBuild = '58245' // 2018.1
            maximumBuild = '78938' // 2020.1.5

            parameters {
                parameter 'name1', 'value1'
                parameter 'name2', 'value2'
            }

            dependencies {
                plugin 'plugin1-name'
                plugin 'plugin2-name'
                tool 'tool1-name'
                tool 'tool2-name'
            }
        }

        web {
            webpack // A task the runs webpack
            // or
            project.files("${buildDir}/frontend") // the contents of a directory
        }

        // Additional files can be included in the server plugin archive using the files configuration block
        files {
            into('tooldir') {
                from('tooldir')
            }
        }
    }
}

The build numbers for the properties minimumBuild and maximumBuild can be found on the {uri-jetbrains-teamcity-releases}[previous releases] page.

Plugin descriptor defined in an external file at the root of the project. A map of tokens to be replaced in the descriptor file can be provided using the tokens property.

[source,groovy] [subs="attributes"]

teamcity {
    // Use TeamCity {example-api-version} API
    version = '{example-api-version}'

    server {
        // Locate the plugin descriptor in the root directory of the project
        descriptor = file('teamcity-plugin.xml')
        tokens = [VERSION: project.version, VENDOR_NAME: 'vendor name']
    }
}

The following example uses the Kotlin DSL.

[source,groovy] [subs="attributes"] .build.gradle.kts

teamcity {
    version = "{example-api-version}"

    server {
        descriptor {
            // required properties
            name = project.name
            displayName = "TeamCity Plugin"
            version = project.version as String?
            vendorName = "vendor name"

            // optional properties
            description = "Example TeamCity plugin"
            downloadUrl = "download url"
            email = "me@example.com"
            vendorUrl = "vendor url"
            vendorLogo = "vendor logo"

            // deployment properties
            useSeparateClassloader = true
            allowRuntimeReload = true
            nodeResponsibilitiesAware = true

            // requirements properties
            minimumBuild = "58245" // 2018.1
            maximumBuild = "78938" // 2020.1.5

            parameters {
                parameter("name1", "value1")
                parameter("name2", "value2")
            }

            dependencies {
                plugin("plugin1-name")
                plugin("plugin2-name")
                tool("tool1-name")
                tool("tool2-name")
            }
        }

        web {
            tasks.named("webpack") // A task the runs webpack
            // or
            project.files("${buildDir}/frontend") // the contents of a directory
        }

        files {
            into("tooldir") {
                from("tooldir")
            }
        }
    }
}

==== Signing a plugin

The signPlugin task is used to sign the plugin archive before publishing to the {uri-jetbrains-plugin-portal}[JetBrains TeamCity Plugin Repository].

[source,groovy] .build.gradle

teamcity { server { descriptor { ... } sign { certificateFile = findProperty('ca.crt') privateKeyFile = findProperty('private-key.file') password = findProperty('private-key.password') } } }

==== Publishing a plugin

The publishPlugin task is used to upload the plugin archive to the {uri-jetbrains-plugin-portal}[JetBrains TeamCity Plugin Repository]. Before publishing a plugin you will need to create a JetBrains Account, follow the 'Sign In' link at the top of the plugin repository page. The publishPlugin task cannot be used to publish new plugins, the first upload must be completed using the Upload plugin link on the plugin repository website.

The publishPlugin task requires a {uri-jetbrains-hub-token}[JetBrains Hub token] to publish a plugin to the repository as shown in the following examples.

The following example configures the publishPlugin task.

[source,groovy] [subs="attributes"] .build.gradle

publishPlugin { token = findProperty('jetbrains.token') }

The following example uses the Kotlin DSL.

[source,groovy] .build.gradle.kts

tasks.withType { token = findProperty("jetbrains.token") as String? }

The token and other properties can also be configured in the publish section of the server configuration as shown in the following example. Optionally one or more channels can be specified using the channels property, by default the plugin is published to the 'Stable' channel. An optional notes property can be set to describe the changes made to the version of the plugin to be uploaded. The change or update notes text is shown on the plugin repository next to each plugin version.

[source,groovy] .build.gradle

teamcity { server { descriptor { ... } publish { channels = ['Beta'] token = findProperty('jetbrains.token') notes = 'change notes' } } }

It is recommended to store the credentials for the JetBrains Plugin Repository in $HOME/.gradle/gradle.properties.

=== TeamCity Agent Plugin

The plugin when applied with the Java Plugin, adds the JetBrains Maven repository and adds the TeamCity agent-api and tests-support dependencies to the provided and testImplementation configurations. If the Java Plugin is not applied the plugin provides only the tasks to package the agent side plugin archive if a plugin descriptor is defined.

==== Configuration

The following properties can be defined in the agent configuration block.

The plugin descriptor properties are shown in the examples below and described in the TeamCity documentation for {uri-agent-descriptor}[Agent-Side Plugin Descriptor]

==== Tasks

The plugin enhances the jar task to perform validation of the bean definition file and outputs a warning if there are no beans defined or if a class is missing from the jar file.

==== Examples

Agent side plugin descriptor

[source,groovy] [subs="attributes"]

teamcity {
    version = teamcityVersion

    agent {
        descriptor {
            pluginDeployment {
                useSeparateClassloader = false
                executableFiles {
                    include 'file1'
                    include 'file2'
                }
            }
            dependencies {
                plugin 'plugin-name'
                tool 'tool-name'
            }
        }
    }
}

Agent tool descriptor

[source,groovy] [subs="attributes"]

teamcity {
    version = teamcityVersion

    agent {
        descriptor {
            toolDeployment {
                executableFiles {
                    include 'tooldir/file1'
                    include 'tooldir/file2'
                }
            }
            dependencies {
                plugin 'plugin-name'
                tool 'tool-name'
            }
        }

        // Additional files can be included in the agent plugin archive using the files configuration block
        files {
            into('tooldir') {
                from('tooldir')
            }
        }
    }
}

The following example uses the Kotlin DSL.

[source,groovy] [subs="attributes"] .build.gradle.kts

val teamcityVersion by extra((findProperty("teamcity.api.version") ?: "{example-api-version}") as String)

teamcity {
    version = teamcityVersion

    agent {
        descriptor {
            pluginDeployment {
                useSeparateClassloader = false
                executableFiles {
                    include("file1")
                    include("file2")
                }
            }
            dependencies {
                plugin("plugin-name")
                tool("tool-name")
            }
        }

        files {
            into("tooldir") {
                from("tooldir")
            }
        }
    }
}

=== TeamCity Environments Plugin

Applying this plugin provides tasks to download, install, start and stop a local TeamCity Server and Build Agent or start and stop a server or agent using Docker. This allows a plugin to be debugged or tested against multiple versions of TeamCity.

The environments plugin supports two different types of environment a local environment and a Docker environment. The types of environments supported are implemented using Gradle's ExtensiblePolymorphicDomainObjectContainer that can be extended to support other environment types. An example plugin that extends the environments plugin to support running multiple nodes can be found in the following project {uri-github-example-project}[teamcity-docker-multi-node].

==== Configuration

The environments configuration is available by applying the com.github.rodm.teamcity-environments plugin.

The following properties can be defined in the environments configuration block and are only used by local TeamCity environments.

The following Gradle properties can be used to override the shared environment properties from the command line or by setting a value in a gradle.properties file.

The environments configuration block supports defining multiple TeamCity environments. When creating an environment the default is to create or register a local TeamCity environment.

==== A Local TeamCity Environment

===== Configuration

To configure a local TeamCity environment the configuration block supports the following properties

The following Gradle properties can be used to override the properties for a specific environment from the command line or by setting a value in a gradle.properties file. Replace <environment> with the name defined in the build script.

The following Gradle property teamcity.environments.shutdownTimeout is used to set the seconds the Stop Server task will wait for the TeamCity Server to stop. This property is only used by the Stop Server task for local TeamCity environments. The default value is 10 seconds.

===== Tasks

For each environment the following tasks are created based on the environment name:

===== Examples

[source,groovy] [subs="attributes"]

teamcity {
    // Use TeamCity {example-api-version} API
    version = '{example-api-version}'

    server {
        // Locate the plugin descriptor in the root directory of the project
        descriptor = file('teamcity-plugin.xml')
    }

    environments {
        // use a local web server for downloading TeamCity distributions
        baseDownloadUrl = "http://repository/"

        // store the downloaded TeamCity distributions in /tmp
        downloadsDir = '/tmp'

        // base properties for TeamCity servers and data directories
        baseHomeDir = 'teamcity/servers'
        baseDataDir = 'teamcity/data'

        teamcity91 {
            version = '9.1.7'
            javaHome = file('/opt/jdk1.7.0_80')
            // Add to the default server options
            serverOptions '-Dproperty=value'
            serverOptions '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5500'
        }

        teamcity20172 {
            version = '2017.2.4'
            downloadUrl = 'http://repository/teamcity/TeamCity-2017.2.4.tar.gz'
            homeDir = file("$rootDir/teamcity/servers/TeamCity-2017.2.4")
            dataDir = file("$rootDir/teamcity/data/2017.2")
            javaHome = file('/opt/jdk1.8.0_202')
            // Replace the default server options
            serverOptions = '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5500'
        }

        'teamcity2018.2' {
            version = '2018.2.2'
            javaHome = file('/opt/jdk1.8.0_202')
        }

        // explicitly specifying the environment type
        'teamcity2022.04'(LocalTeamCityEnvironment) {
            version = '2022.04.3'
        }
    }
}

The following example shows environments being configured using the Kotlin DSL.

[source,groovy] [subs="attributes"] .build.gradle.kts

val downloadsDir by extra((project.findProperty("downloads.dir") ?: "${rootDir}/downloads") as String)
val java7Home by extra((project.findProperty("java7.home") ?: "/opt/jdk1.7.0_80") as String)
val java8Home by extra((project.findProperty("java8.home") ?: "/opt/jdk1.8.0_202") as String)

teamcity {
    version = "{example-api-version}"

    server {
        descriptor = file("teamcity-plugin.xml")
    }

    environments {
        baseDownloadUrl = "http://repository/"
        downloadsDir = extra["downloadsDir"] as String
        baseHomeDir = "teamcity/servers"
        baseDataDir = "${rootDir}/data"

        create("teamcity9") {
            version = "9.1.7"
            javaHome = java7Home

            // Add to the default server options
            serverOptions("-Dproperty=value")
            serverOptions("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5500")
        }

        register("teamcity2017.2") {
            version = "2017.2.4"
            javaHome = java8Home

            // Replace the default server options
            setServerOptions("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5500")
        }

        register("teamcity2020.2") {
            version = "2020.2.4"
        }

        // explicitly specifying the environment type
        register("teamcity2022.04", LocalTeamCityEnvironment::class.java) {
            version = '2022.04.3'
        }
    }
}

==== A Docker TeamCity Environment

To create an environment that uses Docker to start and stop a TeamCity Server and Build Agent an environment can specify the type when creating or registering the environment.

The following example shows the different ways Docker environments can be configured using the Groovy DSL.

[source,groovy] [subs="attributes"] .build.gradle

teamcity {
    environments {
        'teamcity2021.1'(DockerTeamCityEnvironment) {
            version = '2021.1.4'
        }
        create('teamcity2021.2', DockerTeamCityEnvironment) {
            version = '2021.2.3'
        }
        register('teamcity2022.04', DockerTeamCityEnvironment) {
            version = '2022.04'
        }
    }
}

The following example shows the different ways Docker environments can be configured using the Kotlin DSL.

[source,groovy] [subs="attributes"] .build.gradle.kts

teamcity {
    environments {
        create('teamcity2021.2', DockerTeamCityEnvironment::class.java) {
            version = '2021.2.3'
        }
        register("teamcity2022.04", DockerTeamCityEnvironment::class.java) {
            version = '2021.2.3'
        }
    }
}

===== Configuration

To configure a Docker TeamCity environment the configuration block supports the following properties

===== Tasks

For each environment the following tasks are created based on the environment name:

To start and stop a TeamCity Server and Build Agent using Docker requires {uri-docker-desktop}[Docker] to be installed and running before executing the environment tasks.

===== Example

[source,groovy] [subs="attributes"] .build.gradle

teamcity {
    version = "{example-api-version}"

    environments {
        'teamcity2022.04'(DockerTeamCityEnvironment) {
            version = '2022.04.3'
            port = '7111'
            serverImage = 'my-teamcity-server'
            serverName = 'my-server'
            agentImage = 'my-teamcity-agent'
            agentName = 'my-agent'

            // Add to the default server and agent options
            serverOptions '-Dproperty=value'
            serverOptions '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5500'
            agentOptions '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5501'
        }
    }
}

== Samples

The link:samples[samples] directory contains a number of projects using the plugin.

The following projects use the plugin.