diff --git a/naisful-postgres/README.md b/naisful-postgres/README.md
new file mode 100644
index 0000000..8794dc7
--- /dev/null
+++ b/naisful-postgres/README.md
@@ -0,0 +1,40 @@
+Naisful Postgres
+================
+
+Enkel måte å komme kjapt i gang med en databasetilkobling.
+
+Auto-detecter miljøvariabler og lager JDBC-url for deg.
+
+## Fra miljøvariabler
+```kotlin
+
+// lager jdbc-url fra env
+val jdbcUrl = defaultJdbcUrl()
+```
+
+Miljøvariabler som slutter på `_HOST`, `_USERNAME` osv. vil bli brukt.
+Hvis appen har flere slike miljøvariabler så kan de skilles ved å spesifisere prefix:
+```kotlin
+ConnectionConfigFactory.Env(envVarPrefix = "DB")
+```
+Da vil det søkes etter `DB_HOST`, `DB_USERNAME`, osv.
+
+## Fra mount path
+Hvis du vil laste sql secrets fra mount path så kan de konfigureres slik i nais.yml.
+```yml
+ filesFrom:
+ - secret: google-sql-APP
+ mountPath: /var/run/secrets/sql/APP
+```
+og så gjøre dette i kotlin:
+```kotlin
+// lager jdbc-url fra mount path
+val jdbcUrl = defaultJdbcUrl(ConnectionConfigFactory.MountPath("/var/run/secrets/sql/APP"))
+```
+
+## Google SocketFactory
+
+Det er også en variant som vil konfe opp `com.google.cloud.sql.postgres.SocketFactory` i JDBC-url:
+```kotlin
+val jdbcUrl = jdbcUrlWithGoogleSocketFactory("dbinstance", ConnectionConfigFactory.Env())
+```
\ No newline at end of file
diff --git a/naisful-postgres/build.gradle.kts b/naisful-postgres/build.gradle.kts
new file mode 100644
index 0000000..e69de29
diff --git a/naisful-postgres/src/main/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfig.kt b/naisful-postgres/src/main/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfig.kt
new file mode 100644
index 0000000..de47275
--- /dev/null
+++ b/naisful-postgres/src/main/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfig.kt
@@ -0,0 +1,92 @@
+package com.github.navikt.tbd_libs.naisful.postgres
+
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.plus
+import kotlin.io.path.Path
+import kotlin.io.path.listDirectoryEntries
+import kotlin.io.path.name
+import kotlin.io.path.readText
+
+
+/**
+ * bruker _JDBC_URL hvis den er satt, ellers bygges opp en jdbc-url:
+ * @see nais doc
+ */
+fun defaultJdbcUrl(metode: ConnectionConfigFactory = ConnectionConfigFactory.Env(), options: Map = emptyMap()): String? {
+ return metode.buildJdbcUrl(options)
+}
+
+/**
+ * bruker _JDBC_URL hvis den er satt, ellers bygges opp en jdbc-url med gitte options.
+ * det betyr at socketFactory kun brukes hvis _JDBC_URL ikke finnes
+ *
+ * @see nais doc
+ */
+fun jdbcUrlWithGoogleSocketFactory(databaseInstance: String, metode: ConnectionConfigFactory, gcpTeamProjectId: String = System.getenv("GCP_TEAM_PROJECT_ID"), databaseRegion: String = "europe-north1"): String? {
+ return defaultJdbcUrl(metode, mapOf(
+ "socketFactory" to "com.google.cloud.sql.postgres.SocketFactory",
+ "cloudSqlInstance" to "$gcpTeamProjectId:$databaseRegion:$databaseInstance"
+ ))
+}
+
+sealed class ConnectionConfigFactory {
+ abstract fun buildJdbcUrl(options: Map): String?
+
+
+ data class Env(val env: Map = System.getenv(), val envVarPrefix: String? = null) : ConnectionConfigFactory() {
+ override fun buildJdbcUrl(options: Map): String? {
+ return buildJdbcUrl(options) { key ->
+ env.getKeySuffixOrNull(envVarPrefix, key)
+ }
+ }
+ }
+ data class MountPath(val path: String) : ConnectionConfigFactory() {
+ override fun buildJdbcUrl(options: Map): String? {
+ val secretsPath = Path(path).listDirectoryEntries()
+ return buildJdbcUrl(options) { key ->
+ secretsPath.firstOrNull { it.name.endsWith(key) }?.readText()
+ }
+ }
+ }
+
+ private companion object {
+ private fun buildJdbcUrl(options: Map, strategi: (String) -> String?): String? {
+ val jdbcUrlFromPlatform = strategi("_JDBC_URL")
+ if (jdbcUrlFromPlatform != null) return jdbcUrlFromPlatform
+
+ val hostname = strategi("_HOST") ?: return null
+ val port = strategi("_PORT")?.toInt() ?: return null
+ val databaseName = strategi("_DATABASE") ?: return null
+ val username = strategi("_USERNAME") ?: return null
+ val password = strategi("_PASSWORD") ?: return null
+
+ val sslOptions = buildMap {
+ strategi("_SSLCERT")?.also { this["sslcert"] = it }
+ strategi("_SSLROOTCERT")?.also { this["sslrootcert"] = it }
+ strategi("_SSLKEY_PK8")?.also { this["sslkey"] = it }
+ strategi("_SSLMODE")?.also { this["sslmode"] = it }
+ }
+
+ return buildPostgresCompliantJdbcUrl(hostname, port, databaseName, username, password, options + sslOptions)
+ }
+ }
+}
+
+fun Map.getKeySuffixOrNull(prefix: String?, suffix: String): String? {
+ val searchKey = "${prefix ?: ""}$suffix"
+ return entries.firstOrNull { (k, _) -> k.endsWith(searchKey) }?.value
+}
+
+private fun buildPostgresCompliantJdbcUrl(hostname: String, port: Int, databaseName: String, username: String, password: String, options: Map = emptyMap()): String {
+ val defaultOptions = mapOf(
+ "user" to username,
+ "password" to password
+ )
+ val optionsString = optionsString(defaultOptions + options)
+ return "jdbc:postgresql://$hostname:$port/$databaseName?$optionsString"
+}
+
+private fun optionsString(options: Map): String {
+ return (options).entries.joinToString(separator = "&") { (k, v) -> "$k=$v" }
+}
\ No newline at end of file
diff --git a/naisful-postgres/src/test/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfigEnvTest.kt b/naisful-postgres/src/test/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfigEnvTest.kt
new file mode 100644
index 0000000..7c3f410
--- /dev/null
+++ b/naisful-postgres/src/test/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfigEnvTest.kt
@@ -0,0 +1,80 @@
+package com.github.navikt.tbd_libs.naisful.postgres
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+
+class DataSourceConfigEnvTest {
+
+ @Test
+ fun `default jdbc url`() {
+ val fakeEnv = mapOf(
+ "DB_HOST" to "localhost",
+ "DB_PORT" to "5432",
+ "DB_DATABASE" to "postgres",
+ "DB_USERNAME" to "username",
+ "DB_PASSWORD" to "secret",
+ )
+ val jdbcUrl = defaultJdbcUrl(ConnectionConfigFactory.Env(fakeEnv))
+ assertEquals("jdbc:postgresql://localhost:5432/postgres?user=username&password=secret", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - jdbc_url set`() {
+ val fakeEnv = mapOf(
+ "DB_HOST" to "localhost",
+ "DB_PORT" to "5432",
+ "DB_DATABASE" to "postgres",
+ "DB_USERNAME" to "username",
+ "DB_PASSWORD" to "secret",
+ "DB_JDBC_URL" to "jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar",
+ )
+ val jdbcUrl = defaultJdbcUrl(ConnectionConfigFactory.Env(fakeEnv))
+ assertEquals("jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - env with prefix`() {
+ val fakeEnv = mapOf(
+ "CONFLICTING_HOST" to "remote_ip",
+ "CONFLICTING_PORT" to "2345",
+ "CONFLICTING_DATABASE" to "dev-db",
+ "CONFLICTING_USERNAME" to "willy",
+ "CONFLICTING_PASSWORD" to "wonka",
+
+ "DB_HOST" to "localhost",
+ "DB_PORT" to "5432",
+ "DB_DATABASE" to "postgres",
+ "DB_USERNAME" to "username",
+ "DB_PASSWORD" to "secret",
+ )
+ val jdbcUrl = defaultJdbcUrl(ConnectionConfigFactory.Env(fakeEnv, envVarPrefix = "DB"))
+ assertEquals("jdbc:postgresql://localhost:5432/postgres?user=username&password=secret", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - google factory`() {
+ val fakeEnv = mapOf(
+ "DB_HOST" to "localhost",
+ "DB_PORT" to "5432",
+ "DB_DATABASE" to "postgres",
+ "DB_USERNAME" to "username",
+ "DB_PASSWORD" to "secret",
+ )
+ val jdbcUrl = jdbcUrlWithGoogleSocketFactory("dbinstance", ConnectionConfigFactory.Env(fakeEnv), gcpTeamProjectId = "project_id")
+ assertEquals("jdbc:postgresql://localhost:5432/postgres?user=username&password=secret&socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance=project_id:europe-north1:dbinstance", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - google factory - with jdbc_url set`() {
+ val fakeEnv = mapOf(
+ "DB_HOST" to "localhost",
+ "DB_PORT" to "5432",
+ "DB_DATABASE" to "postgres",
+ "DB_USERNAME" to "username",
+ "DB_PASSWORD" to "secret",
+ "DB_JDBC_URL" to "jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar",
+ )
+ val jdbcUrl = jdbcUrlWithGoogleSocketFactory("dbinstance", ConnectionConfigFactory.Env(fakeEnv), gcpTeamProjectId = "project_id")
+ assertEquals("jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar", jdbcUrl)
+ }
+}
\ No newline at end of file
diff --git a/naisful-postgres/src/test/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfigMountPathTest.kt b/naisful-postgres/src/test/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfigMountPathTest.kt
new file mode 100644
index 0000000..2722350
--- /dev/null
+++ b/naisful-postgres/src/test/kotlin/com/github/navikt/tbd_libs/naisful/postgres/DataSourceConfigMountPathTest.kt
@@ -0,0 +1,56 @@
+package com.github.navikt.tbd_libs.naisful.postgres
+
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.io.TempDir
+import java.nio.file.Path
+import kotlin.io.path.writeText
+
+class DataSourceConfigMountPathTest {
+
+ @Test
+ fun `default jdbc url - env`(@TempDir tempDir: Path) {
+ tempDir.resolve("DB_HOST").writeText("localhost")
+ tempDir.resolve("DB_PORT").writeText("5432")
+ tempDir.resolve("DB_DATABASE").writeText("postgres")
+ tempDir.resolve("DB_USERNAME").writeText("username")
+ tempDir.resolve("DB_PASSWORD").writeText("secret")
+ val jdbcUrl = defaultJdbcUrl(ConnectionConfigFactory.MountPath(tempDir.toString()))
+ assertEquals("jdbc:postgresql://localhost:5432/postgres?user=username&password=secret", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - jdbc_url set`(@TempDir tempDir: Path) {
+ tempDir.resolve("DB_HOST").writeText("localhost")
+ tempDir.resolve("DB_PORT").writeText("5432")
+ tempDir.resolve("DB_DATABASE").writeText("postgres")
+ tempDir.resolve("DB_USERNAME").writeText("username")
+ tempDir.resolve("DB_PASSWORD").writeText("secret")
+ tempDir.resolve("DB_JDBC_URL").writeText("jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar")
+ val jdbcUrl = defaultJdbcUrl(ConnectionConfigFactory.MountPath(tempDir.toString()))
+ assertEquals("jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - google factory`(@TempDir tempDir: Path) {
+ tempDir.resolve("DB_HOST").writeText("localhost")
+ tempDir.resolve("DB_PORT").writeText("5432")
+ tempDir.resolve("DB_DATABASE").writeText("postgres")
+ tempDir.resolve("DB_USERNAME").writeText("username")
+ tempDir.resolve("DB_PASSWORD").writeText("secret")
+ val jdbcUrl = jdbcUrlWithGoogleSocketFactory("dbinstance", ConnectionConfigFactory.MountPath(tempDir.toString()), gcpTeamProjectId = "project_id")
+ assertEquals("jdbc:postgresql://localhost:5432/postgres?user=username&password=secret&socketFactory=com.google.cloud.sql.postgres.SocketFactory&cloudSqlInstance=project_id:europe-north1:dbinstance", jdbcUrl)
+ }
+
+ @Test
+ fun `default jdbc url - google factory - with jdbc_url set`(@TempDir tempDir: Path) {
+ tempDir.resolve("DB_HOST").writeText("localhost")
+ tempDir.resolve("DB_PORT").writeText("5432")
+ tempDir.resolve("DB_DATABASE").writeText("postgres")
+ tempDir.resolve("DB_USERNAME").writeText("username")
+ tempDir.resolve("DB_PASSWORD").writeText("secret")
+ tempDir.resolve("DB_JDBC_URL").writeText("jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar")
+ val jdbcUrl = jdbcUrlWithGoogleSocketFactory("dbinstance", ConnectionConfigFactory.MountPath(tempDir.toString()), gcpTeamProjectId = "project_id")
+ assertEquals("jdbc:postgresql://remote_ip:5432/testdb?user=foo&password=bar", jdbcUrl)
+ }
+}
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index c3e0579..8e63ca6 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -9,6 +9,7 @@ include(
"minimal-soap-client",
"naisful-app",
"naisful-test-app",
+ "naisful-postgres",
"postgres-testdatabaser",
"retry",
"rapids-and-rivers-api",