stackctl-1.1.0.2: src/Stackctl/Spec/Discover.hs
module Stackctl.Spec.Discover
( discoverSpecs
, buildSpecPath
) where
import Stackctl.Prelude
import Data.List.Extra (dropPrefix)
import qualified Data.List.NonEmpty as NE
import Stackctl.AWS
import Stackctl.AWS.Scope
import Stackctl.DirectoryOption (HasDirectoryOption(..))
import Stackctl.FilterOption (HasFilterOption(..), filterFilePaths)
import Stackctl.StackSpec
import Stackctl.StackSpecPath
import System.FilePath (isPathSeparator)
import System.FilePath.Glob
discoverSpecs
:: ( MonadMask m
, MonadResource m
, MonadLogger m
, MonadReader env m
, HasAwsScope env
, HasDirectoryOption env
, HasFilterOption env
)
=> m [StackSpec]
discoverSpecs = do
dir <- view directoryOptionL
scope@AwsScope {..} <- view awsScopeL
discovered <- globRelativeTo
dir
[ compile
$ "stacks"
</> unpack (unAccountId awsAccountId)
<> ".*"
</> unpack (fromRegion awsRegion)
<> "**"
</> "*"
<.> "yaml"
, compile
$ "stacks"
</> "*."
<> unpack (unAccountId awsAccountId)
</> unpack (fromRegion awsRegion)
<> "**"
</> "*"
<.> "yaml"
]
filterOption <- view filterOptionL
let
matched = filterFilePaths filterOption discovered
toSpecPath = stackSpecPathFromFilePath scope
(errs, specPaths) = partitionEithers $ map toSpecPath matched
context =
[ "path" .= dir
, "filters" .= filterOption
, "discovered" .= length discovered
, "matched" .= length matched
, "errors" .= length errs
]
withThreadContext context $ do
logDebug "Discovered specs"
when (null matched) $ logWarn "No specs found"
checkForDuplicateStackNames specPaths
sortStackSpecs <$> traverse (readStackSpec dir) specPaths
checkForDuplicateStackNames
:: (MonadIO m, MonadLogger m) => [StackSpecPath] -> m ()
checkForDuplicateStackNames =
traverse_ reportCollisions
. NE.nonEmpty
. filter ((> 1) . length)
. NE.groupAllWith stackSpecPathStackName
where
reportCollisions
:: (MonadIO m, MonadLogger m) => NonEmpty (NonEmpty StackSpecPath) -> m ()
reportCollisions errs = do
for_ errs $ \specPaths -> do
let collidingPaths = stackSpecPathBasePath <$> specPaths
logError
$ "Multiple specifications produced the same Stack name"
:# [ "name" .= stackSpecPathStackName (NE.head specPaths)
, "paths" .= collidingPaths
]
exitFailure
buildSpecPath
:: (MonadReader env m, HasAwsScope env)
=> StackName
-> FilePath
-> m StackSpecPath
buildSpecPath stackName stackPath = do
scope <- view awsScopeL
pure $ stackSpecPath scope stackName stackPath
globRelativeTo :: MonadIO m => FilePath -> [Pattern] -> m [FilePath]
globRelativeTo dir ps = liftIO $ do
map (dropWhile isPathSeparator . dropPrefix dir) . concat <$> globDir ps dir