{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Kubernetes.Client.KubeConfig where
import Data.Aeson (FromJSON (..), Options, ToJSON (..),
Value (..), camelTo2, defaultOptions,
fieldLabelModifier, genericParseJSON,
genericToJSON, object, omitNothingFields,
withObject, (.:), (.=))
import qualified Data.Map as Map
import Data.Proxy
import Data.Semigroup ((<>))
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable
import GHC.Generics
import GHC.TypeLits
camelToWithOverrides :: Char -> Map.Map String String -> Options
camelToWithOverrides c overrides = defaultOptions
{ fieldLabelModifier = modifier
, omitNothingFields = True
}
where modifier s = Map.findWithDefault (camelTo2 c s) s overrides
data Config = Config
{ kind :: Maybe Text
, apiVersion :: Maybe Text
, preferences :: Maybe Preferences
, clusters :: [NamedEntity Cluster "cluster"]
, authInfos :: [NamedEntity AuthInfo "user"]
, contexts :: [NamedEntity Context "context"]
, currentContext :: Text
} deriving (Eq, Generic, Show)
configJSONOptions = camelToWithOverrides
'-'
(Map.fromList [("apiVersion", "apiVersion"), ("authInfos", "users")])
instance ToJSON Config where
toJSON = genericToJSON configJSONOptions
instance FromJSON Config where
parseJSON = genericParseJSON configJSONOptions
newtype Preferences = Preferences
{ colors :: Maybe Bool
} deriving (Eq, Generic, Show)
instance ToJSON Preferences where
toJSON = genericToJSON $ camelToWithOverrides '-' Map.empty
instance FromJSON Preferences where
parseJSON = genericParseJSON $ camelToWithOverrides '-' Map.empty
data Cluster = Cluster
{ server :: Text
, insecureSkipTLSVerify :: Maybe Bool
, certificateAuthority :: Maybe Text
, certificateAuthorityData :: Maybe Text
} deriving (Eq, Generic, Show, Typeable)
instance ToJSON Cluster where
toJSON = genericToJSON $ camelToWithOverrides '-' Map.empty
instance FromJSON Cluster where
parseJSON = genericParseJSON $ camelToWithOverrides '-' Map.empty
data NamedEntity a (typeKey :: Symbol) = NamedEntity
{ name :: Text
, entity :: a } deriving (Eq, Generic, Show)
instance (FromJSON a, Typeable a, KnownSymbol s) =>
FromJSON (NamedEntity a s) where
parseJSON = withObject ("Named" <> (show $ typeOf (undefined :: a))) $ \v ->
NamedEntity <$> v .: "name" <*> v .: T.pack (symbolVal (Proxy :: Proxy s))
instance (ToJSON a, KnownSymbol s) =>
ToJSON (NamedEntity a s) where
toJSON (NamedEntity {..}) = object
["name" .= toJSON name, T.pack (symbolVal (Proxy :: Proxy s)) .= toJSON entity]
toMap :: [NamedEntity a s] -> Map.Map Text a
toMap = Map.fromList . fmap (\NamedEntity {..} -> (name, entity))
data AuthInfo = AuthInfo
{ clientCertificate :: Maybe FilePath
, clientCertificateData :: Maybe Text
, clientKey :: Maybe FilePath
, clientKeyData :: Maybe Text
, token :: Maybe Text
, tokenFile :: Maybe FilePath
, impersonate :: Maybe Text
, impersonateGroups :: Maybe [Text]
, impersonateUserExtra :: Maybe (Map.Map Text [Text])
, username :: Maybe Text
, password :: Maybe Text
, authProvider :: Maybe AuthProviderConfig
} deriving (Eq, Generic, Show, Typeable)
authInfoJSONOptions = camelToWithOverrides
'-'
( Map.fromList
[ ("tokenFile" , "tokenFile")
, ("impersonate" , "as")
, ("impersonateGroups" , "as-groups")
, ("impersonateUserExtra", "as-user-extra")
]
)
instance ToJSON AuthInfo where
toJSON = genericToJSON authInfoJSONOptions
instance FromJSON AuthInfo where
parseJSON = genericParseJSON authInfoJSONOptions
data Context = Context
{ cluster :: Text
, authInfo :: Text
, namespace :: Maybe Text
} deriving (Eq, Generic, Show, Typeable)
contextJSONOptions =
camelToWithOverrides '-' (Map.fromList [("authInfo", "user")])
instance ToJSON Context where
toJSON = genericToJSON contextJSONOptions
instance FromJSON Context where
parseJSON = genericParseJSON contextJSONOptions
data AuthProviderConfig = AuthProviderConfig
{ name :: Text
, config :: Maybe (Map.Map Text Text)
} deriving (Eq, Generic, Show)
instance ToJSON AuthProviderConfig where
toJSON = genericToJSON $ camelToWithOverrides '-' Map.empty
instance FromJSON AuthProviderConfig where
parseJSON = genericParseJSON $ camelToWithOverrides '-' Map.empty
getContext :: Config -> Either String Context
getContext Config {..} =
let maybeContext = Map.lookup currentContext (toMap contexts)
in case maybeContext of
Just ctx -> Right ctx
Nothing -> Left ("No context named " <> T.unpack currentContext)
getAuthInfo :: Config -> Either String (Text, AuthInfo)
getAuthInfo cfg@Config {..} = do
Context {..} <- getContext cfg
let maybeAuth = Map.lookup authInfo (toMap authInfos)
case maybeAuth of
Just auth -> Right (authInfo, auth)
Nothing -> Left ("No user named " <> T.unpack authInfo)
getCluster :: Config -> Either String Cluster
getCluster cfg@Config {clusters=clusters} = do
Context {cluster=clusterName} <- getContext cfg
let maybeCluster = Map.lookup clusterName (toMap clusters)
case maybeCluster of
Just cluster -> Right cluster
Nothing -> Left ("No cluster named " <> T.unpack clusterName)