From f39d531f40e3c0643c0b2c6096e5e4298230e1ad Mon Sep 17 00:00:00 2001 From: Erik Rasmussen Date: Thu, 8 Jul 2021 09:03:17 -0600 Subject: [PATCH] Implement withVault (#26) * Update version * Add Vault support. * Remove WrapPass. * Adding tests. * Formatting updates and unit tests. * spotlessApply * Added javadoc. Co-authored-by: Release Automation --- .../pipelinekt/core/secrets/Secrets.kt | 5 ++ .../pipelinekt/core/secrets/VaultSecret.kt | 14 +++++ .../pipelinekt/core/secrets/VaultSecrets.kt | 28 ++++++++++ .../pipelinekt/core/secrets/SecretsTest.kt | 43 +++++++++++++++ .../dsl/step/declarative/WithVaultDsl.kt | 11 ++++ .../internal/step/declarative/WithVault.kt | 19 +++++++ .../step/declarative/WithVaultTest.kt | 52 +++++++++++++++++++ version.txt | 2 +- 8 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/Secrets.kt create mode 100644 core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecret.kt create mode 100644 core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecrets.kt create mode 100644 core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/secrets/SecretsTest.kt create mode 100644 dsl/src/main/kotlin/com/code42/jenkins/pipelinekt/dsl/step/declarative/WithVaultDsl.kt create mode 100644 internal/src/main/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVault.kt create mode 100644 internal/src/test/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVaultTest.kt diff --git a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/Secrets.kt b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/Secrets.kt new file mode 100644 index 00000000..04d6c550 --- /dev/null +++ b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/Secrets.kt @@ -0,0 +1,5 @@ +package com.code42.jenkins.pipelinekt.core.secrets + +interface Secrets { + fun toGroovy(): String +} diff --git a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecret.kt b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecret.kt new file mode 100644 index 00000000..058c1b30 --- /dev/null +++ b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecret.kt @@ -0,0 +1,14 @@ +package com.code42.jenkins.pipelinekt.core.secrets + +/** + * Represents a secret retrieved from Vault that will be available to steps executed + * in the same context. + * + * @param envVar The environment variable that will store the value of the secret. + * @param vaultKey The key that will be retrieved from Vault containing the secret. + */ +data class VaultSecret(val envVar: String, val vaultKey: String) { + fun toGroovy(): String { + return "[envVar: '$envVar', vaultKey: '$vaultKey']" + } +} diff --git a/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecrets.kt b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecrets.kt new file mode 100644 index 00000000..038424a1 --- /dev/null +++ b/core/src/main/kotlin/com/code42/jenkins/pipelinekt/core/secrets/VaultSecrets.kt @@ -0,0 +1,28 @@ +package com.code42.jenkins.pipelinekt.core.secrets + +/** + * The Vault secrets that will be made available to steps executed in the same context. + * @param path The path in vault where secrets will be retrieved from. + * @param engineVersion The engine version that Vault is storing secrets in on the path specified. + * @param secrets The list of VaultSecret objects that will be retrieved and stored in the environment. + */ +data class VaultSecrets( + val path: String, + val engineVersion: String, + val secrets: List +) : + Secrets { + override fun toGroovy(): String { + val builder = StringBuilder() + builder.appendln(" vaultSecrets: [[path: '$path', engineVersion: $engineVersion, secretValues: [") + val listIterator = secrets.listIterator() + while (listIterator.hasNext()) { + builder.append( + " " + listIterator.next().toGroovy() + + (if (listIterator.hasNext()) ",\n" else "\n") + ) + } + builder.append(" ]]]") + return builder.toString() + } +} diff --git a/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/secrets/SecretsTest.kt b/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/secrets/SecretsTest.kt new file mode 100644 index 00000000..3d892fcd --- /dev/null +++ b/core/src/test/kotlin/com/code42/jenkins/pipelinekt/core/secrets/SecretsTest.kt @@ -0,0 +1,43 @@ +package com.code42.jenkins.pipelinekt.core.secrets + +import com.code42.jenkins.pipelinekt.core.GroovyScriptTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class SecretsTest : GroovyScriptTest() { + @Test + fun secretsBlockTest_singleVars() { + val secrets1 = VaultSecrets( + path = "some/vault/path", + engineVersion = "1", + secrets = listOf( + VaultSecret(envVar = "VAR_1", vaultKey = "KEY_1") + ) + ) + + val expected = " vaultSecrets: [[path: 'some/vault/path', engineVersion: 1, secretValues: [\n" + + " [envVar: 'VAR_1', vaultKey: 'KEY_1']\n" + + " ]]]" + val out = secrets1.toGroovy() + assertEquals(expected, out) + } + + @Test + fun secretsBlockTest_multipleVars() { + val secrets1 = VaultSecrets( + path = "some/vault/path", + engineVersion = "1", + secrets = listOf( + VaultSecret(envVar = "VAR_1", vaultKey = "KEY_1"), + VaultSecret(envVar = "VAR_2", vaultKey = "KEY_2") + ) + ) + + val expected = " vaultSecrets: [[path: 'some/vault/path', engineVersion: 1, secretValues: [\n" + + " [envVar: 'VAR_1', vaultKey: 'KEY_1'],\n" + + " [envVar: 'VAR_2', vaultKey: 'KEY_2']\n" + + " ]]]" + val out = secrets1.toGroovy() + assertEquals(expected, out) + } +} diff --git a/dsl/src/main/kotlin/com/code42/jenkins/pipelinekt/dsl/step/declarative/WithVaultDsl.kt b/dsl/src/main/kotlin/com/code42/jenkins/pipelinekt/dsl/step/declarative/WithVaultDsl.kt new file mode 100644 index 00000000..cbf3ad91 --- /dev/null +++ b/dsl/src/main/kotlin/com/code42/jenkins/pipelinekt/dsl/step/declarative/WithVaultDsl.kt @@ -0,0 +1,11 @@ +package com.code42.jenkins.pipelinekt.dsl.step.declarative + +import com.code42.jenkins.pipelinekt.core.secrets.Secrets +import com.code42.jenkins.pipelinekt.core.step.Step +import com.code42.jenkins.pipelinekt.core.writer.ext.toStep +import com.code42.jenkins.pipelinekt.dsl.DslContext +import com.code42.jenkins.pipelinekt.internal.step.declarative.WithVault + +fun DslContext.withVault(secrets: Secrets, steps: DslContext.() -> Unit) { + add(WithVault(secrets, DslContext.into(steps).toStep())) +} diff --git a/internal/src/main/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVault.kt b/internal/src/main/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVault.kt new file mode 100644 index 00000000..d494bc95 --- /dev/null +++ b/internal/src/main/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVault.kt @@ -0,0 +1,19 @@ +package com.code42.jenkins.pipelinekt.internal.step.declarative + +import com.code42.jenkins.pipelinekt.core.secrets.Secrets +import com.code42.jenkins.pipelinekt.core.step.DeclarativeStep +import com.code42.jenkins.pipelinekt.core.step.NestedStep +import com.code42.jenkins.pipelinekt.core.step.Step +import com.code42.jenkins.pipelinekt.core.writer.GroovyWriter + +/** + * Make Secrets from Vault available to steps within the block. + * + * @param secrets the list secrets made available to the steps + * @param steps the steps to inject + */ +data class WithVault(val secrets: Secrets, override val steps: Step) : DeclarativeStep, NestedStep { + override fun toGroovy(writer: GroovyWriter) { + writer.closure(listOf("withVault([") + secrets.toGroovy() + "])", steps::toGroovy) + } +} diff --git a/internal/src/test/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVaultTest.kt b/internal/src/test/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVaultTest.kt new file mode 100644 index 00000000..4fca3dfd --- /dev/null +++ b/internal/src/test/kotlin/com/code42/jenkins/pipelinekt/internal/step/declarative/WithVaultTest.kt @@ -0,0 +1,52 @@ +package com.code42.jenkins.pipelinekt.internal.step.declarative + +import com.code42.jenkins.pipelinekt.GroovyScriptTest +import com.code42.jenkins.pipelinekt.core.secrets.VaultSecret +import com.code42.jenkins.pipelinekt.core.secrets.VaultSecrets +import com.code42.jenkins.pipelinekt.core.vars.ext.strDouble +import kotlin.test.assertEquals +import org.junit.Test + +class WithVaultTest : GroovyScriptTest() { + @Test + fun withVaultBlockTest_singleVar() { + val expected1 = "withVault([\n" + + " vaultSecrets: [[path: 'some/vault/path', engineVersion: 1, secretValues: [\n" + + " [envVar: 'ENV_VAR', vaultKey: 'VAULT_KEY']\n" + + " ]]]\n" + + "]) {\n" + + "${indentStr}sh (script: \"echo testing...\", returnStdout: false)\n" + + "}\n" + val secrets1 = VaultSecrets( + path = "some/vault/path", + engineVersion = "1", + secrets = listOf( + VaultSecret(envVar = "ENV_VAR", vaultKey = "VAULT_KEY") + ) + ) + WithVault(secrets = secrets1, steps = Sh("echo testing...".strDouble())).toGroovy(writer) + assertEquals(expected1, out.toString()) + } + + @Test + fun withVaultBlockTest_multipleVars() { + val expected1 = "withVault([\n" + + " vaultSecrets: [[path: 'some/vault/path', engineVersion: 1, secretValues: [\n" + + " [envVar: 'ENV_VAR1', vaultKey: 'VAULT_KEY1'],\n" + + " [envVar: 'ENV_VAR2', vaultKey: 'VAULT_KEY2']\n" + + " ]]]\n" + + "]) {\n" + + "${indentStr}sh (script: \"echo testing...\", returnStdout: false)\n" + + "}\n" + val secrets1 = VaultSecrets( + path = "some/vault/path", + engineVersion = "1", + secrets = listOf( + VaultSecret(envVar = "ENV_VAR1", vaultKey = "VAULT_KEY1"), + VaultSecret(envVar = "ENV_VAR2", vaultKey = "VAULT_KEY2") + ) + ) + WithVault(secrets = secrets1, steps = Sh("echo testing...".strDouble())).toGroovy(writer) + assertEquals(expected1, out.toString()) + } +} diff --git a/version.txt b/version.txt index de564aec..4fc63a3d 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.18.3 \ No newline at end of file +0.18.4 \ No newline at end of file