module Stackctl.Spec.Capture
  ( CaptureOptions (..)
  , parseCaptureOptions
  , runCapture
  ) where

import Stackctl.Prelude

import Options.Applicative
import Stackctl.AWS
import Stackctl.AWS.Scope
import Stackctl.Config (HasConfig)
import Stackctl.DirectoryOption (HasDirectoryOption)
import Stackctl.Spec.Generate
import Stackctl.StackSpec
import System.FilePath.Glob

data CaptureOptions = CaptureOptions
  { CaptureOptions -> Maybe Text
scoAccountName :: Maybe Text
  , CaptureOptions -> Maybe FilePath
scoTemplatePath :: Maybe FilePath
  , CaptureOptions -> Maybe FilePath
scoStackPath :: Maybe FilePath
  , CaptureOptions -> Maybe [StackName]
scoDepends :: Maybe [StackName]
  , CaptureOptions -> TemplateFormat
scoTemplateFormat :: TemplateFormat
  , CaptureOptions -> Pattern
scoStackName :: Pattern
  }

-- brittany-disable-next-binding

parseCaptureOptions :: Parser CaptureOptions
parseCaptureOptions :: Parser CaptureOptions
parseCaptureOptions =
  Maybe Text
-> Maybe FilePath
-> Maybe FilePath
-> Maybe [StackName]
-> TemplateFormat
-> Pattern
-> CaptureOptions
CaptureOptions
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional
      ( forall s. IsString s => Mod OptionFields s -> Parser s
strOption
          ( forall (f :: * -> *) a. HasName f => Char -> Mod f a
short Char
'n'
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"account-name"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"NAME"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Account name to use in generated files"
          )
      )
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional
      ( forall s. IsString s => Mod OptionFields s -> Parser s
strOption
          ( forall (f :: * -> *) a. HasName f => Char -> Mod f a
short Char
't'
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"template-path"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"PATH"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Write Template to PATH. Default is based on STACK"
          )
      )
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional
      ( forall s. IsString s => Mod OptionFields s -> Parser s
strOption
          ( forall (f :: * -> *) a. HasName f => Char -> Mod f a
short Char
'p'
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"path"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"PATH"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Write specification to PATH. Default is based on STACK"
          )
      )
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional
      ( forall (f :: * -> *) a. Alternative f => f a -> f [a]
some
          ( Text -> StackName
StackName
              forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s. IsString s => Mod OptionFields s -> Parser s
strOption
                ( forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"depend"
                    forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"STACK"
                    forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Add a dependency on STACK"
                )
          )
      )
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall a. a -> a -> Mod FlagFields a -> Parser a
flag
      TemplateFormat
TemplateFormatYaml
      TemplateFormat
TemplateFormatJson
      ( forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"no-flip"
          forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Don't flip JSON templates to Yaml"
      )
    forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> forall s. IsString s => Mod ArgumentFields s -> Parser s
strArgument
      ( forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"STACK"
          forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Name of deployed Stack to capture"
      )

runCapture
  :: ( MonadMask m
     , MonadUnliftIO m
     , MonadResource m
     , MonadLogger m
     , MonadReader env m
     , HasAwsScope env
     , HasAwsEnv env
     , HasConfig env
     , HasDirectoryOption env
     )
  => CaptureOptions
  -> m ()
runCapture :: forall (m :: * -> *) env.
(MonadMask m, MonadUnliftIO m, MonadResource m, MonadLogger m,
 MonadReader env m, HasAwsScope env, HasAwsEnv env, HasConfig env,
 HasDirectoryOption env) =>
CaptureOptions -> m ()
runCapture CaptureOptions {Maybe FilePath
Maybe [StackName]
Maybe Text
Pattern
TemplateFormat
scoStackName :: Pattern
scoTemplateFormat :: TemplateFormat
scoDepends :: Maybe [StackName]
scoStackPath :: Maybe FilePath
scoTemplatePath :: Maybe FilePath
scoAccountName :: Maybe Text
scoStackName :: CaptureOptions -> Pattern
scoTemplateFormat :: CaptureOptions -> TemplateFormat
scoDepends :: CaptureOptions -> Maybe [StackName]
scoStackPath :: CaptureOptions -> Maybe FilePath
scoTemplatePath :: CaptureOptions -> Maybe FilePath
scoAccountName :: CaptureOptions -> Maybe Text
..} = do
  let
    setScopeName :: AwsScope -> AwsScope
setScopeName AwsScope
scope =
      forall b a. b -> (a -> b) -> Maybe a -> b
maybe AwsScope
scope (\Text
name -> AwsScope
scope {awsAccountName :: Text
awsAccountName = Text
name}) Maybe Text
scoAccountName

    generate' :: Stack -> Value -> Maybe FilePath -> Maybe FilePath -> m ()
generate' Stack
stack Value
template Maybe FilePath
path Maybe FilePath
templatePath = do
      let
        stackName :: StackName
stackName = Text -> StackName
StackName forall a b. (a -> b) -> a -> b
$ Stack
stack forall s a. s -> Getting a s a -> a
^. Lens' Stack Text
stack_stackName
        templateBody :: TemplateBody
templateBody = Value -> TemplateBody
templateBodyFromValue Value
template

      forall (f :: * -> *) a. Functor f => f a -> f ()
void
        forall a b. (a -> b) -> a -> b
$ forall r (m :: * -> *) a. MonadReader r m => (r -> r) -> m a -> m a
local (forall env. HasAwsScope env => Lens' env AwsScope
awsScopeL forall s t a b. ASetter s t a b -> (a -> b) -> s -> t
%~ AwsScope -> AwsScope
setScopeName)
        forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) env.
(MonadMask m, MonadUnliftIO m, MonadLogger m, MonadReader env m,
 HasConfig env, HasAwsScope env, HasDirectoryOption env) =>
Generate -> m FilePath
generate
          Generate
            { gDescription :: Maybe StackDescription
gDescription = Stack -> Maybe StackDescription
stackDescription Stack
stack
            , gDepends :: Maybe [StackName]
gDepends = Maybe [StackName]
scoDepends
            , gActions :: Maybe [Action]
gActions = forall a. Maybe a
Nothing
            , gParameters :: Maybe [Parameter]
gParameters = Stack -> Maybe [Parameter]
parameters Stack
stack
            , gCapabilities :: Maybe [Capability]
gCapabilities = Stack -> Maybe [Capability]
capabilities Stack
stack
            , gTags :: Maybe [Tag]
gTags = Stack -> Maybe [Tag]
tags Stack
stack
            , gSpec :: GenerateSpec
gSpec = case Maybe FilePath
path of
                Maybe FilePath
Nothing -> StackName -> GenerateSpec
GenerateSpec StackName
stackName
                Just FilePath
sp -> StackName -> FilePath -> GenerateSpec
GenerateSpecTo StackName
stackName FilePath
sp
            , gTemplate :: GenerateTemplate
gTemplate = case Maybe FilePath
templatePath of
                Maybe FilePath
Nothing -> TemplateBody -> TemplateFormat -> GenerateTemplate
GenerateTemplate TemplateBody
templateBody TemplateFormat
scoTemplateFormat
                Just FilePath
tp -> TemplateBody -> FilePath -> GenerateTemplate
GenerateTemplateTo TemplateBody
templateBody FilePath
tp
            , gOverwrite :: Bool
gOverwrite = Bool
False
            }

  [StackName]
results <- forall (m :: * -> *) env.
(MonadResource m, MonadReader env m, HasAwsEnv env) =>
Pattern -> m [StackName]
awsCloudFormationGetStackNamesMatching Pattern
scoStackName

  case [StackName]
results of
    [] -> do
      forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logError
        forall a b. (a -> b) -> a -> b
$ Text
"No Active Stacks match "
        forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
pack (Pattern -> FilePath
decompile Pattern
scoStackName)
        Text -> [SeriesElem] -> Message
:# []
      forall (m :: * -> *) a. MonadIO m => m a
exitFailure
    [StackName
stackName] -> do
      Stack
stack <- forall (m :: * -> *) env.
(MonadResource m, MonadReader env m, HasAwsEnv env) =>
StackName -> m Stack
awsCloudFormationDescribeStack StackName
stackName
      Value
template <- forall (m :: * -> *) env.
(MonadResource m, MonadReader env m, HasAwsEnv env) =>
StackName -> m Value
awsCloudFormationGetTemplate StackName
stackName
      Stack -> Value -> Maybe FilePath -> Maybe FilePath -> m ()
generate' Stack
stack Value
template Maybe FilePath
scoStackPath Maybe FilePath
scoTemplatePath
    [StackName]
stackNames -> do
      forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logInfo Message
"Capturing multiple matching Stacks"
      forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ Maybe FilePath
scoStackPath forall a b. (a -> b) -> a -> b
$ \FilePath
_ -> forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logWarn Message
"--path option ignored"
      forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ Maybe FilePath
scoTemplatePath forall a b. (a -> b) -> a -> b
$ \FilePath
_ -> forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logWarn Message
"--template-path option ignored"
      forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
t a -> (a -> f b) -> f ()
for_ [StackName]
stackNames forall a b. (a -> b) -> a -> b
$ \StackName
stackName -> do
        Stack
stack <- forall (m :: * -> *) env.
(MonadResource m, MonadReader env m, HasAwsEnv env) =>
StackName -> m Stack
awsCloudFormationDescribeStack StackName
stackName
        Value
template <- forall (m :: * -> *) env.
(MonadResource m, MonadReader env m, HasAwsEnv env) =>
StackName -> m Value
awsCloudFormationGetTemplate StackName
stackName
        Stack -> Value -> Maybe FilePath -> Maybe FilePath -> m ()
generate' Stack
stack Value
template forall a. Maybe a
Nothing forall a. Maybe a
Nothing