sandwich-contexts-kubernetes-0.1.0.0: lib/Test/Sandwich/Contexts/Kubernetes/KubectlLogs.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
module Test.Sandwich.Contexts.Kubernetes.KubectlLogs (
withKubectlLogs
, KubectlLogsContext (..)
) where
import Control.Monad
import Control.Monad.IO.Unlift
import Control.Monad.Logger
import Data.String.Interpolate
import qualified Data.Text as T
import Relude hiding (withFile)
import System.FilePath
import Test.Sandwich
import Test.Sandwich.Contexts.Files
import Test.Sandwich.Util.Process (gracefullyStopProcess)
import UnliftIO.Exception
import UnliftIO.IO (withFile)
import UnliftIO.Process
-- * Types
data KubectlLogsContext = KubectlLogsContext {
kubectlProcessHandle :: ProcessHandle
}
-- * Implementation
-- | Run a @kubectl logs@ process, placing the logs in a file in the current test node directory.
--
-- Note that this will stop working if the pod you're talking to goes away (even if you do it against a service).
-- If this happens, a rerun of the command is needed to resume log forwarding.
withKubectlLogs :: (
MonadLogger m, MonadFail m, MonadUnliftIO m
, HasBaseContextMonad ctx m, HasFile ctx "kubectl"
)
-- | Kubeconfig file
=> FilePath
-- | Namespace
-> Text
-- | Log target (pod, service, etc.)
-> Text
-- | Specific container to get logs from
-> Maybe Text
-- | Whether to interrupt the process to shut it down while cleaning up
-> Bool
-- | Callback receiving the 'KubectlLogsContext'
-> (KubectlLogsContext -> m a)
-> m a
withKubectlLogs kubeConfigFile namespace target maybeContainer interruptWhenDone action = do
kubectlBinary <- askFile @"kubectl"
let args = ["logs", toString target
, "--namespace", toString namespace
, "--kubeconfig", kubeConfigFile]
<> (maybe [] (\x -> ["--container", toString x]) maybeContainer)
Just dir <- getCurrentFolder
let logPath = dir </> toString (T.replace "/" "_" target) <.> "log"
debug [i|Running kubectl #{unwords $ fmap toText args} --> #{logPath}|]
withFile logPath WriteMode $ \h -> do
hSetBuffering h LineBuffering
bracket (createProcess ((proc kubectlBinary args) {
std_out = UseHandle h
, std_err = UseHandle h
, create_group = True
}))
(\(_, _, _, ps) -> if
| interruptWhenDone -> void $ gracefullyStopProcess ps 30_000_000
| otherwise -> void $ waitForProcess ps
)
(\(_, _, _, ps) -> do
action $ KubectlLogsContext ps
)