sandwich-contexts-kubernetes-0.1.3.0: lib/Test/Sandwich/Contexts/Kubernetes/MinikubeCluster/Forwards.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeOperators #-}
module Test.Sandwich.Contexts.Kubernetes.MinikubeCluster.Forwards where
import Control.Monad
import Control.Monad.IO.Unlift
import Control.Monad.Logger
import qualified Data.List as L
import Data.String.Interpolate
import Data.Text as T
import qualified Data.Text.IO as T
import Network.URI
import Relude hiding (withFile)
import System.FilePath
import System.IO (hClose, hGetLine, openTempFile)
import System.Process (getPid)
import Test.Sandwich
import Test.Sandwich.Contexts.Kubernetes.Types
import Test.Sandwich.Util.Process
import UnliftIO.Concurrent (threadDelay)
import UnliftIO.Environment
import UnliftIO.Exception
import UnliftIO.Process
withForwardKubernetesService' :: (
HasCallStack, MonadLoggerIO m, MonadUnliftIO m, HasBaseContextMonad context m
) => KubernetesClusterContext -> Text -> Text -> Text -> (URI -> m a) -> m a
withForwardKubernetesService' (KubernetesClusterContext {kubernetesClusterType=(KubernetesClusterMinikube {..}), ..}) profile namespace service action = do
baseEnv <- liftIO getEnvironment
let env = L.nubBy (\x y -> fst x == fst y) (("KUBECONFIG", kubernetesClusterKubeConfigPath) : baseEnv)
let extraFlags = case "--rootless" `L.elem` kubernetesClusterTypeMinikubeFlags of
True -> ["--rootless"]
False -> []
let args = extraFlags <> [
"--profile", toString profile
, "--namespace", toString namespace
, "--logtostderr"
, "service"
, toString service
, "--url"]
info [i|#{kubernetesClusterTypeMinikubeBinary} #{T.unwords $ fmap toText args}|]
(stdoutRead, stdoutWrite) <- liftIO createPipe
(stderrRead, stderrWrite) <- liftIO createPipe
let forwardStderr =
flip withException (\(e :: SomeException) -> debug [i|withForwardKubernetesService: stderr reader exited due to exception: #{e}|]) $
forever $ do
line <- liftIO $ hGetLine stderrRead
info [i|minikube service stderr: #{line}|]
managedWithAsync_ "minikube-service-stderr" forwardStderr $ do
let cp = (proc kubernetesClusterTypeMinikubeBinary args) {
env = Just env
, std_out = UseHandle stdoutWrite
, std_err = UseHandle stderrWrite
, create_group = True
}
let stop (_, _, _, p) = liftIO (getPid p) >>= \case
Nothing -> return ()
Just _pid -> gracefullyStopProcess p 120_000_000
bracket (createProcess cp) stop $ \_ -> do
raw <- liftIO $ hGetLine stdoutRead
info [i|withForwardKubernetesService': (#{namespace}) #{service} -> #{raw}|]
action =<< case parseURI (toString (T.strip (toText raw))) of
Nothing -> expectationFailure [i|Couldn't parse URI in withForwardKubernetesService': #{raw}|]
Just x -> pure x
withForwardKubernetesService' _ _profile _namespace _service _action = error "Expected Minikube KubernetesClusterContext"
withForwardKubernetesServiceFileLogging' :: (
HasCallStack, MonadLoggerIO m, MonadUnliftIO m, HasBaseContextMonad context m
) => KubernetesClusterContext -> Text -> Text -> Text -> (URI -> m a) -> m a
withForwardKubernetesServiceFileLogging' (KubernetesClusterContext {kubernetesClusterType=(KubernetesClusterMinikube {..}), ..}) profile namespace service action = do
baseEnv <- liftIO getEnvironment
let env = L.nubBy (\x y -> fst x == fst y) (("KUBECONFIG", kubernetesClusterKubeConfigPath) : baseEnv)
let extraFlags = case "--rootless" `L.elem` kubernetesClusterTypeMinikubeFlags of
True -> ["--rootless"]
False -> []
let args = extraFlags <> [
"--profile", toString profile
, "--namespace", toString namespace
, "--logtostderr"
, "service"
, toString service
, "--url"]
info [i|#{kubernetesClusterTypeMinikubeBinary} #{T.unwords $ fmap toText args}|]
let cp = (proc kubernetesClusterTypeMinikubeBinary args) {
env = Just env
, create_group = True
}
let fileName = namespace <> "-" <> "service"
getCurrentFolder >>= \case
Nothing -> expectationFailure [i|withForwardKubernetesServiceFileLogging': no current folder.|]
Just dir ->
bracket (liftIO $ openTempFile dir (toString fileName <.> "out")) (\(_outFile, hOut) -> liftIO $ hClose hOut) $ \(outFile, hOut) ->
bracket (liftIO $ openTempFile dir (toString fileName <.> "err")) (\(_errFile, hErr) -> liftIO $ hClose hErr) $ \(_errFile, hErr) -> do
let stop (_, _, _, p) = liftIO (getPid p) >>= \case
Nothing -> return ()
Just _pid -> gracefullyStopProcess p 120_000_000
bracket (createProcess (cp { std_out = UseHandle hOut, std_err = UseHandle hErr })) stop $ \_ -> do
raw <- readFirstLine outFile
info [i|withForwardKubernetesServiceFileLogging': (#{namespace}) #{service} -> #{raw}|]
action =<< case parseURI (toString (T.strip raw)) of
Nothing -> expectationFailure [i|Couldn't parse URI in withForwardKubernetesServiceFileLogging': #{raw}|]
Just x -> pure x
where
readFirstLine :: MonadIO m => FilePath -> m Text
readFirstLine fp = liftIO loop
where
loop = do
contents <- T.readFile fp
case T.lines contents of
(x:_) -> pure x
[] -> threadDelay 50_000 >> loop
withForwardKubernetesServiceFileLogging' _ _profile _namespace _service _action = error "Expected Minikube KubernetesClusterContext"