generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add (hacky) support for config and secrets
This currently loads from environment variables `FTL_{CONFIG,SECRET}_<MODULE>_<KEY>`, which will eventually be delivered by the Runner.
- Loading branch information
1 parent
3ae98d0
commit 7e6f705
Showing
10 changed files
with
197 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package sdk | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"runtime" | ||
"strings" | ||
) | ||
|
||
// ConfigType is a type that can be used as a configuration value. | ||
// | ||
// Supported types are currently limited, but will eventually be extended to | ||
// allow any type that FTL supports, including structs. | ||
type ConfigType interface { | ||
string | int | float64 | bool | | ||
[]string | []int | []float64 | []bool | | ||
map[string]string | map[string]int | map[string]float64 | map[string]bool | ||
} | ||
|
||
// Config declares a typed configuration key for the current module. | ||
func Config[Type ConfigType](name string) ConfigValue[Type] { | ||
module := callerModule() | ||
return ConfigValue[Type]{module, name} | ||
} | ||
|
||
// ConfigValue is a typed configuration key for the current module. | ||
type ConfigValue[Type ConfigType] struct { | ||
module string | ||
name string | ||
} | ||
|
||
// Get returns the value of the configuration key from FTL. | ||
func (c *ConfigValue[Type]) Get() (out Type) { | ||
value, ok := os.LookupEnv(fmt.Sprintf("FTL_CONFIG_%s_%s", strings.ToUpper(c.module), strings.ToUpper(c.name))) | ||
if !ok { | ||
return out | ||
} | ||
if err := json.Unmarshal([]byte(value), &out); err != nil { | ||
panic(fmt.Errorf("failed to parse config value %s: %w", value, err)) | ||
} | ||
return | ||
} | ||
|
||
func callerModule() string { | ||
pc, _, _, ok := runtime.Caller(2) | ||
if !ok { | ||
panic("failed to get caller") | ||
} | ||
details := runtime.FuncForPC(pc) | ||
if details == nil { | ||
panic("failed to get caller") | ||
} | ||
module := details.Name() | ||
if strings.HasPrefix(module, "github.com/TBD54566975/ftl/go-runtime/sdk") { | ||
return "testing" | ||
} | ||
if !strings.HasPrefix(module, "ftl/") { | ||
panic(fmt.Sprintf("must be called from an FTL module not %s", module)) | ||
} | ||
return strings.Split(strings.Split(module, "/")[1], ".")[0] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package sdk | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/alecthomas/assert/v2" | ||
) | ||
|
||
func TestConfig(t *testing.T) { | ||
t.Setenv("FTL_CONFIG_TESTING_TEST", `["one", "two", "three"]`) | ||
config := Config[[]string]("test") | ||
assert.Equal(t, []string{"one", "two", "three"}, config.Get()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package sdk | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
"strings" | ||
) | ||
|
||
// SecretType is a type that can be used as a secret value. | ||
// | ||
// Supported types are currently limited, but will eventually be extended to | ||
// allow any type that FTL supports, including structs. | ||
type SecretType interface { | ||
string | int | float64 | bool | | ||
[]string | []int | []float64 | []bool | | ||
map[string]string | map[string]int | map[string]float64 | map[string]bool | ||
} | ||
|
||
// Secret declares a typed secret for the current module. | ||
func Secret[Type SecretType](name string) SecretValue[Type] { | ||
module := callerModule() | ||
return SecretValue[Type]{module, name} | ||
} | ||
|
||
// SecretValue is a typed secret for the current module. | ||
type SecretValue[Type SecretType] struct { | ||
module string | ||
name string | ||
} | ||
|
||
// Get returns the value of the secret from FTL. | ||
func (c *SecretValue[Type]) Get() (out Type) { | ||
value, ok := os.LookupEnv(fmt.Sprintf("FTL_SECRET_%s_%s", strings.ToUpper(c.module), strings.ToUpper(c.name))) | ||
if !ok { | ||
return out | ||
} | ||
if err := json.Unmarshal([]byte(value), &out); err != nil { | ||
panic(fmt.Errorf("failed to parse secret %s.%s: %w", c.module, c.name, err)) | ||
} | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/config/Config.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package xyz.block.ftl.config | ||
|
||
import xyz.block.ftl.serializer.makeGson | ||
|
||
class Secret<T>(val name: String) { | ||
val _module: String | ||
val _gson = makeGson() | ||
|
||
init { | ||
val caller = Thread.currentThread().stackTrace[2].className | ||
require(caller.startsWith("ftl.") || caller.startsWith("xyz.block.ftl.config.")) { "Config must be defined in an FTL module not $caller" } | ||
val parts = caller.split(".") | ||
_module = parts[parts.size - 2] | ||
} | ||
|
||
inline fun <reified T> get(): T { | ||
val key = "FTL_CONFIG_${_module.uppercase()}_${name.uppercase()}" | ||
val value = System.getenv(key) ?: throw Exception("Config key ${_module}.${name} not found") | ||
return _gson.fromJson(value, T::class.java) | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/config/ConfigTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package xyz.block.ftl.config | ||
|
||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Test | ||
import org.junitpioneer.jupiter.SetEnvironmentVariable | ||
|
||
class ConfigTest { | ||
@Test | ||
@SetEnvironmentVariable(key = "FTL_SECRET_SECRETS_TEST", value = "testingtesting") | ||
fun testSecret() { | ||
val secret = Secret<String>("test") | ||
assertEquals("testingtesting", secret.get()) | ||
} | ||
} |
26 changes: 15 additions & 11 deletions
26
kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/secrets/Secrets.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,21 @@ | ||
package xyz.block.ftl.secrets | ||
|
||
object Secrets { | ||
private const val FTL_SECRETS_ENV_VAR_PREFIX = "FTL_SECRET_" | ||
import xyz.block.ftl.serializer.makeGson | ||
|
||
fun get(name: String): String { | ||
if (!name.startsWith(FTL_SECRETS_ENV_VAR_PREFIX)) { | ||
throw Exception("Invalid secret name; must start with $FTL_SECRETS_ENV_VAR_PREFIX") | ||
} | ||
class Secret<T>(val name: String) { | ||
val module: String | ||
val gson = makeGson() | ||
|
||
return try { | ||
System.getenv(name) | ||
} catch (e: Exception) { | ||
throw Exception("Secret $name not found") | ||
} | ||
init { | ||
val caller = Thread.currentThread().getStackTrace()[2].className | ||
require(caller.startsWith("ftl.") || caller.startsWith("xyz.block.ftl.secrets.")) { "Secrets must be defined in an FTL module not ${caller}" } | ||
val parts = caller.split(".") | ||
module = parts[parts.size - 2] | ||
} | ||
|
||
inline fun <reified T> get(): T { | ||
val key = "FTL_SECRET_${module.uppercase()}_${name.uppercase()}" | ||
val value = System.getenv(key) ?: throw Exception("Secret ${module}.${name} not found") | ||
return gson.fromJson(value, T::class.java) | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
kotlin-runtime/ftl-runtime/src/test/kotlin/xyz/block/ftl/secrets/SecretTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package xyz.block.ftl.secrets | ||
|
||
import org.junit.jupiter.api.Assertions.* | ||
import org.junit.jupiter.api.Test | ||
import org.junitpioneer.jupiter.SetEnvironmentVariable | ||
|
||
class SecretTest { | ||
@Test | ||
@SetEnvironmentVariable(key = "FTL_SECRET_SECRETS_TEST", value = "testingtesting") | ||
fun testSecret() { | ||
val secret = Secret<String>("test") | ||
assertEquals("testingtesting", secret.get()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters