packages feed

podenv-0.2.0: src/Podenv/Context.hs

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
-- makeLenses does not produce those
{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# OPTIONS_GHC -fno-warn-missing-export-lists #-}

-- | Runtime Context data types and lenses
module Podenv.Context where

import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Lens.Family.TH (makeLenses)
import Podenv.Prelude
import System.Linux.Capabilities (Capability)

newtype ImageName = ImageName {unImageName :: Text}
  deriving (Show)

data RuntimeContext
  = Container ImageName
  | Bubblewrap FilePath
  deriving (Show)

data Mode = RO | RW
  deriving (Show)

data VolumeType = HostPath FilePath | TmpFS | Volume Text
  deriving (Show)

data Volume = MkVolume Mode VolumeType
  deriving (Show)

data RunAs = RunAsRoot | RunAsHostUID | RunAsAnyUID
  deriving (Show)

data Port = PortTcp Natural | PortUdp Natural
  deriving (Show)

newtype Name = Name {unName :: Text}
  deriving (Show)

-- | The application context to be executed by podman or kubectl
data Context = Context
  { -- | identifier
    _name :: Name,
    _namespace :: Maybe Text,
    -- | container image name
    _runtimeCtx :: RuntimeContext,
    -- | network namespace name
    _network :: Bool,
    _ports :: [Port],
    _runAs :: Maybe RunAs,
    _selinux :: Bool,
    -- | the unique uid for this container
    _anyUid :: UserID,
    -- | host uid
    _uid :: UserID,
    -- | container command
    _command :: [Text],
    _workdir :: Maybe FilePath,
    -- | container env
    _environ :: Map Text Text,
    -- | container volumes
    _mounts :: Map FilePath Volume,
    _syscaps :: Set.Set Capability,
    _ro :: Bool,
    -- | container devices
    _devices :: Set FilePath,
    _hostname :: Maybe Text,
    _interactive :: Bool,
    _terminal :: Bool,
    _privileged :: Bool
  }
  deriving (Show)

$(makeLenses ''Context)

defaultContext :: Name -> RuntimeContext -> Context
defaultContext _name _runtimeCtx =
  Context
    { _name,
      _runtimeCtx,
      _command = [],
      _uid = 0,
      _namespace = Nothing,
      -- todo keep track of fresh uid
      _anyUid = 4242,
      _selinux = True,
      _network = False,
      _ports = mempty,
      _runAs = Nothing,
      _environ = mempty,
      _mounts = mempty,
      _devices = mempty,
      _syscaps = mempty,
      -- TODO: make ro work for podman
      _ro = True,
      _workdir = Nothing,
      _hostname = Nothing,
      _interactive = False,
      _terminal = False,
      _privileged = False
    }

rwHostPath :: FilePath -> Volume
rwHostPath = MkVolume RW . HostPath

roHostPath :: FilePath -> Volume
roHostPath = MkVolume RO . HostPath

tmpfs :: Volume
tmpfs = MkVolume RW TmpFS

-- Env and mounts head value takes priority
addEnv :: Text -> Text -> Context -> Context
addEnv k v = environ %~ Map.insertWith (\_n o -> o) k v

addMount :: FilePath -> Volume -> Context -> Context
addMount containerPath hostPath = mounts %~ Map.insertWith (\_n o -> o) containerPath hostPath

directMount :: FilePath -> Context -> Context
directMount fp = addMount fp (rwHostPath fp)

addDevice :: FilePath -> Context -> Context
addDevice dev = devices %~ Set.insert dev