Skip to content

Commit

Permalink
Add ability to test feature branches in TeamCity (#8388) (#15246)
Browse files Browse the repository at this point in the history
* Add comments, link to TeamCity Kotlin DSL docs

* Change default value of environment from "public" to "default"

This is because Google provider has no public TeamCity resources

* Enable different default cron values for `ga`/`beta` downstreams

* Enable non-default cron values to be used, based on value of `environment` parameter in TeamCity

* Add ability to make VCS root and nightly trigger use non-main branch

* Update tests to reflect how entrypoint func requires `branch` parameter now

* Update `MAJOR_RELEASE_TESTING` value, and update code to handle unsupported chars

* Update `uniqueID` method to uppercase the environment value

Signed-off-by: Modular Magician <magic-modules@google.com>
  • Loading branch information
modular-magician authored Jul 21, 2023
1 parent 801808b commit d93944f
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 36 deletions.
2 changes: 2 additions & 0 deletions .changelog/8388.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
```release-note:none
```
15 changes: 12 additions & 3 deletions .teamcity/components/build_config_package.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import jetbrains.buildServer.configs.kotlin.*
import jetbrains.buildServer.configs.kotlin.AbsoluteId

class packageDetails(name: String, displayName: String, environment: String) {
class packageDetails(name: String, displayName: String, environment: String, branchRef: String) {
val packageName = name
val displayName = displayName
val environment = environment
val branchRef = branchRef

// buildConfiguration returns a BuildType for a service package
// For BuildType docs, see https://teamcity.jetbrains.com/app/dsl-documentation/root/build-type/index.html
fun buildConfiguration(providerName : String, path : String, manualVcsRoot: AbsoluteId, nightlyTestsEnabled: Boolean, startHour: Int, parallelism: Int, daysOfWeek: String, daysOfMonth: String) : BuildType {
return BuildType {
// TC needs a consistent ID for dynamically generated packages
Expand Down Expand Up @@ -50,12 +53,18 @@ class packageDetails(name: String, displayName: String, environment: String) {
}

triggers {
RunNightly(nightlyTestsEnabled, startHour, daysOfWeek, daysOfMonth)
RunNightly(nightlyTestsEnabled, startHour, daysOfWeek, daysOfMonth, branchRef)
}
}
}

fun uniqueID(provider : String) : String {
return "%s_SERVICE_%s_%s".format(provider.replace("-", "").toUpperCase(), environment.toUpperCase(), packageName.toUpperCase())
// Replacing chars can be necessary, due to limitations on IDs
// "ID should start with a latin letter and contain only latin letters, digits and underscores (at most 225 characters)."
var pv = provider.replace("-", "").toUpperCase()
var env = environment.toUpperCase().replace("-", "").replace(".", "").toUpperCase()
var pkg = packageName.toUpperCase()

return "%s_SERVICE_%s_%s".format(pv, env, pkg)
}
}
3 changes: 3 additions & 0 deletions .teamcity/components/build_google.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ class ClientConfiguration(var custId: String,
val identityUser : String ) {
}

// ParametrizedWithType.ConfigureGoogleSpecificTestParameters allows build configs to be created
// with the environment variables needed to configure the provider and/or configure test code.
// Extension of ParametrizedWithType. For docs, see https://teamcity.jetbrains.com/app/dsl-documentation/root/parametrized-with-type/index.html
fun ParametrizedWithType.ConfigureGoogleSpecificTestParameters(config: ClientConfiguration) {
hiddenPasswordVariable("env.GOOGLE_CUST_ID", config.custId, "The ID of the Google Identity Customer")
hiddenPasswordVariable("env.GOOGLE_ORG", config.org, "The Google Organization Id")
Expand Down
15 changes: 13 additions & 2 deletions .teamcity/components/generated/build_components.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import jetbrains.buildServer.configs.kotlin.triggers.schedule
//
// Until that changes, we'll continue to use `teamcity-go-test` to run
// each test individually

// NOTE: this file includes Extensions of Kotlin DSL classes
// See
// - BuildFeatures https://teamcity.jetbrains.com/app/dsl-documentation/root/build-features/index.html
// - BuildSteps https://teamcity.jetbrains.com/app/dsl-documentation/root/build-steps/index.html
// - ParametrizedWithType https://teamcity.jetbrains.com/app/dsl-documentation/root/parametrized-with-type/index.html
// - Triggers https://teamcity.jetbrains.com/app/dsl-documentation/root/triggers/index.html


const val useTeamCityGoTest = false

fun BuildFeatures.Golang() {
Expand Down Expand Up @@ -120,10 +129,12 @@ fun ParametrizedWithType.hiddenPasswordVariable(name: String, value: String, des
password(name, value, "", description, ParameterDisplay.HIDDEN)
}

fun Triggers.RunNightly(nightlyTestsEnabled: Boolean, startHour: Int, daysOfWeek: String, daysOfMonth: String) {
fun Triggers.RunNightly(nightlyTestsEnabled: Boolean, startHour: Int, daysOfWeek: String, daysOfMonth: String, branchRef: String) {
val filter = "+:" + branchRef // e.g. "+:refs/heads/main"

schedule{
enabled = nightlyTestsEnabled
branchFilter = "+:refs/heads/main"
branchFilter = filter

schedulingPolicy = cron {
hours = startHour.toString()
Expand Down
35 changes: 27 additions & 8 deletions .teamcity/components/generated/project.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,33 @@ import jetbrains.buildServer.configs.kotlin.AbsoluteId

const val providerName = "google"

fun Google(environment: String, manualVcsRoot: AbsoluteId, configuration: ClientConfiguration) : Project {
// Google returns an instance of Project,
// which has multiple build configurations defined within it.
// See https://teamcity.jetbrains.com/app/dsl-documentation/root/project/index.html
fun Google(environment: String, manualVcsRoot: AbsoluteId, branchRef: String, configuration: ClientConfiguration) : Project {
return Project{

var buildConfigs = buildConfigurationsForPackages(packages, providerName, "google", environment, manualVcsRoot, configuration)
var buildConfigs = buildConfigurationsForPackages(packages, providerName, "google", environment, manualVcsRoot, branchRef, configuration)
buildConfigs.forEach { buildConfiguration ->
buildType(buildConfiguration)
}
}
}

fun buildConfigurationsForPackages(packages: Map<String, String>, providerName : String, path : String, environment: String, manualVcsRoot: AbsoluteId, config: ClientConfiguration): List<BuildType> {
fun buildConfigurationsForPackages(packages: Map<String, String>, providerName : String, path : String, environment: String, manualVcsRoot: AbsoluteId, branchRef: String, config: ClientConfiguration): List<BuildType> {
var list = ArrayList<BuildType>()

packages.forEach { (packageName, displayName) ->
if (packageName == "services") {
var serviceList = buildConfigurationsForPackages(services, providerName, path+"/"+packageName, environment, manualVcsRoot, config)
// `services` is a folder containing packages, not a package itself; call buildConfigurationsForPackages to iterate through directories found within `services`
var serviceList = buildConfigurationsForPackages(services, providerName, path+"/"+packageName, environment, manualVcsRoot, branchRef, config)
list.addAll(serviceList)
} else {
var defaultTestConfig = testConfiguration()
// other folders assumed to be packages
var testConfig = testConfiguration(environment)

var pkg = packageDetails(packageName, displayName, environment)
var buildConfig = pkg.buildConfiguration(providerName, path, manualVcsRoot, true, defaultTestConfig.startHour, defaultTestConfig.parallelism, defaultTestConfig.daysOfWeek, defaultTestConfig.daysOfMonth)
var pkg = packageDetails(packageName, displayName, environment, branchRef)
var buildConfig = pkg.buildConfiguration(providerName, path, manualVcsRoot, true, testConfig.startHour, testConfig.parallelism, testConfig.daysOfWeek, testConfig.daysOfMonth)

buildConfig.params.ConfigureGoogleSpecificTestParameters(config)

Expand All @@ -38,9 +43,23 @@ fun buildConfigurationsForPackages(packages: Map<String, String>, providerName :
return list
}

class testConfiguration(parallelism: Int = defaultParallelism, startHour: Int = defaultStartHour, daysOfWeek: String = defaultDaysOfWeek, daysOfMonth: String = defaultDaysOfMonth) {
class testConfiguration(environment: String, parallelism: Int = defaultParallelism, startHour: Int = defaultStartHour, daysOfWeek: String = defaultDaysOfWeek, daysOfMonth: String = defaultDaysOfMonth) {

// Default values are present if init doesn't change them
var parallelism = parallelism
var startHour = startHour
var daysOfWeek = daysOfWeek
var daysOfMonth = daysOfMonth

init {
// If the environment parameter is set to the value of MAJOR_RELEASE_TESTING,
// change the days of week to the day for v5.0.0 feature branch testing
if (environment == MAJOR_RELEASE_TESTING) {
this.parallelism = parallelism
this.startHour = startHour
this.daysOfWeek = "4" // Thursday for GA
this.daysOfMonth = daysOfMonth
}
}

}
20 changes: 20 additions & 0 deletions .teamcity/components/generated/settings.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// this file is auto-generated with mmv1, any changes made here will be overwritten

// specifies the default hour (UTC) at which tests should be triggered, if enabled
var defaultStartHour = 4

// specifies the default level of parallelism per-service-package
var defaultParallelism = 12

// specifies the default version of Terraform Core which should be used for testing
var defaultTerraformCoreVersion = "1.2.5"

// This represents a cron view of days of the week
const val defaultDaysOfWeek = "1-3,5-7" // All nights except Thursday for GA; feature branch testing happens on Thursdays

// Cron value for any day of month
const val defaultDaysOfMonth = "*"

// Values that `environment` parameter is checked against,
// when deciding to change how TeamCity objects are configured
const val MAJOR_RELEASE_TESTING = "major-release-5.0.0-testing"
16 changes: 0 additions & 16 deletions .teamcity/components/settings.kt

This file was deleted.

2 changes: 1 addition & 1 deletion .teamcity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<format>kotlin</format>
<dstDir>target/generated-configs</dstDir>
<contextParameters>
<environment>public</environment>
<environment>default</environment>
</contextParameters>
</configuration>
</plugin>
Expand Down
15 changes: 13 additions & 2 deletions .teamcity/settings.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import jetbrains.buildServer.configs.kotlin.*

version = "2023.05"

// The code below pulls context parameters from the TeamCity project.
// Context parameters aren't stored in VCS, and are managed manually.
// Due to this, the code needs to explicitly pull in values via the DSL and pass the values into other code.
// For DslContext docs, see https://teamcity.jetbrains.com/app/dsl-documentation/root/dsl-context/index.html

// Values of these parameters are used to set ENVs needed for acceptance tests within the build configurations.
var custId = DslContext.getParameter("custId", "")
var org = DslContext.getParameter("org", "")
var org2 = DslContext.getParameter("org2", "")
Expand All @@ -19,14 +25,19 @@ var region = DslContext.getParameter("region", "")
var serviceAccount = DslContext.getParameter("serviceAccount", "")
var zone = DslContext.getParameter("zone", "")
var credentials = DslContext.getParameter("credentials", "")
var environment = DslContext.getParameter("environment", "public")
var firestoreProject = DslContext.getParameter("firestoreProject", "")
var identityUser = DslContext.getParameter("identityUser", "")

// Get details of the VCS Root that's manually made when VCS is first
// connected to the Project in TeamCity
var manualVcsRoot = DslContext.settingsRootId

// Values of these parameters change configuration code behaviour.
var environment = DslContext.getParameter("environment", "default")
var branchRef = DslContext.getParameter("branch", "refs/heads/main")

var clientConfig = ClientConfiguration(custId, org, org2, billingAccount, billingAccount2, masterBillingAccount, credentials, project, orgDomain, projectNumber, region, serviceAccount, zone, firestoreProject, identityUser)

project(Google(environment, manualVcsRoot, clientConfig))
// This is the entry point of the code in .teamcity/
// See https://teamcity.jetbrains.com/app/dsl-documentation/root/project.html
project(Google(environment, manualVcsRoot, branchRef, clientConfig))
6 changes: 3 additions & 3 deletions .teamcity/tests/generated/configuration.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import useTeamCityGoTest
class ConfigurationTests {
@Test
fun buildShouldFailOnError() {
val project = Google("public", TestVcsRootId(), TestConfiguration())
val project = Google("public", TestVcsRootId(), "refs/heads/main", TestConfiguration())
project.buildTypes.forEach { bt ->
assertTrue("Build '${bt.id}' should fail on errors!", bt.failureConditions.errorMessage)
}
}

@Test
fun buildShouldHaveGoTestFeature() {
val project = Google("public", TestVcsRootId(), TestConfiguration())
val project = Google("public", TestVcsRootId(), "refs/heads/main",TestConfiguration())
project.buildTypes.forEach{ bt ->
var exists = false
bt.features.items.forEach { f ->
Expand All @@ -35,7 +35,7 @@ class ConfigurationTests {

@Test
fun buildShouldHaveTrigger() {
val project = Google("public", TestVcsRootId(), TestConfiguration())
val project = Google("public", TestVcsRootId(), "refs/heads/main", TestConfiguration())
var exists = false
project.buildTypes.forEach{ bt ->
bt.triggers.items.forEach { t ->
Expand Down
2 changes: 1 addition & 1 deletion .teamcity/tests/generated/vcs_roots.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.junit.Test
class VcsTests {
@Test
fun buildsHaveCleanCheckOut() {
val project = Google("public", TestVcsRootId(), TestConfiguration())
val project = Google("public", TestVcsRootId(), "refs/heads/main", TestConfiguration())
project.buildTypes.forEach { bt ->
assertTrue("Build '${bt.id}' doesn't use clean checkout", bt.vcs.cleanCheckout)
}
Expand Down

0 comments on commit d93944f

Please sign in to comment.