Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
9288373
Update log4cats-slf4j to 2.4.0
Jul 13, 2022
245e06b
Update http4s-ember-client to 0.23.14
Jul 26, 2022
0c489ed
Update secretsmanager to 2.17.257
Aug 20, 2022
72b55ce
Update shapeless to 2.3.13
Mar 3, 2025
d2b85ff
Update circe-generic, circe-literal, ... to 0.14.4
Feb 9, 2023
4da3d65
Update log4j-slf4j-impl to 2.25.2
Sep 22, 2025
20af401
Update http4s-ember-client to 0.23.20
Jun 13, 2023
37f7e8f
Update skunk-core to 0.3.2
Oct 6, 2022
34c3bc6
Update log4cats-slf4j to 2.7.1
May 26, 2025
2cdbe40
Update scala-library to 2.13.15
Sep 25, 2024
020b689
Update secretsmanager to 2.17.295
Oct 20, 2022
61133c7
Update sbt-ci-release to 1.9.0
Oct 21, 2024
855e983
Update sbt to 1.7.3
Oct 31, 2022
13942bf
Update sbt-native-packager to 1.9.16
Feb 20, 2023
8c29b28
Update http4s-ember-client to 0.23.21
Jun 17, 2023
958ad87
Update fs2-aws-java-sdk2 to 3.0.0-RC2
Jun 30, 2023
96de415
Update sbt-tpolecat to 0.4.4
Jul 11, 2023
4774841
Update aws-lambda-java-log4j2 to 1.6.0
Nov 3, 2023
cb9ab69
Update circe-generic, circe-literal, ... to 0.14.9
Jul 1, 2024
f8563fe
Update natchez-http4s to 0.6.0
Aug 29, 2024
d0a9e88
Update newtypes-circe-v0-14, ... to 0.3.0
Sep 7, 2024
6aee74f
Update feral-lambda-cloudformation-custom-resource to 0.3.1
Sep 27, 2024
a1a3da1
Update munit to 1.0.3
Dec 2, 2024
96a5feb
Update natchez-http4s to 0.6.1
Jan 17, 2025
0ea3643
Update munit to 1.1.0
Jan 21, 2025
620e818
Update cats-tagless-macros to 0.16.3
Jan 22, 2025
0fc4862
Update natchez-xray to 0.3.8
May 2, 2025
3ad5dd7
Update sbt-ci-release to 1.11.2
Aug 17, 2025
5e52206
Update munit-scalacheck to 1.2.0
Sep 9, 2025
61a5452
Update sbt-github-actions to 0.28.0
Sep 11, 2025
7fa4b22
Update http4s-ember-client to 0.23.32
Sep 24, 2025
2e6d8bc
Update circe-generic, circe-literal, ... to 0.14.15
Sep 30, 2025
460c755
Update kind-projector to 0.13.4
Sep 30, 2025
9da1b12
Update scala-library to 2.13.17
Oct 7, 2025
df20445
Update munit to 1.2.1
Oct 13, 2025
02aca1c
Update sbt to 1.11.7
bpholt Nov 11, 2025
bbcaf37
switch to sbt-typelevel
bpholt Nov 11, 2025
8124135
separate versions of munit and munit-scalacheck
bpholt Nov 11, 2025
1e6b4a2
separate circe-refined version
bpholt Nov 11, 2025
1d8a366
Update skunk to 0.6.4
bpholt Nov 11, 2025
bc28091
Remove fs2-aws-java-sdk2, which is no longer maintained
bpholt Nov 11, 2025
5d21220
Add Smithy4s SecretsManager client and finish updating the code to wo…
bpholt Nov 11, 2025
79e77dd
add LocalApp as a way to run the code locally
bpholt Nov 12, 2025
43161d9
redact MasterDatabasePassword in traces
bpholt Nov 12, 2025
26115ec
use ip4s for parsing hostnames and ports
bpholt Nov 12, 2025
330261f
refactor PostgresqlDatabaseInitHandlerImpl to make trace instrumentat…
bpholt Nov 12, 2025
c2407e7
build and execute on Java 17
bpholt Nov 13, 2025
6d2fa88
Update to Scala 3.7.4
bpholt Nov 13, 2025
6de16c0
more Scala 3 syntax
bpholt Nov 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dwollaci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ stages:
build:
nodeLabel: sbt
steps:
- sbt universal:packageBin
- sbt Universal/packageBin
filesToStash:
- '**'
publish:
Expand Down
52 changes: 24 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,45 @@ on:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


concurrency:
group: ${{ github.workflow }} @ ${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build and Test
name: Test
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.8]
java: [temurin@8, temurin@11]
os: [ubuntu-22.04]
scala: [3]
java: [temurin@17]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
steps:
- name: Checkout current branch (full)
uses: actions/checkout@v2
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup Java (temurin@8)
if: matrix.java == 'temurin@8'
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 8
- name: Setup sbt
uses: sbt/setup-sbt@v1

- name: Setup Java (temurin@11)
if: matrix.java == 'temurin@11'
uses: actions/setup-java@v2
- name: Setup Java (temurin@17)
id: setup-java-temurin-17
if: matrix.java == 'temurin@17'
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 11
java-version: 17
cache: sbt

- name: Cache sbt
uses: actions/cache@v2
with:
path: |
~/.sbt
~/.ivy2/cache
~/.coursier/cache/v1
~/.cache/coursier/v1
~/AppData/Local/Coursier/Cache/v1
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}
- name: sbt update
if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false'
run: sbt +update

- name: Check that workflows are up to date
run: sbt ++${{ matrix.scala }} githubWorkflowCheck
run: sbt githubWorkflowCheck

- name: Build project
run: sbt ++${{ matrix.scala }} test
run: sbt '++ ${{ matrix.scala }}' test
48 changes: 21 additions & 27 deletions .mergify.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,23 @@
queue_rules:
- name: default
conditions:
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8)
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@11)
# This file was automatically generated by sbt-typelevel-mergify using the
# mergifyGenerate task. You should add and commit this file to
# your git repository. It goes without saying that you shouldn't edit
# this file by hand! Instead, if you wish to make changes, you should
# change your sbt build configuration to revise the mergify configuration
# to meet your needs, then regenerate this file.

pull_request_rules:
- name: assign and label scala-steward's PRs
conditions:
- author=dwolla-oss-scala-steward[bot]
actions:
label:
add: [dependency-update]
- name: automatic update pull requests
conditions:
- author=dwolla-oss-scala-steward[bot]
- -conflict # skip PRs with conflicts
- -draft # filter-out GH draft PRs
actions:
update:
- name: merge scala-steward's PRs
conditions:
- author=dwolla-oss-scala-steward[bot]
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8)
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@11)
actions:
queue:
method: squash
name: default
- name: merge scala-steward's PRs
conditions:
- author=scala-steward
- body~=labels:.*early-semver-patch
- status-success=Test (ubuntu-22.04, 3, temurin@17)
actions:
merge: {}
- name: Label smithy PRs
conditions:
- files~=^smithy/
actions:
label:
add:
- smithy
remove: []
67 changes: 41 additions & 26 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ ThisBuild / organization := "com.dwolla"
ThisBuild / description := "CloudFormation custom resource to initialize a PostgreSQL database with a new user"
ThisBuild / homepage := Some(url("https://github.com/Dwolla/postgresql-init-custom-resource"))
ThisBuild / licenses += ("MIT", url("https://opensource.org/licenses/MIT"))
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / scalaVersion := "3.7.4"
ThisBuild / developers := List(
Developer(
"bpholt",
Expand All @@ -12,52 +12,67 @@ ThisBuild / developers := List(
),
)
ThisBuild / startYear := Option(2021)
ThisBuild / libraryDependencies ++= Seq(
compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full),
compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"),
)
ThisBuild / resolvers += Resolver.sonatypeCentralSnapshots

lazy val smithy = (project in file("smithy"))
.enablePlugins(Smithy4sCodegenPlugin)
.settings(
libraryDependencies ++= Seq(
"com.dwolla" %% "natchez-smithy4s" % "0.1.1",
"com.disneystreaming.smithy4s" %% "smithy4s-core" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %% "smithy4s-http4s" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %% "smithy4s-cats" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %% "smithy4s-json" % smithy4sVersion.value,
"com.disneystreaming.smithy4s" %% "smithy4s-aws-http4s" % smithy4sVersion.value
),
smithy4sAwsSpecs ++= Seq(AWS.secretsManager)
)

ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("8"), JavaSpec.temurin("11"))
ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("17"))
ThisBuild / githubWorkflowTargetTags ++= Seq("v*")
ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty
ThisBuild / githubWorkflowPublish := Seq.empty

lazy val munitV = "0.7.29"
lazy val circeV = "0.14.2"
lazy val circeV = "0.14.15"

lazy val `postgresql-init-core` = (project in file("."))
.settings(
maintainer := developers.value.head.email,
topLevelDirectory := None,
libraryDependencies ++= {
val natchezVersion = "0.1.6"
val feralVersion = "0.1.0-M13"
val natchezVersion = "0.3.8"
val feralVersion = "0.3.1-79-260ee83-SNAPSHOT"

Seq(
"org.typelevel" %% "feral-lambda-cloudformation-custom-resource" % feralVersion,
"org.tpolecat" %% "natchez-xray" % natchezVersion,
"org.tpolecat" %% "natchez-http4s" % "0.3.2",
"org.typelevel" %% "cats-tagless-macros" % "0.14.0",
"org.http4s" %% "http4s-ember-client" % "0.23.13",
"org.tpolecat" %% "natchez-http4s" % "0.6.1",
"org.typelevel" %% "cats-tagless-core" % "0.16.3-85-591274f-SNAPSHOT", // see https://github.com/typelevel/cats-tagless/issues/652
"org.http4s" %% "http4s-ember-client" % "0.23.32",
"io.circe" %% "circe-parser" % circeV,
"io.circe" %% "circe-generic" % circeV,
"io.circe" %% "circe-refined" % circeV,
"io.monix" %% "newtypes-core" % "0.2.3",
"io.monix" %% "newtypes-circe-v0-14" % "0.2.3",
"org.tpolecat" %% "skunk-core" % "0.3.1",
"org.typelevel" %% "log4cats-slf4j" % "2.3.2",
"com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.1",
"org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.18.0",
"com.chuusai" %% "shapeless" % "2.3.9",
"com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC1",
"software.amazon.awssdk" % "secretsmanager" % "2.17.229",
"org.scalameta" %% "munit" % munitV % Test,
"org.scalameta" %% "munit-scalacheck" % munitV % Test,
"io.circe" %% "circe-literal" % circeV,
"io.circe" %% "circe-refined" % "0.14.9",
"io.monix" %% "newtypes-core" % "0.3.0",
"io.monix" %% "newtypes-circe-v0-14" % "0.3.0",
"org.tpolecat" %% "skunk-core" % "0.6.4",
"org.typelevel" %% "log4cats-slf4j" % "2.7.1",
"com.amazonaws" % "aws-lambda-java-log4j2" % "1.6.0",
"org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2",
"com.dwolla" %% "natchez-tagless" % "0.2.6-131-d6a1c7c-SNAPSHOT",
"org.typelevel" %% "mouse" % "1.4.0",
"com.comcast" %% "ip4s-core" % "3.7.0",
"org.scalameta" %% "munit" % "1.2.1" % Test,
"org.scalameta" %% "munit-scalacheck" % "1.2.0" % Test,
"io.circe" %% "circe-literal" % circeV % Test,
"com.dwolla" %% "dwolla-otel-natchez" % "0.2.8" % Test,
)
},
buildInfoPackage := "com.dwolla.buildinfo.postgres.init",
buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion),
)
.enablePlugins(UniversalPlugin, JavaAppPackaging)
.dependsOn(smithy)
.enablePlugins(UniversalPlugin, JavaAppPackaging, BuildInfoPlugin)

lazy val serverlessDeployCommand = settingKey[Seq[String]]("serverless command to deploy the application")
serverlessDeployCommand := "serverless deploy --verbose".split(' ').toSeq
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version = 1.7.1
sbt.version = 1.11.7
9 changes: 5 additions & 4 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.3.3")
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10")
addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9")
addSbtPlugin("org.typelevel" % "sbt-typelevel-settings" % "0.8.2")
addSbtPlugin("org.typelevel" % "sbt-typelevel-mergify" % "0.8.2")
addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16")
addSbtPlugin("com.disneystreaming.smithy4s" % "smithy4s-sbt-codegen" % "0.18.43")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1")
2 changes: 1 addition & 1 deletion serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ variablesResolutionMode: 20210326

provider:
name: aws
runtime: java11
runtime: java17
memorySize: 1024
timeout: 60
region: us-west-2
Expand Down
19 changes: 19 additions & 0 deletions smithy/src/main/smithy/secrets-manager.smithy
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
$version: "2.0"

namespace com.dwolla.aws.secretsManager

use smithy4s.meta#only
use com.dwolla.tracing.smithy#traceable

apply com.amazonaws.secretsmanager#GetSecretValue @only

apply com.amazonaws.secretsmanager#CreatedDateType @traceable
apply com.amazonaws.secretsmanager#ErrorMessage @traceable
apply com.amazonaws.secretsmanager#SecretARNType @traceable
apply com.amazonaws.secretsmanager#SecretBinaryType @traceable
apply com.amazonaws.secretsmanager#SecretIdType @traceable
apply com.amazonaws.secretsmanager#SecretNameType @traceable
apply com.amazonaws.secretsmanager#SecretStringType @traceable(redacted: "redacted SecretString")
apply com.amazonaws.secretsmanager#SecretVersionIdType @traceable
apply com.amazonaws.secretsmanager#SecretVersionStageType @traceable
apply com.amazonaws.secretsmanager#SecretVersionStagesType @traceable
Original file line number Diff line number Diff line change
@@ -1,27 +1,44 @@
package com.dwolla.postgres.init

import cats.syntax.all._
import cats.syntax.all.*
import com.amazonaws.secretsmanager.SecretIdType
import com.comcast.ip4s.*
import io.circe.generic.semiauto.deriveDecoder
import io.circe.refined._
import io.circe.{Decoder, HCursor}
import io.circe.refined.*
import io.circe.{Decoder, HCursor, Json}
import io.circe.literal.*
import io.circe.syntax.EncoderOps
import natchez.*

case class DatabaseMetadata(host: Host,
port: Port,
name: Database,
username: MasterDatabaseUsername,
password: MasterDatabasePassword,
secretIds: List[SecretId],
secretIds: List[SecretIdType],
)

object DatabaseMetadata {
implicit val DecodeDatabaseMetadata: Decoder[DatabaseMetadata] = (c: HCursor) =>
(c.downField("Host").as[Host],
c.downField("Port").as[Port],
c.downField("DatabaseName").as[Database],
c.downField("MasterDatabaseUsername").as[MasterDatabaseUsername],
c.downField("MasterDatabasePassword").as[MasterDatabasePassword],
c.downField("UserConnectionSecrets").as[List[SecretId]],
).mapN(DatabaseMetadata.apply)
given Decoder[DatabaseMetadata] = Decoder.accumulatingInstance { (c: HCursor) =>
(c.downField("Host").asAcc[Host],
c.downField("Port").asAcc[Port],
c.downField("DatabaseName").asAcc[Database],
c.downField("MasterDatabaseUsername").asAcc[MasterDatabaseUsername],
c.downField("MasterDatabasePassword").asAcc[MasterDatabasePassword],
c.downField("UserConnectionSecrets").asAcc[List[String]].nested.map(SecretIdType(_)).value,
).mapN(DatabaseMetadata.apply)
}

given TraceableValue[DatabaseMetadata] = TraceableValue[String].contramap { dm =>
json"""{
"Host": ${dm.host.toTraceValue},
"Port": ${dm.port.toTraceValue},
"DatabaseName": ${dm.name.toTraceValue},
"MasterDatabaseUsername": ${dm.username.toTraceValue},
"MasterDatabasePassword": ${dm.password.toTraceValue},
"UserConnectionSecrets": ${dm.secretIds.map(_.toTraceValue)}
}""".noSpaces
}
}

case class UserConnectionInfo(database: Database,
Expand All @@ -32,5 +49,36 @@ case class UserConnectionInfo(database: Database,
)

object UserConnectionInfo {
implicit val UserConnectionInfoDecoder: Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo]
given Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo]

given TraceableValue[UserConnectionInfo] = TraceableValue[String].contramap { uci =>
json"""{
"host": ${uci.host.toTraceValue},
"port": ${uci.port.toTraceValue},
"database": ${uci.database.toTraceValue},
"user": ${uci.user.toTraceValue},
"password": ${uci.password.toTraceValue}
}""".noSpaces
}
}

extension [A](a: A)
def toTraceValue(using TraceableValue[A]): Json = TraceableValue[A].toTraceValue(a) match
case TraceValue.StringValue(value) => value.asJson
case TraceValue.BooleanValue(value) => value.asJson
case TraceValue.NumberValue(value) =>
value match
case i: java.lang.Byte => Json.fromInt(i.intValue)
case s: java.lang.Short => Json.fromInt(s.intValue)
case i: java.lang.Integer => Json.fromInt(i)
case l: java.lang.Long => Json.fromLong(l)
case f: java.lang.Float => Json.fromFloat(f).getOrElse(Json.Null)
case d: java.lang.Double => Json.fromDouble(d).getOrElse(Json.Null)
case bd: java.math.BigDecimal =>
Json.fromBigDecimal(scala.math.BigDecimal(bd))
case bi: java.math.BigInteger =>
Json.fromBigInt(scala.math.BigInt(bi))
case _ =>
// Fallback: try BigDecimal to preserve value if possible
val bd = new java.math.BigDecimal(value.toString)
Json.fromBigDecimal(scala.math.BigDecimal(bd))
Loading