diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..c9d1064 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,43 @@ +name: Java CI with Maven + +on: + push: + branches: [ develop, master ] + pull_request: + branches: [ develop ] + +jobs: + build_jdk11: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - name: Cache Maven packages + uses: actions/cache@v2 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build with Maven + run: mvn -B verify + build_jdk8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'adopt' + - name: Cache Maven packages + uses: actions/cache@v2 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + - name: Build with Maven + run: mvn -B verify diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1219759..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: java - -jdk: - - openjdk8 - - openjdk11 - -cache: - directories: - - $HOME/.m2 - -install: /bin/true - -script: mvn verify -B - -dist: bionic diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index b85e93f..e23287c 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,6 +1,10 @@ = Changelog -== Unreleased +== Version 2.11.0 + +* Support authentication to the helm repository using credentials from server definitions in maven `settings.xml` via `chartRepoServerId`. +* Add an option to pass `--force-update` when adding helm repositories managed by this plugin. +* Cleanup dependencies, readme, and deprecated Kotlin functions == Version 2.10.0 diff --git a/README.adoc b/README.adoc index 6e1aacf..2980a6b 100644 --- a/README.adoc +++ b/README.adoc @@ -1,7 +1,7 @@ = Helm Maven Plugin -:uri-build-status: https://travis-ci.org/deviceinsight/helm-maven-plugin -:img-build-status: https://api.travis-ci.org/deviceinsight/helm-maven-plugin.svg?branch=develop +:uri-build-status: https://github.com/deviceinsight/helm-maven-plugin/actions/workflows/maven.yml +:img-build-status: https://github.com/deviceinsight/helm-maven-plugin/actions/workflows/maven.yml/badge.svg image:{img-build-status}[Build Status Badge,link={uri-build-status}] @@ -20,7 +20,7 @@ Add the following to your `pom.xml` com.deviceinsight.helm helm-maven-plugin - 2.10.0 + 2.11.0 my-chart https://charts.helm.sh/stable @@ -82,11 +82,13 @@ that the correct docker image is used. An example snippet: |chartRepoUrl |`null` |The URL of the Chart repository where dependencies are required from and where charts should be published to |incubatorRepoUrl |`https://charts.helm.sh/incubator` |The URL to the incubator Chart repository |addIncubatorRepo |`true` |Whether the repository defined in `incubatorRepoUrl` should be added when running the package goal +|forceAddRepos |`false` |Whether to overwrite the repository configuration when adding a new repository with the same name. This flag is only relevant when using Helm 3 and emulates Helm 2 behavior |chartPublishUrl |`${chartRepoUrl}/api/charts` |The URL that will be used for publishing the chart. The default value will work if `chartRepoUrl` refers to a ChartMuseum. |chartPublishMethod |"POST" |The HTTP method that will be used for publishing requests |chartDeleteUrl |`${chartRepoUrl}/api/charts/${chartName}/${chartVersion}` |The URL that will be used for deleting a previous version of the chart. This is used for updating SNAPSHOT versions. The default value will work if `chartRepoUrl` refers to a ChartMuseum. |chartRepoUsername |None |The username for basic authentication against the chart repo |chartRepoPassword |None |The password for basic authentication against the chart repo +|chartRepoServerId |None |The ID of the server definition from the settings.xml server list to obtain the chart repo username/password from. If both chartRepoUsername/chartRepoPassword and chartRepoServerId are specified then the values specified in chartRepoUsername/chartRepoPassword take precedence. |chartFolder |`"src/main/helm/"` |The location of the chart files (e.g. Chart.yaml). |skipSnapshots |`true` |If true, SNAPSHOT versions will be built, but not deployed. |helmGroupId |`"com.deviceinsight.helm"` |The helm binary `groupId` @@ -143,7 +145,7 @@ To use the `deployAtEnd` functionality it's mandatory to put the Helm Maven Plug com.deviceinsight.helm helm-maven-plugin - 2.10.0 + 2.11.0 my-chart https://charts.helm.sh/stable @@ -177,7 +179,7 @@ Problem:: The following error message is a common source of trouble, lately: ... -[ERROR] Failed to execute goal com.deviceinsight.helm:helm-maven-plugin:2.10.0:package (default) on project my-project: Error creating helm chart: When executing '/home/user/.m2/repository/com/deviceinsight/helm/helm/2.16.2/helm-2.16.2-linux-amd64.binary init --client-only' got result code '1' -> [Help 1] +[ERROR] Failed to execute goal com.deviceinsight.helm:helm-maven-plugin:2.11.0:package (default) on project my-project: Error creating helm chart: When executing '/home/user/.m2/repository/com/deviceinsight/helm/helm/2.16.2/helm-2.16.2-linux-amd64.binary init --client-only' got result code '1' -> [Help 1] ---- Solution:: This is likely due to an old version of helm itself. Make sure to configure `` to a version >= 3.4.0 or, if you are still using Helm 2, a version >= 2.17.0 (https://github.com/helm/charts#%EF%B8%8F-deprecation-and-archive-notice[background information]). . {blank} diff --git a/pom.xml b/pom.xml index b1d7cdb..83cb8b2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.deviceinsight.helm helm-maven-plugin - 2.10.0 + 2.11.0 maven-plugin Helm Maven Plugin @@ -12,41 +12,52 @@ https://github.com/deviceinsight/helm-maven-plugin - 1.4.30 + 1.8.10 1.8 1.8 1.8 UTF-8 - 3.6.0 + 3.6.2 2.2.1 - 0.13.1 - 2.12.1 - 4.5.13 + 4.5.14 + 1.4 - 5.7.1 - 3.19.0 + 5.9.2 + 3.24.2 - 1.4.20 - 3.8.1 + 1.7.20 + 3.10.1 3.2.1 - 1.15.1 - 1.6.8 - 1.6 + 1.19.0 + 1.6.13 + 3.0.1 + + + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + pom + import + + + + org.jetbrains.kotlin kotlin-stdlib-jdk8 - ${kotlin.version} org.apache.maven maven-plugin-api ${maven.version} + provided org.apache.maven.plugin-tools @@ -58,48 +69,31 @@ org.apache.maven maven-artifact ${maven.version} + provided org.apache.maven maven-core ${maven.version} + provided org.apache.maven maven-project ${maven-project.version} - - - org.apache.maven.shared - maven-artifact-transfer - ${maven-artifact-transfer.version} + provided - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - ${jackson.version} - - - com.fasterxml.jackson.module - jackson-module-kotlin - ${jackson.version} - org.apache.httpcomponents httpclient ${httpclient.version} + + org.sonatype.plexus + plexus-sec-dispatcher + ${plexus-sec-dispatcher.version} + org.junit.jupiter @@ -195,6 +189,38 @@ true + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.jetbrains.dokka + dokka-maven-plugin + ${dokka-maven-plugin.version} + + + prepare-package + + dokka + javadocJar + + + + ${project.build.sourceDirectory} + + + + + @@ -257,45 +283,6 @@ release - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - verify - - jar-no-fork - - - - - - org.jetbrains.dokka - dokka-maven-plugin - ${dokka-maven-plugin.version} - - - prepare-package - - dokka - javadocJar - - - - ${project.build.sourceDirectory} - - - - com.deviceinsight.helm - false - - - - - - org.sonatype.plugins nexus-staging-maven-plugin diff --git a/src/main/kotlin/com/deviceinsight/helm/AbstractHelmMojo.kt b/src/main/kotlin/com/deviceinsight/helm/AbstractHelmMojo.kt index 66f3ef9..47c0e1c 100644 --- a/src/main/kotlin/com/deviceinsight/helm/AbstractHelmMojo.kt +++ b/src/main/kotlin/com/deviceinsight/helm/AbstractHelmMojo.kt @@ -20,6 +20,7 @@ import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject import java.io.File +import kotlin.concurrent.thread abstract class AbstractHelmMojo : AbstractMojo() { @@ -38,22 +39,35 @@ abstract class AbstractHelmMojo : AbstractMojo() { @Parameter(property = "chartName", required = false) private var chartName: String? = null - protected fun executeCmd(cmd: String, directory: File = target(), - redirectOutput: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE) { - val proc = ProcessBuilder(cmd.split(" ")) + protected fun executeCmd( + cmd: List, + directory: File = target(), + logStdoutToInfo: Boolean = false, + redirectOutput: ProcessBuilder.Redirect = ProcessBuilder.Redirect.PIPE + ) { + val proc = ProcessBuilder(cmd) .directory(directory) .redirectOutput(redirectOutput) .redirectError(ProcessBuilder.Redirect.PIPE) .start() + val stdoutPrinter = thread(name = "Stdout printer") { + val logFunction: (String) -> Unit = if (logStdoutToInfo) log::info else log::debug + proc.inputStream.bufferedReader().lines().forEach { logFunction("Output: $it") } + } + + val stderrPrinter = thread(name = "Stderr printer") { + proc.errorStream.bufferedReader().lines().forEach { log.error("Output: $it") } + } + proc.waitFor() + stdoutPrinter.join() + stderrPrinter.join() - log.debug("When executing '$cmd' in '${directory.absolutePath}', result was ${proc.exitValue()}") - proc.inputStream.bufferedReader().lines().forEach { log.debug("Output: $it") } - proc.errorStream.bufferedReader().lines().forEach { log.error("Output: $it") } + log.debug("When executing '${cmd.joinToString(" ")}' in '${directory.absolutePath}', result was ${proc.exitValue()}") if (proc.exitValue() != 0) { - throw RuntimeException("When executing '$cmd' got result code '${proc.exitValue()}'") + throw RuntimeException("When executing '${cmd.joinToString(" ")}' got result code '${proc.exitValue()}'") } } diff --git a/src/main/kotlin/com/deviceinsight/helm/DeployMojo.kt b/src/main/kotlin/com/deviceinsight/helm/DeployMojo.kt index 40a47b1..0e9410e 100644 --- a/src/main/kotlin/com/deviceinsight/helm/DeployMojo.kt +++ b/src/main/kotlin/com/deviceinsight/helm/DeployMojo.kt @@ -16,6 +16,7 @@ package com.deviceinsight.helm +import com.deviceinsight.helm.util.ServerAuthentication import org.apache.http.auth.AuthScope import org.apache.http.auth.UsernamePasswordCredentials import org.apache.http.client.methods.HttpDelete @@ -26,10 +27,13 @@ import org.apache.http.impl.client.CloseableHttpClient import org.apache.http.impl.client.HttpClientBuilder import org.apache.maven.plugin.AbstractMojo import org.apache.maven.plugin.MojoExecutionException +import org.apache.maven.plugins.annotations.Component import org.apache.maven.plugins.annotations.LifecyclePhase import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter import org.apache.maven.project.MavenProject +import org.apache.maven.settings.Settings +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher import java.io.File import java.util.Collections import java.util.concurrent.atomic.AtomicInteger @@ -38,7 +42,7 @@ import java.util.concurrent.atomic.AtomicInteger * Publishes helm charts */ @Mojo(name = "deploy", defaultPhase = LifecyclePhase.DEPLOY) -class DeployMojo : AbstractMojo() { +class DeployMojo : AbstractMojo(), ServerAuthentication { companion object { private val deployAtEndDeploymentRequests: MutableList = @@ -47,6 +51,9 @@ class DeployMojo : AbstractMojo() { private val readyProjectsCounter: AtomicInteger = AtomicInteger() } + @Component(role = SecDispatcher::class, hint = "default") + override lateinit var securityDispatcher: SecDispatcher + /** * Name of the chart */ @@ -73,6 +80,10 @@ class DeployMojo : AbstractMojo() { @Parameter(property = "chartRepoPassword", required = false) private var chartRepoPassword: String? = null + + @Parameter(property = "chartRepoServerId", required = false) + private var chartRepoServerId: String? = null + @Parameter(property = "skipSnapshots", required = false, defaultValue = "true") private var skipSnapshots: Boolean = true @@ -82,6 +93,9 @@ class DeployMojo : AbstractMojo() { @Parameter(defaultValue = "\${reactorProjects}", required = true, readonly = true) private lateinit var reactorProjects: List + @Parameter(defaultValue = "\${settings}", readonly = true) + override lateinit var settings: Settings + @Parameter(property = "helm.skip", defaultValue = "false") private var skip: Boolean = false @@ -97,6 +111,10 @@ class DeployMojo : AbstractMojo() { } try { + val server by lazy { getServer(chartRepoServerId) } + + val chartRepoUsername = chartRepoUsername ?: server?.username + val chartRepoPassword = chartRepoPassword ?: decryptPassword(server?.password) val chartDeploymentRequest = ChartDeploymentRequest(chartName, chartVersion, chartPublishMethod, chartRepoUrl, chartPublishUrl, diff --git a/src/main/kotlin/com/deviceinsight/helm/LintMojo.kt b/src/main/kotlin/com/deviceinsight/helm/LintMojo.kt index 8bd22e7..00b0f25 100644 --- a/src/main/kotlin/com/deviceinsight/helm/LintMojo.kt +++ b/src/main/kotlin/com/deviceinsight/helm/LintMojo.kt @@ -20,7 +20,6 @@ import org.apache.maven.plugin.MojoExecutionException import org.apache.maven.plugins.annotations.LifecyclePhase import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter -import java.io.File @Mojo(name = "lint", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST) @@ -66,31 +65,10 @@ class LintMojo : ResolveHelmMojo() { command.add(quoteFilePath(project.basedir.resolve(valuesFile!!).absolutePath)) } - executeCommand(command) + executeCmd(command, logStdoutToInfo = true) } catch (e: Exception) { throw MojoExecutionException("Error rendering helm lint: ${e.message}", e) } } - - private fun executeCommand(command: List, directory: File = target()) { - val proc = ProcessBuilder(command) - .directory(directory) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectErrorStream(true) - .start() - - proc.waitFor() - - log.debug("When executing '${command.joinToString(" ")}' in '${directory.absolutePath}', " + - "result was ${proc.exitValue()}") - proc.inputStream.bufferedReader().lines().forEach { - log.info("Output: $it") - } - - if (proc.exitValue() != 0) { - throw RuntimeException( - "When executing '${command.joinToString(" ")}' got result code '${proc.exitValue()}'") - } - } } diff --git a/src/main/kotlin/com/deviceinsight/helm/PackageMojo.kt b/src/main/kotlin/com/deviceinsight/helm/PackageMojo.kt index 2e98c23..2427a58 100644 --- a/src/main/kotlin/com/deviceinsight/helm/PackageMojo.kt +++ b/src/main/kotlin/com/deviceinsight/helm/PackageMojo.kt @@ -16,23 +16,30 @@ package com.deviceinsight.helm +import com.deviceinsight.helm.util.ServerAuthentication import org.apache.maven.plugin.MojoExecutionException +import org.apache.maven.plugins.annotations.Component import org.apache.maven.plugins.annotations.LifecyclePhase import org.apache.maven.plugins.annotations.Mojo import org.apache.maven.plugins.annotations.Parameter +import org.apache.maven.settings.Settings +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher import java.io.File /** * Packages helm charts */ @Mojo(name = "package", defaultPhase = LifecyclePhase.PACKAGE) -class PackageMojo : ResolveHelmMojo() { +class PackageMojo : ResolveHelmMojo(), ServerAuthentication { companion object { private val PLACEHOLDER_REGEX = Regex("""\$\{(.*?)}""") private val SUBSTITUTED_EXTENSIONS = setOf("json", "tpl", "yml", "yaml") } + @Component(role = SecDispatcher::class, hint = "default") + override lateinit var securityDispatcher: SecDispatcher + @Parameter(property = "chartRepoUrl", required = false) private var chartRepoUrl: String? = null @@ -45,6 +52,9 @@ class PackageMojo : ResolveHelmMojo() { @Parameter(property = "chartRepoPassword", required = false) private var chartRepoPassword: String? = null + @Parameter(property = "chartRepoServerId", required = false) + private var chartRepoServerId: String? = null + @Parameter(property = "incubatorRepoUrl", defaultValue = "https://charts.helm.sh/incubator") private var incubatorRepoUrl: String = "https://charts.helm.sh/incubator" @@ -54,6 +64,12 @@ class PackageMojo : ResolveHelmMojo() { @Parameter(property = "addIncubatorRepo", defaultValue = "true") private var addIncubatorRepo: Boolean = true + @Parameter(property = "forceAddRepos", defaultValue = "false") + private var forceAddRepos: Boolean = false + + @Parameter(defaultValue = "\${settings}", readonly = true) + override lateinit var settings: Settings + @Throws(MojoExecutionException::class) override fun execute() { @@ -72,6 +88,7 @@ class PackageMojo : ResolveHelmMojo() { super.execute() val targetHelmDir = File(target(), chartName()) + val isHelm2 = majorHelmVersion() < 3 log.info("Clear target directory to ensure clean target package") if (targetHelmDir.exists()) { @@ -82,22 +99,29 @@ class PackageMojo : ResolveHelmMojo() { processHelmConfigFiles(targetHelmDir) - if (majorHelmVersion() < 3) { - executeCmd("$helm init --client-only --stable-repo-url $stableRepoUrl") + val helmAddFlags = if (isHelm2 || !forceAddRepos) emptyList() else listOf("--force-update") + + if (isHelm2) { + executeCmd(listOf(helm, "init", "--client-only", "--stable-repo-url", stableRepoUrl)) } if (addIncubatorRepo) { - executeCmd("$helm repo add incubator $incubatorRepoUrl") + executeCmd(listOf("helm", "repo", "add", "incubator", incubatorRepoUrl) + helmAddFlags) } if (chartRepoUrl != null) { + val server by lazy { getServer(chartRepoServerId) } + + val chartRepoUsername = chartRepoUsername ?: server?.username + val chartRepoPassword = chartRepoPassword ?: decryptPassword(server?.password) + val authParams = if (chartRepoUsername != null && chartRepoPassword != null) { - " --username $chartRepoUsername --password $chartRepoPassword" + listOf("--username", chartRepoUsername, "--password", chartRepoPassword) } else { - "" + emptyList() } - executeCmd("$helm repo add chartRepo $chartRepoUrl$authParams") + executeCmd(listOf(helm, "repo", "add", "chartRepo", chartRepoUrl!!) + authParams + helmAddFlags) } - executeCmd("$helm dependency update", directory = targetHelmDir) - executeCmd("$helm package ${chartName()} --version $chartVersion") + executeCmd(listOf(helm, "dependency", "update"), directory = targetHelmDir) + executeCmd(listOf(helm, "package", chartName(), "--version", chartVersion)) ensureChartFileExists() @@ -111,8 +135,9 @@ class PackageMojo : ResolveHelmMojo() { val chartTarGzFile = chartTarGzFile() if (!chartTarGzFile.exists()) { - throw RuntimeException("File ${chartTarGzFile.absolutePath} not found. " + - "Chart must be created in package phase first.") + throw RuntimeException( + "File ${chartTarGzFile.absolutePath} not found. Chart must be created in package phase first." + ) } else { log.info("Successfully packaged chart and saved it to: $chartTarGzFile") } @@ -129,7 +154,7 @@ class PackageMojo : ResolveHelmMojo() { parentFile.mkdirs() } - if (!SUBSTITUTED_EXTENSIONS.contains(file.extension.toLowerCase())) { + if (!SUBSTITUTED_EXTENSIONS.contains(file.extension.lowercase())) { file.copyTo(targetFile, true) return@onEach } @@ -146,7 +171,7 @@ class PackageMojo : ResolveHelmMojo() { } } }.forEach { - writer.appendln(it) + writer.appendLine(it) } } } diff --git a/src/main/kotlin/com/deviceinsight/helm/TemplateMojo.kt b/src/main/kotlin/com/deviceinsight/helm/TemplateMojo.kt index ea945cf..2f037fc 100644 --- a/src/main/kotlin/com/deviceinsight/helm/TemplateMojo.kt +++ b/src/main/kotlin/com/deviceinsight/helm/TemplateMojo.kt @@ -59,9 +59,9 @@ class TemplateMojo : ResolveHelmMojo() { val command = if (valuesFile != null) { val valuesFilePath = project.basedir.resolve(valuesFile!!).absolutePath - "$helm template --values ${quoteFilePath(valuesFilePath)} ${chartName()}" + listOf(helm, "template", "--values", valuesFilePath, chartName()) } else { - "$helm template ${chartName()}" + listOf(helm, "template", chartName()) } var file = File(outputFile) diff --git a/src/main/kotlin/com/deviceinsight/helm/util/PlatformDetector.kt b/src/main/kotlin/com/deviceinsight/helm/util/PlatformDetector.kt index 68af9bb..7fc6315 100644 --- a/src/main/kotlin/com/deviceinsight/helm/util/PlatformDetector.kt +++ b/src/main/kotlin/com/deviceinsight/helm/util/PlatformDetector.kt @@ -52,7 +52,7 @@ object PlatformDetector { } private fun normalizeIdentifier(identifier: String): String { - return identifier.toLowerCase(Locale.US).replace(IGNORED_CHARACTERS, "") + return identifier.lowercase(Locale.US).replace(IGNORED_CHARACTERS, "") } } diff --git a/src/main/kotlin/com/deviceinsight/helm/util/ServerAuthentication.kt b/src/main/kotlin/com/deviceinsight/helm/util/ServerAuthentication.kt new file mode 100644 index 0000000..db46c2d --- /dev/null +++ b/src/main/kotlin/com/deviceinsight/helm/util/ServerAuthentication.kt @@ -0,0 +1,35 @@ +package com.deviceinsight.helm.util + +import org.apache.maven.plugin.logging.Log +import org.apache.maven.settings.Server +import org.apache.maven.settings.Settings +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher +import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException + +interface ServerAuthentication { + + val settings: Settings + val securityDispatcher: SecDispatcher + + fun getLog(): Log + + fun getServer(chartRepoServerId: String?): Server? { + if (chartRepoServerId != null) { + val server = settings.getServer(chartRepoServerId) + + if (server != null) { + return server + } + + getLog().warn("No server definition found for $chartRepoServerId in the maven settings.xml server list.") + } + + return null + } + + @Throws(SecDispatcherException::class) + fun decryptPassword(password: String?): String? { + return if (password != null) securityDispatcher.decrypt(password) else null + } + +}