diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index f1773ad..aa8349b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,8 +18,24 @@ jobs: # needed for FirstCommitHashTaskTest fetch-depth: 0 - name: Cache - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v2.4.2 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Download and Install Octo CLI + run: | + wget https://github.com/OctopusDeploy/OctopusCLI/releases/download/v9.1.7/OctopusTools.9.1.7.linux-x64.tar.gz + mkdir -p $HOME/.local/bin + tar -xzf OctopusTools.9.1.7.linux-x64.tar.gz -C $HOME/.local/bin + echo "$HOME/.local/bin" >> $GITHUB_PATH - name: Build run: ./gradlew build - name: Validate run: ./gradlew check validatePlugins --continue + - name: Integration Test + run: | + octo --version + java -version + ./gradlew integrationTest --no-daemon diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 573bdd1..0a6640b 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -18,7 +18,7 @@ jobs: # needed for FirstCommitHashTaskTest fetch-depth: 0 - name: Cache - uses: gradle/gradle-build-action@v2 + uses: gradle/gradle-build-action@v2.4.2 - name: Build run: ./gradlew build - name: Validate diff --git a/README.md b/README.md index 002c242..9ee884d 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,16 @@ Requirements: # Usage The plugin can be configured with the `octopus` DSL block: -``` +```kotlin plugins { id("com.liftric.octopus-deploy-plugin") version "whatever" } -[...] +// ... octopus { serverUrl.set("http://localhost:8080/") apiKey.set("API-TESTTEST123TRESDTSDD") - generateChangelogSinceLastTag = true + generateChangelogSinceLastTag.set(true) val jar by tasks.existing(Jar::class) packageName.set(jar.get().archiveBaseName.get()) @@ -46,8 +46,8 @@ uploadBuildInformation | Uploads the created octopus build-information file. uploadPackage | Uploads the package to octopus. PromoteReleaseTask | Promotes octopus project from one environment to another -For normale use-cases, only `uploadBuildInformation` are `uploadPackage` are needed to call explicitly. Depending -task will be called implicitly by both as needed. +For normal use-cases, only `uploadBuildInformation` and `uploadPackage` are needed to call explicitly. Depending +tasks will be called implicitly by both as needed. **Noteworthy**: The build-information can be uploaded before the package itself. Useful when creating automatic releases and using the commits in the release notes in octopus. @@ -56,7 +56,7 @@ Useful when creating automatic releases and using the commits in the release not The PromoteReleaseTask has no default implementation and must be created explicitly: ```kotlin import com.liftric.octopusdeploy.task.PromoteReleaseTask -[...] +// ... tasks { val devToDemo by creating(PromoteReleaseTask::class) { projectName.set("example-project") @@ -79,19 +79,19 @@ After the plugin is applied, the octopus extension is registered with the follow Property | Description | default value ---|---|--- -apiKey | Octopus deploy server API key (lazy gradle property)| - -serverUrl | Octopus deploy server URL (lazy gradle property)| - +apiKey | Octopus deploy server API key | - +serverUrl | Octopus deploy server URL | - generateChangelogSinceLastTag | Enable to calculate the commits for the changelog when uploading build-information (needs git installed) | false commitLinkBaseUrl | Prefix / Baseurl for the build-information commit urls | http://git.example.com/repo/commits/ outputDir | Output folder for files generated by the plugin | build/octopus gitRoot | Directory to run the git helpers in. By default the projects root dir | project.rootDir pushPackage | Target file (package) which will be uploaded to octopus | - -version | Package version (lazy gradle property)| - -packageName | Package name (lazy gradle property)| - +version | Package version | - +packageName | Package name | - buildInformationOverwriteMode | octo build-information OverwriteMode | - pushOverwriteMode | octo push OverwriteMode | - buildInformationAddition | Customize the final octopus build-information before uploading | {} -gitlab() | Default `buildInformationAddition` implementation adding context from the CI environment for Gitlab CI. Also sets `commitLinkBaseUrl`. | not applied +gitlab | Default `buildInformationAddition` implementation adding context from the CI environment for Gitlab CI. Also sets `commitLinkBaseUrl`. | not applied httpLogLevel | configures the http logging while using the Octopus API | `HttpLoggingInterceptor.Level.NONE` issueTrackerName | When parsing issues the target issue tracker name is needed. Currently only `Jira` supported | **optional/none** parseCommitsForJiraIssues | Enable Jira Issue parsing. This needs the changelog generation enabled to parse the commits there. | **optional/none** @@ -102,13 +102,30 @@ useShortCommitHashes | Use short (7 char) commit hashes. | true If no tag is found, the first commit in the history tree is used instead. You can configure `serverUrl` and `apiKey` using a provider which enables configuring them on demand, not at configuration time: -``` +```kotlin apiKey.set(provider { - "API-TESTTEST123TRESDTSDD" - // read from file / vault / etc. + "API-TESTTEST123TRESDTSDD" + // read from file / vault / etc. }) ``` +To customize the build information, use the `buildInformationAddition` block: +```kotlin +buildInformationAddition.set({ + Id = "custom-id" + // Add other properties as needed +}) + +``` + +For GitLab CI integration, set the `gitlab` block: +```kotlin +gitlab { + Id = "custom-id" + // Add other properties as needed +} +``` + ### task specific configuration Both the **PromoteReleaseTask** and **UploadPackageTask** provide support for waiting for any deployment on octopus deploy and will block task finishing until all octopus deploy tasks are finished. @@ -125,10 +142,10 @@ delayBetweenChecksSeconds | how long to delay between polls initialWaitSeconds | initial delay before waitForReleaseDeployments logic starts | - Example: -``` +```kotlin import com.liftric.octopusdeploy.task.PromoteReleaseTask import com.liftric.octopusdeploy.task.UploadPackageTask -[...] +// ... tasks { val devToDemo by creating(PromoteReleaseTask::class) { waitForReleaseDeployments.set(true) diff --git a/build.gradle.kts b/build.gradle.kts index 48587b4..4938d47 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,9 +5,9 @@ import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion plugins { `kotlin-dsl` `maven-publish` - id("com.gradle.plugin-publish") version "0.18.0" - id("net.nemerosa.versioning") version "2.15.1" - id("com.avast.gradle.docker-compose") version "0.14.11" + id("com.gradle.plugin-publish") version "1.2.1" + id("net.nemerosa.versioning") version "3.1.0" + id("com.avast.gradle.docker-compose") version "0.17.7" } group = "com.liftric.octopusdeploy" @@ -41,19 +41,19 @@ dependencies { implementation(gradleApi()) implementation(kotlin("gradle-plugin")) implementation(kotlin("stdlib-jdk8")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.0") - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-jackson:2.9.0") - implementation("com.squareup.retrofit2:converter-scalars:2.9.0") - implementation("com.squareup.okhttp3:logging-interceptor:4.9.3") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2") + implementation("com.squareup.retrofit2:retrofit:2.11.0") + implementation("com.squareup.retrofit2:converter-jackson:2.11.0") + implementation("com.squareup.retrofit2:converter-scalars:2.11.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") testImplementation(gradleTestKit()) testImplementation("junit:junit:4.13.2") testImplementation("com.github.stefanbirkner:system-rules:1.19.0") integrationTestImplementation("junit:junit:4.13.2") - integrationTestImplementation("org.apache.httpcomponents:httpclient:4.5.13") + integrationTestImplementation("org.apache.httpcomponents:httpclient:4.5.14") } java { sourceCompatibility = JavaVersion.VERSION_1_8 @@ -131,25 +131,22 @@ publishing { } } gradlePlugin { + website.set("https://github.com/Liftric/octopus-deploy-plugin") + vcsUrl.set("https://github.com/Liftric/octopus-deploy-plugin") plugins { create("OctopusDeployPlugin") { id = "com.liftric.octopus-deploy-plugin" displayName = "octopus-deploy-plugin" implementationClass = "com.liftric.octopusdeploy.OctopusDeployPlugin" description = "Common tasks for Octopus Deploy interaction, like package or build-information uploading" + tags.set(listOf("octopus", "deploy", "releases", "build-information", "upload", "packages")) } } } -pluginBundle { - website = "https://github.com/Liftric/octopus-deploy-plugin" - vcsUrl = "https://github.com/Liftric/octopus-deploy-plugin" - description = "Common tasks for Octopus Deploy interaction, like package or build-information uploading" - tags = listOf("octopus", "deploy", "releases", "build-information", "upload", "packages") -} dockerCompose { useComposeFiles.set(listOf("docker-compose.yml")) waitForTcpPorts.set(true) - captureContainersOutput.set(true) + captureContainersOutput.set(false) stopContainers.set(true) removeContainers.set(true) buildBeforeUp.set(true) diff --git a/docker-compose.yml b/docker-compose.yml index 62d00e5..18bb9fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: ACCEPT_EULA: Y DB_CONNECTION_STRING: Server=db,1433;Database=OctopusDeploy;User=sa;Password=yourStrong(!)Password ADMIN_USERNAME: admin - ADMIN_PASSWORD: testtest123 + ADMIN_PASSWORD: testTEST123! ADMIN_API_KEY: API-TESTTEST123TRESDTSDD ports: - "8080:8080" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3cd8500..2fa91c5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationOverwriteTaskIntegrationTest.kt b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationOverwriteTaskIntegrationTest.kt new file mode 100644 index 0000000..d56310e --- /dev/null +++ b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationOverwriteTaskIntegrationTest.kt @@ -0,0 +1,89 @@ +package com.liftric.octopusdeploy.task + +import com.liftric.octopusdeploy.apiKey +import com.liftric.octopusdeploy.getBuildInformationResponse +import com.liftric.octopusdeploy.serverUrl +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import kotlin.random.Random + +class UploadBuildInformationOverwriteTaskIntegrationTest { + @get:Rule + val testProjectDir = TemporaryFolder() + + @Test + fun testExecute() { + val major = Random.Default.nextInt(0, 100) + val minor = Random.Default.nextInt(0, 100) + val micro = Random.Default.nextInt(0, 100) + println(testProjectDir.root.absolutePath) + setupBuild(major, minor, micro) + val result = GradleRunner.create() + .forwardOutput() + .withProjectDir(testProjectDir.root) + .withArguments("build", "uploadBuildInformation") + .withPluginClasspath() + .build() + println(result.output) + assertEquals(TaskOutcome.SUCCESS, result.task(":uploadBuildInformation")?.outcome) + val buildInfoItem = getBuildInformationResponse() + .items + ?.firstOrNull { + it.version == "$major.$minor.$micro" + } + assertNotNull(buildInfoItem) + assertEquals("Git", buildInfoItem?.vcsType) + + val secondResult = GradleRunner.create() + .forwardOutput() + .withProjectDir(testProjectDir.root) + .withArguments("build", "uploadBuildInformation") + .withPluginClasspath() + .build() + println(secondResult.output) + assertEquals(TaskOutcome.SUCCESS, secondResult.task(":uploadBuildInformation")?.outcome) + } + + fun setupBuild(major: Int, minor: Int, micro: Int) { + testProjectDir.newFile("build.gradle.kts").apply { + writeText( + """ +import com.liftric.octopusdeploy.api.OverwriteMode +plugins { + java + id("com.liftric.octopus-deploy-plugin") +} +group = "com.liftric.test" +version = "$major.$minor.$micro" + +tasks { + withType { + archiveFileName.set( + "${'$'}{archiveBaseName.get() + .removeSuffix("-")}.${'$'}{archiveVersion.get()}.${'$'}{archiveExtension.get()}" + ) + } +} +octopus { + serverUrl.set("$serverUrl") + apiKey.set("$apiKey") + + generateChangelogSinceLastTag.set(true) + buildInformationOverwriteMode.set(OverwriteMode.OverwriteExisting) + + val jar by tasks.existing(Jar::class) + packageName.set(jar.get().archiveBaseName.get().removeSuffix("-")) + version.set(jar.get().archiveVersion.get()) + pushPackage.set(jar.get().archiveFile) +} +""" + ) + } + testProjectDir.root.setupGitRepoCopy() + } +} diff --git a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTaskIntegrationTest.kt b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTaskIntegrationTest.kt index a732ed6..1a3ffea 100644 --- a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTaskIntegrationTest.kt +++ b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTaskIntegrationTest.kt @@ -73,7 +73,7 @@ octopus { serverUrl.set("$serverUrl") apiKey.set("$apiKey") - generateChangelogSinceLastTag = true + generateChangelogSinceLastTag.set(true) val jar by tasks.existing(Jar::class) packageName.set(jar.get().archiveBaseName.get().removeSuffix("-")) diff --git a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskIntegrationTest.kt b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskIntegrationTest.kt index 78d98a5..c2e29ba 100644 --- a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskIntegrationTest.kt +++ b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskIntegrationTest.kt @@ -75,7 +75,7 @@ octopus { serverUrl.set("$serverUrl") apiKey.set("$apiKey") - generateChangelogSinceLastTag = true + generateChangelogSinceLastTag.set(true) val jar by tasks.existing(Jar::class) packageName.set(jar.get().archiveBaseName.get().removeSuffix("-")) diff --git a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithInitialWaitIntegrationTest.kt b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithInitialWaitIntegrationTest.kt index f3d501e..9848680 100644 --- a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithInitialWaitIntegrationTest.kt +++ b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithInitialWaitIntegrationTest.kt @@ -85,7 +85,7 @@ octopus { serverUrl.set("$serverUrl") apiKey.set("$apiKey") - generateChangelogSinceLastTag = true + generateChangelogSinceLastTag.set(true) val jar by tasks.existing(Jar::class) packageName.set(jar.get().archiveBaseName.get().removeSuffix("-")) diff --git a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithWaitIntegrationTest.kt b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithWaitIntegrationTest.kt index fbbe1a4..45afd45 100644 --- a/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithWaitIntegrationTest.kt +++ b/src/integrationTest/kotlin/com/liftric/octopusdeploy/task/UploadPackageTaskWithWaitIntegrationTest.kt @@ -23,7 +23,6 @@ class UploadPackageTaskWithWaitIntegrationTest { val micro = Random.Default.nextInt(0, 100) println(testProjectDir.root.absolutePath) setupBuild(major, minor, micro) - val result = GradleRunner.create() .forwardOutput() .withProjectDir(testProjectDir.root) @@ -82,7 +81,7 @@ octopus { serverUrl.set("$serverUrl") apiKey.set("$apiKey") - generateChangelogSinceLastTag = true + generateChangelogSinceLastTag.set(true) val jar by tasks.existing(Jar::class) packageName.set(jar.get().archiveBaseName.get().removeSuffix("-")) diff --git a/src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployPlugin.kt b/src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployPlugin.kt index 7695e4b..c65cac3 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployPlugin.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployPlugin.kt @@ -1,5 +1,6 @@ package com.liftric.octopusdeploy +import com.liftric.octopusdeploy.extensions.OctopusDeployExtension import com.liftric.octopusdeploy.task.* import org.gradle.api.Plugin import org.gradle.api.Project @@ -7,117 +8,114 @@ import org.gradle.api.Project internal const val extensionName = "octopus" class OctopusDeployPlugin : Plugin { - @Suppress("UNUSED_VARIABLE") override fun apply(project: Project) { val extension = project.extensions.create(extensionName, OctopusDeployExtension::class.java, project) - extension.outputDir.apply { - mkdirs() + extension.outputDir.convention( + project.layout.buildDirectory.dir(extensionName) + ) + extension.gitRoot.convention( + project.rootProject.layout.projectDirectory + ) + extension.commitLinkBaseUrl.convention("https://git.example.com/repo/commits/") + extension.generateChangelogSinceLastTag.convention(false) + extension.buildInformationAddition.convention({ /* no-op */ }) + extension.useShortCommitHashes.convention(true) + + val getFirstCommitHashTask = project.tasks.create("firstCommitHash", FirstCommitHashTask::class.java).apply { + group = "octopus" + description = "Calls git log to get the first commit hash of the current history tree" + gitRoot.set(extension.gitRoot) + outputFile.set(extension.outputDir.file("firstCommitHash")) } - val getFirstCommitHashTask = - project.tasks.create("firstCommitHash", FirstCommitHashTask::class.java).apply { - project.afterEvaluate { - outputDir = extension.outputDir - workingDir = extension.gitRoot - } - } - val getPreviousTagTask = - project.tasks.create("previousTag", PreviousTagTask::class.java).apply { - project.afterEvaluate { - outputDir = extension.outputDir - workingDir = extension.gitRoot - } - } + + val getPreviousTagTask = project.tasks.create("previousTag", PreviousTagTask::class.java).apply { + group = "octopus" + description = "Calls git describe to receive the previous tag name. Will fail if no tag is found." + gitRoot.set(extension.gitRoot) + outputFile.set(extension.outputDir.file("previousTagName")) + mustRunAfter(getFirstCommitHashTask) + } + val commitsSinceLastTagTask = project.tasks.create("commitsSinceLastTag", CommitsSinceLastTagTask::class.java).apply { + group = "octopus" + description = + "Calls git log to receive all commits since the previous tag or the first commit of the current history." + gitRoot.set(extension.gitRoot) + useShortCommitHashes.set(extension.useShortCommitHashes) + commitLinkBaseUrl.set(extension.commitLinkBaseUrl) + firstCommitFile.set(getFirstCommitHashTask.outputFile) + previousTagFile.set(getPreviousTagTask.outputFile) dependsOn(getFirstCommitHashTask, getPreviousTagTask) - project.afterEvaluate { - useShortCommitHashes.set(extension.useShortCommitHashes) - workingDir = extension.gitRoot - commitLinkBaseUrl = extension.commitLinkBaseUrl - } - doFirst { - firstCommitFile = getFirstCommitHashTask.outputFile - previousTagFile = getPreviousTagTask.outputFile - } } + val createBuildInformationTask = project.tasks.create("createBuildInformation", CreateBuildInformationTask::class.java).apply { - val task = this - project.afterEvaluate { - if (extension.generateChangelogSinceLastTag) { - dependsOn(commitsSinceLastTagTask) - } - commits = emptyList() - outputDir = extension.outputDir - packageName.set(extension.packageName) - issueTrackerName.set(extension.issueTrackerName) - parseCommitsForJiraIssues.set(extension.parseCommitsForJiraIssues) - jiraBaseBrowseUrl.set(extension.jiraBaseBrowseUrl) - task.version.set(extension.version) - } - doFirst { - commits = commitsSinceLastTagTask.commits - buildInformationAddition = extension.buildInformationAddition - } - } - val createBuildInformationMarkdownTask = - project.tasks.create("createBuildInformationMarkdown", CreateBuildInformationMarkdownTask::class.java) - .apply { - val task = this - project.afterEvaluate { - if (extension.generateChangelogSinceLastTag) { - dependsOn(commitsSinceLastTagTask) - } - commits = emptyList() - packageName.set(extension.packageName) - issueTrackerName.set(extension.issueTrackerName) - parseCommitsForJiraIssues.set(extension.parseCommitsForJiraIssues) - jiraBaseBrowseUrl.set(extension.jiraBaseBrowseUrl) - task.version.set(extension.version) - } - doFirst { - commits = commitsSinceLastTagTask.commits - buildInformationAddition = extension.buildInformationAddition - } - } - val uploadBuildInformationTask = - project.tasks.create("uploadBuildInformation", UploadBuildInformationTask::class.java).apply { - dependsOn(createBuildInformationTask) - val task = this - project.afterEvaluate { - apiKey.set(extension.apiKey) - octopusUrl.set(extension.serverUrl) - packageName.set(extension.packageName) - task.version.set(extension.version) - overwriteMode = extension.buildInformationOverwriteMode?.name - } - doFirst { - buildInformation = createBuildInformationTask.outputFile - } - } - val uploadPackageTask = - project.tasks.create("uploadPackage", UploadPackageTask::class.java).apply { - val task = this - project.afterEvaluate { - apiKey.set(extension.apiKey) - octopusUrl.set(extension.serverUrl) - packageFile.set(extension.pushPackage) - overwriteMode = extension.pushOverwriteMode?.name - packageName.set(extension.packageName) - task.version.set(extension.version) - httpLogLevel.set(extension.httpLogLevel) - } + group = "octopus" + description = "Creates the octopus build-information file." + packageName.set(extension.packageName) + issueTrackerName.set(extension.issueTrackerName) + parseCommitsForJiraIssues.set(extension.parseCommitsForJiraIssues) + jiraBaseBrowseUrl.set(extension.jiraBaseBrowseUrl) + version.set(extension.version) + buildInformationAddition.set(extension.buildInformationAddition) + outputFile.set(extension.outputDir.file("build-information.json")) + commits.set(commitsSinceLastTagTask.commits) + dependsOn(commitsSinceLastTagTask) } + + + project.tasks.create("createBuildInformationMarkdown", CreateBuildInformationMarkdownTask::class.java).apply { + group = "octopus" + description = "Creates a markdown file from the build-information for octo cli release creation." + packageName.set(extension.packageName) + commits.set(commitsSinceLastTagTask.commits) + issueTrackerName.set(extension.issueTrackerName) + parseCommitsForJiraIssues.set(extension.parseCommitsForJiraIssues) + jiraBaseBrowseUrl.set(extension.jiraBaseBrowseUrl) + version.set(extension.version) + buildInformationAddition.set(extension.buildInformationAddition) + outputFile.set(extension.outputDir.file("build-information.md")) + dependsOn(commitsSinceLastTagTask) + } + + project.tasks.create("uploadBuildInformation", UploadBuildInformationTask::class.java).apply { + group = "octopus" + description = "Uploads the created octopus build-information file." + apiKey.set(extension.apiKey) + octopusUrl.set(extension.serverUrl) + version.set(extension.version) + packageName.set(extension.packageName) + overwriteMode.set(extension.buildInformationOverwriteMode) + buildInformationFile.set(createBuildInformationTask.outputFile) + dependsOn(createBuildInformationTask) + } + + project.tasks.create("uploadPackage", UploadPackageTask::class.java).apply { + group = "octopus" + description = "Uploads the package to octopus." + apiKey.set(extension.apiKey) + octopusUrl.set(extension.serverUrl) + version.set(extension.version) + packageName.set(extension.packageName) + pushPackage.set(extension.pushPackage) + overwriteMode.set(extension.pushOverwriteMode) + httpLogLevel.set(extension.httpLogLevel) + } + project.tasks.withType(PromoteReleaseTask::class.java) { - project.afterEvaluate { - apiKey.set(extension.apiKey) - octopusUrl.set(extension.serverUrl) - httpLogLevel.set(extension.httpLogLevel) - } + group = "octopus" + description = "Promotes a release." + apiKey.set(extension.apiKey) + octopusUrl.set(extension.serverUrl) + httpLogLevel.set(extension.httpLogLevel) } + project.tasks.withType(CreateReleaseTask::class.java) { - apiKey.convention(extension.apiKey) - octopusUrl.convention(extension.serverUrl) + group = "octopus" + description = "Creates a new release." + apiKey.set(extension.apiKey) + octopusUrl.set(extension.serverUrl) } } } diff --git a/src/main/kotlin/com/liftric/octopusdeploy/api/BuildInformationCli.kt b/src/main/kotlin/com/liftric/octopusdeploy/extensions/BuildInformationAddition.kt similarity index 66% rename from src/main/kotlin/com/liftric/octopusdeploy/api/BuildInformationCli.kt rename to src/main/kotlin/com/liftric/octopusdeploy/extensions/BuildInformationAddition.kt index 74eb323..b359566 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/api/BuildInformationCli.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/extensions/BuildInformationAddition.kt @@ -1,4 +1,8 @@ -package com.liftric.octopusdeploy.api +package com.liftric.octopusdeploy.extensions + +import com.liftric.octopusdeploy.api.CommitCli +import com.liftric.octopusdeploy.api.LinksCli +import com.liftric.octopusdeploy.api.WorkItem data class BuildInformationCli( var Id: String? = null, @@ -13,11 +17,11 @@ data class BuildInformationCli( var VcsCommitNumber: String? = null, var VcsCommitUrl: String? = null, var IssueTrackerName: String? = null, - var WorkItems: List? = null, - var Commits: List? = null, + var WorkItems: List = emptyList(), + var Commits: List = emptyList(), var IncompleteDataWarning: String? = null, var Created: String? = null, var LastModifiedOn: String? = null, var LastModifiedBy: String? = null, - var Links: LinksCli? = null + var Links: LinksCli? = null, ) diff --git a/src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployExtension.kt b/src/main/kotlin/com/liftric/octopusdeploy/extensions/OctopusDeployExtension.kt similarity index 55% rename from src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployExtension.kt rename to src/main/kotlin/com/liftric/octopusdeploy/extensions/OctopusDeployExtension.kt index 0af4d26..3de225b 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/OctopusDeployExtension.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/extensions/OctopusDeployExtension.kt @@ -1,124 +1,104 @@ -package com.liftric.octopusdeploy +package com.liftric.octopusdeploy.extensions -import com.liftric.octopusdeploy.api.BuildInformationCli import com.liftric.octopusdeploy.api.OverwriteMode import okhttp3.logging.HttpLoggingInterceptor import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputDirectory -import org.gradle.kotlin.dsl.property -import java.io.File -open class OctopusDeployExtension(project: Project) { +abstract class OctopusDeployExtension(val project: Project) { /** * Octopus deploy server API key */ - val apiKey: Property = project.objects.property() + abstract val apiKey: Property /** * Octopus deploy server URL */ - val serverUrl: Property = project.objects.property() + abstract val serverUrl: Property /** * Enable to calculate the commits for the changelog when uploading build-information */ - var generateChangelogSinceLastTag: Boolean = false + abstract val generateChangelogSinceLastTag: Property /** * Prefix / Baseurl for the build-information commit urls. */ - var commitLinkBaseUrl: String = "http://git.example.com/repo/commits/" + abstract val commitLinkBaseUrl: Property /** * Output folder for files generated by the plugin */ - @OutputDirectory - var outputDir: File = project.file("${project.buildDir}/$extensionName") + abstract val outputDir: DirectoryProperty /** * Directory to run the git helpers in. By default the projects root dir */ - var gitRoot: File = project.rootDir + abstract val gitRoot: DirectoryProperty /** * Target file (package) which will be uploaded to octopus. */ - @InputFile - val pushPackage: RegularFileProperty = project.objects.fileProperty() + abstract val pushPackage: RegularFileProperty /** * Package version. */ - @Input - val version: Property = project.objects.property() + abstract val version: Property /** * Package name */ - @Input - val packageName: Property = project.objects.property() + abstract val packageName: Property /** * octo build-information OverwriteMode */ - var buildInformationOverwriteMode: OverwriteMode? = null + abstract val buildInformationOverwriteMode: Property /** * octo push OverwriteMode */ - var pushOverwriteMode: OverwriteMode? = null + abstract val pushOverwriteMode: Property /** * Customize the final octopus build-information before uploading */ - var buildInformationAddition: BuildInformationCli.() -> Unit = {} + abstract val buildInformationAddition: Property Unit> /** * Configures the http logging of the underlying okhttp client used for octopus api requests */ - @Input - @Optional - val httpLogLevel: Property = project.objects.property() + abstract val httpLogLevel: Property /** * When parsing issues the target issue tracker name is needed. Currently only `Jira` supported */ - @Input - @Optional - val issueTrackerName: Property = project.objects.property() + abstract val issueTrackerName: Property /** * Enable Jira Issue parsing. This needs the changelog generation enabled to parse the commits there. */ - @Input - @Optional - val parseCommitsForJiraIssues: Property = project.objects.property() + abstract val parseCommitsForJiraIssues: Property /** * For proper Jira URLs we need the base URL, something like `https://testric.atlassian.net/browse/`. */ - @Input - @Optional - val jiraBaseBrowseUrl: Property = project.objects.property() + abstract val jiraBaseBrowseUrl: Property /** - * Use short (7 char) commit hashes. Default is [true] + * Use short (7 char) commit hashes. Default is `true` */ - @Input - @Optional - val useShortCommitHashes: Property = project.objects.property() + abstract val useShortCommitHashes: Property /** * Default `buildInformationAddition` implementation adding context from the CI environment for Gitlab CI. Also sets `commitLinkBaseUrl`. */ - fun gitlab(): Unit { - commitLinkBaseUrl = "${System.getenv("CI_PROJECT_URL")?.removeSuffix("/")}/commit/" - buildInformationAddition = { + fun gitlab(additional: BuildInformationCli.() -> Unit = {}) { + commitLinkBaseUrl.set("${System.getenv("CI_PROJECT_URL")?.removeSuffix("/")}/commit/") + buildInformationAddition.set({ BuildEnvironment = if (System.getenv("CI") != null) { "GitLabCI" } else { @@ -134,6 +114,7 @@ open class OctopusDeployExtension(project: Project) { "${System.getenv("CI_PROJECT_URL") ?.removeSuffix("/")}/commit/${System.getenv("CI_COMMIT_SHA")}" LastModifiedBy = System.getenv("GITLAB_USER_NAME") - } + additional() + }) } } diff --git a/src/main/kotlin/com/liftric/octopusdeploy/rest/Releases.kt b/src/main/kotlin/com/liftric/octopusdeploy/rest/Releases.kt index 296fd23..714800c 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/rest/Releases.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/rest/Releases.kt @@ -67,7 +67,7 @@ fun Releases.allFiltered(packageID: String, version: String): List = ge /** * handle exceptions as false */ -inline fun Iterable.catchingFilter(predicate: (T) -> Boolean): List { +internal inline fun Iterable.catchingFilter(predicate: (T) -> Boolean): List { return filter { try { predicate(it) diff --git a/src/main/kotlin/com/liftric/octopusdeploy/shell.kt b/src/main/kotlin/com/liftric/octopusdeploy/shell.kt index cadc9b3..b1dbe04 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/shell.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/shell.kt @@ -7,13 +7,20 @@ internal fun shell(cmd: String, logger: Logger): ShellResult = File(".").shell(c internal fun File.shell(cmd: String, logger: Logger): ShellResult { val tmpDir = System.getProperty("java.io.tmpdir") + // GitHub Workflow Fix for Integration Test + val env = System.getenv().toMutableMap() + env["GITHUB_PATH"]?.let { githubPath -> + env["PATH"] = "${env["PATH"]}:$githubPath" + } + val envArray = env.map { (key, value) -> "$key=$value" }.toTypedArray() + // DOTNET_BUNDLE_EXTRACT_BASE_DIR is a workaround for https://github.com/dotnet/runtime/issues/3846 val cmdarray = arrayOf("sh", "-c", "DOTNET_BUNDLE_EXTRACT_BASE_DIR=$tmpDir $cmd") val cmdDir = this logger.debug("shell: cmdarray='${cmdarray.toList()}'") logger.debug("shell: cmdDir='$cmdDir'") logger.debug("shell: tmpDir='$tmpDir'") - val process = Runtime.getRuntime().exec(cmdarray, emptyArray(), cmdDir) + val process = Runtime.getRuntime().exec(cmdarray, envArray, cmdDir) val exitCode = process.waitFor() val inputText = process.inputStream.bufferedReader().readText().trim() val errorText = process.errorStream.bufferedReader().readText().trim() diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/AbstractBuildInformationTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/AbstractBuildInformationTask.kt deleted file mode 100644 index 6bf947d..0000000 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/AbstractBuildInformationTask.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.liftric.octopusdeploy.task - -import com.liftric.octopusdeploy.api.BuildInformationCli -import com.liftric.octopusdeploy.api.CommitCli -import org.gradle.api.DefaultTask -import org.gradle.api.provider.Property -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.Optional -import org.gradle.api.tasks.OutputFile -import org.gradle.kotlin.dsl.property -import java.io.File - -abstract class AbstractBuildInformationTask : DefaultTask() { - - @Input - val packageName: Property = project.objects.property() - - @Input - val version: Property = project.objects.property() - - @Input - lateinit var commits: List - - @Input - var buildInformationAddition: BuildInformationCli.() -> Unit = {} - - @OutputFile - @Optional - var outputFile: File? = null - - @Input - @Optional - val issueTrackerName: Property = project.objects.property() - - @Input - @Optional - val parseCommitsForJiraIssues: Property = project.objects.property() - - @Input - @Optional - val jiraBaseBrowseUrl: Property = project.objects.property() -} diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/CommitsSinceLastTagTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/CommitsSinceLastTagTask.kt index 8a7c1fa..9550631 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/CommitsSinceLastTagTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/CommitsSinceLastTagTask.kt @@ -3,57 +3,60 @@ package com.liftric.octopusdeploy.task import com.liftric.octopusdeploy.api.CommitCli import com.liftric.octopusdeploy.shell import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* -import org.gradle.kotlin.dsl.property -import java.io.File +import org.gradle.work.DisableCachingByDefault -open class CommitsSinceLastTagTask : DefaultTask() { - init { - group = "octopus" - description = - "Calls git log to receive all commits since the previous tag or the first commit of the current history." - outputs.upToDateWhen { false } - } +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class CommitsSinceLastTagTask : DefaultTask() { + @get:Internal + abstract val gitRoot: DirectoryProperty - @InputDirectory - lateinit var workingDir: File + @get:Input + abstract val commitLinkBaseUrl: Property - @Input - lateinit var commitLinkBaseUrl: String + @get:InputFile + abstract val firstCommitFile: RegularFileProperty - @InputFile - @Optional - var firstCommitFile: File? = null + @get:InputFile + @get:Optional + abstract val previousTagFile: RegularFileProperty - @InputFile - @Optional - var previousTagFile: File? = null + @get:InputFile + @get:Optional + abstract val gitlabCi: Property - @Input - @Optional - val useShortCommitHashes: Property = project.objects.property() + @get:Input + abstract val useShortCommitHashes: Property - @Input - var commits: List = emptyList() + @get:Internal + abstract val commits: ListProperty @TaskAction fun execute() { val useShortHash = useShortCommitHashes.getOrElse(true) + val gitlabCiValue = gitlabCi.getOrElse(false) + val commitLinkBaseUrlValue = if (gitlabCiValue) { + "${System.getenv("CI_PROJECT_URL")?.removeSuffix("/")}/commit/" + } else { + commitLinkBaseUrl.get() + } - val previousTag: String? = previousTagFile?.readText() + val previousTag: String? = previousTagFile.orNull?.asFile?.readText() if (previousTag == null) { - logger.info("couldn't get previous tag, will use the first commit instead.") + logger.info("Couldn't get previous tag, will use the first commit instead.") } - val firstCommitHash: String = - firstCommitFile?.readText() ?: error("couldn't read firstCommitFile!") - val (exitCode, inputText, errorText) = workingDir.shell( + val firstCommitHash: String = firstCommitFile.get().asFile.readText() + val (exitCode, inputText, errorText) = gitRoot.get().asFile.shell( "git log --pretty='format:%H#%s \\(%an\\)' ${previousTag ?: firstCommitHash}..HEAD", logger ) if (exitCode == 0) { logger.info("previous tag: $inputText") - commits = inputText.trim() + commits.set(inputText.trim() .split("\n") .map { it.split("#") @@ -62,14 +65,12 @@ open class CommitsSinceLastTagTask : DefaultTask() { CommitCli( Id = it[0].shorten(useShortHash), Comment = it.subList(1, it.size).joinToString(" ").replace("\\", ""), - LinkUrl = "${commitLinkBaseUrl.removeSuffix("/")}/${it[0]}" + LinkUrl = "${commitLinkBaseUrlValue.removeSuffix("/")}/${it[0]}" ) - }.also { commits -> - commits.forEach { - logger.debug(it.toString()) - println(it.toString()) - } - } + }.onEach { + logger.debug(it.toString()) + println(it.toString()) + }) } else { logger.error("git describe returned non-zero exitCode: $exitCode") logger.error(errorText) diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationMarkdownTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationMarkdownTask.kt index 37b9e86..068667e 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationMarkdownTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationMarkdownTask.kt @@ -1,37 +1,56 @@ package com.liftric.octopusdeploy.task -import com.liftric.octopusdeploy.api.BuildInformationCli +import com.liftric.octopusdeploy.api.CommitCli import com.liftric.octopusdeploy.api.WorkItem -import com.liftric.octopusdeploy.extensionName +import com.liftric.octopusdeploy.extensions.BuildInformationCli import com.liftric.octopusdeploy.parseCommitsForJira -import org.gradle.api.file.DirectoryProperty +import org.gradle.api.DefaultTask import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -open class CreateBuildInformationMarkdownTask : AbstractBuildInformationTask() { - init { - group = "octopus" - description = "Creates a markdown file freom the build-information for octo cli release creation." - outputs.upToDateWhen { false } - } +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class CreateBuildInformationMarkdownTask : DefaultTask() { + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @get:Input + abstract val packageName: Property + + @get:Input + abstract val version: Property + + @get:Internal + abstract val commits: ListProperty + + @get:Nested + @get:Optional + abstract val buildInformationAddition: Property Unit> + + @get:Optional + @get:Input + abstract val issueTrackerName: Property - @OutputDirectory - val outputDirectory: DirectoryProperty = project.objects.directoryProperty().convention(project.layout.buildDirectory.dir(extensionName)) + @get:Input + @get:Optional + abstract val parseCommitsForJiraIssues: Property - @OutputFile - val outputMarkdown: RegularFileProperty = project.objects.fileProperty().convention(outputDirectory.file("build-information.md")) + @get:Input + @get:Optional + abstract val jiraBaseBrowseUrl: Property @TaskAction fun execute() { + val commits = commits.getOrElse(listOf()) val workItems: List? = if (parseCommitsForJiraIssues.getOrElse(false)) { parseCommitsForJira(commits, jiraBaseBrowseUrl.getOrElse("")) } else { null } - val buildInformationCli = BuildInformationCli().apply(buildInformationAddition) - outputMarkdown.get().asFile.apply { + val buildInformationCli = BuildInformationCli().apply(buildInformationAddition.get()) + outputFile.get().asFile.apply { writeText("") appendText("# ${packageName.get()}: ${version.get()}\n") appendText("[VCS Root](${buildInformationCli.VcsRoot})\n\n") diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTask.kt index 9652e79..c4e17cd 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTask.kt @@ -3,45 +3,73 @@ package com.liftric.octopusdeploy.task import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.databind.PropertyNamingStrategies import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.liftric.octopusdeploy.api.BuildInformationCli +import com.liftric.octopusdeploy.api.CommitCli import com.liftric.octopusdeploy.api.WorkItem +import com.liftric.octopusdeploy.extensions.BuildInformationCli import com.liftric.octopusdeploy.parseCommitsForJira -import org.gradle.api.tasks.OutputDirectory -import org.gradle.api.tasks.TaskAction -import java.io.File - -open class CreateBuildInformationTask : AbstractBuildInformationTask() { - init { - group = "octopus" - description = "Creates the octopus build-information file." - outputs.upToDateWhen { false } - } +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.work.DisableCachingByDefault + +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class CreateBuildInformationTask : DefaultTask() { + + @get:OutputFile + abstract val outputFile: RegularFileProperty + + @get:Input + abstract val packageName: Property + + @get:Input + abstract val version: Property + + @get:Internal + abstract val commits: ListProperty - @OutputDirectory - lateinit var outputDir: File + @get:Nested + @get:Optional + abstract val buildInformationAddition: Property Unit> + + @get:Optional + @get:Input + abstract val issueTrackerName: Property + + @get:Input + @get:Optional + abstract val parseCommitsForJiraIssues: Property + + @get:Input + @get:Optional + abstract val jiraBaseBrowseUrl: Property @TaskAction fun execute() { + val commits = commits.getOrElse(listOf()) + + val buildInformationCli = BuildInformationCli().apply(buildInformationAddition.get()) + val workItems: List? = if (parseCommitsForJiraIssues.getOrElse(false)) { parseCommitsForJira(commits, jiraBaseBrowseUrl.getOrElse("")) } else { null } - outputFile = File(outputDir, "build-information.json").apply { - writeText( - jacksonObjectMapper().apply { - propertyNamingStrategy = PropertyNamingStrategies.UPPER_CAMEL_CASE - setSerializationInclusion(JsonInclude.Include.NON_NULL) - }.writeValueAsString(BuildInformationCli().apply { + outputFile.get().asFile.writeText( + jacksonObjectMapper().apply { + propertyNamingStrategy = PropertyNamingStrategies.UPPER_CAMEL_CASE + setSerializationInclusion(JsonInclude.Include.NON_NULL) + }.writeValueAsString( + buildInformationCli.apply { PackageId = packageName.get() Version = version.get() VcsType = "Git" Commits = commits - buildInformationAddition() issueTrackerName.orNull.let { IssueTrackerName = it } workItems?.let { WorkItems = it } - }).also { println(it) } - ) - } + } + ).also { println(it) } + ) } } diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/CreateReleaseTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/CreateReleaseTask.kt index 0a6bf96..63729d1 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/CreateReleaseTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/CreateReleaseTask.kt @@ -10,39 +10,34 @@ import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.listProperty -import org.gradle.kotlin.dsl.property +import org.gradle.work.DisableCachingByDefault -open class CreateReleaseTask : DefaultTask() { - init { - group = "octopus" - description = "Creates a new release." - outputs.upToDateWhen { false } - } - - @Input - val octopusUrl: Property = project.objects.property() +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class CreateReleaseTask : DefaultTask() { + @get:Input + abstract val octopusUrl: Property - @Input - val apiKey: Property = project.objects.property() + @get:Input + abstract val apiKey: Property - @Input - val projectName: Property = project.objects.property() + @get:Input + abstract val projectName: Property - @Input - @Optional - val releaseNumber: Property = project.objects.property() + @get:Input + @get:Optional + abstract val releaseNumber: Property - @Input - @Optional - val dryRun: Property = project.objects.property() + @get:Input + @get:Optional + abstract val dryRun: Property - @Input - @Optional - val waitForReleaseDeployments: Property = project.objects.property() + @get:Input + @get:Optional + abstract val waitForReleaseDeployments: Property - @Optional - @InputFile - val releaseNoteFile: RegularFileProperty = project.objects.fileProperty() + @get:Optional + @get:InputFile + abstract val releaseNoteFile: RegularFileProperty /** * Version number to use for a package diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTask.kt index 46e19c6..87f3340 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTask.kt @@ -2,37 +2,28 @@ package com.liftric.octopusdeploy.task import com.liftric.octopusdeploy.shell import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* -import java.io.File +import org.gradle.work.DisableCachingByDefault -open class FirstCommitHashTask : DefaultTask() { - init { - group = "octopus" - description = "Calls git log to get the first commit hash of the current history tree" - outputs.upToDateWhen { false } - } - - @OutputDirectory - lateinit var outputDir: File - - @InputDirectory - lateinit var workingDir: File +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class FirstCommitHashTask : DefaultTask() { + @get:Internal + abstract val gitRoot: DirectoryProperty - @OutputFile - @Optional - var outputFile: File? = null + @get:OutputFile + abstract val outputFile: RegularFileProperty @TaskAction fun execute() { - val (exitCode, inputText, errorText) = workingDir.shell( + val (exitCode, inputText, errorText) = gitRoot.get().asFile.shell( "git log --pretty='format:%H' --reverse | head -1", logger ) if (exitCode == 0) { logger.info("first commit hash: $inputText") - outputFile = File(outputDir, "firstCommitHash").apply { - writeText(inputText) - } + outputFile.get().asFile.writeText(inputText) } else { logger.error("git log returned non-zero exitCode: $exitCode") logger.error(errorText) diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/PreviousTagTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/PreviousTagTask.kt index 66fbd03..a0d07b0 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/PreviousTagTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/PreviousTagTask.kt @@ -2,36 +2,32 @@ package com.liftric.octopusdeploy.task import com.liftric.octopusdeploy.shell import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty import org.gradle.api.tasks.* -import java.io.File +import org.gradle.work.DisableCachingByDefault -open class PreviousTagTask : DefaultTask() { - init { - group = "octopus" - description = "Calls git describe to receive the previous tag name. Will fail if no tag is found." - outputs.upToDateWhen { false } - } - - @OutputDirectory - lateinit var outputDir: File +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class PreviousTagTask : DefaultTask() { + @get:Internal + abstract val gitRoot: DirectoryProperty - @InputDirectory - lateinit var workingDir: File - @OutputFile - @Optional - var outputFile: File? = null + @get:OutputFile + abstract val outputFile: RegularFileProperty @TaskAction fun execute() { - val (exitCode, inputText, errorText) = workingDir.shell("git describe --tags --abbrev=0 @^", logger) + val (exitCode, inputText, errorText) = gitRoot.get().asFile.shell( + "git describe --tags --abbrev=0 @^", + logger + ) if (exitCode == 0) { logger.info("previous tag: $inputText") - outputFile = File(outputDir, "previousTagName").apply { - writeText(inputText) - } + outputFile.get().asFile.writeText(inputText) } else { logger.error("git describe returned non-zero exitCode: $exitCode") logger.error(errorText) + throw IllegalStateException("git describe exitCode: $exitCode") } } } diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/PromoteReleaseTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/PromoteReleaseTask.kt index 4bbc242..7ff5cea 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/PromoteReleaseTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/PromoteReleaseTask.kt @@ -7,65 +7,57 @@ import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.property +import org.gradle.work.DisableCachingByDefault -open class PromoteReleaseTask : DefaultTask() { - init { - group = "octopus" - description = "Promotes a release." - outputs.upToDateWhen { false } - } - - @Input - val octopusUrl = project.objects.property(String::class.java) +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class PromoteReleaseTask : DefaultTask() { + @get:Input + abstract val octopusUrl: Property - @Input - val apiKey = project.objects.property(String::class.java) + @get:Input + abstract val apiKey: Property - /** - * Target octopus project name - */ - @Input - val projectName = project.objects.property(String::class.java) + @get:Input + abstract val projectName: Property /** * Source octopus environment name */ - @Input - val from = project.objects.property(String::class.java) + @get:Input + abstract val from: Property /** * Target octopus environment name */ - @Input - val to = project.objects.property(String::class.java) + @get:Input + abstract val to: Property - @Input - @Optional - val waitForReleaseDeployments: Property = project.objects.property() + @get:Input + @get:Optional + abstract val waitForReleaseDeployments: Property - @Input - @Optional - val waitTimeoutSeconds: Property = project.objects.property() + @get:Input + @get:Optional + abstract val waitTimeoutSeconds: Property /** * Octopus server might need longer for the deployment to trigger, so we can define an * additional, initial, minimum wait before the actual release check logic even happens */ - @Input - @Optional - val initialWaitSeconds: Property = project.objects.property() + @get:Input + @get:Optional + abstract val initialWaitSeconds: Property - @Input - @Optional - val delayBetweenChecksSeconds: Property = project.objects.property() + @get:Input + @get:Optional + abstract val delayBetweenChecksSeconds: Property /** * Configures the http logging of the underlying okhttp client used for octopus api requests */ - @Input - @Optional - val httpLogLevel: Property = project.objects.property() + @get:Input + @get:Optional + abstract val httpLogLevel: Property @TaskAction fun execute() { diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTask.kt index aeb7fe2..54d91e9 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/UploadBuildInformationTask.kt @@ -1,41 +1,36 @@ package com.liftric.octopusdeploy.task +import com.liftric.octopusdeploy.api.OverwriteMode import com.liftric.octopusdeploy.shell import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.property -import java.io.File +import org.gradle.work.DisableCachingByDefault -open class UploadBuildInformationTask : DefaultTask() { - init { - group = "octopus" - description = "Uploads the created octopus build-information file." - outputs.upToDateWhen { false } - } - - @Input - val octopusUrl: Property = project.objects.property() +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class UploadBuildInformationTask : DefaultTask() { + @get:Input + abstract val octopusUrl: Property - @Input - val apiKey: Property = project.objects.property() + @get:Input + abstract val apiKey: Property - @Input - val packageName: Property = project.objects.property() + @get:Input + abstract val packageName: Property - @Input - val version: Property = project.objects.property() + @get:Input + abstract val version: Property - @Input - @Optional - var overwriteMode: String? = null + @get:Input + @get:Optional + abstract val overwriteMode: Property - @InputFile - @Optional - var buildInformation: File? = null + @get:InputFile + abstract val buildInformationFile: RegularFileProperty @TaskAction fun execute() { @@ -45,11 +40,11 @@ open class UploadBuildInformationTask : DefaultTask() { "--server=${octopusUrl.get()}", "--apiKey=${apiKey.get()}", "--file", - buildInformation?.absolutePath ?: error("couldn't find build-information.json"), + buildInformationFile.get().asFile, "--package-id", "\"${packageName.get()}\"", "--version=${version.get()}", - overwriteMode?.let { "--overwrite-mode=$it" } + overwriteMode.orNull?.let { "--overwrite-mode=$it" } ).filterNotNull().joinToString(" ").let { shell(it, logger) } if (exitCode == 0) { println(inputText) diff --git a/src/main/kotlin/com/liftric/octopusdeploy/task/UploadPackageTask.kt b/src/main/kotlin/com/liftric/octopusdeploy/task/UploadPackageTask.kt index 986333b..0d529bf 100644 --- a/src/main/kotlin/com/liftric/octopusdeploy/task/UploadPackageTask.kt +++ b/src/main/kotlin/com/liftric/octopusdeploy/task/UploadPackageTask.kt @@ -1,5 +1,6 @@ package com.liftric.octopusdeploy.task +import com.liftric.octopusdeploy.api.OverwriteMode import com.liftric.octopusdeploy.shell import okhttp3.logging.HttpLoggingInterceptor import org.gradle.api.DefaultTask @@ -9,63 +10,56 @@ import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Optional import org.gradle.api.tasks.TaskAction -import org.gradle.kotlin.dsl.property +import org.gradle.work.DisableCachingByDefault -open class UploadPackageTask : DefaultTask() { - init { - group = "octopus" - description = "Uploads the package to octopus." - outputs.upToDateWhen { false } - } - - @Input - val octopusUrl: Property = project.objects.property() +@DisableCachingByDefault(because = "Gradle would require more information to cache this task") +abstract class UploadPackageTask : DefaultTask() { + @get:Input + abstract val octopusUrl: Property - @Input - val apiKey: Property = project.objects.property() + @get:Input + abstract val apiKey: Property - @Input - @Optional - var overwriteMode: String? = null + @get:Input + @get:Optional + abstract val overwriteMode: Property - @Input - @Optional - val packageName: Property = project.objects.property() + @get:Input + abstract val packageName: Property - @Input - @Optional - val version: Property = project.objects.property() + @get:Input + abstract val version: Property - @Optional - @InputFile - val packageFile: RegularFileProperty = project.objects.fileProperty() + @get:Optional + @get:InputFile + abstract val pushPackage: RegularFileProperty - @Input - @Optional - val waitForReleaseDeployments: Property = project.objects.property() + @get:Input + @get:Optional + abstract val waitForReleaseDeployments: Property - @Input - @Optional - val waitTimeoutSeconds: Property = project.objects.property() + @get:Input + @get:Optional + abstract val waitTimeoutSeconds: Property /** * Octopus server might need longer for the deployment to trigger, so we can define an * additional, initial, minimum wait before the actual release check logic even happens */ - @Input - @Optional - val initialWaitSeconds: Property = project.objects.property() + @get:Input + @get:Optional + abstract val initialWaitSeconds: Property - @Input - @Optional - val delayBetweenChecksSeconds: Property = project.objects.property() + @get:Input + @get:Optional + abstract val delayBetweenChecksSeconds: Property /** * Configures the http logging of the underlying okhttp client used for octopus api requests */ - @Input - @Optional - val httpLogLevel: Property = project.objects.property() + @get:Input + @get:Optional + abstract val httpLogLevel: Property @TaskAction fun execute() { @@ -78,8 +72,8 @@ open class UploadPackageTask : DefaultTask() { "--server=$octopusUrlValue", "--apiKey=$apiKeyValue", "--package", - packageFile.get().asFile.absolutePath ?: error("couldn't find build-information.json"), - overwriteMode?.let { "--overwrite-mode=$it" } + pushPackage.get().asFile.absolutePath ?: error("couldn't find pushPackage"), + overwriteMode.orNull?.let { "--overwrite-mode=$it" } ).filterNotNull().joinToString(" ").let { shell(it, logger) } if (exitCode == 0) { println(inputText) diff --git a/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationBuilderTaskTest.kt b/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationBuilderTaskTest.kt new file mode 100644 index 0000000..fdb9402 --- /dev/null +++ b/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationBuilderTaskTest.kt @@ -0,0 +1,101 @@ +package com.liftric.octopusdeploy.task + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.liftric.octopusdeploy.api.CommitCli +import com.liftric.octopusdeploy.api.WorkItem +import com.liftric.octopusdeploy.extensions.BuildInformationCli +import junit.framework.TestCase.* +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class CreateBuildInformationBuilderTaskTest { + @get:Rule + val outputDir = TemporaryFolder() + + @Test + fun testParseJira() { + val project: Project = ProjectBuilder.builder().build() + project.pluginManager.apply("com.liftric.octopus-deploy-plugin") + + assertTrue(project.tasks.getByName("createBuildInformation") is CreateBuildInformationTask) + + val task = project.tasks.getByName("createBuildInformation") as CreateBuildInformationTask + val baseJiraUrl = "https://testric.atlassian.net/browser/" + + val outputFileBuildInformation = outputDir.newFile("build-information.json") + task.apply { + packageName.set("test-package") + commits.set(createTestCommits()) + version.set("2.1.4") + buildInformationAddition.set({Id = "foo"}) + outputFile.set(outputFileBuildInformation) + issueTrackerName.set("Jira") + parseCommitsForJiraIssues.set(true) + jiraBaseBrowseUrl.set(baseJiraUrl) + } + task.execute() + assertTrue(task.outputFile.get().asFile.exists()) + val jsonText = task.outputFile.get().asFile.readText() + val taskResult = jacksonObjectMapper().readValue(jsonText) + assertTrue(taskResult.Id == "foo") + assertNotNull(taskResult.WorkItems) + taskResult.WorkItems.let { workItems -> + assertEquals(2, workItems.size) + workItems.verify("LIF-71", baseJiraUrl) + workItems.verify("LIF-72", baseJiraUrl) + } + } + + private fun createTestCommits(): List { + return listOf( + testCommit1, + testCommit2, + testCommit3, + testCommit4, + testCommit5 + ) + } + + companion object { + val testCommit1 = CommitCli( + "7119e5a28ef691cf95ec98cbf8b2e6bc4b1d84fc", + "null/commit/7119e5a28ef691cf95ec98cbf8b2e6bc4b1d84fc", + "[Gradle Release Plugin] - pre tag commit: '0.1.44'. " + ) + val testCommit2 = CommitCli( + "c6dc72fa67b5eb8fb134153ab240b169dea0d565", + "null/commit/c6dc72fa67b5eb8fb134153ab240b169dea0d565", + "Merge branch 'feature/test-octopus-integration' into 'master' " + ) + val testCommit3 = CommitCli( + "cb230f92c95ee1118d1348a9e96d6d45e7c611c9", + "null/commit/cb230f92c95ee1118d1348a9e96d6d45e7c611c9", + "feat(build): dummy commit for octopus - LIF-71 " + ) + val testCommit4 = CommitCli( + "cb230f92c95ee1118d1348a9e96d6d35e7c611c9", + "null/commit/cb230f92c95ee1218d1348a9e96d6d45e7c611c9", + "feat(build): dummy commit 2 for octopus - LIF-72" + ) + val testCommit5 = CommitCli( + "0f1352918b05338eba2275cbab8d33601a810251", + "null/commit/0f1352918b05338eba2275cbab8d33601a810251", + "[Gradle Release Plugin] [ci skip] - new version commit: '0.1.44-SNAPSHOT'" + ) + } +} + +private fun List.verify( + key: String, + baseJiraUrl: String +) { + assertEquals(1, count { it.Id == key }) + first { it.Id == key }.let { workItem -> + assertEquals(workItem.Id, key) + assertEquals(workItem.LinkUrl, "${baseJiraUrl}${key}") + } +} diff --git a/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationGitlabCiTaskTest.kt b/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationGitlabCiTaskTest.kt new file mode 100644 index 0000000..146d8b3 --- /dev/null +++ b/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationGitlabCiTaskTest.kt @@ -0,0 +1,84 @@ +package com.liftric.octopusdeploy.task + +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.module.kotlin.readValue +import com.liftric.octopusdeploy.extensionName +import com.liftric.octopusdeploy.extensions.BuildInformationCli +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import java.io.File + +class CreateBuildInformationGitlabCiTaskTest { + @get:Rule + val testProjectDir = TemporaryFolder() + + @Test + fun testExecute() { + println(testProjectDir.root.absolutePath) + setupBuild() + + val envVars = mapOf( + "PATH" to System.getenv("PATH"), + "CI_PIPELINE_IID" to "123", + "CI_PIPELINE_URL" to "https://gitlab.com/project/-/pipelines/123", + "CI_COMMIT_REF_NAME" to "main", + "CI_PROJECT_URL" to "https://gitlab.com/project", + "CI_COMMIT_SHORT_SHA" to "abc1234", + "CI_COMMIT_SHA" to "abc1234defghijk", + "GITLAB_USER_NAME" to "Test User" + ) + + val result = GradleRunner.create() + .forwardOutput() + .withProjectDir(testProjectDir.root) + .withArguments("createBuildInformation") + .withEnvironment(envVars) + .withPluginClasspath() + .build() + + println(result.output) + assertEquals(TaskOutcome.SUCCESS, result.task(":createBuildInformation")?.outcome) + File("${testProjectDir.root.absolutePath}/build/$extensionName/build-information.json").apply { + assertTrue(exists()) + val taskResult = jacksonObjectMapper().readValue(readText()) + println(taskResult) + assertEquals("foo", taskResult.Id) + assertEquals("gradle", taskResult.BuildEnvironment) + assertEquals(envVars["CI_PIPELINE_IID"], taskResult.BuildNumber) + assertEquals(envVars["CI_PROJECT_URL"], taskResult.VcsRoot) + assertEquals(envVars["CI_PIPELINE_URL"], taskResult.BuildUrl) + assertEquals(envVars["CI_COMMIT_REF_NAME"], taskResult.Branch) + assertEquals(envVars["CI_COMMIT_SHORT_SHA"], taskResult.VcsCommitNumber) + assertEquals("${envVars["CI_PROJECT_URL"]}/commit/${envVars["CI_COMMIT_SHA"]}", taskResult.VcsCommitUrl) + assertEquals(envVars["GITLAB_USER_NAME"], taskResult.LastModifiedBy) + } + } + + fun setupBuild() { + testProjectDir.newFile("build.gradle.kts").apply { + writeText( + """ +import com.liftric.octopusdeploy.extensions.* +plugins { + id("com.liftric.octopus-deploy-plugin") +} +octopus { + apiKey.set("fakefake") + version.set("whatever") + gitRoot.set(file("${File("").absolutePath}")) + packageName.set("whatever") + serverUrl.set("whatever") + gitlab { + Id = "foo" + } +} +""" + ) + } + } +} diff --git a/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTaskTest.kt b/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTaskTest.kt index cfa13fa..a1f7c23 100644 --- a/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTaskTest.kt +++ b/src/test/kotlin/com/liftric/octopusdeploy/task/CreateBuildInformationTaskTest.kt @@ -2,7 +2,7 @@ package com.liftric.octopusdeploy.task import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import com.liftric.octopusdeploy.api.BuildInformationCli +import com.liftric.octopusdeploy.extensions.BuildInformationCli import com.liftric.octopusdeploy.api.CommitCli import com.liftric.octopusdeploy.api.WorkItem import junit.framework.TestCase.* @@ -25,18 +25,20 @@ class CreateBuildInformationTaskTest { val task = project.tasks.getByName("createBuildInformation") as CreateBuildInformationTask val baseJiraUrl = "https://testric.atlassian.net/browser/" + + val outputFileBuildInformation = outputDir.newFile("build-information.json") task.apply { packageName.set("test-package") version.set("2.1.4") - commits = createTestCommits() - outputDir = this@CreateBuildInformationTaskTest.outputDir.root + commits.set(createTestCommits()) + outputFile.set(outputFileBuildInformation) issueTrackerName.set("Jira") parseCommitsForJiraIssues.set(true) jiraBaseBrowseUrl.set(baseJiraUrl) } task.execute() - assertTrue(task.outputFile?.exists() == true) - val jsonText = task.outputFile!!.readText() + assertTrue(task.outputFile.get().asFile.exists()) + val jsonText = task.outputFile.get().asFile.readText() val taskResult = jacksonObjectMapper().readValue(jsonText) assertNotNull(taskResult.WorkItems) taskResult.WorkItems?.let { workItems -> diff --git a/src/test/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTaskTest.kt b/src/test/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTaskTest.kt index a859fca..233e996 100644 --- a/src/test/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTaskTest.kt +++ b/src/test/kotlin/com/liftric/octopusdeploy/task/FirstCommitHashTaskTest.kt @@ -42,8 +42,8 @@ plugins { } octopus { apiKey.set("fakefake") - gitRoot = file("${File(".").absolutePath}") version.set("whatever") + gitRoot.set(file("${File("").absolutePath}")) packageName.set("whatever") serverUrl.set("whatever") } @@ -58,4 +58,4 @@ tasks { ) } } -} +} \ No newline at end of file