packages feed

stackctl 1.1.3.1 → 1.1.4.0

raw patch · 8 files changed

+113/−25 lines, 8 files

Files

CHANGELOG.md view
@@ -1,4 +1,9 @@-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.1.3.1...main)+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.1.4.0...main)++## [v1.1.4.0](https://github.com/freckle/stackctl/compare/v1.1.3.1...v1.1.4.0)++- Support matching Stacks by glob in `capture`+- Add `--tag` to `changes` and `deploy`  ## [v1.1.3.1](https://github.com/freckle/stackctl/compare/v1.1.3.0...v1.1.3.1) 
src/Stackctl/AWS/CloudFormation.hs view
@@ -1,5 +1,6 @@ module Stackctl.AWS.CloudFormation   ( Stack(..)+  , stack_stackName   , stackDescription   , stackIsRollbackComplete   , StackId(..)@@ -35,6 +36,7 @@   , awsCloudFormationDescribeStackMaybe   , awsCloudFormationDescribeStackOutputs   , awsCloudFormationDescribeStackEvents+  , awsCloudFormationGetStackNamesMatching   , awsCloudFormationGetMostRecentStackEventId   , awsCloudFormationDeleteStack   , awsCloudFormationWait@@ -70,6 +72,7 @@ import Amazonka.CloudFormation.ExecuteChangeSet import Amazonka.CloudFormation.GetTemplate import Amazonka.CloudFormation.ListChangeSets+import Amazonka.CloudFormation.ListStacks import Amazonka.CloudFormation.Types import qualified Amazonka.CloudFormation.Types.ChangeSetSummary as Summary import Amazonka.CloudFormation.Waiters@@ -96,6 +99,7 @@ import Stackctl.AWS.Core import Stackctl.Sort import Stackctl.StackDescription+import System.FilePath.Glob import UnliftIO.Exception.Lens (handling_, trying)  stackDescription :: Stack -> Maybe StackDescription@@ -213,6 +217,22 @@     .| takeWhileC (\e -> Just (e ^. stackEvent_eventId) /= mLastId)     .| sinkList +awsCloudFormationGetStackNamesMatching+  :: (MonadResource m, MonadReader env m, HasAwsEnv env)+  => Pattern+  -> m [StackName]+awsCloudFormationGetStackNamesMatching p = do+  let req = newListStacks & listStacks_stackStatusFilter ?~ runningStatuses++  runConduit+    $ awsPaginate req+    .| concatMapC (^. listStacksResponse_stackSummaries)+    .| concatC+    .| mapC (^. stackSummary_stackName)+    .| filterC ((p `match`) . unpack)+    .| mapC StackName+    .| sinkList+ awsCloudFormationGetMostRecentStackEventId   :: (MonadResource m, MonadReader env m, HasAwsEnv env)   => StackName@@ -462,6 +482,13 @@ stackIsRollbackComplete :: Stack -> Bool stackIsRollbackComplete stack =   stack ^. stack_stackStatus == StackStatus_ROLLBACK_COMPLETE++runningStatuses :: [StackStatus]+runningStatuses =+  [ StackStatus_CREATE_COMPLETE+  , StackStatus_UPDATE_COMPLETE+  , StackStatus_UPDATE_ROLLBACK_COMPLETE+  ]  _ValidationError :: AsError a => Getting (First ServiceError) a ServiceError _ValidationError =
src/Stackctl/Spec/Capture.hs view
@@ -13,6 +13,7 @@ import Stackctl.DirectoryOption (HasDirectoryOption(..)) import Stackctl.Spec.Generate import Stackctl.StackSpec+import System.FilePath.Glob  data CaptureOptions = CaptureOptions   { scoAccountName :: Maybe Text@@ -20,7 +21,7 @@   , scoStackPath :: Maybe FilePath   , scoDepends :: Maybe [StackName]   , scoTemplateFormat :: TemplateFormat-  , scoStackName :: StackName+  , scoStackName :: Pattern   }  -- brittany-disable-next-binding@@ -54,10 +55,10 @@       (  long "no-flip"       <> help "Don't flip JSON templates to Yaml"       )-    <*> (StackName <$> argument str+    <*> strArgument       (  metavar "STACK"       <> help "Name of deployed Stack to capture"-      ))+      )  runCapture   :: ( MonadMask m@@ -74,24 +75,46 @@   -> m () runCapture CaptureOptions {..} = do   dir <- view directoryOptionL-  stack <- awsCloudFormationDescribeStack scoStackName-  template <- awsCloudFormationGetTemplate scoStackName    let     setScopeName scope =       maybe scope (\name -> scope { awsAccountName = name }) scoAccountName -  void $ local (awsScopeL %~ setScopeName) $ generate Generate-    { gOutputDirectory = dir-    , gTemplatePath = scoTemplatePath-    , gTemplateFormat = scoTemplateFormat-    , gStackPath = scoStackPath-    , gStackName = scoStackName-    , gDescription = stackDescription stack-    , gDepends = scoDepends-    , gActions = Nothing-    , gParameters = parameters stack-    , gCapabilities = capabilities stack-    , gTags = tags stack-    , gTemplateBody = templateBodyFromValue template-    }+    generate' stack template path templatePath = do+      void $ local (awsScopeL %~ setScopeName) $ generate Generate+        { gOutputDirectory = dir+        , gTemplatePath = templatePath+        , gTemplateFormat = scoTemplateFormat+        , gStackPath = path+        , gStackName = StackName $ stack ^. stack_stackName+        , gDescription = stackDescription stack+        , gDepends = scoDepends+        , gActions = Nothing+        , gParameters = parameters stack+        , gCapabilities = capabilities stack+        , gTags = tags stack+        , gTemplateBody = templateBodyFromValue template+        }++  results <- awsCloudFormationGetStackNamesMatching scoStackName++  case results of+    [] -> do+      logError+        $ "No Active Stacks match "+        <> pack (decompile scoStackName)+        :# []+      exitFailure++    [stackName] -> do+      stack <- awsCloudFormationDescribeStack stackName+      template <- awsCloudFormationGetTemplate stackName+      generate' stack template scoStackPath scoTemplatePath+    stackNames -> do+      logInfo "Capturing multiple matching Stacks"+      for_ scoStackPath $ \_ -> logWarn "--path option ignored"+      for_ scoTemplatePath $ \_ -> logWarn "--template-path option ignored"+      for_ stackNames $ \stackName -> do+        stack <- awsCloudFormationDescribeStack stackName+        template <- awsCloudFormationGetTemplate stackName+        generate' stack template Nothing Nothing
src/Stackctl/Spec/Changes.hs view
@@ -20,10 +20,12 @@ import Stackctl.Spec.Discover import Stackctl.StackSpec import Stackctl.StackSpecPath+import Stackctl.TagOption  data ChangesOptions = ChangesOptions   { scoFormat :: Format   , scoParameters :: [Parameter]+  , scoTags :: [Tag]   , scoOutput :: Maybe FilePath   } @@ -33,6 +35,7 @@ runChangesOptions = ChangesOptions   <$> formatOption   <*> many parameterOption+  <*> many tagOption   <*> optional (argument str     (  metavar "PATH"     <> help "Write changes summary to PATH"@@ -62,7 +65,7 @@    for_ specs $ \spec -> do     withThreadContext ["stackName" .= stackSpecStackName spec] $ do-      emChangeSet <- createChangeSet spec scoParameters+      emChangeSet <- createChangeSet spec scoParameters scoTags        case emChangeSet of         Left err -> do
src/Stackctl/Spec/Deploy.hs view
@@ -23,10 +23,12 @@ import Stackctl.Spec.Changes.Format import Stackctl.Spec.Discover import Stackctl.StackSpec+import Stackctl.TagOption import UnliftIO.Directory (createDirectoryIfMissing)  data DeployOptions = DeployOptions   { sdoParameters :: [Parameter]+  , sdoTags :: [Tag]   , sdoSaveChangeSets :: Maybe FilePath   , sdoDeployConfirmation :: DeployConfirmation   , sdoClean :: Bool@@ -37,6 +39,7 @@ runDeployOptions :: Parser DeployOptions runDeployOptions = DeployOptions   <$> many parameterOption+  <*> many tagOption   <*> optional (strOption     (  long "save-change-sets"     <> metavar "DIRECTORY"@@ -74,7 +77,7 @@     withThreadContext ["stackName" .= stackSpecStackName spec] $ do       handleRollbackComplete sdoDeployConfirmation $ stackSpecStackName spec -      emChangeSet <- createChangeSet spec sdoParameters+      emChangeSet <- createChangeSet spec sdoParameters sdoTags        case emChangeSet of         Left err -> do
src/Stackctl/StackSpec.hs view
@@ -170,15 +170,16 @@      )   => StackSpec   -> [Parameter]+  -> [Tag]   -> m (Either Text (Maybe ChangeSet))-createChangeSet spec parameters = awsCloudFormationCreateChangeSet+createChangeSet spec parameters tags = awsCloudFormationCreateChangeSet   (stackSpecStackName spec)   (stackSpecStackDescription spec)   (stackSpecTemplate spec)   (nubOrdOn (^. parameter_parameterKey) $ parameters <> stackSpecParameters spec   )   (stackSpecCapabilities spec)-  (stackSpecTags spec)+  (nubOrdOn (^. tag_key) $ tags <> stackSpecTags spec)  sortStackSpecs :: [StackSpec] -> [StackSpec] sortStackSpecs = sortByDependencies stackSpecStackName stackSpecDepends
+ src/Stackctl/TagOption.hs view
@@ -0,0 +1,25 @@+module Stackctl.TagOption+  ( tagOption+  ) where++import Stackctl.Prelude++import qualified Data.Text as T+import Options.Applicative+import Stackctl.AWS.CloudFormation (Tag, newTag)++tagOption :: Parser Tag+tagOption = option (eitherReader readTag) $ mconcat+  [ short 't'+  , long "tag"+  , metavar "KEY=[VALUE]"+  , help "Override the given Tag for this operation"+  ]++readTag :: String -> Either String Tag+readTag s = case T.breakOn "=" t of+  (_, v) | T.null v -> Left $ "No '=' found (" <> s <> ")"+  (k, _) | T.null k -> Left $ "Empty key (" <> s <> ")"+  (k, "=") -> Right $ newTag k ""+  (k, v) -> Right $ newTag k $ T.drop 1 v+  where t = pack s
stackctl.cabal view
@@ -1,6 +1,6 @@ cabal-version:   1.18 name:            stackctl-version:         1.1.3.1+version:         1.1.4.0 license:         MIT license-file:    LICENSE copyright:       2022 Renaissance Learning Inc@@ -54,6 +54,7 @@         Stackctl.StackSpecPath         Stackctl.StackSpecYaml         Stackctl.Subcommand+        Stackctl.TagOption         Stackctl.VerboseOption         Stackctl.Version         UnliftIO.Exception.Lens