stackctl 1.1.1.1 → 1.1.2.0
raw patch · 10 files changed
+110/−48 lines, 10 filesdep −fast-loggerdep ~Blammo
Dependencies removed: fast-logger
Dependency ranges changed: Blammo
Files
- CHANGELOG.md +8/−1
- src/Stackctl/CLI.hs +5/−1
- src/Stackctl/Colors.hs +6/−0
- src/Stackctl/Commands.hs +3/−4
- src/Stackctl/Spec/Capture.hs +8/−1
- src/Stackctl/Spec/Changes.hs +15/−8
- src/Stackctl/Spec/Deploy.hs +5/−14
- src/Stackctl/Spec/Generate.hs +19/−8
- src/Stackctl/StackSpec.hs +39/−8
- stackctl.cabal +2/−3
CHANGELOG.md view
@@ -1,4 +1,11 @@-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.1.1.1...main)+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.1.2.0...main)++## [v1.1.2.0](https://github.com/freckle/stackctl/compare/v1.1.1.1...v1.1.2.0)++- Fix incorrect ordering of log-messages by setting `LOG_CONCURRENCY=1`+- Fix potential coloring of changes being redirected to a file+- Make `PATH` optional (again) in `stackctl changes`+- Add `--no-flip` to `stackctl capture` ## [v1.1.1.1](https://github.com/freckle/stackctl/compare/v1.1.1.0...v1.1.1.1)
src/Stackctl/CLI.hs view
@@ -75,7 +75,11 @@ -> AppT (App options) m a -> m a runAppT options f = do- envLogSettings <- liftIO LoggingEnv.parse+ envLogSettings <-+ liftIO+ . LoggingEnv.parseWith+ . setLogSettingsConcurrency (Just 1)+ $ defaultLogSettings logger <- newLogger $ adjustLogSettings (options ^. colorOptionL)
src/Stackctl/Colors.hs view
@@ -3,12 +3,14 @@ ( Colors(..) , HasColorOption , getColorsStdout+ , getColorsLogger , noColors ) where import Stackctl.Prelude import Blammo.Logging.Colors+import Blammo.Logging.Logger import Stackctl.ColorOption (HasColorOption(..), colorHandle) -- | Return 'Colors' based on options and 'stdout'@@ -23,6 +25,10 @@ colorOption <- view colorOptionL c <- colorHandle h colorOption pure $ getColors c++-- | Return 'Colors' consistent with the ambient 'Logger'+getColorsLogger :: (MonadReader env m, HasLogger env) => m Colors+getColorsLogger = view $ loggerL . to (getColors . getLoggerShouldColor) noColors :: Colors noColors = getColors False
src/Stackctl/Commands.hs view
@@ -10,7 +10,7 @@ import Stackctl.AWS import Stackctl.AWS.Scope-import Stackctl.ColorOption+import Stackctl.Colors import Stackctl.DirectoryOption import Stackctl.FilterOption import Stackctl.Spec.Capture@@ -46,11 +46,11 @@ } changes- :: ( HasAwsScope env+ :: ( HasLogger env+ , HasAwsScope env , HasAwsEnv env , HasDirectoryOption env , HasFilterOption env- , HasColorOption env ) => Subcommand ChangesOptions env changes = Subcommand@@ -66,7 +66,6 @@ , HasAwsEnv env , HasDirectoryOption env , HasFilterOption env- , HasColorOption env ) => Subcommand DeployOptions env deploy = Subcommand
src/Stackctl/Spec/Capture.hs view
@@ -11,12 +11,14 @@ import Stackctl.AWS.Scope import Stackctl.DirectoryOption (HasDirectoryOption(..)) import Stackctl.Spec.Generate+import Stackctl.StackSpec data CaptureOptions = CaptureOptions { scoAccountName :: Maybe Text , scoTemplatePath :: Maybe FilePath , scoStackPath :: Maybe FilePath , scoDepends :: Maybe [StackName]+ , scoTemplateFormat :: TemplateFormat , scoStackName :: StackName } @@ -47,6 +49,10 @@ <> metavar "STACK" <> help "Add a dependency on STACK" )))+ <*> flag TemplateFormatYaml TemplateFormatJson+ ( long "no-flip"+ <> help "Don't flip JSON templates to Yaml"+ ) <*> (StackName <$> argument str ( metavar "STACK" <> help "Name of deployed Stack to capture"@@ -76,6 +82,7 @@ void $ local (awsScopeL %~ setScopeName) $ generate Generate { gOutputDirectory = dir , gTemplatePath = scoTemplatePath+ , gTemplateFormat = scoTemplateFormat , gStackPath = scoStackPath , gStackName = scoStackName , gDepends = scoDepends@@ -83,5 +90,5 @@ , gParameters = parameters stack , gCapabilities = capabilities stack , gTags = tags stack- , gTemplate = template+ , gTemplateBody = templateBodyFromValue template }
src/Stackctl/Spec/Changes.hs view
@@ -6,6 +6,7 @@ import Stackctl.Prelude +import Blammo.Logging.Logger (pushLoggerLn) import qualified Data.Text.IO as T import Options.Applicative import Stackctl.AWS hiding (action)@@ -22,7 +23,7 @@ data ChangesOptions = ChangesOptions { scoFormat :: Format , scoParameters :: [Parameter]- , scoOutput :: FilePath+ , scoOutput :: Maybe FilePath } -- brittany-disable-next-binding@@ -31,11 +32,11 @@ runChangesOptions = ChangesOptions <$> formatOption <*> many parameterOption- <*> argument str+ <*> optional (argument str ( metavar "PATH"- <> help "Where to write the changes summary"+ <> help "Write changes summary to PATH" <> action "file"- )+ )) runChanges :: ( MonadMask m@@ -43,17 +44,17 @@ , MonadResource m , MonadLogger m , MonadReader env m+ , HasLogger env , HasAwsScope env , HasAwsEnv env , HasDirectoryOption env , HasFilterOption env- , HasColorOption env ) => ChangesOptions -> m () runChanges ChangesOptions {..} = do -- Clear file before starting, as we have to use append for each spec- liftIO $ T.writeFile scoOutput ""+ liftIO $ traverse_ (`T.writeFile` "") scoOutput specs <- discoverSpecs @@ -66,8 +67,14 @@ logError $ "Error creating ChangeSet" :# ["error" .= err] exitFailure Right mChangeSet -> do- colors <- getColorsStdout+ colors <- case scoOutput of+ Nothing -> getColorsLogger+ Just{} -> pure noColors+ let name = pack $ stackSpecPathFilePath $ stackSpecSpecPath spec formatted = formatChangeSet colors name scoFormat mChangeSet- liftIO $ T.appendFile scoOutput $ formatted <> "\n"++ case scoOutput of+ Nothing -> pushLoggerLn formatted+ Just p -> liftIO $ T.appendFile p $ formatted <> "\n"
src/Stackctl/Spec/Deploy.hs view
@@ -7,7 +7,7 @@ import Stackctl.Prelude -import Blammo.Logging.Logger (pushLogStrLn)+import Blammo.Logging.Logger (pushLoggerLn) import qualified Data.Text as T import Data.Time (defaultTimeLocale, formatTime, utcToLocalZonedTime) import Options.Applicative@@ -22,7 +22,6 @@ import Stackctl.Spec.Changes.Format import Stackctl.Spec.Discover import Stackctl.StackSpec-import System.Log.FastLogger (toLogStr) import UnliftIO.Directory (createDirectoryIfMissing) data DeployOptions = DeployOptions@@ -63,7 +62,6 @@ , HasAwsEnv env , HasDirectoryOption env , HasFilterOption env- , HasColorOption env ) => DeployOptions -> m ()@@ -137,15 +135,14 @@ , MonadReader env m , HasLogger env , HasAwsEnv env- , HasColorOption env ) => DeployConfirmation -> ChangeSet -> m () deployChangeSet confirmation changeSet = do- colors <- getColorsStdout+ colors <- getColorsLogger - pushLogger $ formatTTY colors (unStackName stackName) $ Just changeSet+ pushLoggerLn $ formatTTY colors (unStackName stackName) $ Just changeSet case confirmation of DeployWithConfirmation -> promptContinue@@ -184,15 +181,14 @@ , MonadReader env m , HasLogger env , HasAwsEnv env- , HasColorOption env ) => StackName -> Maybe Text -- ^ StackEventId -> m a tailStackEventsSince stackName mLastId = do- colors <- getColorsStdout+ colors <- getColorsLogger events <- awsCloudFormationDescribeStackEvents stackName mLastId- traverse_ (pushLogger <=< formatStackEvent colors) $ reverse events+ traverse_ (pushLoggerLn <=< formatStackEvent colors) $ reverse events -- Without this small delay before looping, our requests seem to hang -- intermittently (without errors) and often we miss events.@@ -229,8 +225,3 @@ getLastEventId :: [StackEvent] -> Maybe Text getLastEventId = fmap (^. stackEvent_eventId) . listToMaybe--pushLogger :: (MonadIO m, MonadReader env m, HasLogger env) => Text -> m ()-pushLogger msg = do- logger <- view loggerL- pushLogStrLn logger $ toLogStr msg
src/Stackctl/Spec/Generate.hs view
@@ -1,11 +1,11 @@ module Stackctl.Spec.Generate ( Generate(..) , generate+ , TemplateFormat(..) ) where import Stackctl.Prelude -import Data.Aeson import Stackctl.AWS import Stackctl.AWS.Scope import Stackctl.Action@@ -17,18 +17,24 @@ data Generate = Generate { gOutputDirectory :: FilePath , gTemplatePath :: Maybe FilePath- -- ^ If not given will use @{stack-name}.yaml@+ -- ^ If not given, will use @{stack-name}.(yaml|json)@+ , gTemplateFormat :: TemplateFormat+ -- ^ Ignored if 'gTemplatePath' is given , gStackPath :: Maybe FilePath- -- ^ If not given will use @{stack-name}.yaml@+ -- ^ If not given, will use @{stack-name}.yaml@ , gStackName :: StackName , gDepends :: Maybe [StackName] , gActions :: Maybe [Action] , gParameters :: Maybe [Parameter] , gCapabilities :: Maybe [Capability] , gTags :: Maybe [Tag]- , gTemplate :: Value+ , gTemplateBody :: TemplateBody } +data TemplateFormat+ = TemplateFormatYaml+ | TemplateFormatJson+ generate :: ( MonadMask m , MonadUnliftIO m@@ -40,13 +46,18 @@ -> m FilePath generate Generate {..} = do let- path = unpack (unStackName gStackName) <.> "yaml"- stackPath = fromMaybe path gStackPath+ defaultStackPath = unpack (unStackName gStackName) <.> "yaml"+ defaultTemplatePath =+ unpack (unStackName gStackName) <.> case gTemplateFormat of+ TemplateFormatYaml -> "yaml"+ TemplateFormatJson -> "json" + stackPath = fromMaybe defaultStackPath gStackPath+ specPath <- buildSpecPath gStackName stackPath let- templatePath = fromMaybe path gTemplatePath+ templatePath = fromMaybe defaultTemplatePath gTemplatePath specYaml = StackSpecYaml { ssyTemplate = templatePath , ssyDepends = gDepends@@ -60,5 +71,5 @@ withThreadContext ["stackName" .= stackSpecStackName stackSpec] $ do logInfo "Generating specification"- writeStackSpec gOutputDirectory stackSpec gTemplate+ writeStackSpec gOutputDirectory stackSpec gTemplateBody pure $ stackSpecPathFilePath specPath
src/Stackctl/StackSpec.hs view
@@ -8,6 +8,8 @@ , stackSpecCapabilities , stackSpecTags , buildStackSpec+ , TemplateBody+ , templateBodyFromValue , writeStackSpec , readStackSpec , createChangeSet@@ -18,6 +20,7 @@ import qualified CfnFlip import Data.Aeson+import qualified Data.ByteString.Lazy as BSL import Data.List.Extra (nubOrdOn) import qualified Data.Yaml as Yaml import Stackctl.AWS@@ -25,6 +28,7 @@ import Stackctl.Sort import Stackctl.StackSpecPath import Stackctl.StackSpecYaml+import System.FilePath (takeExtension) import UnliftIO.Directory (createDirectoryIfMissing) data StackSpec = StackSpec@@ -65,20 +69,47 @@ buildStackSpec :: FilePath -> StackSpecPath -> StackSpecYaml -> StackSpec buildStackSpec = StackSpec +data TemplateBody+ = TemplateText Text+ | TemplateJson Value++newtype UnexpectedTemplateJson = UnexpectedTemplateJson+ { _unexpectedTemplateJsonExtension :: String+ }+ deriving stock Show++instance Exception UnexpectedTemplateJson where+ displayException (UnexpectedTemplateJson ext) =+ "TemplateJson must be written to .yaml or .json, encountered "+ <> ext+ <> ". To write to an arbitrary path, use TemplateText."++templateBodyFromValue :: Value -> TemplateBody+templateBodyFromValue = \case+ String x -> TemplateText x+ v -> TemplateJson v++writeTemplateBody :: MonadUnliftIO m => FilePath -> TemplateBody -> m ()+writeTemplateBody path body = do+ createDirectoryIfMissing True dir++ case (body, ext) of+ (TemplateText t, _) -> writeFileUtf8 path t+ (TemplateJson v, ".yaml") -> CfnFlip.jsonToYamlFile path v+ (TemplateJson v, ".json") -> writeFileBinary path $ BSL.toStrict $ encode v+ (TemplateJson _, _) -> throwIO $ UnexpectedTemplateJson ext+ where+ dir = takeDirectory path+ ext = takeExtension path+ writeStackSpec :: MonadUnliftIO m => FilePath -- ^ Parent directory -> StackSpec- -> Value -- ^ Template body+ -> TemplateBody -> m () writeStackSpec parent stackSpec@StackSpec {..} templateBody = do- createDirectoryIfMissing True $ takeDirectory templatePath-- case templateBody of- -- Already Yaml- String x -> writeFileUtf8 templatePath x- _ -> CfnFlip.jsonToYamlFile templatePath templateBody-+ writeTemplateBody templatePath templateBody createDirectoryIfMissing True $ takeDirectory specPath liftIO $ Yaml.encodeFile specPath ssSpecBody where
stackctl.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: stackctl-version: 1.1.1.1+version: 1.1.2.0 license: MIT license-file: LICENSE copyright: 2022 Renaissance Learning Inc@@ -74,7 +74,7 @@ -Wno-unsafe -optP-Wno-nonportable-include-path build-depends:- Blammo >=1.1.1.0,+ Blammo >=1.1.1.1, Glob, aeson, aeson-casing,@@ -93,7 +93,6 @@ errors, exceptions, extra,- fast-logger, filepath, lens, lens-aeson,