From 9288373946eb1b64dc2f2d0027ebc1b66930dfa5 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Wed, 13 Jul 2022 20:06:00 +0000 Subject: [PATCH 01/49] Update log4cats-slf4j to 2.4.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ab60c95..78cebaf 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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", + "org.typelevel" %% "log4cats-slf4j" % "2.4.0", "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", From 245e06b779103de6dc7f8aaaf0e6ec939f4f6c49 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 26 Jul 2022 04:32:26 +0000 Subject: [PATCH 02/49] Update http4s-ember-client to 0.23.14 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 78cebaf..af30738 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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.http4s" %% "http4s-ember-client" % "0.23.14", "io.circe" %% "circe-parser" % circeV, "io.circe" %% "circe-generic" % circeV, "io.circe" %% "circe-refined" % circeV, From 0c489ede078e6905474bd239f70c2674609e6ee9 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Sat, 20 Aug 2022 00:09:55 +0000 Subject: [PATCH 03/49] Update secretsmanager to 2.17.257 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index af30738..0d9ea44 100644 --- a/build.sbt +++ b/build.sbt @@ -50,7 +50,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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", + "software.amazon.awssdk" % "secretsmanager" % "2.17.257", "org.scalameta" %% "munit" % munitV % Test, "org.scalameta" %% "munit-scalacheck" % munitV % Test, "io.circe" %% "circe-literal" % circeV % Test, From 72b55cef291cf41bed8d9cbade9b40a6178fd325 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 00:33:56 +0000 Subject: [PATCH 04/49] Update shapeless to 2.3.13 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 0d9ea44..a684718 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.typelevel" %% "log4cats-slf4j" % "2.4.0", "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.chuusai" %% "shapeless" % "2.3.13", "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC1", "software.amazon.awssdk" % "secretsmanager" % "2.17.257", "org.scalameta" %% "munit" % munitV % Test, From d2b85ff1c23e74fb741d89d8c2d3f374a3a3c7af Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Thu, 9 Feb 2023 00:07:16 +0000 Subject: [PATCH 05/49] Update circe-generic, circe-literal, ... to 0.14.4 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a684718..6b460be 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,7 @@ 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.4" lazy val `postgresql-init-core` = (project in file(".")) .settings( From 4da3d65e53218c16622f69dc46ce10cba6a861c7 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:05:16 +0000 Subject: [PATCH 06/49] Update log4j-slf4j-impl to 2.25.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6b460be..ef54bd1 100644 --- a/build.sbt +++ b/build.sbt @@ -47,7 +47,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.tpolecat" %% "skunk-core" % "0.3.1", "org.typelevel" %% "log4cats-slf4j" % "2.4.0", "com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.1", - "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.18.0", + "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC1", "software.amazon.awssdk" % "secretsmanager" % "2.17.257", From 20af4010ea6f8c76227fb1cd98ea5b15a0811902 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 13 Jun 2023 00:18:54 +0000 Subject: [PATCH 07/49] Update http4s-ember-client to 0.23.20 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index ef54bd1..f074b2d 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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.14", + "org.http4s" %% "http4s-ember-client" % "0.23.20", "io.circe" %% "circe-parser" % circeV, "io.circe" %% "circe-generic" % circeV, "io.circe" %% "circe-refined" % circeV, From 37f7e8f084d92a0f94c80c71f22630fbb2543c1a Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Thu, 6 Oct 2022 21:16:55 +0000 Subject: [PATCH 08/49] Update skunk-core to 0.3.2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f074b2d..f72740e 100644 --- a/build.sbt +++ b/build.sbt @@ -44,7 +44,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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.tpolecat" %% "skunk-core" % "0.3.2", "org.typelevel" %% "log4cats-slf4j" % "2.4.0", "com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.1", "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", From 34c3bc6df409d1cb24787a93b2785d355bcb3c97 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 20:09:14 +0000 Subject: [PATCH 09/49] Update log4cats-slf4j to 2.7.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f72740e..e3895f6 100644 --- a/build.sbt +++ b/build.sbt @@ -45,7 +45,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "io.monix" %% "newtypes-core" % "0.2.3", "io.monix" %% "newtypes-circe-v0-14" % "0.2.3", "org.tpolecat" %% "skunk-core" % "0.3.2", - "org.typelevel" %% "log4cats-slf4j" % "2.4.0", + "org.typelevel" %% "log4cats-slf4j" % "2.7.1", "com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.1", "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", From 2cdbe4024eb599dc08ac3737b39a392bf8989e76 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Wed, 25 Sep 2024 20:10:21 +0000 Subject: [PATCH 10/49] Update scala-library to 2.13.15 --- .github/workflows/ci.yml | 2 +- build.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1854251..11d947b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.8] + scala: [2.13.15] java: [temurin@8, temurin@11] runs-on: ${{ matrix.os }} steps: diff --git a/build.sbt b/build.sbt index e3895f6..982a8fc 100644 --- a/build.sbt +++ b/build.sbt @@ -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 := "2.13.15" ThisBuild / developers := List( Developer( "bpholt", From 020b689c985471d460bb70146e0f592b359bb05a Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 00:18:38 +0000 Subject: [PATCH 11/49] Update secretsmanager to 2.17.295 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 982a8fc..d09d976 100644 --- a/build.sbt +++ b/build.sbt @@ -50,7 +50,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC1", - "software.amazon.awssdk" % "secretsmanager" % "2.17.257", + "software.amazon.awssdk" % "secretsmanager" % "2.17.295", "org.scalameta" %% "munit" % munitV % Test, "org.scalameta" %% "munit-scalacheck" % munitV % Test, "io.circe" %% "circe-literal" % circeV % Test, From 61133c7ba491547405b2323c85e280d49af0c28b Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:08:45 +0000 Subject: [PATCH 12/49] Update sbt-ci-release to 1.9.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index d112a03..917087d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.3.3") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.10") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9") From 855e983561e33f8d7ce3dd8b40a621b42fcd1d93 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 04:25:02 +0000 Subject: [PATCH 13/49] Update sbt to 1.7.3 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index d738b85..f5b9ea7 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.7.1 +sbt.version = 1.7.3 From 13942bfc0f9b76ac37fa7d3219e7d14573d606d2 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 20:02:12 +0000 Subject: [PATCH 14/49] Update sbt-native-packager to 1.9.16 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 917087d..c351499 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.3.3") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") -addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.9") +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") From 8c29b286c1046bfb7b82590e6ecf72dc9fcd2cef Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Sat, 17 Jun 2023 20:09:33 +0000 Subject: [PATCH 15/49] Update http4s-ember-client to 0.23.21 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d09d976..78cf17d 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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.20", + "org.http4s" %% "http4s-ember-client" % "0.23.21", "io.circe" %% "circe-parser" % circeV, "io.circe" %% "circe-generic" % circeV, "io.circe" %% "circe-refined" % circeV, From 958ad87898e5ec4d5a2c3790a3217dac6731f2af Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Fri, 30 Jun 2023 20:09:00 +0000 Subject: [PATCH 16/49] Update fs2-aws-java-sdk2 to 3.0.0-RC2 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 78cf17d..08a62a4 100644 --- a/build.sbt +++ b/build.sbt @@ -49,7 +49,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.1", "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", - "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC1", + "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC2", "software.amazon.awssdk" % "secretsmanager" % "2.17.295", "org.scalameta" %% "munit" % munitV % Test, "org.scalameta" %% "munit-scalacheck" % munitV % Test, From 96de4157f47b9dcb303c65e0dafc70b3c647a542 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 11 Jul 2023 12:05:55 +0000 Subject: [PATCH 17/49] Update sbt-tpolecat to 0.4.4 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index c351499..c2e434d 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.3.3") +addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.4") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") From 47748414c56fd2eb90db58ab328748aad9969c36 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:05:59 +0000 Subject: [PATCH 18/49] Update aws-lambda-java-log4j2 to 1.6.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 08a62a4..36dc47b 100644 --- a/build.sbt +++ b/build.sbt @@ -46,7 +46,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "io.monix" %% "newtypes-circe-v0-14" % "0.2.3", "org.tpolecat" %% "skunk-core" % "0.3.2", "org.typelevel" %% "log4cats-slf4j" % "2.7.1", - "com.amazonaws" % "aws-lambda-java-log4j2" % "1.5.1", + "com.amazonaws" % "aws-lambda-java-log4j2" % "1.6.0", "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC2", From cb9ab699b1b33c9ae0c840c8602fa1116fd4c8c8 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:05:25 +0000 Subject: [PATCH 19/49] Update circe-generic, circe-literal, ... to 0.14.9 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 36dc47b..8b379cc 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,7 @@ ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty lazy val munitV = "0.7.29" -lazy val circeV = "0.14.4" +lazy val circeV = "0.14.9" lazy val `postgresql-init-core` = (project in file(".")) .settings( From f8563fe1d0c2413bb6d36368e65e299323b684a3 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 20:06:21 +0000 Subject: [PATCH 20/49] Update natchez-http4s to 0.6.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 8b379cc..dd54dac 100644 --- a/build.sbt +++ b/build.sbt @@ -36,7 +36,7 @@ lazy val `postgresql-init-core` = (project in file(".")) Seq( "org.typelevel" %% "feral-lambda-cloudformation-custom-resource" % feralVersion, "org.tpolecat" %% "natchez-xray" % natchezVersion, - "org.tpolecat" %% "natchez-http4s" % "0.3.2", + "org.tpolecat" %% "natchez-http4s" % "0.6.0", "org.typelevel" %% "cats-tagless-macros" % "0.14.0", "org.http4s" %% "http4s-ember-client" % "0.23.21", "io.circe" %% "circe-parser" % circeV, From d0a9e88ba2ce5afc98d1dfddc03375e3ec80d991 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 08:06:55 +0000 Subject: [PATCH 21/49] Update newtypes-circe-v0-14, ... to 0.3.0 --- build.sbt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index dd54dac..6cfa6a0 100644 --- a/build.sbt +++ b/build.sbt @@ -42,8 +42,8 @@ lazy val `postgresql-init-core` = (project in file(".")) "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", + "io.monix" %% "newtypes-core" % "0.3.0", + "io.monix" %% "newtypes-circe-v0-14" % "0.3.0", "org.tpolecat" %% "skunk-core" % "0.3.2", "org.typelevel" %% "log4cats-slf4j" % "2.7.1", "com.amazonaws" % "aws-lambda-java-log4j2" % "1.6.0", From 6aee74fecd9e8343752fb3a548826c6ccf5f29fe Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 20:07:38 +0000 Subject: [PATCH 22/49] Update feral-lambda-cloudformation-custom-resource to 0.3.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6cfa6a0..2639ab7 100644 --- a/build.sbt +++ b/build.sbt @@ -31,7 +31,7 @@ lazy val `postgresql-init-core` = (project in file(".")) topLevelDirectory := None, libraryDependencies ++= { val natchezVersion = "0.1.6" - val feralVersion = "0.1.0-M13" + val feralVersion = "0.3.1" Seq( "org.typelevel" %% "feral-lambda-cloudformation-custom-resource" % feralVersion, From a1a3da1f4e3239e5f9843c99c09addb94022f0be Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:06:57 +0000 Subject: [PATCH 23/49] Update munit to 1.0.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 2639ab7..76bb945 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,7 @@ ThisBuild / githubWorkflowTargetTags ++= Seq("v*") ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty -lazy val munitV = "0.7.29" +lazy val munitV = "1.0.3" lazy val circeV = "0.14.9" lazy val `postgresql-init-core` = (project in file(".")) From 96a5febaced1a36bcc5a0a30863310829d76cbff Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 01:24:26 +0000 Subject: [PATCH 24/49] Update natchez-http4s to 0.6.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 76bb945..fd9f632 100644 --- a/build.sbt +++ b/build.sbt @@ -36,7 +36,7 @@ lazy val `postgresql-init-core` = (project in file(".")) Seq( "org.typelevel" %% "feral-lambda-cloudformation-custom-resource" % feralVersion, "org.tpolecat" %% "natchez-xray" % natchezVersion, - "org.tpolecat" %% "natchez-http4s" % "0.6.0", + "org.tpolecat" %% "natchez-http4s" % "0.6.1", "org.typelevel" %% "cats-tagless-macros" % "0.14.0", "org.http4s" %% "http4s-ember-client" % "0.23.21", "io.circe" %% "circe-parser" % circeV, From 0ea3643114141dc95d5eec1b2edebc9f1a76f5f4 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 21 Jan 2025 20:09:26 +0000 Subject: [PATCH 25/49] Update munit to 1.1.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index fd9f632..e8a9860 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,7 @@ ThisBuild / githubWorkflowTargetTags ++= Seq("v*") ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty -lazy val munitV = "1.0.3" +lazy val munitV = "1.1.0" lazy val circeV = "0.14.9" lazy val `postgresql-init-core` = (project in file(".")) From 620e818fb8536cb0a7abb59a70196bce64398ac2 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 12:12:39 +0000 Subject: [PATCH 26/49] Update cats-tagless-macros to 0.16.3 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index e8a9860..f1380ed 100644 --- a/build.sbt +++ b/build.sbt @@ -37,7 +37,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.typelevel" %% "feral-lambda-cloudformation-custom-resource" % feralVersion, "org.tpolecat" %% "natchez-xray" % natchezVersion, "org.tpolecat" %% "natchez-http4s" % "0.6.1", - "org.typelevel" %% "cats-tagless-macros" % "0.14.0", + "org.typelevel" %% "cats-tagless-macros" % "0.16.3", "org.http4s" %% "http4s-ember-client" % "0.23.21", "io.circe" %% "circe-parser" % circeV, "io.circe" %% "circe-generic" % circeV, From 0fc4862ff2dd8d3c34ff6894c5564551fb28a17d Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 00:34:46 +0000 Subject: [PATCH 27/49] Update natchez-xray to 0.3.8 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f1380ed..44b81e0 100644 --- a/build.sbt +++ b/build.sbt @@ -30,7 +30,7 @@ lazy val `postgresql-init-core` = (project in file(".")) maintainer := developers.value.head.email, topLevelDirectory := None, libraryDependencies ++= { - val natchezVersion = "0.1.6" + val natchezVersion = "0.3.8" val feralVersion = "0.3.1" Seq( From 3ad5dd78eaecd6e224a936074fa2a928f54affa6 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Sun, 17 Aug 2025 08:04:20 +0000 Subject: [PATCH 28/49] Update sbt-ci-release to 1.11.2 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index c2e434d..6337300 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.4") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.0") +addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.2") addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") From 5e52206f4a0269f72b820ff5c34fe2631c11e75b Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 20:03:33 +0000 Subject: [PATCH 29/49] Update munit-scalacheck to 1.2.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 44b81e0..fb6a829 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,7 @@ ThisBuild / githubWorkflowTargetTags ++= Seq("v*") ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty -lazy val munitV = "1.1.0" +lazy val munitV = "1.2.0" lazy val circeV = "0.14.9" lazy val `postgresql-init-core` = (project in file(".")) From 61a545228a6028ee8e3edc2a5671685277bec9d8 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 04:06:31 +0000 Subject: [PATCH 30/49] Update sbt-github-actions to 0.28.0 --- project/plugins.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 6337300..708c961 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,4 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.4") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.2") -addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.14.2") +addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.28.0") addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16") From 7fa4b220984b2a6d6b67728429a2016b36c99954 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:05:59 +0000 Subject: [PATCH 31/49] Update http4s-ember-client to 0.23.32 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index fb6a829..797141a 100644 --- a/build.sbt +++ b/build.sbt @@ -38,7 +38,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.tpolecat" %% "natchez-xray" % natchezVersion, "org.tpolecat" %% "natchez-http4s" % "0.6.1", "org.typelevel" %% "cats-tagless-macros" % "0.16.3", - "org.http4s" %% "http4s-ember-client" % "0.23.21", + "org.http4s" %% "http4s-ember-client" % "0.23.32", "io.circe" %% "circe-parser" % circeV, "io.circe" %% "circe-generic" % circeV, "io.circe" %% "circe-refined" % circeV, From 2e6d8bc8b9b4b79d9854f2d9861dda29b77c7c1b Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:05:59 +0000 Subject: [PATCH 32/49] Update circe-generic, circe-literal, ... to 0.14.15 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 797141a..a1a3ba6 100644 --- a/build.sbt +++ b/build.sbt @@ -23,7 +23,7 @@ ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty lazy val munitV = "1.2.0" -lazy val circeV = "0.14.9" +lazy val circeV = "0.14.15" lazy val `postgresql-init-core` = (project in file(".")) .settings( From 460c7556efcd2a7bb046777215e987f850b754cf Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 20:06:05 +0000 Subject: [PATCH 33/49] Update kind-projector to 0.13.4 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index a1a3ba6..fa21d1e 100644 --- a/build.sbt +++ b/build.sbt @@ -13,7 +13,7 @@ ThisBuild / developers := List( ) ThisBuild / startYear := Option(2021) ThisBuild / libraryDependencies ++= Seq( - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.2" cross CrossVersion.full), + compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.4" cross CrossVersion.full), compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), ) From 9da1b122f3559e8cc6fd52694f9786a63e9fde30 Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Tue, 7 Oct 2025 04:08:22 +0000 Subject: [PATCH 34/49] Update scala-library to 2.13.17 --- .github/workflows/ci.yml | 2 +- build.sbt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11d947b..9e6defa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.15] + scala: [2.13.17] java: [temurin@8, temurin@11] runs-on: ${{ matrix.os }} steps: diff --git a/build.sbt b/build.sbt index fa21d1e..f28aa73 100644 --- a/build.sbt +++ b/build.sbt @@ -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.15" +ThisBuild / scalaVersion := "2.13.17" ThisBuild / developers := List( Developer( "bpholt", From df2044501dcef059c4c018d1c20bd76034ce204f Mon Sep 17 00:00:00 2001 From: "dwolla-oss-scala-steward[bot]" <212073+dwolla-oss-scala-steward[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 08:06:13 +0000 Subject: [PATCH 35/49] Update munit to 1.2.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f28aa73..bf497e0 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,7 @@ ThisBuild / githubWorkflowTargetTags ++= Seq("v*") ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty -lazy val munitV = "1.2.0" +lazy val munitV = "1.2.1" lazy val circeV = "0.14.15" lazy val `postgresql-init-core` = (project in file(".")) From 02aca1c9197bd9e8a9fb389ae8d3d475ad3e5101 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 10 Nov 2025 19:22:21 -0600 Subject: [PATCH 36/49] Update sbt to 1.11.7 --- project/build.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.properties b/project/build.properties index f5b9ea7..a360cca 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version = 1.7.3 +sbt.version = 1.11.7 From bbcaf37077b967eb54084fa10e595dc437ff9dc2 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 10 Nov 2025 19:25:49 -0600 Subject: [PATCH 37/49] switch to sbt-typelevel --- project/plugins.sbt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/project/plugins.sbt b/project/plugins.sbt index 708c961..331fb2e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,3 @@ -addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.4") -addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.2") -addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.28.0") +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") From 81241355bcb1e0b9c4f5833fbb35713f92edc1f7 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 10 Nov 2025 19:26:12 -0600 Subject: [PATCH 38/49] separate versions of munit and munit-scalacheck --- build.sbt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.sbt b/build.sbt index bf497e0..f80b5f6 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,6 @@ ThisBuild / githubWorkflowTargetTags ++= Seq("v*") ThisBuild / githubWorkflowPublishTargetBranches := Seq.empty ThisBuild / githubWorkflowPublish := Seq.empty -lazy val munitV = "1.2.1" lazy val circeV = "0.14.15" lazy val `postgresql-init-core` = (project in file(".")) @@ -51,8 +50,8 @@ lazy val `postgresql-init-core` = (project in file(".")) "com.chuusai" %% "shapeless" % "2.3.13", "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC2", "software.amazon.awssdk" % "secretsmanager" % "2.17.295", - "org.scalameta" %% "munit" % munitV % Test, - "org.scalameta" %% "munit-scalacheck" % munitV % Test, + "org.scalameta" %% "munit" % "1.2.1" % Test, + "org.scalameta" %% "munit-scalacheck" % "1.2.0" % Test, "io.circe" %% "circe-literal" % circeV % Test, ) }, From 1e6b4a232041067d3c4072899f423e4b7d70a6ce Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 10 Nov 2025 19:26:35 -0600 Subject: [PATCH 39/49] separate circe-refined version --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index f80b5f6..6bce753 100644 --- a/build.sbt +++ b/build.sbt @@ -40,7 +40,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.http4s" %% "http4s-ember-client" % "0.23.32", "io.circe" %% "circe-parser" % circeV, "io.circe" %% "circe-generic" % circeV, - "io.circe" %% "circe-refined" % 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.3.2", From 1d8a366ab557c0d769a33fe313ed9113085aff1d Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 10 Nov 2025 19:27:19 -0600 Subject: [PATCH 40/49] Update skunk to 0.6.4 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6bce753..6a7c718 100644 --- a/build.sbt +++ b/build.sbt @@ -43,7 +43,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "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.3.2", + "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", From bc28091601f662c27fe49394cc41e70325889816 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Mon, 10 Nov 2025 19:29:28 -0600 Subject: [PATCH 41/49] Remove fs2-aws-java-sdk2, which is no longer maintained --- build.sbt | 1 - 1 file changed, 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6a7c718..ad653d2 100644 --- a/build.sbt +++ b/build.sbt @@ -48,7 +48,6 @@ lazy val `postgresql-init-core` = (project in file(".")) "com.amazonaws" % "aws-lambda-java-log4j2" % "1.6.0", "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", - "com.dwolla" %% "fs2-aws-java-sdk2" % "3.0.0-RC2", "software.amazon.awssdk" % "secretsmanager" % "2.17.295", "org.scalameta" %% "munit" % "1.2.1" % Test, "org.scalameta" %% "munit-scalacheck" % "1.2.0" % Test, From 5d21220dc8ad3bf2d1c538d267a30dbb1f287c64 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Tue, 11 Nov 2025 14:59:35 -0600 Subject: [PATCH 42/49] Add Smithy4s SecretsManager client and finish updating the code to work with the new dependency versions --- .dwollaci.yml | 2 +- .github/workflows/ci.yml | 47 +++++--- .mergify.yml | 49 ++++---- build.sbt | 24 +++- project/plugins.sbt | 1 + smithy/src/main/smithy/secrets-manager.smithy | 19 +++ .../init/ExtractRequestProperties.scala | 79 +++++++++++-- .../init/PostgresqlDatabaseInitHandler.scala | 81 +++++++++---- .../PostgresqlDatabaseInitHandlerImpl.scala | 21 ++-- .../postgres/init/aws/SecretsManagerAlg.scala | 110 ++++++++++++++---- .../com/dwolla/postgres/init/package.scala | 35 ++++-- .../repositories/CreateSkunkSession.scala | 15 ++- .../repositories/DatabaseRepository.scala | 27 +++-- .../init/repositories/RoleRepository.scala | 30 ++--- .../init/repositories/UserRepository.scala | 31 +++-- src/main/scala/com/dwolla/tracing.scala | 21 ---- .../init/ExtractRequestPropertiesSpec.scala | 7 +- 17 files changed, 392 insertions(+), 207 deletions(-) create mode 100644 smithy/src/main/smithy/secrets-manager.smithy delete mode 100644 src/main/scala/com/dwolla/tracing.scala diff --git a/.dwollaci.yml b/.dwollaci.yml index 8723180..da0bc72 100644 --- a/.dwollaci.yml +++ b/.dwollaci.yml @@ -2,7 +2,7 @@ stages: build: nodeLabel: sbt steps: - - sbt universal:packageBin + - sbt Universal/packageBin filesToStash: - '**' publish: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e6defa..2ade28a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,49 +17,58 @@ 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.17] + os: [ubuntu-22.04] + scala: [2.13] java: [temurin@8, temurin@11] 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 sbt + uses: sbt/setup-sbt@v1 + - name: Setup Java (temurin@8) + id: setup-java-temurin-8 if: matrix.java == 'temurin@8' - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 8 + cache: sbt + + - name: sbt update + if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' + run: sbt +update - name: Setup Java (temurin@11) + id: setup-java-temurin-11 if: matrix.java == 'temurin@11' - uses: actions/setup-java@v2 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 11 + 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@11' && steps.setup-java-temurin-11.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 diff --git a/.mergify.yml b/.mergify.yml index 91ee2b5..01062fb 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -1,29 +1,24 @@ -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, 2.13, temurin@8) + - status-success=Test (ubuntu-22.04, 2.13, temurin@11) + actions: + merge: {} +- name: Label smithy PRs + conditions: + - files~=^smithy/ + actions: + label: + add: + - smithy + remove: [] diff --git a/build.sbt b/build.sbt index ad653d2..cb2aee1 100644 --- a/build.sbt +++ b/build.sbt @@ -16,6 +16,21 @@ ThisBuild / libraryDependencies ++= Seq( compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.4" 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 / githubWorkflowTargetTags ++= Seq("v*") @@ -30,16 +45,17 @@ lazy val `postgresql-init-core` = (project in file(".")) topLevelDirectory := None, libraryDependencies ++= { val natchezVersion = "0.3.8" - val feralVersion = "0.3.1" + 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.6.1", - "org.typelevel" %% "cats-tagless-macros" % "0.16.3", + "org.typelevel" %% "cats-tagless-macros" % "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-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", @@ -48,13 +64,15 @@ lazy val `postgresql-init-core` = (project in file(".")) "com.amazonaws" % "aws-lambda-java-log4j2" % "1.6.0", "org.apache.logging.log4j" % "log4j-slf4j-impl" % "2.25.2", "com.chuusai" %% "shapeless" % "2.3.13", - "software.amazon.awssdk" % "secretsmanager" % "2.17.295", + "com.dwolla" %% "natchez-tagless" % "0.2.6-131-d6a1c7c-SNAPSHOT", + "org.typelevel" %% "mouse" % "1.4.0", "org.scalameta" %% "munit" % "1.2.1" % Test, "org.scalameta" %% "munit-scalacheck" % "1.2.0" % Test, "io.circe" %% "circe-literal" % circeV % Test, ) }, ) + .dependsOn(smithy) .enablePlugins(UniversalPlugin, JavaAppPackaging) lazy val serverlessDeployCommand = settingKey[Seq[String]]("serverless command to deploy the application") diff --git a/project/plugins.sbt b/project/plugins.sbt index 331fb2e..2dc2258 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ 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") diff --git a/smithy/src/main/smithy/secrets-manager.smithy b/smithy/src/main/smithy/secrets-manager.smithy new file mode 100644 index 0000000..ee1af66 --- /dev/null +++ b/smithy/src/main/smithy/secrets-manager.smithy @@ -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 diff --git a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala index 4c9f666..52b5c86 100644 --- a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala +++ b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala @@ -1,27 +1,46 @@ package com.dwolla.postgres.init -import cats.syntax.all._ +import cats.syntax.all.* +import com.amazonaws.secretsmanager.SecretIdType +import com.dwolla.tracing.LowPriorityTraceableValueInstances.nonPrimitiveTraceValueViaJson 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) + implicit val DecodeDatabaseMetadata: 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) + } + + implicit val traceableValue: TraceableValue[DatabaseMetadata] = TraceableValue[Json].contramap { dm => + implicit def toTraceableValueOps[A](a: A): TraceableValueOps[A] = new TraceableValueOps[A](a) + + 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)} + }""" + } } case class UserConnectionInfo(database: Database, @@ -33,4 +52,40 @@ case class UserConnectionInfo(database: Database, object UserConnectionInfo { implicit val UserConnectionInfoDecoder: Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo] + + implicit val traceableValue: TraceableValue[UserConnectionInfo] = TraceableValue[Json].contramap { uci => + implicit def toTraceableValueOps[A](a: A): TraceableValueOps[A] = new TraceableValueOps[A](a) + + json"""{ + "host": ${uci.host.toTraceValue}, + "port": ${uci.port.toTraceValue}, + "database": ${uci.database.toTraceValue}, + "user": ${uci.user.toTraceValue}, + "password": ${uci.password.toTraceValue} + }""" + } +} + +private class TraceableValueOps[A](val a: A) extends AnyVal { + def toTraceValue(implicit T: TraceableValue[A]): Json = T.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)) + } + } } diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala index 5d06888..a6f75c5 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala @@ -1,57 +1,88 @@ package com.dwolla.postgres.init -import cats.data._ -import cats.effect.std.{Console, Random} -import cats.effect.{Trace => _, _} -import cats.tagless.FunctorK.ops.toAllFunctorKOps +import cats.effect.std.{Console, Env, Random} +import cats.effect.syntax.all.* +import cats.effect.{Trace as _, *} +import cats.mtl.Local +import cats.syntax.all.* +import com.amazonaws.secretsmanager.SecretsManager import com.dwolla.postgres.init.aws.SecretsManagerAlg -import com.dwolla.tracing._ -import feral.lambda.cloudformation._ -import feral.lambda.{INothing, IOLambda, KernelSource, LambdaEnv, TracedHandler} -import natchez._ +import feral.lambda.cloudformation.* +import feral.lambda.{AwsTags, INothing, IOLambda, Invocation, KernelSource} +import fs2.compression.Compression +import fs2.io.file.Files +import fs2.io.net.Network +import mouse.all.* +import natchez.* import natchez.http4s.NatchezMiddleware +import natchez.mtl.* import natchez.xray.{XRay, XRayEnvironment} import org.http4s.client.{Client, middleware} import org.http4s.ember.client.EmberClientBuilder import org.typelevel.log4cats.Logger import org.typelevel.log4cats.slf4j.Slf4jLogger +import smithy4s.aws.kernel.AwsRegion +import smithy4s.aws.{AwsClient, AwsEnvironment} class PostgresqlDatabaseInitHandler extends IOLambda[CloudFormationCustomResourceRequest[DatabaseMetadata], INothing] { - private def resources[F[_] : Async : Console]: Resource[F, LambdaEnv[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => F[Option[INothing]]] = + private def resources[F[_] : Async : Compression : Console : Env : Files : Network](implicit L: Local[F, Span[F]]): Resource[F, Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => F[Option[INothing]]] = for { - implicit0(logger: Logger[F]) <- Resource.eval(Slf4jLogger.create[F]) - implicit0(random: Random[F]) <- Resource.eval(Random.scalaUtilRandom[F]) + case implicit0(logger: Logger[F]) <- Resource.eval(Slf4jLogger.create[F]) + case implicit0(random: Random[F]) <- Resource.eval(Random.scalaUtilRandom[F]) client <- httpClient[F] - entryPoint <- XRayEnvironment[Resource[F, *]].daemonAddress.flatMap { + entryPoint <- XRayEnvironment[F].daemonAddress.toResource.flatMap { case Some(addr) => XRay.entryPoint(addr) case None => XRay.entryPoint[F]() } - secretsManager <- SecretsManagerAlg.resource[F].map(_.mapK(Kleisli.liftK[F, Span[F]]).withTracing) - } yield { implicit env: LambdaEnv[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => - TracedHandler(entryPoint, Kleisli { (span: Span[F]) => - CloudFormationCustomResource(tracedHttpClient(client, span), PostgresqlDatabaseInitHandlerImpl(secretsManager)).run(span) - }) + region <- Env[F].get("AWS_REGION").liftEitherT(new RuntimeException("missing AWS_REGION environment variable")).map(AwsRegion(_)).rethrowT.toResource + awsEnv <- AwsEnvironment.default(client, region) + secretsManager <- AwsClient(SecretsManager, awsEnv).map(SecretsManagerAlg[F](_)) + } yield { implicit env: Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => + TracedHandler(entryPoint) { _ => + CloudFormationCustomResource(client, PostgresqlDatabaseInitHandlerImpl(secretsManager)) + } } - override def handler: Resource[IO, LambdaEnv[IO, CloudFormationCustomResourceRequest[DatabaseMetadata]] => IO[Option[INothing]]] = - resources[IO] - - private implicit def kleisliLogger[F[_] : Logger, A]: Logger[Kleisli[F, A, *]] = Logger[F].mapK(Kleisli.liftK) + override def handler: Resource[IO, Invocation[IO, CloudFormationCustomResourceRequest[DatabaseMetadata]] => IO[Option[INothing]]] = + IO.local(Span.noop[IO]).toResource + .flatMap(implicit l => resources[IO]) /** - * The XRay kernel comes from environment variables, so we don't need to extract anything from the incoming event + * The X-Ray kernel comes from environment variables, so we don't need to extract anything from the incoming event. + * The kernel will be sourced from the environment/system properties if useEnvironmentFallback is true when + * initializing the X-Ray entrypoint. */ private implicit def kernelSource[Event]: KernelSource[Event] = KernelSource.emptyKernelSource - private def httpClient[F[_] : Async]: Resource[F, Client[F]] = + private def httpClient[F[_] : Async : Network : Trace]: Resource[F, Client[F]] = EmberClientBuilder .default[F] .build .map(middleware.Logger[F](logHeaders = true, logBody = true)) + .map(NatchezMiddleware.client(_)) - private def tracedHttpClient[F[_] : MonadCancelThrow](client: Client[F], span: Span[F]): Client[Kleisli[F, Span[F], *]] = - NatchezMiddleware.client(client.translate(Kleisli.liftK[F, Span[F]])(Kleisli.applyK(span))) +} +// TODO replace with https://github.com/typelevel/feral/pull/591 once it's merged +object TracedHandler { + def apply[F[_] : MonadCancelThrow, Event, Result](entryPoint: EntryPoint[F]) + (handler: Trace[F] => F[Option[Result]]) + (implicit inv: Invocation[F, Event], + KS: KernelSource[Event], + L: Local[F, Span[F]]): F[Option[Result]] = + for { + event <- Invocation[F, Event].event + context <- Invocation[F, Event].context + kernel = KernelSource[Event].extract(event) + result <- entryPoint.continueOrElseRoot(context.functionName, kernel).use { + Local[F, Span[F]].scope { + Trace[F].put( + AwsTags.arn(context.invokedFunctionArn), + AwsTags.requestId(context.awsRequestId) + ) >> handler(Trace[F]) + } + } + } yield result } diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala index 4541744..660c5da 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala @@ -1,20 +1,21 @@ package com.dwolla.postgres.init -import cats._ +import cats.* import cats.effect.std.Console -import cats.effect.{Trace => _, _} -import cats.syntax.all._ +import cats.effect.{Trace as _, *} +import cats.syntax.all.* +import com.amazonaws.secretsmanager.{ResourceNotFoundException, SecretIdType} import com.dwolla.postgres.init.PostgresqlDatabaseInitHandlerImpl.databaseAsPhysicalResourceId -import com.dwolla.postgres.init.aws.{ResourceNotFoundException, SecretsManagerAlg} -import com.dwolla.postgres.init.repositories.CreateSkunkSession._ -import com.dwolla.postgres.init.repositories._ +import com.dwolla.postgres.init.aws.SecretsManagerAlg +import com.dwolla.postgres.init.repositories.* +import com.dwolla.postgres.init.repositories.CreateSkunkSession.* import feral.lambda.INothing -import feral.lambda.cloudformation._ +import feral.lambda.cloudformation.* import fs2.io.net.Network -import natchez._ +import natchez.* import org.typelevel.log4cats.Logger -class PostgresqlDatabaseInitHandlerImpl[F[_] : Concurrent : Trace : Network : Console : Logger](secretsManagerAlg: SecretsManagerAlg[F], +class PostgresqlDatabaseInitHandlerImpl[F[_] : Temporal : Trace : Network : Console : Logger](secretsManagerAlg: SecretsManagerAlg[F], databaseRepository: DatabaseRepository[InSession[F, *]], roleRepository: RoleRepository[InSession[F, *]], userRepository: UserRepository[InSession[F, *]], @@ -48,7 +49,7 @@ class PostgresqlDatabaseInitHandlerImpl[F[_] : Concurrent : Trace : Network : Co id <- f(userPasswords).inSession(input.host, input.port, input.username, input.password) } yield id - private def getUsernamesFromSecrets(secretIds: List[SecretId], fallback: Username): F[List[Username]] = + private def getUsernamesFromSecrets(secretIds: List[SecretIdType], fallback: Username): F[List[Username]] = secretIds.traverse { secretId => secretsManagerAlg.getSecretAs[UserConnectionInfo](secretId) .map(_.user) diff --git a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala index 683bc13..2b18083 100644 --- a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala +++ b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala @@ -1,40 +1,100 @@ package com.dwolla.postgres.init package aws -import cats.syntax.all._ -import cats.effect.{Trace => _, _} -import cats.tagless.Derive -import cats.tagless.aop.Instrument -import com.dwolla.fs2aws.AwsEval -import io.circe.{Decoder, parser} +import cats.* +import cats.effect.{Trace as _, *} +import cats.syntax.all.* +import cats.tagless.aop.* +import com.amazonaws.secretsmanager.* +import com.dwolla.tagless.WeaveKnot +import com.dwolla.tracing.syntax.* +import io.circe.jawn.JawnParser +import io.circe.{Decoder, Errors} +import natchez.{Trace, TraceableValue} import org.typelevel.log4cats.Logger -import software.amazon.awssdk.services.secretsmanager.SecretsManagerAsyncClient -import software.amazon.awssdk.services.secretsmanager.model._ trait SecretsManagerAlg[F[_]] { - def getSecret(secretId: SecretId): F[String] - def getSecretAs[A : Decoder](secretId: SecretId): F[A] + def getSecret(secretId: SecretIdType): F[Secret] + def getSecretAs[A : Decoder](secretId: SecretIdType): F[A] } object SecretsManagerAlg { - implicit val SecretsManagerAlgInstrumentation: Instrument[SecretsManagerAlg] = Derive.instrument + implicit val traceableValueAspect: Aspect[SecretsManagerAlg, TraceableValue, TraceableValue] = new Aspect[SecretsManagerAlg, TraceableValue, TraceableValue] { + override def weave[F[_]](af: SecretsManagerAlg[F]): SecretsManagerAlg[Aspect.Weave[F, TraceableValue, TraceableValue, *]] = + new SecretsManagerAlg[Aspect.Weave[F, TraceableValue, TraceableValue, *]] { + override def getSecret(secretId: SecretIdType): Aspect.Weave[F, TraceableValue, TraceableValue, Secret] = + Aspect.Weave( + "SecretsManagerAlg", + List(List( + Aspect.Advice.byValue("secretId", secretId), + )), + Aspect.Advice("getSecret", af.getSecret(secretId)) + ) - def resource[F[_] : Async : Logger]: Resource[F, SecretsManagerAlg[F]] = - Resource.fromAutoCloseable(Sync[F].delay(SecretsManagerAsyncClient.builder().build())) - .map(SecretsManagerAlg[F](_)) + override def getSecretAs[A: Decoder](secretId: SecretIdType): Aspect.Weave[F, TraceableValue, TraceableValue, A] = + Aspect.Weave( + "SecretsManagerAlg", + List( + List(Aspect.Advice.byValue("secretId", secretId)), + List(Aspect.Advice.byValue("implicit decoder", Decoder[A].toString)), + ), + Aspect.Advice("getSecretAs", af.getSecretAs(secretId))(TraceableValue[String].contramap[A](_ => "redacted successfully parsed and decoded secret")) + ) + } - def apply[F[_] : Async : Logger](client: SecretsManagerAsyncClient): SecretsManagerAlg[F] = new SecretsManagerAlg[F] { - override def getSecret(secretId: SecretId): F[String] = + override def mapK[F[_], G[_]](af: SecretsManagerAlg[F])(fk: F ~> G): SecretsManagerAlg[G] = + new SecretsManagerAlg[G] { + override def getSecret(secretId: SecretIdType): G[Secret] = fk(af.getSecret(secretId)) + override def getSecretAs[A: Decoder](secretId: SecretIdType): G[A] = fk(af.getSecretAs[A](secretId)) + } + } + + def apply[F[_] : Async : Logger : Trace](client: SecretsManager[F]): SecretsManagerAlg[F] = + WeaveKnot[SecretsManagerAlg, F](apply(client, _))(_.traceWithInputsAndOutputs) + + private def apply[F[_] : Async : Logger](client: SecretsManager[F], + self: Eval[SecretsManagerAlg[F]], + ): SecretsManagerAlg[F] = new SecretsManagerAlg[F] { + private val parser = new JawnParser + + override def getSecret(secretId: SecretIdType): F[Secret] = Logger[F].info(s"retrieving secret id $secretId") >> - AwsEval.eval[F](GetSecretValueRequest.builder().secretId(secretId.value).build())(client.getSecretValue)(_.secretString()) - - override def getSecretAs[A: Decoder](secretId: SecretId): F[A] = - for { - secretString <- getSecret(secretId) - secretJson <- parser.parse(secretString).liftTo[F] - a <- secretJson.as[A].liftTo[F] - } yield a + client.getSecretValue(secretId) + .flatMap { + case GetSecretValueResponse(_, _, _, None, Some(txt), _, _) => + SecretString(txt).pure[F].widen + case GetSecretValueResponse(_, _, _, Some(blob), None, _, _) => + SecretBinary(blob).pure[F].widen + case GetSecretValueResponse(_, _, _, Some(blob), Some(_), _, _) => + SecretBinary(blob).pure[F].widen + case _ => + NoSecretInResponseException(secretId).raiseError[F, Secret] + } + + override def getSecretAs[A: Decoder](secretId: SecretIdType): F[A] = + self.value.getSecret(secretId) + .flatMap { + case SecretString(SecretStringType(value)) => parser.parse(value).liftTo[F] + case SecretBinary(SecretBinaryType(value)) => parser.parseByteBuffer(value.asByteBuffer).liftTo[F] + } + .flatMap { + _.asAccumulating[A] + .toEither + .leftMap(Errors(_)) + .liftTo[F] + } + } +} + +sealed trait Secret +case class SecretString(value: SecretStringType) extends Secret +case class SecretBinary(value: SecretBinaryType) extends Secret + +object Secret { + implicit val traceableValue: TraceableValue[Secret] = TraceableValue[String].contramap { + case SecretString(_) => "redacted string secret" + case SecretBinary(_) => "redacted binary secret" } } -case class ResourceNotFoundException(resource: String, cause: Option[Throwable]) extends RuntimeException(resource, cause.orNull) +case class NoSecretInResponseException(resource: SecretIdType) extends RuntimeException(resource.value) diff --git a/src/main/scala/com/dwolla/postgres/init/package.scala b/src/main/scala/com/dwolla/postgres/init/package.scala index e9ee779..136698e 100644 --- a/src/main/scala/com/dwolla/postgres/init/package.scala +++ b/src/main/scala/com/dwolla/postgres/init/package.scala @@ -1,13 +1,14 @@ package com.dwolla.postgres -import eu.timepit.refined._ +import eu.timepit.refined.* import eu.timepit.refined.api.Refined -import eu.timepit.refined.string._ +import eu.timepit.refined.string.* import monix.newtypes.NewtypeWrapped import monix.newtypes.integrations.DerivedCirceCodec +import natchez.TraceableValue import shapeless.ops.hlist -import shapeless.ops.tuple._ -import shapeless.syntax.std.tuple._ +import shapeless.ops.tuple.* +import shapeless.syntax.std.tuple.* import shapeless.{HList, LabelledGeneric} package object init { @@ -32,6 +33,8 @@ package object init { ): Migration[A, B] = a => bGen.from(inter.apply(aGen.to(a))) + implicit def refinedTraceableValue[A: TraceableValue, P]: TraceableValue[A Refined P] = TraceableValue[A].contramap(_.value) + type SqlIdentifierPredicate = MatchesRegex[W.`"[A-Za-z][A-Za-z0-9_]*"`.T] type SqlIdentifier = String Refined SqlIdentifierPredicate type GeneratedPasswordPredicate = MatchesRegex[W.`"""[-A-Za-z0-9!"#$%&()*+,./:<=>?@\\[\\]\\\\^_{|}~]+"""`.T] @@ -43,9 +46,6 @@ package object init { type MasterDatabasePassword = MasterDatabasePassword.Type object MasterDatabasePassword extends NewtypeWrapped[String] with DerivedCirceCodec - type SecretId = SecretId.Type - object SecretId extends NewtypeWrapped[String] with DerivedCirceCodec - type Host = Host.Type object Host extends NewtypeWrapped[String] with DerivedCirceCodec @@ -53,20 +53,33 @@ package object init { object Port extends NewtypeWrapped[Int] with DerivedCirceCodec type Username = Username.Type - object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec + object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype type Password = Password.Type - object Password extends NewtypeWrapped[GeneratedPassword] with DerivedCirceCodec + object Password extends NewtypeWrapped[GeneratedPassword] with DerivedCirceCodec { + implicit val traceableValue: TraceableValue[Password] = TraceableValue[String].contramap(_ => "redacted password") + } type Database = Database.Type - object Database extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec + object Database extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype type RoleName = RoleName.Type - object RoleName extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec + object RoleName extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype } package init { + + import monix.newtypes.HasExtractor + trait Migration[A, B] { def apply(a: A): B } + + trait DerivedTraceableValueFromNewtype { + implicit def traceableValue[T, S](implicit + extractor: HasExtractor.Aux[T, S], + enc: TraceableValue[S], + ): TraceableValue[T] = + enc.contramap(extractor.extract) + } } diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala index 20eb71f..2842fd2 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala @@ -2,15 +2,18 @@ package com.dwolla.postgres.init package repositories import cats.MonadThrow -import cats.data._ -import cats.effect._ +import cats.data.* +import cats.effect.* import cats.effect.std.Console -import cats.syntax.all._ +import cats.syntax.all.* import fs2.io.net.Network import natchez.Trace -import skunk._ +import skunk.* import skunk.util.Typer +import scala.concurrent.duration.Duration + +@FunctionalInterface trait CreateSkunkSession[F[_]] { def single(host: String, port: Int = 5432, @@ -23,6 +26,8 @@ trait CreateSkunkSession[F[_]] { parameters: Map[String, String] = Session.DefaultConnectionParameters, commandCache: Int = 1024, queryCache: Int = 1024, + parseCache: Int = 1024, + readTimeout: Duration = Duration.Inf, ): Resource[F, Session[F]] } @@ -59,6 +64,6 @@ object CreateSkunkSession { def apply[F[_] : CreateSkunkSession]: CreateSkunkSession[F] = implicitly - implicit def instance[F[_] : Concurrent : Trace : Network : Console]: CreateSkunkSession[F] = + implicit def instance[F[_] : Temporal : Trace : Network : Console]: CreateSkunkSession[F] = Session.single _ } diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala index 590537a..9527adb 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala @@ -1,18 +1,18 @@ package com.dwolla.postgres.init package repositories -import cats.data._ -import cats.effect.{Trace => _, _} -import cats.syntax.all._ +import cats.data.* +import cats.effect.{Trace as _, *} +import cats.syntax.all.* import cats.tagless.Derive -import cats.tagless.aop.Instrument -import com.dwolla.postgres.init.repositories.CreateSkunkSession._ -import com.dwolla.tracing._ -import natchez.Trace +import cats.tagless.aop.Aspect +import com.dwolla.postgres.init.repositories.CreateSkunkSession.* +import com.dwolla.tracing.syntax.* +import natchez.{Trace, TraceableValue} import org.typelevel.log4cats.Logger -import skunk._ -import skunk.codec.all._ -import skunk.implicits._ +import skunk.* +import skunk.codec.all.* +import skunk.implicits.* trait DatabaseRepository[F[_]] { def createDatabase(db: DatabaseMetadata): F[Database] @@ -20,7 +20,7 @@ trait DatabaseRepository[F[_]] { } object DatabaseRepository { - implicit val DatabaseRepositoryInstrument: Instrument[DatabaseRepository] = Derive.instrument + implicit val aspectTraceableValue: Aspect[DatabaseRepository, TraceableValue, TraceableValue] = Derive.aspect def apply[F[_] : MonadCancelThrow : Logger : Trace]: DatabaseRepository[InSession[F, *]] = new DatabaseRepository[InSession[F, *]] { override def createDatabase(db: DatabaseMetadata): Kleisli[F, Session[F], Database] = @@ -30,8 +30,7 @@ object DatabaseRepository { private def checkDatabaseExists(db: DatabaseMetadata): Kleisli[F, Session[F], Boolean] = Kleisli { _ - .prepare(DatabaseQueries.checkDatabaseExists) - .use(_.unique(db.name)) + .unique(DatabaseQueries.checkDatabaseExists)(db.name) .flatTap { count => Logger[F].info(s"Found $count databases matching ${db.name} on ${db.username}@${db.host}:${db.port}") } @@ -56,7 +55,7 @@ object DatabaseRepository { .as(database) .recoverUndefinedAs(database) } - }.withTracing + }.traceWithInputsAndOutputs } object DatabaseQueries { diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala index 89fe8ad..a33e3ae 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala @@ -1,20 +1,21 @@ package com.dwolla.postgres.init package repositories -import cats.data._ -import cats.effect.{Trace => _, _} -import cats.syntax.all._ +import cats.data.* +import cats.effect.{Trace as _, *} +import cats.syntax.all.* import cats.tagless.Derive -import cats.tagless.aop.Instrument -import com.dwolla.postgres.init.repositories.CreateSkunkSession._ -import com.dwolla.tracing._ +import cats.tagless.aop.Aspect +import com.dwolla.postgres.init.repositories.CreateSkunkSession.* +import com.dwolla.tracing.syntax.* +import com.dwolla.tracing.LowPriorityTraceableValueInstances.* import eu.timepit.refined.api.Refined -import eu.timepit.refined.auto._ -import natchez.Trace +import eu.timepit.refined.auto.* +import natchez.{Trace, TraceableValue} import org.typelevel.log4cats.Logger -import skunk._ -import skunk.codec.all._ -import skunk.implicits._ +import skunk.* +import skunk.codec.all.* +import skunk.implicits.* trait RoleRepository[F[_]] { def createRole(database: Database): F[Unit] @@ -24,7 +25,7 @@ trait RoleRepository[F[_]] { } object RoleRepository { - implicit val RoleRepositoryInstrument: Instrument[RoleRepository] = Derive.instrument + implicit val traceableValueAspect: Aspect[RoleRepository, TraceableValue, TraceableValue] = Derive.aspect def roleNameForDatabase(database: Database): RoleName = RoleName(Refined.unsafeApply(database.value + "_role")) @@ -39,8 +40,7 @@ object RoleRepository { private def checkRoleExists(role: RoleName): Kleisli[F, Session[F], Boolean] = Kleisli { _ - .prepare(RoleQueries.countRoleByName) - .use(_.unique(role)) + .unique(RoleQueries.countRoleByName)(role) .flatTap { count => Logger[F].info(s"found $count roles named $role") } @@ -108,7 +108,7 @@ object RoleRepository { .void .recoverUndefinedAs(()) } - }.withTracing + }.traceWithInputsAndOutputs } object RoleQueries { diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala index 918f1fd..b73736b 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala @@ -1,22 +1,22 @@ package com.dwolla.postgres.init package repositories -import cats.data._ -import cats.effect.{Trace => _, _} -import cats.syntax.all._ +import cats.data.* +import cats.effect.{Trace as _, *} +import cats.syntax.all.* import cats.tagless.Derive -import cats.tagless.aop.Instrument -import com.dwolla.postgres.init.repositories.CreateSkunkSession._ -import com.dwolla.tracing._ +import cats.tagless.aop.Aspect +import com.dwolla.postgres.init.repositories.CreateSkunkSession.* +import com.dwolla.tracing.syntax.* import eu.timepit.refined.api.Refined import eu.timepit.refined.refineV -import natchez.Trace -import org.typelevel.log4cats._ -import skunk._ -import skunk.codec.all._ -import skunk.implicits._ +import natchez.{Trace, TraceableValue} +import org.typelevel.log4cats.* +import skunk.* +import skunk.codec.all.* +import skunk.implicits.* -import scala.concurrent.duration._ +import scala.concurrent.duration.* trait UserRepository[F[_]] { def addOrUpdateUser(userConnectionInfo: UserConnectionInfo): F[Username] @@ -24,7 +24,7 @@ trait UserRepository[F[_]] { } object UserRepository { - implicit val UserRepositoryInstrument: Instrument[UserRepository] = Derive.instrument + implicit val traceableValueAspect: Aspect[UserRepository, TraceableValue, TraceableValue] = Derive.aspect def usernameForDatabase(database: Database): Username = Username(Refined.unsafeApply(database.value.value)) @@ -41,8 +41,7 @@ object UserRepository { private def determineCommandFor(username: Username, password: Password): Kleisli[F, Session[F], Command[Void]] = Kleisli { _ - .prepare(UserQueries.checkUserExists) - .use(_.option(username)) + .option(UserQueries.checkUserExists)(username) .flatMap { case Some(_) => Logger[F].info(s"Found and updating user named $username").as(UserQueries.updateUser(username, password)) case None => Logger[F].info(s"Creating user $username").as(UserQueries.createUser(username, password)) @@ -82,7 +81,7 @@ object UserRepository { override def removeUser(username: Username): Kleisli[F, Session[F], Username] = removeUser(username, 5) - }.withTracing + }.traceWithInputsAndOutputs } object UserQueries { diff --git a/src/main/scala/com/dwolla/tracing.scala b/src/main/scala/com/dwolla/tracing.scala deleted file mode 100644 index 9325665..0000000 --- a/src/main/scala/com/dwolla/tracing.scala +++ /dev/null @@ -1,21 +0,0 @@ -package com.dwolla - -import cats._ -import cats.effect.{Trace => _} -import cats.tagless.aop._ -import cats.tagless.syntax.all._ -import natchez._ - -object tracing { - class WithTracingSyntax[Alg[_[_]] : Instrument, F[_] : Trace](f: Alg[F]) { - private def toTraceFunctionK: Instrumentation[F, *] ~> F = new (Instrumentation[F, *] ~> F) { - override def apply[A](fa: Instrumentation[F, A]): F[A] = Trace[F].span(s"${fa.algebraName}.${fa.methodName}")(fa.value) - } - - def withTracing: Alg[F] = - Instrument[Alg].instrument(f).mapK(toTraceFunctionK) - } - - implicit def toWithTracingSyntax[Alg[_[_]] : Instrument, F[_] : Trace](f: Alg[F]): WithTracingSyntax[Alg, F] = - new WithTracingSyntax(f) -} diff --git a/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala b/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala index 0bd6d9e..d2392eb 100644 --- a/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala +++ b/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala @@ -1,8 +1,9 @@ package com.dwolla.postgres.init +import com.amazonaws.secretsmanager.SecretIdType import io.circe.Decoder -import io.circe.literal._ -import eu.timepit.refined.auto._ +import io.circe.literal.* +import eu.timepit.refined.auto.* class ExtractRequestPropertiesSpec extends munit.FunSuite { @@ -29,7 +30,7 @@ class ExtractRequestPropertiesSpec extends munit.FunSuite { Database("mydb"), MasterDatabaseUsername("masterdb"), MasterDatabasePassword("master-pass"), - List("secret1", "secret2").map(SecretId(_)), + List("secret1", "secret2").map(SecretIdType(_)), )) ) From 79e77dd1efd7de5447e9a8e6786455b9f5ab6cc0 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 16:33:58 -0600 Subject: [PATCH 43/49] add LocalApp as a way to run the code locally --- build.sbt | 5 +- project/plugins.sbt | 1 + .../init/PostgresqlDatabaseInitHandler.scala | 17 +++-- .../com/dwolla/postgres/init/LocalApp.scala | 69 +++++++++++++++++++ 4 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 src/test/scala/com/dwolla/postgres/init/LocalApp.scala diff --git a/build.sbt b/build.sbt index cb2aee1..b624ba3 100644 --- a/build.sbt +++ b/build.sbt @@ -69,11 +69,14 @@ lazy val `postgresql-init-core` = (project in file(".")) "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), ) .dependsOn(smithy) - .enablePlugins(UniversalPlugin, JavaAppPackaging) + .enablePlugins(UniversalPlugin, JavaAppPackaging, BuildInfoPlugin) lazy val serverlessDeployCommand = settingKey[Seq[String]]("serverless command to deploy the application") serverlessDeployCommand := "serverless deploy --verbose".split(' ').toSeq diff --git a/project/plugins.sbt b/project/plugins.sbt index 2dc2258..659f30e 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,3 +2,4 @@ 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") diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala index a6f75c5..92d2892 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala @@ -27,14 +27,17 @@ import smithy4s.aws.{AwsClient, AwsEnvironment} class PostgresqlDatabaseInitHandler extends IOLambda[CloudFormationCustomResourceRequest[DatabaseMetadata], INothing] { - private def resources[F[_] : Async : Compression : Console : Env : Files : Network](implicit L: Local[F, Span[F]]): Resource[F, Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => F[Option[INothing]]] = + private def resources[F[_] : Async : Compression : Console : Env : Files : Network](entryPointOverride: Option[EntryPoint[F]]) + (implicit L: Local[F, Span[F]]): Resource[F, Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => F[Option[INothing]]] = for { case implicit0(logger: Logger[F]) <- Resource.eval(Slf4jLogger.create[F]) case implicit0(random: Random[F]) <- Resource.eval(Random.scalaUtilRandom[F]) client <- httpClient[F] - entryPoint <- XRayEnvironment[F].daemonAddress.toResource.flatMap { - case Some(addr) => XRay.entryPoint(addr) - case None => XRay.entryPoint[F]() + entryPoint <- entryPointOverride.toOptionT[Resource[F, *]].getOrElseF { + XRayEnvironment[F].daemonAddress.toResource.flatMap { + case Some(addr) => XRay.entryPoint(addr) + case None => XRay.entryPoint[F]() + } } region <- Env[F].get("AWS_REGION").liftEitherT(new RuntimeException("missing AWS_REGION environment variable")).map(AwsRegion(_)).rethrowT.toResource awsEnv <- AwsEnvironment.default(client, region) @@ -45,9 +48,13 @@ class PostgresqlDatabaseInitHandler } } + def handler(entryPoint: EntryPoint[IO]): Resource[IO, Invocation[IO, CloudFormationCustomResourceRequest[DatabaseMetadata]] => IO[Option[INothing]]] = + IO.local(Span.noop[IO]).toResource + .flatMap(implicit l => resources[IO](entryPoint.some)) + override def handler: Resource[IO, Invocation[IO, CloudFormationCustomResourceRequest[DatabaseMetadata]] => IO[Option[INothing]]] = IO.local(Span.noop[IO]).toResource - .flatMap(implicit l => resources[IO]) + .flatMap(implicit l => resources[IO](None)) /** * The X-Ray kernel comes from environment variables, so we don't need to extract anything from the incoming event. diff --git a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala new file mode 100644 index 0000000..053252c --- /dev/null +++ b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala @@ -0,0 +1,69 @@ +package com.dwolla.postgres.init + +import cats.effect.* +import cats.syntax.all.* +import com.dwolla.buildinfo.postgres.init.BuildInfo +import com.dwolla.tracing.{DwollaEnvironment, OpenTelemetryAtDwolla} +import eu.timepit.refined.auto.* +import feral.lambda.cloudformation.* +import feral.lambda.{Context, Invocation} +import org.http4s.syntax.all.* +import org.typelevel.log4cats.LoggerFactory +import org.typelevel.log4cats.slf4j.Slf4jFactory + +import scala.concurrent.duration.* + +/** + * Runs the PostgresqlDatabaseInitHandler locally using a hard-coded input message. + * + * You'll need a Postgres database running at the coordinates specified in the input message, + * and a Secrets Manager secret containing the [[UserConnectionInfo]]. Make sure to set + * up AWS credentials that have access to retrieve the secret value. + * + * View the result at the response URL. There is a webhook.site URL in the example which + * should continue to work. OTel traces will be emitted and sent to the collector configured + * via environment variables. + */ +object LocalApp extends IOApp { + + override def run(args: List[String]): IO[ExitCode] = { + implicit val loggerFactory: LoggerFactory[IO] = Slf4jFactory.create[IO] + + OpenTelemetryAtDwolla[IO](BuildInfo.name, BuildInfo.version, DwollaEnvironment.Local) + .flatMap(new PostgresqlDatabaseInitHandler().handler(_)) + .use { handler => + val event = CloudFormationCustomResourceRequest[DatabaseMetadata]( + RequestType = CloudFormationRequestType.CreateRequest, + ResponseURL = uri"https://webhook.site/0df31272-1810-4b2e-aea6-e4838da33846", // view at https://webhook.site/#!/view/0df31272-1810-4b2e-aea6-e4838da33846/dff27799-9148-4c4c-8b2d-8c60bb2850f0/1 + StackId = StackId("my-stack"), + RequestId = RequestId("my-request"), + ResourceType = ResourceType("Custom::PostgresqlDatabaseInitHandler"), // TODO confirm name + LogicalResourceId = LogicalResourceId("my-resource"), + PhysicalResourceId = None, + ResourceProperties = DatabaseMetadata( + host = Host("localhost"), + port = Port(5432), + name = Database("transactionactivitymonitor"), + username = MasterDatabaseUsername("root"), + password = MasterDatabasePassword("root"), + secretIds = List("my-UserConnectionInfo-secret"), + ), + OldResourceProperties = None, + ) + + val context = Context(functionName = BuildInfo.name, + functionVersion = BuildInfo.version, + invokedFunctionArn = "arn", + memoryLimitInMB = 1024, + awsRequestId = "request-id", + logGroupName = "log-group-name", + logStreamName = "log-stream-name", + identity = None, + clientContext = None, + remainingTime = 60.minutes.pure[IO]) + + handler.apply(Invocation.pure(event, context)) + } + .as(ExitCode.Success) + } +} From 43161d95becc9704d84e1416e06162fdc2e9a416 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 16:34:16 -0600 Subject: [PATCH 44/49] redact MasterDatabasePassword in traces --- .../postgres/init/ExtractRequestProperties.scala | 9 ++++----- .../init/PostgresqlDatabaseInitHandlerImpl.scala | 14 ++++++++++++-- .../scala/com/dwolla/postgres/init/package.scala | 10 ++++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala index 52b5c86..75037ff 100644 --- a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala +++ b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala @@ -2,7 +2,6 @@ package com.dwolla.postgres.init import cats.syntax.all.* import com.amazonaws.secretsmanager.SecretIdType -import com.dwolla.tracing.LowPriorityTraceableValueInstances.nonPrimitiveTraceValueViaJson import io.circe.generic.semiauto.deriveDecoder import io.circe.refined.* import io.circe.{Decoder, HCursor, Json} @@ -29,7 +28,7 @@ object DatabaseMetadata { ).mapN(DatabaseMetadata.apply) } - implicit val traceableValue: TraceableValue[DatabaseMetadata] = TraceableValue[Json].contramap { dm => + implicit val traceableValue: TraceableValue[DatabaseMetadata] = TraceableValue[String].contramap { dm => implicit def toTraceableValueOps[A](a: A): TraceableValueOps[A] = new TraceableValueOps[A](a) json"""{ @@ -39,7 +38,7 @@ object DatabaseMetadata { "MasterDatabaseUsername": ${dm.username.toTraceValue}, "MasterDatabasePassword": ${dm.password.toTraceValue}, "UserConnectionSecrets": ${dm.secretIds.map(_.toTraceValue)} - }""" + }""".noSpaces } } @@ -53,7 +52,7 @@ case class UserConnectionInfo(database: Database, object UserConnectionInfo { implicit val UserConnectionInfoDecoder: Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo] - implicit val traceableValue: TraceableValue[UserConnectionInfo] = TraceableValue[Json].contramap { uci => + implicit val traceableValue: TraceableValue[UserConnectionInfo] = TraceableValue[String].contramap { uci => implicit def toTraceableValueOps[A](a: A): TraceableValueOps[A] = new TraceableValueOps[A](a) json"""{ @@ -62,7 +61,7 @@ object UserConnectionInfo { "database": ${uci.database.toTraceValue}, "user": ${uci.user.toTraceValue}, "password": ${uci.password.toTraceValue} - }""" + }""".noSpaces } } diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala index 660c5da..e7867fa 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala @@ -4,11 +4,14 @@ import cats.* import cats.effect.std.Console import cats.effect.{Trace as _, *} import cats.syntax.all.* +import cats.tagless.aop.Aspect +import cats.tagless.{Derive, Trivial} import com.amazonaws.secretsmanager.{ResourceNotFoundException, SecretIdType} import com.dwolla.postgres.init.PostgresqlDatabaseInitHandlerImpl.databaseAsPhysicalResourceId import com.dwolla.postgres.init.aws.SecretsManagerAlg import com.dwolla.postgres.init.repositories.* import com.dwolla.postgres.init.repositories.CreateSkunkSession.* +import com.dwolla.tracing.TraceWeaveCapturingInputs import feral.lambda.INothing import feral.lambda.cloudformation.* import fs2.io.net.Network @@ -71,14 +74,21 @@ class PostgresqlDatabaseInitHandlerImpl[F[_] : Temporal : Trace : Network : Cons } object PostgresqlDatabaseInitHandlerImpl { - def apply[F[_] : Temporal : Trace : Network : Console : Logger](secretsManager: SecretsManagerAlg[F]): PostgresqlDatabaseInitHandlerImpl[F] = - new PostgresqlDatabaseInitHandlerImpl( + def apply[F[_] : Temporal : Trace : Network : Console : Logger](secretsManager: SecretsManagerAlg[F]): CloudFormationCustomResource[F, DatabaseMetadata, INothing] = { + val impl: CloudFormationCustomResource[F, DatabaseMetadata, INothing] = new PostgresqlDatabaseInitHandlerImpl( secretsManager, DatabaseRepository[F], RoleRepository[F], UserRepository[F], ) + cloudFormationCustomResourceAspect[DatabaseMetadata].mapK(cloudFormationCustomResourceAspect[DatabaseMetadata].weave(impl))(new TraceWeaveCapturingInputs) + } + + implicit val physicalResourceIdTraceableValue: TraceableValue[feral.lambda.cloudformation.PhysicalResourceId] = TraceableValue[String].contramap(_.value) + + implicit def cloudFormationCustomResourceAspect[Input: TraceableValue]: Aspect[CloudFormationCustomResource[*[_], Input, INothing], TraceableValue, Trivial] = Derive.aspect + private[PostgresqlDatabaseInitHandlerImpl] def databaseAsPhysicalResourceId[F[_] : ApplicativeThrow](db: Database): F[PhysicalResourceId] = PhysicalResourceId(db.value.value).liftTo[F](new RuntimeException("Database name was invalid as Physical Resource ID")) } diff --git a/src/main/scala/com/dwolla/postgres/init/package.scala b/src/main/scala/com/dwolla/postgres/init/package.scala index 136698e..c3d8238 100644 --- a/src/main/scala/com/dwolla/postgres/init/package.scala +++ b/src/main/scala/com/dwolla/postgres/init/package.scala @@ -41,16 +41,18 @@ package object init { type GeneratedPassword = String Refined GeneratedPasswordPredicate type MasterDatabaseUsername = MasterDatabaseUsername.Type - object MasterDatabaseUsername extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec + object MasterDatabaseUsername extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype type MasterDatabasePassword = MasterDatabasePassword.Type - object MasterDatabasePassword extends NewtypeWrapped[String] with DerivedCirceCodec + object MasterDatabasePassword extends NewtypeWrapped[String] with DerivedCirceCodec { + implicit val traceableValue: TraceableValue[MasterDatabasePassword] = TraceableValue[String].contramap(_ => "redacted password") + } type Host = Host.Type - object Host extends NewtypeWrapped[String] with DerivedCirceCodec + object Host extends NewtypeWrapped[String] with DerivedCirceCodec with DerivedTraceableValueFromNewtype type Port = Port.Type - object Port extends NewtypeWrapped[Int] with DerivedCirceCodec + object Port extends NewtypeWrapped[Int] with DerivedCirceCodec with DerivedTraceableValueFromNewtype type Username = Username.Type object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype From 26115ec1ab21c219f8ee0be4fa429f8f58697e82 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 17:47:41 -0600 Subject: [PATCH 45/49] use ip4s for parsing hostnames and ports --- build.sbt | 1 + .../postgres/init/ExtractRequestProperties.scala | 1 + .../postgres/init/aws/SecretsManagerAlg.scala | 2 +- .../scala/com/dwolla/postgres/init/package.scala | 13 +++++++++---- .../init/repositories/CreateSkunkSession.scala | 5 +++-- .../init/ExtractRequestPropertiesSpec.scala | 5 +++-- .../scala/com/dwolla/postgres/init/LocalApp.scala | 5 +++-- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/build.sbt b/build.sbt index b624ba3..d029a19 100644 --- a/build.sbt +++ b/build.sbt @@ -66,6 +66,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "com.chuusai" %% "shapeless" % "2.3.13", "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, diff --git a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala index 75037ff..19db866 100644 --- a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala +++ b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala @@ -2,6 +2,7 @@ package com.dwolla.postgres.init 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, Json} diff --git a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala index 2b18083..7efb6bb 100644 --- a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala +++ b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala @@ -38,7 +38,7 @@ object SecretsManagerAlg { List(Aspect.Advice.byValue("secretId", secretId)), List(Aspect.Advice.byValue("implicit decoder", Decoder[A].toString)), ), - Aspect.Advice("getSecretAs", af.getSecretAs(secretId))(TraceableValue[String].contramap[A](_ => "redacted successfully parsed and decoded secret")) + Aspect.Advice("getSecretAs", af.getSecretAs[A](secretId))(TraceableValue[String].contramap[A](_ => "redacted successfully parsed and decoded secret")) ) } diff --git a/src/main/scala/com/dwolla/postgres/init/package.scala b/src/main/scala/com/dwolla/postgres/init/package.scala index c3d8238..db937c6 100644 --- a/src/main/scala/com/dwolla/postgres/init/package.scala +++ b/src/main/scala/com/dwolla/postgres/init/package.scala @@ -1,8 +1,11 @@ package com.dwolla.postgres +import cats.syntax.all.* +import com.comcast.ip4s.{Host, Port} import eu.timepit.refined.* import eu.timepit.refined.api.Refined import eu.timepit.refined.string.* +import io.circe.Decoder import monix.newtypes.NewtypeWrapped import monix.newtypes.integrations.DerivedCirceCodec import natchez.TraceableValue @@ -48,11 +51,13 @@ package object init { implicit val traceableValue: TraceableValue[MasterDatabasePassword] = TraceableValue[String].contramap(_ => "redacted password") } - type Host = Host.Type - object Host extends NewtypeWrapped[String] with DerivedCirceCodec with DerivedTraceableValueFromNewtype + private[init] implicit val hostDecoder: Decoder[Host] = + Decoder[String].emap(s => Host.fromString(s).toRight(s"$s could not be decoded as a Host")) + private[init] implicit val hostTraceableValue: TraceableValue[Host] = TraceableValue[String].contramap(_.show) - type Port = Port.Type - object Port extends NewtypeWrapped[Int] with DerivedCirceCodec with DerivedTraceableValueFromNewtype + private[init] implicit val portDecoder: Decoder[Port] = + Decoder[Int].emap(i => Port.fromInt(i).toRight(s"$i could not be decoded as a Port")) + private[init] implicit val portTraceableValue: TraceableValue[Port] = TraceableValue[Int].contramap(_.value) type Username = Username.Type object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala index 2842fd2..a9f9fb4 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala @@ -6,6 +6,7 @@ import cats.data.* import cats.effect.* import cats.effect.std.Console import cats.syntax.all.* +import com.comcast.ip4s.* import fs2.io.net.Network import natchez.Trace import skunk.* @@ -44,12 +45,12 @@ object CreateSkunkSession { `🦨`: CreateSkunkSession[F], `[]`: MonadCancelThrow[F]): F[A] = CreateSkunkSession[F].single( - host = host.value, + host = host.show, port = port.value, user = username.value.value, database = "postgres", password = password.value.some, - ssl = SSL.System, + ssl = if (host == host"localhost") SSL.None else SSL.System, ).use(kleisli.run) } diff --git a/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala b/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala index d2392eb..149edcb 100644 --- a/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala +++ b/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala @@ -1,6 +1,7 @@ package com.dwolla.postgres.init import com.amazonaws.secretsmanager.SecretIdType +import com.comcast.ip4s.* import io.circe.Decoder import io.circe.literal.* import eu.timepit.refined.auto.* @@ -25,8 +26,8 @@ class ExtractRequestPropertiesSpec extends munit.FunSuite { assertEquals( Decoder[DatabaseMetadata].decodeJson(input), Right(DatabaseMetadata( - Host("database-hostname"), - Port(5432), + host"database-hostname", + port"5432", Database("mydb"), MasterDatabaseUsername("masterdb"), MasterDatabasePassword("master-pass"), diff --git a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala index 053252c..e4c6f87 100644 --- a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala +++ b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala @@ -2,6 +2,7 @@ package com.dwolla.postgres.init import cats.effect.* import cats.syntax.all.* +import com.comcast.ip4s.* import com.dwolla.buildinfo.postgres.init.BuildInfo import com.dwolla.tracing.{DwollaEnvironment, OpenTelemetryAtDwolla} import eu.timepit.refined.auto.* @@ -41,8 +42,8 @@ object LocalApp extends IOApp { LogicalResourceId = LogicalResourceId("my-resource"), PhysicalResourceId = None, ResourceProperties = DatabaseMetadata( - host = Host("localhost"), - port = Port(5432), + host = host"localhost", + port = port"5432", name = Database("transactionactivitymonitor"), username = MasterDatabaseUsername("root"), password = MasterDatabasePassword("root"), From 330261f71eadb184e86d82ce9df0984727ca834c Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 17:59:24 -0600 Subject: [PATCH 46/49] refactor PostgresqlDatabaseInitHandlerImpl to make trace instrumentation cleaner --- .../init/PostgresqlDatabaseInitHandler.scala | 8 +- .../PostgresqlDatabaseInitHandlerImpl.scala | 120 ++++++++---------- 2 files changed, 62 insertions(+), 66 deletions(-) diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala index 92d2892..c649f76 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala @@ -7,6 +7,7 @@ import cats.mtl.Local import cats.syntax.all.* import com.amazonaws.secretsmanager.SecretsManager import com.dwolla.postgres.init.aws.SecretsManagerAlg +import com.dwolla.postgres.init.repositories.{DatabaseRepository, RoleRepository, UserRepository} import feral.lambda.cloudformation.* import feral.lambda.{AwsTags, INothing, IOLambda, Invocation, KernelSource} import fs2.compression.Compression @@ -44,7 +45,12 @@ class PostgresqlDatabaseInitHandler secretsManager <- AwsClient(SecretsManager, awsEnv).map(SecretsManagerAlg[F](_)) } yield { implicit env: Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => TracedHandler(entryPoint) { _ => - CloudFormationCustomResource(client, PostgresqlDatabaseInitHandlerImpl(secretsManager)) + CloudFormationCustomResource(client, PostgresqlDatabaseInitHandlerImpl( + secretsManager, + DatabaseRepository[F], + RoleRepository[F], + UserRepository[F], + )) } } diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala index e7867fa..cb29d68 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala @@ -7,88 +7,78 @@ import cats.syntax.all.* import cats.tagless.aop.Aspect import cats.tagless.{Derive, Trivial} import com.amazonaws.secretsmanager.{ResourceNotFoundException, SecretIdType} -import com.dwolla.postgres.init.PostgresqlDatabaseInitHandlerImpl.databaseAsPhysicalResourceId import com.dwolla.postgres.init.aws.SecretsManagerAlg import com.dwolla.postgres.init.repositories.* import com.dwolla.postgres.init.repositories.CreateSkunkSession.* -import com.dwolla.tracing.TraceWeaveCapturingInputs +import com.dwolla.tracing.syntax.* import feral.lambda.INothing import feral.lambda.cloudformation.* import fs2.io.net.Network import natchez.* import org.typelevel.log4cats.Logger -class PostgresqlDatabaseInitHandlerImpl[F[_] : Temporal : Trace : Network : Console : Logger](secretsManagerAlg: SecretsManagerAlg[F], - databaseRepository: DatabaseRepository[InSession[F, *]], - roleRepository: RoleRepository[InSession[F, *]], - userRepository: UserRepository[InSession[F, *]], - ) extends CloudFormationCustomResource[F, DatabaseMetadata, INothing] { - override def createResource(event: DatabaseMetadata): F[HandlerResponse[INothing]] = - handleCreateOrUpdate(event)(createOrUpdate(_, event)).map(HandlerResponse(_, None)) +trait PostgresqlDatabaseInitHandlerImpl[F[_]] extends CloudFormationCustomResource[F, DatabaseMetadata, INothing] - override def updateResource(event: DatabaseMetadata, physicalResourceId: PhysicalResourceId): F[HandlerResponse[INothing]] = - handleCreateOrUpdate(event)(createOrUpdate(_, event)).map(HandlerResponse(_, None)) - - override def deleteResource(event: DatabaseMetadata, physicalResourceId: PhysicalResourceId): F[HandlerResponse[INothing]] = - for { - usernames <- getUsernamesFromSecrets(event.secretIds, UserRepository.usernameForDatabase(event.name)) - dbId <- removeUsersFromDatabase(usernames, event.name).inSession(event.host, event.port, event.username, event.password) - } yield HandlerResponse(dbId, None) +object PostgresqlDatabaseInitHandlerImpl { + implicit val physicalResourceIdTraceableValue: TraceableValue[feral.lambda.cloudformation.PhysicalResourceId] = TraceableValue[String].contramap(_.value) - private def createOrUpdate(userPasswords: List[UserConnectionInfo], input: DatabaseMetadata): InSession[F, PhysicalResourceId] = - for { - db <- databaseAsPhysicalResourceId[InSession[F, *]](input.name) - _ <- databaseRepository.createDatabase(input) - _ <- roleRepository.createRole(input.name) - _ <- userPasswords.traverse { userPassword => - userRepository.addOrUpdateUser(userPassword) >> roleRepository.addUserToRole(userPassword.user, userPassword.database) - } - } yield db + implicit def postgresqlDatabaseInitHandlerImplAspect: Aspect[PostgresqlDatabaseInitHandlerImpl, TraceableValue, Trivial] = Derive.aspect - private def handleCreateOrUpdate(input: DatabaseMetadata) - (f: List[UserConnectionInfo] => InSession[F, PhysicalResourceId]): F[PhysicalResourceId] = - for { - userPasswords <- input.secretIds.traverse(secretsManagerAlg.getSecretAs[UserConnectionInfo]) - id <- f(userPasswords).inSession(input.host, input.port, input.username, input.password) - } yield id + private[PostgresqlDatabaseInitHandlerImpl] def databaseAsPhysicalResourceId[F[_] : ApplicativeThrow](db: Database): F[PhysicalResourceId] = + PhysicalResourceId(db.value.value).liftTo[F](new RuntimeException("Database name was invalid as Physical Resource ID")) - private def getUsernamesFromSecrets(secretIds: List[SecretIdType], fallback: Username): F[List[Username]] = - secretIds.traverse { secretId => - secretsManagerAlg.getSecretAs[UserConnectionInfo](secretId) - .map(_.user) - .recoverWith { - case ex: ResourceNotFoundException => - Logger[F].warn(ex)(s"could not retrieve secret ${secretId.value}, falling back to ${fallback.value}") - .as(fallback) - } - } + def apply[F[_] : Temporal : Trace : Network : Console : Logger](secretsManagerAlg: SecretsManagerAlg[F], + databaseRepository: DatabaseRepository[InSession[F, *]], + roleRepository: RoleRepository[InSession[F, *]], + userRepository: UserRepository[InSession[F, *]], + ): PostgresqlDatabaseInitHandlerImpl[F] = new PostgresqlDatabaseInitHandlerImpl[F] { + override def createResource(event: DatabaseMetadata): F[HandlerResponse[INothing]] = + handleCreateOrUpdate(event)(createOrUpdate(_, event)).map(HandlerResponse(_, None)) - private def removeUsersFromDatabase(usernames: List[Username], databaseName: Database): InSession[F, PhysicalResourceId] = - for { - db <- databaseAsPhysicalResourceId[InSession[F, *]](databaseName) - _ <- usernames.traverse(roleRepository.removeUserFromRole(_, databaseName)) - _ <- databaseRepository.removeDatabase(databaseName) - _ <- roleRepository.removeRole(databaseName) - _ <- usernames.traverse(userRepository.removeUser) - } yield db -} + override def updateResource(event: DatabaseMetadata, physicalResourceId: PhysicalResourceId): F[HandlerResponse[INothing]] = + handleCreateOrUpdate(event)(createOrUpdate(_, event)).map(HandlerResponse(_, None)) -object PostgresqlDatabaseInitHandlerImpl { - def apply[F[_] : Temporal : Trace : Network : Console : Logger](secretsManager: SecretsManagerAlg[F]): CloudFormationCustomResource[F, DatabaseMetadata, INothing] = { - val impl: CloudFormationCustomResource[F, DatabaseMetadata, INothing] = new PostgresqlDatabaseInitHandlerImpl( - secretsManager, - DatabaseRepository[F], - RoleRepository[F], - UserRepository[F], - ) + override def deleteResource(event: DatabaseMetadata, physicalResourceId: PhysicalResourceId): F[HandlerResponse[INothing]] = + for { + usernames <- getUsernamesFromSecrets(event.secretIds, UserRepository.usernameForDatabase(event.name)) + dbId <- removeUsersFromDatabase(usernames, event.name).inSession(event.host, event.port, event.username, event.password) + } yield HandlerResponse(dbId, None) - cloudFormationCustomResourceAspect[DatabaseMetadata].mapK(cloudFormationCustomResourceAspect[DatabaseMetadata].weave(impl))(new TraceWeaveCapturingInputs) - } + private def createOrUpdate(userPasswords: List[UserConnectionInfo], input: DatabaseMetadata): InSession[F, PhysicalResourceId] = + for { + db <- databaseAsPhysicalResourceId[InSession[F, *]](input.name) + _ <- databaseRepository.createDatabase(input) + _ <- roleRepository.createRole(input.name) + _ <- userPasswords.traverse { userPassword => + userRepository.addOrUpdateUser(userPassword) >> roleRepository.addUserToRole(userPassword.user, userPassword.database) + } + } yield db - implicit val physicalResourceIdTraceableValue: TraceableValue[feral.lambda.cloudformation.PhysicalResourceId] = TraceableValue[String].contramap(_.value) + private def handleCreateOrUpdate(input: DatabaseMetadata) + (f: List[UserConnectionInfo] => InSession[F, PhysicalResourceId]): F[PhysicalResourceId] = + for { + userPasswords <- input.secretIds.traverse(secretsManagerAlg.getSecretAs[UserConnectionInfo]) + id <- f(userPasswords).inSession(input.host, input.port, input.username, input.password) + } yield id - implicit def cloudFormationCustomResourceAspect[Input: TraceableValue]: Aspect[CloudFormationCustomResource[*[_], Input, INothing], TraceableValue, Trivial] = Derive.aspect + private def getUsernamesFromSecrets(secretIds: List[SecretIdType], fallback: Username): F[List[Username]] = + secretIds.traverse { secretId => + secretsManagerAlg.getSecretAs[UserConnectionInfo](secretId) + .map(_.user) + .recoverWith { + case ex: ResourceNotFoundException => + Logger[F].warn(ex)(s"could not retrieve secret ${secretId.value}, falling back to ${fallback.value}") + .as(fallback) + } + } - private[PostgresqlDatabaseInitHandlerImpl] def databaseAsPhysicalResourceId[F[_] : ApplicativeThrow](db: Database): F[PhysicalResourceId] = - PhysicalResourceId(db.value.value).liftTo[F](new RuntimeException("Database name was invalid as Physical Resource ID")) + private def removeUsersFromDatabase(usernames: List[Username], databaseName: Database): InSession[F, PhysicalResourceId] = + for { + db <- databaseAsPhysicalResourceId[InSession[F, *]](databaseName) + _ <- usernames.traverse(roleRepository.removeUserFromRole(_, databaseName)) + _ <- databaseRepository.removeDatabase(databaseName) + _ <- roleRepository.removeRole(databaseName) + _ <- usernames.traverse(userRepository.removeUser) + } yield db + }.traceWithInputs } From c2407e7777eb8933a2f9e4218e2d8a42a8ceac63 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 18:03:41 -0600 Subject: [PATCH 47/49] build and execute on Java 17 --- .github/workflows/ci.yml | 25 ++++++------------------- .mergify.yml | 3 +-- build.sbt | 2 +- serverless.yml | 2 +- 4 files changed, 9 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ade28a..06e0c3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: matrix: os: [ubuntu-22.04] scala: [2.13] - java: [temurin@8, temurin@11] + java: [temurin@17] runs-on: ${{ matrix.os }} timeout-minutes: 60 steps: @@ -41,30 +41,17 @@ jobs: - name: Setup sbt uses: sbt/setup-sbt@v1 - - name: Setup Java (temurin@8) - id: setup-java-temurin-8 - if: matrix.java == 'temurin@8' + - 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: 8 + java-version: 17 cache: sbt - name: sbt update - if: matrix.java == 'temurin@8' && steps.setup-java-temurin-8.outputs.cache-hit == 'false' - run: sbt +update - - - name: Setup Java (temurin@11) - id: setup-java-temurin-11 - if: matrix.java == 'temurin@11' - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 11 - cache: sbt - - - name: sbt update - if: matrix.java == 'temurin@11' && steps.setup-java-temurin-11.outputs.cache-hit == 'false' + 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 diff --git a/.mergify.yml b/.mergify.yml index 01062fb..a7250cc 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -10,8 +10,7 @@ pull_request_rules: conditions: - author=scala-steward - body~=labels:.*early-semver-patch - - status-success=Test (ubuntu-22.04, 2.13, temurin@8) - - status-success=Test (ubuntu-22.04, 2.13, temurin@11) + - status-success=Test (ubuntu-22.04, 2.13, temurin@17) actions: merge: {} - name: Label smithy PRs diff --git a/build.sbt b/build.sbt index d029a19..721b184 100644 --- a/build.sbt +++ b/build.sbt @@ -32,7 +32,7 @@ lazy val smithy = (project in file("smithy")) 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 diff --git a/serverless.yml b/serverless.yml index 99da87f..a0648dc 100644 --- a/serverless.yml +++ b/serverless.yml @@ -4,7 +4,7 @@ variablesResolutionMode: 20210326 provider: name: aws - runtime: java11 + runtime: java17 memorySize: 1024 timeout: 60 region: us-west-2 From 6d2fa887311e5daf6e31af89b67eaaea6a052c53 Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 18:45:18 -0600 Subject: [PATCH 48/49] Update to Scala 3.7.4 --- .github/workflows/ci.yml | 2 +- .mergify.yml | 2 +- build.sbt | 9 +---- .../init/ExtractRequestProperties.scala | 13 ++----- .../init/PostgresqlDatabaseInitHandler.scala | 17 +++++---- .../PostgresqlDatabaseInitHandlerImpl.scala | 1 + .../postgres/init/aws/SecretsManagerAlg.scala | 2 +- .../com/dwolla/postgres/init/package.scala | 37 ++++--------------- .../repositories/CreateSkunkSession.scala | 4 +- .../repositories/DatabaseRepository.scala | 1 + .../init/repositories/RoleRepository.scala | 1 + .../init/repositories/UserRepository.scala | 1 + .../init/ExtractRequestPropertiesSpec.scala | 5 +-- .../com/dwolla/postgres/init/LocalApp.scala | 10 +++-- .../postgres/init/SqlStringRegexSpec.scala | 9 +++-- 15 files changed, 43 insertions(+), 71 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06e0c3a..d26e441 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - scala: [2.13] + scala: [3] java: [temurin@17] runs-on: ${{ matrix.os }} timeout-minutes: 60 diff --git a/.mergify.yml b/.mergify.yml index a7250cc..8cc6f33 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -10,7 +10,7 @@ pull_request_rules: conditions: - author=scala-steward - body~=labels:.*early-semver-patch - - status-success=Test (ubuntu-22.04, 2.13, temurin@17) + - status-success=Test (ubuntu-22.04, 3, temurin@17) actions: merge: {} - name: Label smithy PRs diff --git a/build.sbt b/build.sbt index 721b184..3639f0a 100644 --- a/build.sbt +++ b/build.sbt @@ -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.17" +ThisBuild / scalaVersion := "3.7.4" ThisBuild / developers := List( Developer( "bpholt", @@ -12,10 +12,6 @@ ThisBuild / developers := List( ), ) ThisBuild / startYear := Option(2021) -ThisBuild / libraryDependencies ++= Seq( - compilerPlugin("org.typelevel" %% "kind-projector" % "0.13.4" cross CrossVersion.full), - compilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1"), -) ThisBuild / resolvers += Resolver.sonatypeCentralSnapshots lazy val smithy = (project in file("smithy")) @@ -51,7 +47,7 @@ lazy val `postgresql-init-core` = (project in file(".")) "org.typelevel" %% "feral-lambda-cloudformation-custom-resource" % feralVersion, "org.tpolecat" %% "natchez-xray" % natchezVersion, "org.tpolecat" %% "natchez-http4s" % "0.6.1", - "org.typelevel" %% "cats-tagless-macros" % "0.16.3-85-591274f-SNAPSHOT", // see https://github.com/typelevel/cats-tagless/issues/652 + "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, @@ -63,7 +59,6 @@ lazy val `postgresql-init-core` = (project in file(".")) "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.chuusai" %% "shapeless" % "2.3.13", "com.dwolla" %% "natchez-tagless" % "0.2.6-131-d6a1c7c-SNAPSHOT", "org.typelevel" %% "mouse" % "1.4.0", "com.comcast" %% "ip4s-core" % "3.7.0", diff --git a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala index 19db866..74965db 100644 --- a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala +++ b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala @@ -30,8 +30,6 @@ object DatabaseMetadata { } implicit val traceableValue: TraceableValue[DatabaseMetadata] = TraceableValue[String].contramap { dm => - implicit def toTraceableValueOps[A](a: A): TraceableValueOps[A] = new TraceableValueOps[A](a) - json"""{ "Host": ${dm.host.toTraceValue}, "Port": ${dm.port.toTraceValue}, @@ -54,8 +52,6 @@ object UserConnectionInfo { implicit val UserConnectionInfoDecoder: Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo] implicit val traceableValue: TraceableValue[UserConnectionInfo] = TraceableValue[String].contramap { uci => - implicit def toTraceableValueOps[A](a: A): TraceableValueOps[A] = new TraceableValueOps[A](a) - json"""{ "host": ${uci.host.toTraceValue}, "port": ${uci.port.toTraceValue}, @@ -66,12 +62,12 @@ object UserConnectionInfo { } } -private class TraceableValueOps[A](val a: A) extends AnyVal { - def toTraceValue(implicit T: TraceableValue[A]): Json = T.toTraceValue(a) match { +extension [A](a: A) + def toTraceValue(implicit T: TraceableValue[A]): Json = T.toTraceValue(a) match case TraceValue.StringValue(value) => value.asJson case TraceValue.BooleanValue(value) => value.asJson case TraceValue.NumberValue(value) => - value match { + 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) @@ -86,6 +82,3 @@ private class TraceableValueOps[A](val a: A) extends AnyVal { // Fallback: try BigDecimal to preserve value if possible val bd = new java.math.BigDecimal(value.toString) Json.fromBigDecimal(scala.math.BigDecimal(bd)) - } - } -} diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala index c649f76..dad49e8 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala @@ -25,14 +25,15 @@ import org.typelevel.log4cats.slf4j.Slf4jLogger import smithy4s.aws.kernel.AwsRegion import smithy4s.aws.{AwsClient, AwsEnvironment} +@annotation.experimental class PostgresqlDatabaseInitHandler extends IOLambda[CloudFormationCustomResourceRequest[DatabaseMetadata], INothing] { - private def resources[F[_] : Async : Compression : Console : Env : Files : Network](entryPointOverride: Option[EntryPoint[F]]) - (implicit L: Local[F, Span[F]]): Resource[F, Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => F[Option[INothing]]] = + private def resources[F[_] : {Async, Compression, Console, Env, Files, Network}](entryPointOverride: Option[EntryPoint[F]]) + (using Local[F, Span[F]]): Resource[F, Invocation[F, CloudFormationCustomResourceRequest[DatabaseMetadata]] => F[Option[INothing]]] = for { - case implicit0(logger: Logger[F]) <- Resource.eval(Slf4jLogger.create[F]) - case implicit0(random: Random[F]) <- Resource.eval(Random.scalaUtilRandom[F]) + given Logger[F] <- Resource.eval(Slf4jLogger.create[F]) + given Random[F] <- Resource.eval(Random.scalaUtilRandom[F]) client <- httpClient[F] entryPoint <- entryPointOverride.toOptionT[Resource[F, *]].getOrElseF { XRayEnvironment[F].daemonAddress.toResource.flatMap { @@ -69,7 +70,7 @@ class PostgresqlDatabaseInitHandler */ private implicit def kernelSource[Event]: KernelSource[Event] = KernelSource.emptyKernelSource - private def httpClient[F[_] : Async : Network : Trace]: Resource[F, Client[F]] = + private def httpClient[F[_] : {Async, Network, Trace}]: Resource[F, Client[F]] = EmberClientBuilder .default[F] .build @@ -82,9 +83,9 @@ class PostgresqlDatabaseInitHandler object TracedHandler { def apply[F[_] : MonadCancelThrow, Event, Result](entryPoint: EntryPoint[F]) (handler: Trace[F] => F[Option[Result]]) - (implicit inv: Invocation[F, Event], - KS: KernelSource[Event], - L: Local[F, Span[F]]): F[Option[Result]] = + (using Invocation[F, Event], + KernelSource[Event], + Local[F, Span[F]]): F[Option[Result]] = for { event <- Invocation[F, Event].event context <- Invocation[F, Event].context diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala index cb29d68..8a32306 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala @@ -19,6 +19,7 @@ import org.typelevel.log4cats.Logger trait PostgresqlDatabaseInitHandlerImpl[F[_]] extends CloudFormationCustomResource[F, DatabaseMetadata, INothing] +@annotation.experimental object PostgresqlDatabaseInitHandlerImpl { implicit val physicalResourceIdTraceableValue: TraceableValue[feral.lambda.cloudformation.PhysicalResourceId] = TraceableValue[String].contramap(_.value) diff --git a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala index 7efb6bb..ef77d0e 100644 --- a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala +++ b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala @@ -38,7 +38,7 @@ object SecretsManagerAlg { List(Aspect.Advice.byValue("secretId", secretId)), List(Aspect.Advice.byValue("implicit decoder", Decoder[A].toString)), ), - Aspect.Advice("getSecretAs", af.getSecretAs[A](secretId))(TraceableValue[String].contramap[A](_ => "redacted successfully parsed and decoded secret")) + Aspect.Advice("getSecretAs", af.getSecretAs[A](secretId))(using TraceableValue[String].contramap[A](_ => "redacted successfully parsed and decoded secret")) ) } diff --git a/src/main/scala/com/dwolla/postgres/init/package.scala b/src/main/scala/com/dwolla/postgres/init/package.scala index db937c6..05ecb30 100644 --- a/src/main/scala/com/dwolla/postgres/init/package.scala +++ b/src/main/scala/com/dwolla/postgres/init/package.scala @@ -3,45 +3,22 @@ package com.dwolla.postgres import cats.syntax.all.* import com.comcast.ip4s.{Host, Port} import eu.timepit.refined.* -import eu.timepit.refined.api.Refined +import eu.timepit.refined.api.* import eu.timepit.refined.string.* import io.circe.Decoder import monix.newtypes.NewtypeWrapped import monix.newtypes.integrations.DerivedCirceCodec import natchez.TraceableValue -import shapeless.ops.hlist -import shapeless.ops.tuple.* -import shapeless.syntax.std.tuple.* -import shapeless.{HList, LabelledGeneric} package object init { - implicit class ApplyAll[P <: Product](p: P) { - def applyAll[A, B, O](a: A) - (implicit - cm: ConstMapper.Aux[P, A, B], - za: ZipApply.Aux[P, B, O], - ): O = - p.zipApply(p.mapConst(a)) - } - - implicit class MigrationOps[A](a: A) { - def migrateTo[B](implicit migration: Migration[A, B]): B = - migration.apply(a) - } - - implicit def genericMigration[A, B, ARepr <: HList, BRepr <: HList](implicit - aGen: LabelledGeneric.Aux[A, ARepr], - bGen: LabelledGeneric.Aux[B, BRepr], - inter: hlist.Intersection.Aux[ARepr, BRepr, BRepr] - ): Migration[A, B] = - a => bGen.from(inter.apply(aGen.to(a))) - implicit def refinedTraceableValue[A: TraceableValue, P]: TraceableValue[A Refined P] = TraceableValue[A].contramap(_.value) - type SqlIdentifierPredicate = MatchesRegex[W.`"[A-Za-z][A-Za-z0-9_]*"`.T] + type SqlIdentifierPredicate = MatchesRegex["[A-Za-z][A-Za-z0-9_]*"] + type SqlIdentifier = String Refined SqlIdentifierPredicate - type GeneratedPasswordPredicate = MatchesRegex[W.`"""[-A-Za-z0-9!"#$%&()*+,./:<=>?@\\[\\]\\\\^_{|}~]+"""`.T] - type GeneratedPassword = String Refined GeneratedPasswordPredicate + object SqlIdentifier extends RefinedTypeOps[SqlIdentifier, String] + + type GeneratedPasswordPredicate = MatchesRegex["""[-A-Za-z0-9!"#$%&()*+,./:<=>?@\[\]\\^_{|}~]+"""] type MasterDatabaseUsername = MasterDatabaseUsername.Type object MasterDatabaseUsername extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype @@ -63,7 +40,7 @@ package object init { object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype type Password = Password.Type - object Password extends NewtypeWrapped[GeneratedPassword] with DerivedCirceCodec { + object Password extends NewtypeWrapped[String Refined GeneratedPasswordPredicate] with DerivedCirceCodec { implicit val traceableValue: TraceableValue[Password] = TraceableValue[String].contramap(_ => "redacted password") } diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala index a9f9fb4..a198eac 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala @@ -65,6 +65,6 @@ object CreateSkunkSession { def apply[F[_] : CreateSkunkSession]: CreateSkunkSession[F] = implicitly - implicit def instance[F[_] : Temporal : Trace : Network : Console]: CreateSkunkSession[F] = - Session.single _ + implicit def instance[F[_] : {Temporal, Trace, Network, Console}]: CreateSkunkSession[F] = + Session.single } diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala index 9527adb..f9b9931 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala @@ -19,6 +19,7 @@ trait DatabaseRepository[F[_]] { def removeDatabase(database: Database): F[Database] } +@annotation.experimental object DatabaseRepository { implicit val aspectTraceableValue: Aspect[DatabaseRepository, TraceableValue, TraceableValue] = Derive.aspect diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala index a33e3ae..27c54f4 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala @@ -24,6 +24,7 @@ trait RoleRepository[F[_]] { def removeUserFromRole(username: Username, database: Database): F[Unit] } +@annotation.experimental object RoleRepository { implicit val traceableValueAspect: Aspect[RoleRepository, TraceableValue, TraceableValue] = Derive.aspect diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala index b73736b..0386b04 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala @@ -23,6 +23,7 @@ trait UserRepository[F[_]] { def removeUser(username: Username): F[Username] } +@annotation.experimental object UserRepository { implicit val traceableValueAspect: Aspect[UserRepository, TraceableValue, TraceableValue] = Derive.aspect diff --git a/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala b/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala index 149edcb..c87d492 100644 --- a/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala +++ b/src/test/scala/com/dwolla/postgres/init/ExtractRequestPropertiesSpec.scala @@ -4,7 +4,6 @@ import com.amazonaws.secretsmanager.SecretIdType import com.comcast.ip4s.* import io.circe.Decoder import io.circe.literal.* -import eu.timepit.refined.auto.* class ExtractRequestPropertiesSpec extends munit.FunSuite { @@ -28,8 +27,8 @@ class ExtractRequestPropertiesSpec extends munit.FunSuite { Right(DatabaseMetadata( host"database-hostname", port"5432", - Database("mydb"), - MasterDatabaseUsername("masterdb"), + Database(SqlIdentifier.unsafeFrom("mydb")), + MasterDatabaseUsername(SqlIdentifier.unsafeFrom("masterdb")), MasterDatabasePassword("master-pass"), List("secret1", "secret2").map(SecretIdType(_)), )) diff --git a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala index e4c6f87..5dcae65 100644 --- a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala +++ b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala @@ -11,6 +11,7 @@ import feral.lambda.{Context, Invocation} import org.http4s.syntax.all.* import org.typelevel.log4cats.LoggerFactory import org.typelevel.log4cats.slf4j.Slf4jFactory +import com.amazonaws.secretsmanager.SecretIdType import scala.concurrent.duration.* @@ -25,6 +26,7 @@ import scala.concurrent.duration.* * should continue to work. OTel traces will be emitted and sent to the collector configured * via environment variables. */ +@annotation.experimental object LocalApp extends IOApp { override def run(args: List[String]): IO[ExitCode] = { @@ -44,10 +46,10 @@ object LocalApp extends IOApp { ResourceProperties = DatabaseMetadata( host = host"localhost", port = port"5432", - name = Database("transactionactivitymonitor"), - username = MasterDatabaseUsername("root"), - password = MasterDatabasePassword("root"), - secretIds = List("my-UserConnectionInfo-secret"), + name = Database(SqlIdentifier.unsafeFrom("transactionactivitymonitor")), + username = MasterDatabaseUsername(SqlIdentifier.unsafeFrom("root")), + password = MasterDatabasePassword(SqlIdentifier.unsafeFrom("root")), + secretIds = List("my-UserConnectionInfo-secret").map(SecretIdType(_)), ), OldResourceProperties = None, ) diff --git a/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala b/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala index 3f6a153..cc2c2e3 100644 --- a/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala +++ b/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala @@ -1,5 +1,6 @@ package com.dwolla.postgres.init +import cats.syntax.all.* import eu.timepit.refined.refineV import org.scalacheck.Prop.forAll import org.scalacheck.{Arbitrary, Gen, Shrink} @@ -23,8 +24,8 @@ class SqlStringRegexSpec extends munit.ScalaCheckSuite { } yield s"$initial$tail" } - forAll { s: String => - assert(refineV[SqlIdentifierPredicate](s).map(_.value) == Right(s)) + forAll { (s: String) => + assertEquals(refineV[SqlIdentifierPredicate](s).map(_.value), s.asRight) } } @@ -39,8 +40,8 @@ class SqlStringRegexSpec extends munit.ScalaCheckSuite { } yield s"$initial$tail" } - forAll { s: String => - assert(refineV[GeneratedPasswordPredicate](s).map(_.value) == Right(s)) + forAll { (s: String) => + assertEquals(refineV[GeneratedPasswordPredicate](s).map(_.value), s.asRight) } } } From 6de16c09cc30e0c482d0afece75ea76849b5a20b Mon Sep 17 00:00:00 2001 From: Brian Holt Date: Wed, 12 Nov 2025 19:10:07 -0600 Subject: [PATCH 49/49] more Scala 3 syntax --- .../init/ExtractRequestProperties.scala | 10 +-- .../init/PostgresqlDatabaseInitHandler.scala | 2 +- .../PostgresqlDatabaseInitHandlerImpl.scala | 14 ++-- .../postgres/init/aws/SecretsManagerAlg.scala | 4 +- .../com/dwolla/postgres/init/package.scala | 76 ++++++++----------- .../repositories/CreateSkunkSession.scala | 18 ++--- .../repositories/DatabaseRepository.scala | 4 +- .../init/repositories/RoleRepository.scala | 2 +- .../init/repositories/UserRepository.scala | 6 +- .../com/dwolla/postgres/init/LocalApp.scala | 2 +- .../postgres/init/SqlStringRegexSpec.scala | 6 +- 11 files changed, 62 insertions(+), 82 deletions(-) diff --git a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala index 74965db..e00e1d5 100644 --- a/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala +++ b/src/main/scala/com/dwolla/postgres/init/ExtractRequestProperties.scala @@ -19,7 +19,7 @@ case class DatabaseMetadata(host: Host, ) object DatabaseMetadata { - implicit val DecodeDatabaseMetadata: Decoder[DatabaseMetadata] = Decoder.accumulatingInstance { (c: HCursor) => + given Decoder[DatabaseMetadata] = Decoder.accumulatingInstance { (c: HCursor) => (c.downField("Host").asAcc[Host], c.downField("Port").asAcc[Port], c.downField("DatabaseName").asAcc[Database], @@ -29,7 +29,7 @@ object DatabaseMetadata { ).mapN(DatabaseMetadata.apply) } - implicit val traceableValue: TraceableValue[DatabaseMetadata] = TraceableValue[String].contramap { dm => + given TraceableValue[DatabaseMetadata] = TraceableValue[String].contramap { dm => json"""{ "Host": ${dm.host.toTraceValue}, "Port": ${dm.port.toTraceValue}, @@ -49,9 +49,9 @@ case class UserConnectionInfo(database: Database, ) object UserConnectionInfo { - implicit val UserConnectionInfoDecoder: Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo] + given Decoder[UserConnectionInfo] = deriveDecoder[UserConnectionInfo] - implicit val traceableValue: TraceableValue[UserConnectionInfo] = TraceableValue[String].contramap { uci => + given TraceableValue[UserConnectionInfo] = TraceableValue[String].contramap { uci => json"""{ "host": ${uci.host.toTraceValue}, "port": ${uci.port.toTraceValue}, @@ -63,7 +63,7 @@ object UserConnectionInfo { } extension [A](a: A) - def toTraceValue(implicit T: TraceableValue[A]): Json = T.toTraceValue(a) match + 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) => diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala index dad49e8..faccd28 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandler.scala @@ -68,7 +68,7 @@ class PostgresqlDatabaseInitHandler * The kernel will be sourced from the environment/system properties if useEnvironmentFallback is true when * initializing the X-Ray entrypoint. */ - private implicit def kernelSource[Event]: KernelSource[Event] = KernelSource.emptyKernelSource + private given [Event]: KernelSource[Event] = KernelSource.emptyKernelSource private def httpClient[F[_] : {Async, Network, Trace}]: Resource[F, Client[F]] = EmberClientBuilder diff --git a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala index 8a32306..4bf5c30 100644 --- a/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala +++ b/src/main/scala/com/dwolla/postgres/init/PostgresqlDatabaseInitHandlerImpl.scala @@ -21,18 +21,18 @@ trait PostgresqlDatabaseInitHandlerImpl[F[_]] extends CloudFormationCustomResour @annotation.experimental object PostgresqlDatabaseInitHandlerImpl { - implicit val physicalResourceIdTraceableValue: TraceableValue[feral.lambda.cloudformation.PhysicalResourceId] = TraceableValue[String].contramap(_.value) + given TraceableValue[feral.lambda.cloudformation.PhysicalResourceId] = TraceableValue[String].contramap(_.value) - implicit def postgresqlDatabaseInitHandlerImplAspect: Aspect[PostgresqlDatabaseInitHandlerImpl, TraceableValue, Trivial] = Derive.aspect + given Aspect[PostgresqlDatabaseInitHandlerImpl, TraceableValue, Trivial] = Derive.aspect private[PostgresqlDatabaseInitHandlerImpl] def databaseAsPhysicalResourceId[F[_] : ApplicativeThrow](db: Database): F[PhysicalResourceId] = PhysicalResourceId(db.value.value).liftTo[F](new RuntimeException("Database name was invalid as Physical Resource ID")) - def apply[F[_] : Temporal : Trace : Network : Console : Logger](secretsManagerAlg: SecretsManagerAlg[F], - databaseRepository: DatabaseRepository[InSession[F, *]], - roleRepository: RoleRepository[InSession[F, *]], - userRepository: UserRepository[InSession[F, *]], - ): PostgresqlDatabaseInitHandlerImpl[F] = new PostgresqlDatabaseInitHandlerImpl[F] { + def apply[F[_] : {Temporal, Trace, Network, Console, Logger}](secretsManagerAlg: SecretsManagerAlg[F], + databaseRepository: DatabaseRepository[InSession[F, *]], + roleRepository: RoleRepository[InSession[F, *]], + userRepository: UserRepository[InSession[F, *]], + ): PostgresqlDatabaseInitHandlerImpl[F] = new PostgresqlDatabaseInitHandlerImpl[F] { override def createResource(event: DatabaseMetadata): F[HandlerResponse[INothing]] = handleCreateOrUpdate(event)(createOrUpdate(_, event)).map(HandlerResponse(_, None)) diff --git a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala index ef77d0e..9c39e1d 100644 --- a/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala +++ b/src/main/scala/com/dwolla/postgres/init/aws/SecretsManagerAlg.scala @@ -19,7 +19,7 @@ trait SecretsManagerAlg[F[_]] { } object SecretsManagerAlg { - implicit val traceableValueAspect: Aspect[SecretsManagerAlg, TraceableValue, TraceableValue] = new Aspect[SecretsManagerAlg, TraceableValue, TraceableValue] { + given Aspect[SecretsManagerAlg, TraceableValue, TraceableValue] = new Aspect[SecretsManagerAlg, TraceableValue, TraceableValue] { override def weave[F[_]](af: SecretsManagerAlg[F]): SecretsManagerAlg[Aspect.Weave[F, TraceableValue, TraceableValue, *]] = new SecretsManagerAlg[Aspect.Weave[F, TraceableValue, TraceableValue, *]] { override def getSecret(secretId: SecretIdType): Aspect.Weave[F, TraceableValue, TraceableValue, Secret] = @@ -91,7 +91,7 @@ case class SecretString(value: SecretStringType) extends Secret case class SecretBinary(value: SecretBinaryType) extends Secret object Secret { - implicit val traceableValue: TraceableValue[Secret] = TraceableValue[String].contramap { + given TraceableValue[Secret] = TraceableValue[String].contramap { case SecretString(_) => "redacted string secret" case SecretBinary(_) => "redacted binary secret" } diff --git a/src/main/scala/com/dwolla/postgres/init/package.scala b/src/main/scala/com/dwolla/postgres/init/package.scala index 05ecb30..8a80f5a 100644 --- a/src/main/scala/com/dwolla/postgres/init/package.scala +++ b/src/main/scala/com/dwolla/postgres/init/package.scala @@ -1,4 +1,4 @@ -package com.dwolla.postgres +package com.dwolla.postgres.init import cats.syntax.all.* import com.comcast.ip4s.{Host, Port} @@ -6,64 +6,50 @@ import eu.timepit.refined.* import eu.timepit.refined.api.* import eu.timepit.refined.string.* import io.circe.Decoder -import monix.newtypes.NewtypeWrapped +import monix.newtypes.{HasExtractor, NewtypeWrapped} import monix.newtypes.integrations.DerivedCirceCodec import natchez.TraceableValue -package object init { - implicit def refinedTraceableValue[A: TraceableValue, P]: TraceableValue[A Refined P] = TraceableValue[A].contramap(_.value) +given [A: TraceableValue, P]: TraceableValue[A Refined P] = TraceableValue[A].contramap(_.value) - type SqlIdentifierPredicate = MatchesRegex["[A-Za-z][A-Za-z0-9_]*"] +type SqlIdentifierPredicate = MatchesRegex["[A-Za-z][A-Za-z0-9_]*"] - type SqlIdentifier = String Refined SqlIdentifierPredicate - object SqlIdentifier extends RefinedTypeOps[SqlIdentifier, String] +type SqlIdentifier = String Refined SqlIdentifierPredicate +object SqlIdentifier extends RefinedTypeOps[SqlIdentifier, String] - type GeneratedPasswordPredicate = MatchesRegex["""[-A-Za-z0-9!"#$%&()*+,./:<=>?@\[\]\\^_{|}~]+"""] +type GeneratedPasswordPredicate = MatchesRegex["""[-A-Za-z0-9!"#$%&()*+,./:<=>?@\[\]\\^_{|}~]+"""] - type MasterDatabaseUsername = MasterDatabaseUsername.Type - object MasterDatabaseUsername extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype +type MasterDatabaseUsername = MasterDatabaseUsername.Type +object MasterDatabaseUsername extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype - type MasterDatabasePassword = MasterDatabasePassword.Type - object MasterDatabasePassword extends NewtypeWrapped[String] with DerivedCirceCodec { - implicit val traceableValue: TraceableValue[MasterDatabasePassword] = TraceableValue[String].contramap(_ => "redacted password") - } - - private[init] implicit val hostDecoder: Decoder[Host] = - Decoder[String].emap(s => Host.fromString(s).toRight(s"$s could not be decoded as a Host")) - private[init] implicit val hostTraceableValue: TraceableValue[Host] = TraceableValue[String].contramap(_.show) - - private[init] implicit val portDecoder: Decoder[Port] = - Decoder[Int].emap(i => Port.fromInt(i).toRight(s"$i could not be decoded as a Port")) - private[init] implicit val portTraceableValue: TraceableValue[Port] = TraceableValue[Int].contramap(_.value) +type MasterDatabasePassword = MasterDatabasePassword.Type +object MasterDatabasePassword extends NewtypeWrapped[String] with DerivedCirceCodec { + given TraceableValue[MasterDatabasePassword] = TraceableValue[String].contramap(_ => "redacted password") +} - type Username = Username.Type - object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype +private[init] given Decoder[Host] = + Decoder[String].emap(s => Host.fromString(s).toRight(s"$s could not be decoded as a Host")) +private[init] given TraceableValue[Host] = TraceableValue[String].contramap(_.show) - type Password = Password.Type - object Password extends NewtypeWrapped[String Refined GeneratedPasswordPredicate] with DerivedCirceCodec { - implicit val traceableValue: TraceableValue[Password] = TraceableValue[String].contramap(_ => "redacted password") - } +private[init] given Decoder[Port] = + Decoder[Int].emap(i => Port.fromInt(i).toRight(s"$i could not be decoded as a Port")) +private[init] given TraceableValue[Port] = TraceableValue[Int].contramap(_.value) - type Database = Database.Type - object Database extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype +type Username = Username.Type +object Username extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype - type RoleName = RoleName.Type - object RoleName extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype +type Password = Password.Type +object Password extends NewtypeWrapped[String Refined GeneratedPasswordPredicate] with DerivedCirceCodec { + given TraceableValue[Password] = TraceableValue[String].contramap(_ => "redacted password") } -package init { - - import monix.newtypes.HasExtractor +type Database = Database.Type +object Database extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype - trait Migration[A, B] { - def apply(a: A): B - } +type RoleName = RoleName.Type +object RoleName extends NewtypeWrapped[SqlIdentifier] with DerivedCirceCodec with DerivedTraceableValueFromNewtype - trait DerivedTraceableValueFromNewtype { - implicit def traceableValue[T, S](implicit - extractor: HasExtractor.Aux[T, S], - enc: TraceableValue[S], - ): TraceableValue[T] = - enc.contramap(extractor.extract) - } +trait DerivedTraceableValueFromNewtype { + given [T, S](using HasExtractor.Aux[T, S], TraceableValue[S]): TraceableValue[T] = + TraceableValue[S].contramap(summon[HasExtractor.Aux[T, S]].extract) } diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala index a198eac..ee2300d 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/CreateSkunkSession.scala @@ -35,15 +35,13 @@ trait CreateSkunkSession[F[_]] { object CreateSkunkSession { type InSession[F[_], A] = Kleisli[F, Session[F], A] - implicit class InSessionOps[F[_], A](val kleisli: Kleisli[F, Session[F], A]) extends AnyVal { + extension [F[_], A](kleisli: Kleisli[F, Session[F], A]) def inSession(host: Host, port: Port, username: MasterDatabaseUsername, password: MasterDatabasePassword, ) - (implicit - `🦨`: CreateSkunkSession[F], - `[]`: MonadCancelThrow[F]): F[A] = + (using CreateSkunkSession[F], MonadCancelThrow[F]): F[A] = CreateSkunkSession[F].single( host = host.show, port = port.value, @@ -52,19 +50,15 @@ object CreateSkunkSession { password = password.value.some, ssl = if (host == host"localhost") SSL.None else SSL.System, ).use(kleisli.run) - } - implicit class IgnoreErrorOps[F[_], A](val fa: F[A]) extends AnyVal { + extension [F[_], A](fa: F[A]) def recoverUndefinedAs(a: A) - (implicit `[]`: MonadThrow[F]): F[A] = - fa.recover { + (using MonadThrow[F]): F[A] = + fa.recover: case SqlState.UndefinedObject(_) => a case SqlState.InvalidCatalogName(_) => a - } - } def apply[F[_] : CreateSkunkSession]: CreateSkunkSession[F] = implicitly - implicit def instance[F[_] : {Temporal, Trace, Network, Console}]: CreateSkunkSession[F] = - Session.single + given [F[_] : {Temporal, Trace, Network, Console}]: CreateSkunkSession[F] = Session.single } diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala index f9b9931..0511a06 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/DatabaseRepository.scala @@ -21,9 +21,9 @@ trait DatabaseRepository[F[_]] { @annotation.experimental object DatabaseRepository { - implicit val aspectTraceableValue: Aspect[DatabaseRepository, TraceableValue, TraceableValue] = Derive.aspect + given Aspect[DatabaseRepository, TraceableValue, TraceableValue] = Derive.aspect - def apply[F[_] : MonadCancelThrow : Logger : Trace]: DatabaseRepository[InSession[F, *]] = new DatabaseRepository[InSession[F, *]] { + def apply[F[_] : {MonadCancelThrow, Logger, Trace}]: DatabaseRepository[InSession[F, *]] = new DatabaseRepository[InSession[F, *]] { override def createDatabase(db: DatabaseMetadata): Kleisli[F, Session[F], Database] = checkDatabaseExists(db) .ifM(createDatabase(db.name, db.username), Logger[F].mapK(Kleisli.liftK[F, Session[F]]).info(s"No-op: database ${db.name} already exists")) diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala index 27c54f4..245ee41 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/RoleRepository.scala @@ -26,7 +26,7 @@ trait RoleRepository[F[_]] { @annotation.experimental object RoleRepository { - implicit val traceableValueAspect: Aspect[RoleRepository, TraceableValue, TraceableValue] = Derive.aspect + given Aspect[RoleRepository, TraceableValue, TraceableValue] = Derive.aspect def roleNameForDatabase(database: Database): RoleName = RoleName(Refined.unsafeApply(database.value + "_role")) diff --git a/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala b/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala index 0386b04..7358239 100644 --- a/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala +++ b/src/main/scala/com/dwolla/postgres/init/repositories/UserRepository.scala @@ -25,13 +25,13 @@ trait UserRepository[F[_]] { @annotation.experimental object UserRepository { - implicit val traceableValueAspect: Aspect[UserRepository, TraceableValue, TraceableValue] = Derive.aspect + given Aspect[UserRepository, TraceableValue, TraceableValue] = Derive.aspect def usernameForDatabase(database: Database): Username = Username(Refined.unsafeApply(database.value.value)) - def apply[F[_] : Logger : Temporal : Trace]: UserRepository[Kleisli[F, Session[F], *]] = new UserRepository[Kleisli[F, Session[F], *]] { - private implicit def kleisliLogger[A]: Logger[Kleisli[F, A, *]] = Logger[F].mapK(Kleisli.liftK) + def apply[F[_] : {Logger, Temporal, Trace}]: UserRepository[Kleisli[F, Session[F], *]] = new UserRepository[Kleisli[F, Session[F], *]] { + private given [A]: Logger[Kleisli[F, A, *]] = Logger[F].mapK(Kleisli.liftK) override def addOrUpdateUser(userConnectionInfo: UserConnectionInfo): Kleisli[F, Session[F], Username] = for { diff --git a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala index 5dcae65..fd191f1 100644 --- a/src/test/scala/com/dwolla/postgres/init/LocalApp.scala +++ b/src/test/scala/com/dwolla/postgres/init/LocalApp.scala @@ -30,7 +30,7 @@ import scala.concurrent.duration.* object LocalApp extends IOApp { override def run(args: List[String]): IO[ExitCode] = { - implicit val loggerFactory: LoggerFactory[IO] = Slf4jFactory.create[IO] + given LoggerFactory[IO] = Slf4jFactory.create[IO] OpenTelemetryAtDwolla[IO](BuildInfo.name, BuildInfo.version, DwollaEnvironment.Local) .flatMap(new PostgresqlDatabaseInitHandler().handler(_)) diff --git a/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala b/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala index cc2c2e3..65f5c3e 100644 --- a/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala +++ b/src/test/scala/com/dwolla/postgres/init/SqlStringRegexSpec.scala @@ -6,7 +6,7 @@ import org.scalacheck.Prop.forAll import org.scalacheck.{Arbitrary, Gen, Shrink} class SqlStringRegexSpec extends munit.ScalaCheckSuite { - implicit val shrinkString: Shrink[String] = Shrink.shrinkAny + given Shrink[String] = Shrink.shrinkAny test("strings containing semicolons don't validate") { assert(refineV[GeneratedPasswordPredicate](";").isLeft) @@ -17,7 +17,7 @@ class SqlStringRegexSpec extends munit.ScalaCheckSuite { } property("sql identifiers match [A-Za-z][A-Za-z0-9_]*") { - implicit val arbString: Arbitrary[String] = Arbitrary { + given Arbitrary[String] = Arbitrary { for { initial <- Gen.alphaChar tail <- Gen.stringOf(Gen.oneOf(Gen.alphaChar, Gen.numChar, Gen.const('_'))) @@ -30,7 +30,7 @@ class SqlStringRegexSpec extends munit.ScalaCheckSuite { } property("passwords contain the allowed characters") { - implicit val arbString: Arbitrary[String] = Arbitrary { + given Arbitrary[String] = Arbitrary { val allowedPunctuation: Gen[Char] = Gen.oneOf("""! " # $ % & ( ) * + , - . / : < = > ? @ [ \ ] ^ _ { | } ~ """.replaceAll(" ", "").toList) val allowedCharacters: Gen[Char] = Gen.oneOf(Gen.alphaChar, Gen.numChar, allowedPunctuation)