Skip to content

Commit

Permalink
feat: adapt plugin and test projects to a gradle lazy configuration a…
Browse files Browse the repository at this point in the history
…pproach
  • Loading branch information
Ingwersaft committed Jul 10, 2020
1 parent 1121e8f commit d81ec74
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 108 deletions.
6 changes: 2 additions & 4 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import net.nemerosa.versioning.tasks.VersionDisplayTask

plugins {
kotlin("jvm") version "1.3.61"
`java-gradle-plugin`
id("org.gradle.kotlin.kotlin-dsl") version "1.3.3"
`kotlin-dsl`
`maven-publish`
id("com.gradle.plugin-publish") version "0.10.1"
id("com.gradle.plugin-publish") version "0.11.0"
id("net.nemerosa.versioning") version "2.12.0"
}

Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
20 changes: 11 additions & 9 deletions integration-token/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask

plugins {
java
id("com.liftric.vault-client-plugin") // version known from buildSrc
}
vault {
vaultAddress = "http://localhost:8200"
vaultToken = "myroottoken" // don't do that in production code!
maxRetries = 2
retryIntervalMilliseconds = 200
vaultAddress.set("http://localhost:8200")
vaultToken.set("myroottoken") // don't do that in production code!
maxRetries.set(2)
retryIntervalMilliseconds.set(200)
}
tasks {
val needsSecrets by creating {
val needsSecrets by creating(GetVaultSecretTask::class) {
secretPath.set("secret/example")
doLast {
val secrets: Map<String, String> = project.vault("secret/example")
if (secrets["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secrets["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secrets succeeded!")
val secret = secret.get()
if (secret["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secret["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secret succeeded!")
}
}
val fromBuildSrc by creating {
Expand Down
12 changes: 8 additions & 4 deletions integration-token/buildSrc/src/main/kotlin/Configs.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask
import org.gradle.api.Project
import org.gradle.kotlin.dsl.getByName

object Configs {
fun Project.secretStuff(): String {
val secrets = project.vault("secret/example")
return "${secrets["examplestring"]}:${secrets["exampleint"]}"
val needsSecrets: GetVaultSecretTask = tasks.getByName<GetVaultSecretTask>("needsSecrets").apply {
execute()
}
val secret = needsSecrets.secret.get()
return "${secret["examplestring"]}:${secret["exampleint"]}"
}
}
}
23 changes: 12 additions & 11 deletions integration-tokenfile/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import com.liftric.vault.vault
import com.liftric.vault.GetVaultSecretTask

plugins {
java
id("com.liftric.vault-client-plugin") version "1.0.0-SNAPSHOT"
id("com.liftric.vault-client-plugin")
}
vault {
vaultAddress = "http://localhost:8200"
vaultTokenFilePath = "${projectDir.path}/.vault-token" // don't put the token file on the repo itself like here!
maxRetries = 2
retryIntervalMilliseconds = 200
vaultAddress.set("http://localhost:8200")
vaultTokenFilePath.set("${projectDir.path}/.vault-token") // don't put the token file on the repo itself like here!
maxRetries.set(2)
retryIntervalMilliseconds.set(200)
}
tasks {
val needsSecrets by creating {
val secrets = project.objects.mapProperty<String, String>()
val needsSecrets by creating(GetVaultSecretTask::class) {
secretPath.set("secret/example")
doLast {
secrets.set(project.vault("secret/example"))
if (secrets.get()["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secrets.get()["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secrets succeeded!")
val secret = secret.get()
if (secret["examplestring"] != "helloworld") throw kotlin.IllegalStateException("examplestring couldn't be read")
if (secret["exampleint"]?.toInt() != 1337) throw kotlin.IllegalStateException("exampleint couldn't be read")
println("getting secret succeeded!")
}
}
val build by existing {
Expand Down
12 changes: 12 additions & 0 deletions src/main/kotlin/com/liftric/vault/Defaults.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.liftric.vault

object Defaults {
// values
const val MAX_RETRIES = 5
const val RETRY_INTERVAL_MILLI = 1000

// env vars
const val VAULT_TOKEN_ENV = "VAULT_TOKEN"
const val VAULT_TOKEN_FILE_PATH_ENV = "VAULT_TOKEN_FILE_PATH"
const val VAULT_ADDR_ENV = "VAULT_ADDR"
}
91 changes: 91 additions & 0 deletions src/main/kotlin/com/liftric/vault/GetVaultSecretTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.liftric.vault

import com.liftric.vault.Defaults.MAX_RETRIES
import com.liftric.vault.Defaults.RETRY_INTERVAL_MILLI
import com.liftric.vault.Defaults.VAULT_ADDR_ENV
import com.liftric.vault.Defaults.VAULT_TOKEN_ENV
import com.liftric.vault.Defaults.VAULT_TOKEN_FILE_PATH_ENV
import org.gradle.api.DefaultTask
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.mapProperty
import org.gradle.kotlin.dsl.property
import java.io.File

/**
* See extension for property documentation
*/
open class GetVaultSecretTask : DefaultTask() {
init {
group = "com.liftric.vault"
description = "Fetches a secret from vault."
outputs.upToDateWhen { false }
}

@Input
val secretPath: Property<String> = project.objects.property()

@Input
@Optional
val vaultAddress: Property<String> = project.objects.property()

@Input
@Optional
val vaultToken: Property<String> = project.objects.property()

@Input
@Optional
val vaultTokenFilePath: Property<String> = project.objects.property()

@Input
@Optional
val maxRetries: Property<Int> = project.objects.property()

@Input
@Optional
val retryIntervalMilliseconds: Property<Int> = project.objects.property()

@Internal
// actually used as output...
val secret: MapProperty<String, String> = project.objects.mapProperty()

@TaskAction
fun execute() {
val token = determineToken(vaultToken = vaultToken.orNull, vaultTokenFilePath = vaultTokenFilePath.orNull)
val address = determinAddress(vaultAddress = vaultAddress.orNull)
val maxRetries = maxRetries.getOrElse(MAX_RETRIES)
val retryIntervalMilliseconds = retryIntervalMilliseconds.getOrElse(RETRY_INTERVAL_MILLI)
val path = secretPath.get()
println("[vault] getting `$path` from $address")
secret.set(
VaultClient(
token = token,
vaultAddress = address,
maxRetries = maxRetries,
retryIntervalMilliseconds = retryIntervalMilliseconds
).get(path)
)
}

companion object {
fun determineToken(vaultToken: String?, vaultTokenFilePath: String?): String {
val finalVaultToken = vaultToken ?: System.getenv()[VAULT_TOKEN_ENV]?.trim()
val finalVaultTokenFilePath = vaultTokenFilePath ?: System.getenv()[VAULT_TOKEN_FILE_PATH_ENV]?.trim()
return when {
finalVaultToken != null -> finalVaultToken.trim()
finalVaultTokenFilePath != null -> File(finalVaultTokenFilePath).apply {
if (exists().not()) error("vault token file doesn't exist!")
}.let {
return@let it.readText().trim()
}
else -> error("neither `vaultToken` nor `vaultTokenFilePath` nor `$VAULT_TOKEN_FILE_PATH_ENV` env var nor `$VAULT_TOKEN_ENV` env var provided!")
}
}

fun determinAddress(vaultAddress: String?): String {
val finalVaultAddress = vaultAddress ?: System.getenv()[VAULT_ADDR_ENV]
return finalVaultAddress?.trim() ?: error("neither `vaultAddress` nor `$VAULT_ADDR_ENV` env var provided!")
}
}
}
72 changes: 55 additions & 17 deletions src/main/kotlin/com/liftric/vault/VaultClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,70 @@ package com.liftric.vault

import com.bettercloud.vault.Vault
import com.bettercloud.vault.VaultConfig
import com.bettercloud.vault.VaultException

/**
* Actual client connecting to the configured vault server
*/
class VaultClient(
private val extension: VaultClientExtension,
token: String
token: String,
private val vaultAddress: String,
private val maxRetries: Int,
private val retryIntervalMilliseconds: Int
) {
private val config by lazy {
VaultConfig()
.address(extension.vaultAddress)
.token(token)
.build()
try {
VaultConfig()
.address(vaultAddress)
.token(token)
.build()
} catch (e: VaultException) {
println("[vault] exception while creating vault client for $vaultAddress: ${e.message}")
throw e
}
}
private val vault by lazy {
try {
Vault(config)
} catch (e: VaultException) {
println(
"[vault] exception while preparing vault client config for $vaultAddress: ${e.message} - token valid?"
.replace('\n', '#')
)
throw e
}
}

fun get(secretPath: String): Map<String, String> {
verifyTokenValid()
return try {
vault.withRetries(maxRetries, retryIntervalMilliseconds)
.logical()
.read(secretPath)
.data.also {
if (it.isEmpty()) error("[vault] secret response contains no data - secret exists? token has correct rights to access it?")
}
} catch (e: VaultException) {
println(
"[vault] exception while calling vault at $vaultAddress: ${e.message} - secret exists? token has correct rights to access it?"
.replace('\n', '#')
)
if (vaultAddress.startsWith("https").not()) {
println("[vault] is your vault address correct? It doesn't start with https!")
}
throw e
}
}
private val vault by lazy { Vault(config) }

fun get(secretPath: String): Map<String, String> = try {
vault.withRetries(extension.maxRetries, extension.retryIntervalMilliseconds)
.logical()
.read(secretPath)
.data
} catch (e: Exception) {
println("[vault] exception while calling vault at ${extension.vaultAddress}: ${e.message}")
if (extension.vaultAddress?.startsWith("https") == false) {
println("[vault] is your vault address correct? It doesn't start with https!")
private fun verifyTokenValid() {
try {
vault.auth().lookupSelf()
} catch (e: VaultException) {
println(
"[vault] exception while verifying vault token validity for $vaultAddress: ${e.message}"
.replace('\n', '#')
)
throw e
}
throw e
}
}
45 changes: 39 additions & 6 deletions src/main/kotlin/com/liftric/vault/VaultClientExtension.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,44 @@
package com.liftric.vault

import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
import org.gradle.kotlin.dsl.property

open class VaultClientExtension(val project: Project) {
var vaultAddress: String? = System.getenv()["VAULT_ADDR"]
var vaultToken: String? = System.getenv()["VAULT_TOKEN"]
var vaultTokenFilePath: String? = System.getenv()["VAULT_TOKEN_FILE_PATH"]
var maxRetries: Int = 5
var retryIntervalMilliseconds = 1000
open class VaultClientExtension(project: Project) {
/**
* vault address to be used. will check env var `VAULT_ADDR` if unset
*/
@Input
@Optional
val vaultAddress: Property<String> = project.objects.property()

/**
* vault token to be used (it's recommend you don't set this in your build file). will check env var `VAULT_TOKEN` if unset
*/
@Input
@Optional
val vaultToken: Property<String> = project.objects.property()

/**
* vault token file path (if set has precedence over vaultToken). will check env var `VAULT_TOKEN_FILE_PATH` if unset
*/
@Input
@Optional
val vaultTokenFilePath: Property<String> = project.objects.property()

/**
* vault client max retry count
*/
@Input
@Optional
val maxRetries: Property<Int> = project.objects.property()

/**
* time between vault request retries
*/
@Input
@Optional
val retryIntervalMilliseconds: Property<Int> = project.objects.property()
}
Loading

0 comments on commit d81ec74

Please sign in to comment.