encapsule-0.5: test/EncapsuleTest.hs
-- SPDX-License-Identifier: Apache-2.0
module EncapsuleTest (
encapsule,
encapsuleChecked,
dryrun,
debugField,
commandOutputLines,
hasTTY,
liveEnabled,
requireLive,
ubuntuImg,
fedoraImg,
hostUid,
hostUser,
requireImage,
withGenericImage,
) where
import Control.Exception (IOException, try)
import Control.Monad (unless)
import Data.List (isInfixOf, isPrefixOf)
import Data.Maybe (fromMaybe, listToMaybe)
import System.Environment (lookupEnv)
import System.Exit (ExitCode(..))
import System.Posix.IO (stdInput)
import System.Posix.Terminal (queryTerminal)
import System.Posix.User (getEffectiveUserID, getEffectiveUserName)
import System.Process (readProcessWithExitCode)
import Test.Hspec (pendingWith)
-- | Run encapsule with args; combined stdout and stderr.
encapsule :: [String] -> IO String
encapsule args = do
exe <- fromMaybe "encapsule" <$> lookupEnv "ENCAPSULE"
(_, out, err) <- readProcessWithExitCode exe args ""
return $ out ++ err
-- | Like 'encapsule', but fail if the process exits non-zero.
encapsuleChecked :: [String] -> IO String
encapsuleChecked args = do
exe <- fromMaybe "encapsule" <$> lookupEnv "ENCAPSULE"
(code, out, err) <- readProcessWithExitCode exe args ""
let combined = out ++ err
case code of
ExitSuccess -> return combined
ExitFailure n ->
fail $ "encapsule failed (" ++ show n ++ "): " ++ combined
dryrun :: [String] -> IO String
dryrun args = encapsule $ ["run", "--dryrun", "--debug", "--no-skel"] ++ args
-- | Non-empty output lines that are not podman/log noise (e.g. "not a TTY").
commandOutputLines :: String -> [String]
commandOutputLines out =
[ l
| l <- map (filter (/= '\r')) (lines out)
, not (null l)
, not (isNoise l)
]
where
isNoise l =
"level=warning" `isInfixOf` l
|| "not a TTY" `isInfixOf` l
|| "msg=" `isInfixOf` l
debugField :: String -> String -> Maybe String
debugField out key =
let prefix = "debug: " ++ key ++ ": "
in listToMaybe
[ filter (/= '\r') rest
| l <- lines out
, Just rest <- [afterInfix prefix l]
]
afterInfix :: String -> String -> Maybe String
afterInfix p s
| p `isPrefixOf` s = Just $ drop (length p) s
| p `isInfixOf` s =
let n = length p
go i
| i + n > length s = Nothing
| take n (drop i s) == p = Just (drop (i + n) s)
| otherwise = go (i + 1)
in go 0
| otherwise = Nothing
hasPodman :: IO Bool
hasPodman = do
r <- try (readProcessWithExitCode "podman" ["--version"] "")
:: IO (Either IOException (ExitCode, String, String))
case r of
Left _ -> return False
Right (code, _, _) -> return $ code == ExitSuccess
imageExists :: String -> IO Bool
imageExists img = do
(code, _, _) <- readProcessWithExitCode "podman" ["image", "exists", img] ""
return $ code == ExitSuccess
hasTTY :: IO Bool
hasTTY = queryTerminal stdInput
liveEnabled :: IO Bool
liveEnabled = do
env <- lookupEnv "ENCAPSULE_LIVE"
return $ env == Just "1"
requireLive :: IO ()
requireLive = do
tty <- hasTTY
forced <- liveEnabled
unless (tty || forced) $
pendingWith "not a TTY (set ENCAPSULE_LIVE=1 to force)"
ubuntuImg :: IO String
ubuntuImg = fromMaybe "ubuntu:latest" <$> lookupEnv "ENCAPSULE_TEST_UBUNTU"
fedoraImg :: IO String
fedoraImg = fromMaybe "fedora:latest" <$> lookupEnv "ENCAPSULE_TEST_FEDORA"
hostUid :: IO String
hostUid = show . toInteger <$> getEffectiveUserID
hostUser :: IO String
hostUser = getEffectiveUserName
requirePodman :: IO ()
requirePodman = do
ok <- hasPodman
unless ok $ pendingWith "podman not found"
requireImage :: String -> IO ()
requireImage img = do
requirePodman
ok <- imageExists img
unless ok $ pendingWith $ "no " ++ img ++ " image"
withGenericImage :: (String -> IO ()) -> IO ()
withGenericImage act = do
requirePodman
u <- ubuntuImg
f <- fedoraImg
mu <- imageExists u
mf <- imageExists f
case (mu, mf) of
(True, _) -> act u
(_, True) -> act f
_ -> pendingWith $ "no " ++ u ++ " or " ++ f ++ " image"