{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeApplications #-}

{-|

Create Kubernetes clusters with either [kind](https://kind.sigs.k8s.io/) or [Minikube](https://kubernetes.io/docs/tutorials/hello-minikube/), obtaining the relevant binary from either the current PATH or from Nix.

Also contains functions for waiting for pods and services to exist, running commands with [kubectl](https://kubernetes.io/docs/reference/kubectl/), logging, service forwarding, and port forwarding.

-}

module Test.Sandwich.Contexts.Kubernetes (
  -- * Kind clusters
  Kind.introduceKindClusterViaNix
  , Kind.introduceKindClusterViaEnvironment
  , Kind.introduceKindCluster'

  , Kind.defaultKindClusterOptions
  , Kind.KindClusterOptions(..)

  -- * Minikube clusters
  , Minikube.introduceMinikubeClusterViaNix
  , Minikube.introduceMinikubeClusterViaEnvironment
  , Minikube.introduceMinikubeCluster'

  , Minikube.defaultMinikubeClusterOptions
  , Minikube.MinikubeClusterOptions(..)

  -- * Wait for pods/services
  , waitForPodsToExist
  , waitForPodsToBeReady
  , waitForServiceEndpointsToExist

  -- * Run commands with kubectl
  , askKubectlArgs
  , askKubectlEnvironment
  , getKubectlEnvironment

  -- * Forward services
  , withForwardKubernetesService
  , withForwardKubernetesService'

  -- * Logs
  , module Test.Sandwich.Contexts.Kubernetes.KubectlLogs

  -- * Port forwarding
  , module Test.Sandwich.Contexts.Kubernetes.KubectlPortForward

  -- * Types
  , kubernetesCluster
  , KubernetesClusterContext(..)
  , KubernetesClusterType(..)
  , HasKubernetesClusterContext

  -- * Constraint aliases
  , KubernetesBasic
  , KubernetesClusterBasic
  , KubectlBasic
  , NixContextBasic
  , KubernetesBasicWithoutReader
  , KubernetesClusterBasicWithoutReader
  , KubectlBasicWithoutReader
  ) where

import Control.Monad.IO.Unlift
import Control.Monad.Logger
import Network.URI
import Relude
import Test.Sandwich
import Test.Sandwich.Contexts.Files
import Test.Sandwich.Contexts.Kubernetes.Kubectl
import Test.Sandwich.Contexts.Kubernetes.KubectlLogs
import Test.Sandwich.Contexts.Kubernetes.KubectlPortForward
import Test.Sandwich.Contexts.Kubernetes.Types
import Test.Sandwich.Contexts.Kubernetes.Waits

import qualified Test.Sandwich.Contexts.Kubernetes.KindCluster as Kind
import qualified Test.Sandwich.Contexts.Kubernetes.KindCluster.ServiceForwardPortForward as Kind

import qualified Test.Sandwich.Contexts.Kubernetes.MinikubeCluster as Minikube
import qualified Test.Sandwich.Contexts.Kubernetes.MinikubeCluster.Forwards as Minikube


-- | Forward a Kubernetes service, so that it can be reached at a local URI.
withForwardKubernetesService :: (
  KubectlBasic context m
  )
  -- | Namespace
  => Text
  -- | Service name
  -> Text
  -- | Callback receiving the service 'URL'.
  -> (URI -> m a)
  -> m a
withForwardKubernetesService :: forall context (m :: * -> *) a.
KubectlBasic context m =>
Text -> Text -> (URI -> m a) -> m a
withForwardKubernetesService Text
namespace Text
serviceName URI -> m a
action = do
  kcc <- Label "kubernetesCluster" KubernetesClusterContext
-> m KubernetesClusterContext
forall context (l :: Symbol) a (m :: * -> *).
(HasLabel context l a, MonadReader context m) =>
Label l a -> m a
getContext Label "kubernetesCluster" KubernetesClusterContext
kubernetesCluster
  kubectlBinary <- askFile @"kubectl"
  withForwardKubernetesService' kcc kubectlBinary namespace serviceName action

-- | Same as 'withForwardKubernetesService', but allows you to pass in the 'KubernetesClusterContext' and @kubectl@ binary.
withForwardKubernetesService' :: (
  MonadLoggerIO m, MonadUnliftIO m
  , HasBaseContextMonad context m
  )
  -- | Kubernetes cluster context
  => KubernetesClusterContext
  -- | Binary path for kubectl
  -> FilePath
  -- | Namespace
  -> Text
  -- | Service name
  -> Text
  -- | Callback receiving the service 'URL'.
  -> (URI -> m a)
  -> m a
withForwardKubernetesService' :: forall (m :: * -> *) context a.
(MonadLoggerIO m, MonadUnliftIO m,
 HasBaseContextMonad context m) =>
KubernetesClusterContext
-> FilePath -> Text -> Text -> (URI -> m a) -> m a
withForwardKubernetesService' kcc :: KubernetesClusterContext
kcc@(KubernetesClusterContext {kubernetesClusterType :: KubernetesClusterContext -> KubernetesClusterType
kubernetesClusterType=(KubernetesClusterMinikube {FilePath
[Text]
Text
kubernetesClusterTypeMinikubeBinary :: FilePath
kubernetesClusterTypeMinikubeProfileName :: Text
kubernetesClusterTypeMinikubeFlags :: [Text]
kubernetesClusterTypeMinikubeFlags :: KubernetesClusterType -> [Text]
kubernetesClusterTypeMinikubeProfileName :: KubernetesClusterType -> Text
kubernetesClusterTypeMinikubeBinary :: KubernetesClusterType -> FilePath
..})}) FilePath
_kubectlBinary =
  KubernetesClusterContext
-> Text -> Text -> Text -> (URI -> m a) -> m a
forall (m :: * -> *) a.
(HasCallStack, MonadLoggerIO m, MonadUnliftIO m) =>
KubernetesClusterContext
-> Text -> Text -> Text -> (URI -> m a) -> m a
Minikube.withForwardKubernetesService' KubernetesClusterContext
kcc Text
kubernetesClusterTypeMinikubeProfileName
withForwardKubernetesService' kcc :: KubernetesClusterContext
kcc@(KubernetesClusterContext {kubernetesClusterType :: KubernetesClusterContext -> KubernetesClusterType
kubernetesClusterType=(KubernetesClusterKind {})}) FilePath
kubectlBinary =
  KubernetesClusterContext
-> FilePath -> Text -> Text -> (URI -> m a) -> m a
forall (m :: * -> *) context a.
(MonadUnliftIO m, MonadLoggerIO m,
 HasBaseContextMonad context m) =>
KubernetesClusterContext
-> FilePath -> Text -> Text -> (URI -> m a) -> m a
Kind.withForwardKubernetesService' KubernetesClusterContext
kcc FilePath
kubectlBinary