stackctl-1.4.2.2: src/Stackctl/StackSpecPath.hs
{-# LANGUAGE NamedFieldPuns #-}
module Stackctl.StackSpecPath
( StackSpecPath
-- * Fields
, stackSpecPathAccountId
, stackSpecPathAccountName
, stackSpecPathRegion
, stackSpecPathStackName
, stackSpecPathBasePath
, stackSpecPathFilePath
-- * Construction
, stackSpecPath
, stackSpecPathFromFilePath
) where
import Stackctl.Prelude
import Data.Char (isDigit)
import qualified Data.Text as T
import Stackctl.AWS
import Stackctl.AWS.Scope
import System.FilePath (joinPath, splitDirectories)
data StackSpecPath = StackSpecPath
{ sspAwsScope :: AwsScope
, sspAccountPathPart :: FilePath
, sspStackName :: StackName
, sspPath :: FilePath
}
deriving stock (Eq, Show)
stackSpecPath :: AwsScope -> StackName -> FilePath -> StackSpecPath
stackSpecPath sspAwsScope@AwsScope {..} sspStackName sspPath =
StackSpecPath
{ sspAwsScope
, sspAccountPathPart
, sspStackName
, sspPath
}
where
sspAccountPathPart =
unpack $ unAccountId awsAccountId <> "." <> awsAccountName
stackSpecPathAccountId :: StackSpecPath -> AccountId
stackSpecPathAccountId = awsAccountId . sspAwsScope
stackSpecPathAccountName :: StackSpecPath -> Text
stackSpecPathAccountName = awsAccountName . sspAwsScope
stackSpecPathAccountPathPart :: StackSpecPath -> FilePath
stackSpecPathAccountPathPart = sspAccountPathPart
stackSpecPathRegion :: StackSpecPath -> Region
stackSpecPathRegion = awsRegion . sspAwsScope
stackSpecPathBasePath :: StackSpecPath -> FilePath
stackSpecPathBasePath = sspPath
stackSpecPathStackName :: StackSpecPath -> StackName
stackSpecPathStackName = sspStackName
-- | Render the (relative) 'StackSpecPath'
stackSpecPathFilePath :: StackSpecPath -> FilePath
stackSpecPathFilePath path =
"stacks"
</> stackSpecPathAccountPathPart path
</> unpack (fromRegion $ stackSpecPathRegion path)
</> stackSpecPathBasePath path
stackSpecPathFromFilePath
:: AwsScope
-> FilePath
-- ^ Must be relative, @stacks/@
-> Either String StackSpecPath
stackSpecPathFromFilePath awsScope@AwsScope {..} path =
case splitDirectories path of
("stacks" : pathAccount : pathRegion : rest) -> do
(accountName, pathAccountId) <- parseAccountPath pathAccount
unless (pathAccountId == awsAccountId)
$ Left
$ "Unexpected account: "
<> unpack (unAccountId pathAccountId)
<> " != "
<> unpack (unAccountId awsAccountId)
unless (unpack (fromRegion awsRegion) == pathRegion)
$ Left
$ "Unexpected region: "
<> pathRegion
<> " != "
<> unpack (fromRegion awsRegion)
stackName <-
maybe (Left "Must end in .yaml") (Right . StackName)
$ T.stripSuffix ".yaml"
$ T.intercalate "-"
$ map pack rest
Right
$ StackSpecPath
{ sspAwsScope = awsScope {awsAccountName = accountName}
, sspAccountPathPart = pathAccount
, sspStackName = stackName
, sspPath = joinPath rest
}
_ -> Left $ "Path is not stacks/././.: " <> path
-- | Handle @{account-name}.{account-id}@ or @{account-id}.{account-name}@
parseAccountPath :: FilePath -> Either String (Text, AccountId)
parseAccountPath path = case second (T.drop 1) $ T.breakOn "." $ pack path of
(a, b) | isAccountId a -> Right (b, AccountId a)
(a, b) | isAccountId b -> Right (a, AccountId b)
_ ->
Left
$ "Path matches neither {account-id}.{account-name}, nor {account-name}.{account-id}: "
<> path
where
isAccountId x = T.length x == 12 && T.all isDigit x