202 lines
5.4 KiB
Haskell
202 lines
5.4 KiB
Haskell
module Main (main) where
|
|
|
|
import Control.Concurrent (threadDelay)
|
|
import Control.Exception (bracket)
|
|
import Control.Monad (unless)
|
|
import qualified Data.ByteString.Char8 as B8
|
|
import Database.PostgreSQL.Simple
|
|
( Only (..)
|
|
, close
|
|
, connectPostgreSQL
|
|
, execute_
|
|
, query_
|
|
)
|
|
import Database.Redis
|
|
( ConnectInfo (..)
|
|
, PortID (..)
|
|
, Reply
|
|
, Status
|
|
, connect
|
|
, defaultConnectInfo
|
|
, disconnect
|
|
, get
|
|
, runRedis
|
|
, set
|
|
)
|
|
import Nuthatch.Probe.Assert
|
|
import Nuthatch.Probe.Curl
|
|
import Nuthatch.Probe.Http hiding (get)
|
|
import qualified Nuthatch.Probe.Http as Http
|
|
|
|
main :: IO ()
|
|
main = do
|
|
client <- newProbeClient
|
|
verifyPostgres
|
|
verifyCoordination "redis"
|
|
verifyCoordination "dragonfly"
|
|
verifyOidc client
|
|
verifyTarget
|
|
"Garage"
|
|
"http://garage:3900"
|
|
"garage"
|
|
"GK00000000000000000000000000000000"
|
|
"0000000000000000000000000000000000000000000000000000000000000000"
|
|
False
|
|
verifyTarget
|
|
"VersityGW"
|
|
"http://versitygw:10000"
|
|
"us-east-1"
|
|
"synthetic-s3-access"
|
|
"synthetic-s3-secret"
|
|
True
|
|
putStrLn "Dependency product interfaces passed"
|
|
|
|
verifyPostgres :: IO ()
|
|
verifyPostgres =
|
|
bracket
|
|
(connectPostgreSQL
|
|
"host=postgres port=5432 user=nuthatch password=synthetic-postgres dbname=nuthatch")
|
|
close
|
|
(\connection -> do
|
|
_ <- execute_ connection
|
|
"CREATE TEMP TABLE dependency_probe (value text NOT NULL)"
|
|
_ <- execute_ connection
|
|
"INSERT INTO dependency_probe VALUES ('passed')"
|
|
values <- query_ connection
|
|
"SELECT value FROM dependency_probe"
|
|
:: IO [Only B8.ByteString]
|
|
assertEqual "PostgreSQL product interface"
|
|
[Only "passed"]
|
|
values)
|
|
|
|
verifyCoordination :: String -> IO ()
|
|
verifyCoordination host =
|
|
bracket
|
|
(connect defaultConnectInfo
|
|
{ connectHost = host
|
|
, connectPort = PortNumber 6379
|
|
})
|
|
disconnect
|
|
(\connection -> do
|
|
stored <- runRedis connection (set "nuthatch-probe" "passed")
|
|
:: IO (Either Reply Status)
|
|
assert "coordination write failed" (isRight stored)
|
|
loaded <- runRedis connection (get "nuthatch-probe")
|
|
:: IO (Either Reply (Maybe B8.ByteString))
|
|
assertEqual (host <> " product interface")
|
|
(Right (Just "passed"))
|
|
loaded)
|
|
|
|
verifyOidc :: ProbeClient -> IO ()
|
|
verifyOidc client =
|
|
mapM_
|
|
(\(label, url, marker) ->
|
|
retry label 30 $ do
|
|
response <- Http.get client url
|
|
pure
|
|
( probeStatus response == 200
|
|
&& marker `B8.isInfixOf` probeBody response
|
|
))
|
|
[ ( "Keycloak discovery"
|
|
, "http://keycloak.nuthatch.test:8080/realms/master/.well-known/openid-configuration"
|
|
, "\"issuer\""
|
|
)
|
|
, ( "Keycloak keys"
|
|
, "http://keycloak.nuthatch.test:8080/realms/master/protocol/openid-connect/certs"
|
|
, "\"keys\""
|
|
)
|
|
, ( "ZITADEL discovery"
|
|
, "http://zitadel.nuthatch.test:8080/.well-known/openid-configuration"
|
|
, "\"issuer\""
|
|
)
|
|
, ( "ZITADEL keys"
|
|
, "http://zitadel.nuthatch.test:8080/oauth/v2/keys"
|
|
, "\"keys\""
|
|
)
|
|
]
|
|
|
|
verifyTarget
|
|
:: String
|
|
-> String
|
|
-> String
|
|
-> String
|
|
-> String
|
|
-> Bool
|
|
-> IO ()
|
|
verifyTarget label endpoint region accessKey secretKey createBucket = do
|
|
let signed =
|
|
[ "--aws-sigv4", "aws:amz:" <> region <> ":s3"
|
|
, "--user", accessKey <> ":" <> secretKey
|
|
]
|
|
bucket = endpoint <> "/nuthatch-fixture"
|
|
payload = "nuthatch-dependency-compatibility-probe"
|
|
payloadDigest =
|
|
"d0d4c515c2723784b6ca1b22b97d89ae8229a9c2abcb9ca912b85c1f02e17303"
|
|
emptyDigest =
|
|
"e3b0c44298fc1c149afbf4c8996fb924"
|
|
<> "27ae41e4649b934ca495991b7852b855"
|
|
if createBucket
|
|
then do
|
|
created <- curl
|
|
( signed
|
|
<> [ "--request", "PUT"
|
|
, "--header", "X-Amz-Content-Sha256:" <> emptyDigest
|
|
, bucket
|
|
]
|
|
)
|
|
assert
|
|
( label
|
|
<> " bucket creation failed: "
|
|
<> B8.unpack (probeBody created)
|
|
) $
|
|
probeStatus created `elem` [200, 409]
|
|
else pure ()
|
|
uploaded <- retryResponse 30 $ curl
|
|
( signed
|
|
<> [ "--request", "PUT"
|
|
, "--header"
|
|
, "X-Amz-Content-Sha256:" <> payloadDigest
|
|
, "--data-binary", payload
|
|
, bucket <> "/probe/payload"
|
|
]
|
|
)
|
|
assertEqual
|
|
(label <> " upload: " <> B8.unpack (probeBody uploaded))
|
|
200
|
|
(probeStatus uploaded)
|
|
downloaded <- curl
|
|
( signed
|
|
<> [ "--header"
|
|
, "X-Amz-Content-Sha256:" <> emptyDigest
|
|
, bucket <> "/probe/payload"
|
|
]
|
|
)
|
|
assertEqual (label <> " download status") 200 (probeStatus downloaded)
|
|
assertEqual (label <> " object body")
|
|
(B8.pack payload)
|
|
(probeBody downloaded)
|
|
|
|
retry :: String -> Int -> IO Bool -> IO ()
|
|
retry label attempts action =
|
|
go attempts
|
|
where
|
|
go remaining = do
|
|
succeeded <- action
|
|
unless succeeded $
|
|
if remaining <= 1
|
|
then fail (label <> " did not become available")
|
|
else threadDelay 1000000 >> go (remaining - 1)
|
|
|
|
retryResponse :: Int -> IO ProbeResponse -> IO ProbeResponse
|
|
retryResponse attempts action =
|
|
go attempts
|
|
where
|
|
go remaining = do
|
|
response <- action
|
|
if probeStatus response == 200 || remaining <= 1
|
|
then pure response
|
|
else threadDelay 1000000 >> go (remaining - 1)
|
|
|
|
isRight :: Either left right -> Bool
|
|
isRight (Right _) = True
|
|
isRight (Left _) = False
|