From 380f63bf1dfd02cd47523a22cf93614c0be48560 Mon Sep 17 00:00:00 2001 From: Marvin Schramm Date: Sat, 27 May 2023 12:41:04 +0200 Subject: [PATCH] handle ci environments (#5) * handle ci environments * make tests CI resilient * remove debug mode on gradle runner test to avoid leaking ci environments into the test env --- README.md | 15 ++ build.gradle.kts | 2 +- .../properties/GenerateGitPropertiesTask.kt | 8 +- .../data/GitBranchPropertiesProvider.kt | 29 ++- .../data/GitConfigPropertiesProvider.kt | 11 +- .../git/properties/TestGitRepository.kt | 4 +- .../data/GitBranchPropertiesProviderTest.kt | 72 +++++- .../data/GitConfigPropertiesProviderTest.kt | 4 +- .../GeneratesPropertiesWithBranchNameTest.kt | 214 ++++++++++++++++++ 9 files changed, 348 insertions(+), 11 deletions(-) create mode 100644 src/test/kotlin/io/hndrs/gradle/plugin/git/properties/functional/GeneratesPropertiesWithBranchNameTest.kt diff --git a/README.md b/README.md index 5294c29..9c2c63f 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,21 @@ tasks.withType(GenerateGitPropertiesTask::class.java) { > File (build.gradle.kts) +#### Behaviour on CI environments + +As you may know, CI environments handle the git repository differently. +This plugin handles the branch resolution for + +- Github Actions +- Gitlab CI +- Travis CI + +If you are using a different CI service you can provide the branch name via the `branch-name` option + +```bash +./gradlew generateGitProperties --branch-name=$CI_SPECIFIC_ENV +``` + #### Available Properties ```properties diff --git a/build.gradle.kts b/build.gradle.kts index ebeafcd..1f6e4d9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ plugins { } group = "io.hndrs.gradle" -version = "1.0.0" +version = "1.0.0-local" repositories { mavenCentral() diff --git a/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/GenerateGitPropertiesTask.kt b/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/GenerateGitPropertiesTask.kt index f0fe7e6..0840d60 100644 --- a/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/GenerateGitPropertiesTask.kt +++ b/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/GenerateGitPropertiesTask.kt @@ -14,12 +14,14 @@ import org.gradle.api.provider.Property import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskExecutionException import org.gradle.api.tasks.options.Option +import org.gradle.kotlin.dsl.property import java.io.File import javax.inject.Inject @@ -44,6 +46,10 @@ abstract class GenerateGitPropertiesTask @Inject constructor( val continueOnError: Property = objectFactory.property(Boolean::class.java) .convention(false) + @get:Input + @get:Optional + @Option(option = "branch-name", description = "Provide branch name via cli") + val branchName: Property = objectFactory.property(String::class) /** * Output file of this given task defaults to `/build/main/resources/git.properties` @@ -56,7 +62,7 @@ abstract class GenerateGitPropertiesTask @Inject constructor( runCatching { val git = Git.open(dotGitDirectory.asFile.get()) val properties = GitPropertiesProviderChain.of( - GitBranchPropertiesProvider(git), + GitBranchPropertiesProvider(git, branchName.orNull), GitConfigPropertiesProvider(git), GitLogPropertiesProvider(git), BuildHostPropertiesProvider(), diff --git a/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProvider.kt b/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProvider.kt index 77951e9..de82576 100644 --- a/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProvider.kt +++ b/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProvider.kt @@ -3,14 +3,39 @@ package io.hndrs.gradle.plugin.git.properties.data import org.eclipse.jgit.api.Git class GitBranchPropertiesProvider( - private val git: Git + private val git: Git, + private val branchName: String? ) : GitPropertyProvider { override fun get(): Map { - return mapOf(GIT_BRANCH to git.repository.branch) + return branchName?.let { + mapOf(GIT_BRANCH to branchName) + } ?: resolveBranchName()?.let { + mapOf(GIT_BRANCH to it) + } ?: mapOf(GIT_BRANCH to git.repository.branch) + } + + private fun resolveBranchName(): String? { + return CI_ENV_BRANCH_MAPPING.entries.firstNotNullOfOrNull { + if (EnvProvider.getEnv(it.key) != null) { + it.value.firstNotNullOfOrNull { EnvProvider.getEnv(it) } + } else null + } + } + + /** + * Wrapper object for mocking + */ + object EnvProvider { + fun getEnv(name: String): String? = System.getenv(name) } companion object { private const val GIT_BRANCH = "git.branch" + private val CI_ENV_BRANCH_MAPPING = mapOf( + "GITLAB_CI" to arrayOf("CI_COMMIT_REF_NAME"), + "CI" to arrayOf("GITHUB_HEAD_REF", "GITHUB_REF_NAME"), + "TRAVIS" to arrayOf("TRAVIS_BRANCH") + ) } } \ No newline at end of file diff --git a/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProvider.kt b/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProvider.kt index 938bdc7..e882e49 100644 --- a/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProvider.kt +++ b/src/main/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProvider.kt @@ -1,6 +1,7 @@ package io.hndrs.gradle.plugin.git.properties.data import org.eclipse.jgit.api.Git +import java.net.URI class GitConfigPropertiesProvider( private val git: Git @@ -13,11 +14,19 @@ class GitConfigPropertiesProvider( mapOf( GIT_BUILD_USER_EMAIL to it.getString("user", null, "email"), GIT_BUILD_USER_NAME to it.getString("user", null, "name"), - GIT_REMOTE_ORIGIN_URL to it.getString("remote", "origin", "url") + GIT_REMOTE_ORIGIN_URL to it.getString("remote", "origin", "url")?.sanitise() ) } } + private fun String.sanitise(): String? { + return runCatching { + URI(this).userInfo?.let { + this.replace(it, "").replace("@", "") + } + }.getOrDefault(this) + } + companion object { private const val GIT_REMOTE_ORIGIN_URL = "git.remote.origin.url" private const val GIT_BUILD_USER_EMAIL = "git.build.user.email" diff --git a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/TestGitRepository.kt b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/TestGitRepository.kt index d508ea8..c1788ef 100644 --- a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/TestGitRepository.kt +++ b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/TestGitRepository.kt @@ -7,7 +7,7 @@ import java.io.File class TestGitRepository(rootDir: File) { private val git = Git.init().setDirectory(rootDir).call() - + fun addCommit() = git.commit() .setAllowEmpty(true) @@ -25,4 +25,6 @@ class TestGitRepository(rootDir: File) { it.setString("user", null, "name", value) it.save() } + + fun branch() = git.repository.branch } diff --git a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProviderTest.kt b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProviderTest.kt index 272c329..beb816b 100644 --- a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProviderTest.kt +++ b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitBranchPropertiesProviderTest.kt @@ -1,22 +1,88 @@ package io.hndrs.gradle.plugin.git.properties.data +import io.hndrs.gradle.plugin.git.properties.data.GitBranchPropertiesProvider.EnvProvider import io.kotest.core.spec.style.StringSpec import io.kotest.matchers.shouldBe import io.mockk.every import io.mockk.mockk +import io.mockk.mockkObject import org.eclipse.jgit.api.Git class GitBranchPropertiesProviderTest : StringSpec({ val git = mockk() { - every { repository.branch } returns "main" + every { repository.branch } returns "git-main" } - val underTest = GitBranchPropertiesProvider(git) + "resolves git branch via branch-name-option" { + val underTest = GitBranchPropertiesProvider(git, "main") + underTest.get() shouldBe mapOf( + "git.branch" to "main" + ) + } "resolves git branch via git" { + val underTest = GitBranchPropertiesProvider(git, null) + mockkObject(EnvProvider) + // to run tests on CI we need to make sure all return null + every { EnvProvider.getEnv(any()) } returns null underTest.get() shouldBe mapOf( - "git.branch" to "main" + "git.branch" to "git-main" + ) + } + + "resolves git branch for github actions via env GITHUB_HEAD_REF" { + val underTest = GitBranchPropertiesProvider(git, null) + mockkObject(EnvProvider) + + every { EnvProvider.getEnv("TRAVIS") } returns null + every { EnvProvider.getEnv("GITLAB_CI") } returns null + every { EnvProvider.getEnv("CI") } returns "true" + every { EnvProvider.getEnv("GITHUB_HEAD_REF") } returns "head-ref-main" + + underTest.get() shouldBe mapOf( + "git.branch" to "head-ref-main" + ) + } + + "resolves git branch for github actions via env GITHUB_REF_NAME" { + val underTest = GitBranchPropertiesProvider(git, null) + mockkObject(EnvProvider) + + every { EnvProvider.getEnv("TRAVIS") } returns null + every { EnvProvider.getEnv("GITLAB_CI") } returns null + every { EnvProvider.getEnv("CI") } returns "true" + every { EnvProvider.getEnv("GITHUB_HEAD_REF") } returns null + every { EnvProvider.getEnv("GITHUB_REF_NAME") } returns "ref-main" + + underTest.get() shouldBe mapOf( + "git.branch" to "ref-main" + ) + } + + "resolves git branch for gitlab-ci via env CI_COMMIT_REF_NAME" { + val underTest = GitBranchPropertiesProvider(git, null) + mockkObject(EnvProvider) + every { EnvProvider.getEnv("TRAVIS") } returns null + every { EnvProvider.getEnv("CI") } returns null + every { EnvProvider.getEnv("GITLAB_CI") } returns "true" + every { EnvProvider.getEnv("CI_COMMIT_REF_NAME") } returns "gitlab-main" + + underTest.get() shouldBe mapOf( + "git.branch" to "gitlab-main" + ) + } + + "resolves git branch for travis-ci via env TRAVIS_BRANCH" { + val underTest = GitBranchPropertiesProvider(git, null) + mockkObject(EnvProvider) + every { EnvProvider.getEnv("GITLAB_CI") } returns null + every { EnvProvider.getEnv("CI") } returns null + every { EnvProvider.getEnv("TRAVIS") } returns "true" + every { EnvProvider.getEnv("TRAVIS_BRANCH") } returns "travis-main" + + underTest.get() shouldBe mapOf( + "git.branch" to "travis-main" ) } diff --git a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProviderTest.kt b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProviderTest.kt index 1b65578..9f2516d 100644 --- a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProviderTest.kt +++ b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/data/GitConfigPropertiesProviderTest.kt @@ -12,7 +12,7 @@ class GitConfigPropertiesProviderTest : StringSpec({ every { repository.config } returns mockk(relaxed = true) { every { getString("user", null, "email") } returns "john.smith@gradlemail.com" every { getString("user", null, "name") } returns "John Smith" - every { getString("remote", "origin", "url") } returns "git@github.com:hndrs/gradle-git-properties-plugin.git" + every { getString("remote", "origin", "url") } returns "https://name:password@github.com/hndrs/gradle-git-properties-plugin.git" } } @@ -22,7 +22,7 @@ class GitConfigPropertiesProviderTest : StringSpec({ underTest.get() shouldBe mapOf( "git.build.user.email" to "john.smith@gradlemail.com", "git.build.user.name" to "John Smith", - "git.remote.origin.url" to "git@github.com:hndrs/gradle-git-properties-plugin.git" + "git.remote.origin.url" to "https://github.com/hndrs/gradle-git-properties-plugin.git" ) } diff --git a/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/functional/GeneratesPropertiesWithBranchNameTest.kt b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/functional/GeneratesPropertiesWithBranchNameTest.kt new file mode 100644 index 0000000..659f441 --- /dev/null +++ b/src/test/kotlin/io/hndrs/gradle/plugin/git/properties/functional/GeneratesPropertiesWithBranchNameTest.kt @@ -0,0 +1,214 @@ +package io.hndrs.gradle.plugin.git.properties.functional + +import io.hndrs.gradle.plugin.git.properties.TestGitRepository +import io.kotest.assertions.assertSoftly +import io.kotest.core.spec.style.StringSpec +import io.kotest.engine.spec.tempdir +import io.kotest.matchers.shouldBe +import io.kotest.matchers.string.shouldContain +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import java.io.File + +class GeneratesPropertiesWithBranchNameTest : StringSpec({ + + var testKitDir: File = tempdir() + + var testProjectDir: File = tempdir() + + var buildFile = File(testProjectDir, "build.gradle.kts") + + var gitRepository = TestGitRepository(testProjectDir) + + beforeAny { + testKitDir = tempdir() + testProjectDir = tempdir() + buildFile = File(testProjectDir, "build.gradle.kts") + gitRepository = TestGitRepository(testProjectDir) + } + + "generate properties with branch-name option" { + buildFile.writeText( + """ + plugins { + id("io.hndrs.git-properties") + } + """.trimIndent() + ) + + // add first commit + gitRepository.addCommit() + val runner = GradleRunner.create() + + .withTestKitDir(testKitDir) + .withProjectDir(testProjectDir) + .withDebug(true) // enabled for test coverage + .withPluginClasspath() + + runner.withArguments("generateGitProperties", "--branch-name=test-branch") + .build().task(":generateGitProperties")?.outcome shouldBe TaskOutcome.SUCCESS + + assertSoftly(File(testProjectDir, "build/resources/main/git.properties")) { + exists() shouldBe true + readText() shouldContain "git.branch=test-branch" + } + } + + // disabled because it cant run on ci + "generate properties with git".config(enabled = false) { + buildFile.writeText( + """ + plugins { + id("io.hndrs.git-properties") + } + """.trimIndent() + ) + + // add first commit + gitRepository.addCommit() + + val runner = GradleRunner.create() + .withTestKitDir(testKitDir) + .withProjectDir(testProjectDir) + .withEnvironment( + mapOf( + "TRAVIS" to null, + "CI" to null, + "GITLAB_CI" to null + ) + ) + .withPluginClasspath() + + runner.withArguments("generateGitProperties") + .build().task(":generateGitProperties")?.outcome shouldBe TaskOutcome.SUCCESS + + assertSoftly(File(testProjectDir, "build/resources/main/git.properties")) { + exists() shouldBe true + readText() shouldContain "git.branch=${gitRepository.branch()}" + } + } + + "generate properties with gitlab-ci envs" { + buildFile.writeText( + """ + plugins { + id("io.hndrs.git-properties") + } + """.trimIndent() + ) + + // add first commit + gitRepository.addCommit() + val runner = GradleRunner.create() + .withTestKitDir(testKitDir) + .withProjectDir(testProjectDir) + .withEnvironment( + mapOf( + "GITLAB_CI" to "true", + "CI_COMMIT_REF_NAME" to "gitlab-ci-branch" + ) + ) + .withPluginClasspath() + + runner.withArguments("generateGitProperties") + .build().task(":generateGitProperties")?.outcome shouldBe TaskOutcome.SUCCESS + + assertSoftly(File(testProjectDir, "build/resources/main/git.properties")) { + exists() shouldBe true + readText() shouldContain "git.branch=gitlab-ci-branch" + } + } + + "generate properties with travis-ci envs" { + buildFile.writeText( + """ + plugins { + id("io.hndrs.git-properties") + } + """.trimIndent() + ) + + // add first commit + gitRepository.addCommit() + val runner = GradleRunner.create() + .withTestKitDir(testKitDir) + .withProjectDir(testProjectDir) + .withEnvironment( + mapOf( + "TRAVIS" to "true", + "TRAVIS_BRANCH" to "travis-branch" + ) + ) + .withPluginClasspath() + + runner.withArguments("generateGitProperties") + .build().task(":generateGitProperties")?.outcome shouldBe TaskOutcome.SUCCESS + + assertSoftly(File(testProjectDir, "build/resources/main/git.properties")) { + exists() shouldBe true + readText() shouldContain "git.branch=travis-branch" + } + } + + "generate properties with github-actions (GITHUB_HEAD_REF) env" { + buildFile.writeText( + """ + plugins { + id("io.hndrs.git-properties") + } + """.trimIndent() + ) + + // add first commit + gitRepository.addCommit() + val runner = GradleRunner.create() + .withTestKitDir(testKitDir) + .withProjectDir(testProjectDir) + .withEnvironment( + mapOf( + "CI" to "true", + "GITHUB_HEAD_REF" to "github-head-ref-branch" + ) + ) + .withPluginClasspath() + + runner.withArguments("generateGitProperties") + .build().task(":generateGitProperties")?.outcome shouldBe TaskOutcome.SUCCESS + + assertSoftly(File(testProjectDir, "build/resources/main/git.properties")) { + exists() shouldBe true + readText() shouldContain "git.branch=github-head-ref-branch" + } + } + + "generate properties with github-actions (GITHUB_REF_NAME) env" { + buildFile.writeText( + """ + plugins { + id("io.hndrs.git-properties") + } + """.trimIndent() + ) + + // add first commit + gitRepository.addCommit() + val runner = GradleRunner.create() + .withTestKitDir(testKitDir) + .withProjectDir(testProjectDir) + .withEnvironment( + mapOf( + "CI" to "true", + "GITHUB_REF_NAME" to "github-ref-name-branch" + ) + ) + .withPluginClasspath() + + runner.withArguments("generateGitProperties") + .build().task(":generateGitProperties")?.outcome shouldBe TaskOutcome.SUCCESS + + assertSoftly(File(testProjectDir, "build/resources/main/git.properties")) { + exists() shouldBe true + readText() shouldContain "git.branch=github-ref-name-branch" + } + } +}) \ No newline at end of file