packages feed

effectful-zoo-0.0.4.0: components/rds-data-test/Effectful/Zoo/RdsData/Test/Cluster.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE NumericUnderscores  #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}

{- HLINT ignore "Redundant id" -}
{- HLINT ignore "Redundant pure" -}
{- HLINT ignore "Use let" -}

module Effectful.Zoo.RdsData.Test.Cluster
  ( RdsClusterDetails(..),
    createRdsDbCluster,
    waitUntilRdsDbClusterAvailable,
  ) where

import Amazonka qualified as AWS
import Amazonka.RDS qualified as AWS
import Amazonka.SecretsManager qualified as AWS
import Data.Aeson ((.=))
import Data.Aeson qualified as J
import Data.ByteString.Base64 qualified as B64
import Data.ByteString.Lazy qualified as LBS
import Data.Function
import Data.Generics.Product.Any
import Data.RdsData.Migration.Types (RdsClusterDetails (RdsClusterDetails))
import Data.Text.Encoding qualified as T
import Data.UUID qualified as UUID
import Data.UUID.V4 qualified as UUID
import Effectful
import Effectful.Concurrent
import Effectful.Zoo.Amazonka.Api.Send
import Effectful.Zoo.Amazonka.Data.AwsError
import Effectful.Zoo.Amazonka.Data.AwsLogEntry
import Effectful.Zoo.Amazonka.Dynamic
import Effectful.Zoo.Core
import Effectful.Zoo.DataLog.Static
import Effectful.Zoo.Error.Static
import Effectful.Zoo.Hedgehog.Api.Assert
import Effectful.Zoo.Hedgehog.Api.Failure
import Effectful.Zoo.Hedgehog.Api.Journal
import Effectful.Zoo.Hedgehog.Effect.Hedgehog
import Effectful.Zoo.TestContainers.LocalStack
import HaskellWorks.Control.Monad
import HaskellWorks.Prelude
import Lens.Micro

createRdsDbCluster :: ()
  => HasCallStack
  => r <: Amazonka
  => r <: Concurrent
  => r <: Error Failure
  => r <: Hedgehog
  => r <: IOE
  => Text
  -> IO Container
  -> Eff r RdsClusterDetails
createRdsDbCluster databaseName getContainer = withFrozenCallStack do
  container <- liftIO getContainer
  jotShowM_ $ getLocalStackEndpoint container
  jotYamlM_ $ inspectContainer container
  masterUsername <- pure "masterUsername"
  masterPassword <- pure "masterPassword"

  let dbClusterId = "my-cluster"

  createDbClusterRequest <-
    pure $
      AWS.newCreateDBCluster dbClusterId "aurora-postgresql"
        & the @"masterUsername" .~ Just masterUsername
        & the @"masterUserPassword" .~ Just masterPassword
        & the @"enableHttpEndpoint" .~ Just True
        & the @"databaseName" .~ Just databaseName

  createDbClusterResponse <-
    sendAws createDbClusterRequest
      & trapFail @AwsError
      & jotShowDataLog @AwsLogEntry

  let secretName = "my-aurora-cluster"

  secretString <-
    jotYaml $
      J.object
        [ "engine" .= id @Text "aurora-postgresql"
        , "username" .= id @Text masterUsername
        , "password" .= id @Text masterPassword
        , "host" .= id @Text "localhost"
        , "dbname" .= id @Text databaseName
        , "port" .= id @Text "4510"
        ]

  uuid <- liftIO UUID.nextRandom

  let clientRequestToken = T.encodeUtf8 $ UUID.toText uuid

  let secretStringText = T.decodeUtf8 $ LBS.toStrict $ J.encode secretString

  createSecretReq <-
    pure $
      AWS.newCreateSecret secretName
        & the @"secretString" ?~ AWS.Sensitive secretStringText
        & the @"clientRequestToken" ?~ T.decodeUtf8 (B64.encode clientRequestToken)

  createSecetResp <-
    sendAws createSecretReq
      & trapFail
      & jotShowDataLog @AwsLogEntry

  createDbInstanceReq <-
    pure $
      AWS.newCreateDBInstance dbClusterId "my-db-instance" "db.t3.medium"
        & the @"engine" .~ "aurora-postgresql"

  _dbInstanceIdResp <-
    jotShowM $
      sendAws createDbInstanceReq
        & trapFail
        & jotShowDataLog @AwsLogEntry

  pure (RdsClusterDetails createDbClusterResponse createSecetResp)

waitUntilRdsDbClusterAvailable :: ()
  => HasCallStack
  => r <: Amazonka
  => r <: Concurrent
  => r <: DataLog AwsLogEntry
  => r <: Error AWS.Error
  => r <: IOE
  => Text
  -> Eff r ()
waitUntilRdsDbClusterAvailable dbClusterArn =
  withFrozenCallStack do
    repeatNWhileM_ 120 $ \_ -> do
      result <- sendAws $
        AWS.newDescribeDBClusters
          & the @"dbClusterIdentifier" .~ Just dbClusterArn

      let mStatus = result ^? the @"dbClusters" . _Just . each . the @"status" . _Just

      if mStatus == Just "available"
        then threadDelay 1_000_000 >> pure False
        else threadDelay 1_000_000 >> pure True