packages feed

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
            )