stackctl 1.6.1.2 → 1.7.0.0
raw patch · 6 files changed
+128/−91 lines, 6 files
Files
- CHANGELOG.md +10/−1
- src/Stackctl/Spec/Capture.hs +28/−16
- src/Stackctl/Spec/Generate.hs +10/−30
- src/Stackctl/StackSpecYaml.hs +79/−21
- stackctl.cabal +1/−1
- test/Stackctl/StackSpecYamlSpec.hs +0/−22
CHANGELOG.md view
@@ -1,4 +1,13 @@-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.6.1.2...main)+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.7.0.0...main)++## [v1.7.0.0](https://github.com/freckle/stackctl/compare/v1.6.1.2...v1.7.0.0)++- Retain numeric parameter types, and add boolean parameter handling, in our own+ yaml generation++ This required changing the `Generate` to use `ParametersYaml` instead of+ `[Parameter]`, and ultimately led us to removing it, hence the major version+ bump. ## [v1.6.1.2](https://github.com/freckle/stackctl/compare/v1.6.1.1...v1.6.1.2)
src/Stackctl/Spec/Capture.hs view
@@ -13,6 +13,13 @@ import Stackctl.DirectoryOption (HasDirectoryOption) import Stackctl.Spec.Generate import Stackctl.StackSpec+import Stackctl.StackSpecYaml+ ( StackSpecYaml (..)+ , TagYaml (..)+ , parameterYaml+ , parametersYaml+ , tagsYaml+ ) import System.FilePath.Glob data CaptureOptions = CaptureOptions@@ -91,7 +98,7 @@ setScopeName scope = maybe scope (\name -> scope {awsAccountName = name}) scoAccountName - generate' stack template path templatePath = do+ generate' stack template mPath mTemplatePath = do let stackName = StackName $ stack ^. stack_stackName templateBody = templateBodyFromValue template@@ -99,21 +106,26 @@ void $ local (awsScopeL %~ setScopeName) $ generate- Generate- { gDescription = stackDescription stack- , gDepends = scoDepends- , gActions = Nothing- , gParameters = parameters stack- , gCapabilities = capabilities stack- , gTags = tags stack- , gSpec = case path of- Nothing -> GenerateSpec stackName- Just sp -> GenerateSpecTo stackName sp- , gTemplate = case templatePath of- Nothing -> GenerateTemplate templateBody scoTemplateFormat- Just tp -> GenerateTemplateTo templateBody tp- , gOverwrite = False- }+ False+ ( case mPath of+ Nothing -> GenerateSpec stackName+ Just sp -> GenerateSpecTo stackName sp+ )+ ( case mTemplatePath of+ Nothing -> GenerateTemplate templateBody scoTemplateFormat+ Just tp -> GenerateTemplateTo templateBody tp+ )+ ( \templatePath ->+ StackSpecYaml+ { ssyDescription = stackDescription stack+ , ssyTemplate = templatePath+ , ssyDepends = scoDepends+ , ssyActions = Nothing+ , ssyParameters = parametersYaml . mapMaybe parameterYaml <$> parameters stack+ , ssyCapabilities = capabilities stack+ , ssyTags = tagsYaml . map TagYaml <$> tags stack+ }+ ) results <- awsCloudFormationGetStackNamesMatching scoStackName
src/Stackctl/Spec/Generate.hs view
@@ -1,6 +1,5 @@ module Stackctl.Spec.Generate- ( Generate (..)- , GenerateSpec (..)+ ( GenerateSpec (..) , GenerateTemplate (..) , generate , TemplateFormat (..)@@ -10,7 +9,6 @@ import Stackctl.AWS import Stackctl.AWS.Scope-import Stackctl.Action import Stackctl.Config (HasConfig) import Stackctl.DirectoryOption import Stackctl.Spec.Discover (buildSpecPath)@@ -18,18 +16,6 @@ import Stackctl.StackSpecPath import Stackctl.StackSpecYaml -data Generate = Generate- { gDescription :: Maybe StackDescription- , gDepends :: Maybe [StackName]- , gActions :: Maybe [Action]- , gParameters :: Maybe [Parameter]- , gCapabilities :: Maybe [Capability]- , gTags :: Maybe [Tag]- , gSpec :: GenerateSpec- , gTemplate :: GenerateTemplate- , gOverwrite :: Bool- }- data GenerateSpec = -- | Generate at an inferred name GenerateSpec StackName@@ -57,15 +43,18 @@ , HasAwsScope env , HasDirectoryOption env )- => Generate+ => Bool+ -> GenerateSpec+ -> GenerateTemplate+ -> (FilePath -> StackSpecYaml) -> m FilePath-generate Generate {..} = do+generate overwrite spec template toStackSpecYaml = do let- (stackName, stackPath) = case gSpec of+ (stackName, stackPath) = case spec of GenerateSpec name -> (name, unpack (unStackName name) <> ".yaml") GenerateSpecTo name path -> (name, path) - (mTemplateBody, templatePath) = case gTemplate of+ (mTemplateBody, templatePath) = case template of GenerateTemplate body format -> ( Just body , case format of@@ -75,21 +64,12 @@ GenerateTemplateTo body path -> (Just body, path) UseExistingTemplate path -> (Nothing, path) - specYaml =- StackSpecYaml- { ssyDescription = gDescription- , ssyTemplate = templatePath- , ssyDepends = gDepends- , ssyActions = gActions- , ssyParameters = parametersYaml . mapMaybe parameterYaml <$> gParameters- , ssyCapabilities = gCapabilities- , ssyTags = tagsYaml . map TagYaml <$> gTags- }+ specYaml = toStackSpecYaml templatePath dir <- view $ directoryOptionL . to unDirectoryOption specPath <- buildSpecPath stackName stackPath stackSpec <- buildStackSpec dir specPath specYaml withThreadContext ["stackName" .= stackSpecStackName stackSpec] $ do- writeStackSpec gOverwrite stackSpec mTemplateBody+ writeStackSpec overwrite stackSpec mTemplateBody pure $ stackSpecPathFilePath specPath
src/Stackctl/StackSpecYaml.hs view
@@ -4,18 +4,24 @@ -- Template: <path> -- -- Depends:--- - <string>+-- - <string> -- -- Parameters:--- - ParameterKey: <string>--- ParameterValue: <string>+-- - ParameterKey: <string>+-- ParameterValue: <string|number|boolean> --+-- # Or+-- <key>: <string|number|boolean>+-- -- Capabilities:--- - <capability>+-- - <capability> -- -- Tags:--- - Key: <string>--- Value: <string>+-- - Key: <string>+-- Value: <string>+--+-- # Or+-- <key>: <string> -- @ module Stackctl.StackSpecYaml ( StackSpecYaml (..)@@ -24,7 +30,11 @@ , unParametersYaml , ParameterYaml , parameterYaml+ , mkParameterYaml , unParameterYaml+ , ParameterValue+ , parameterValueFromText+ , parameterValueTemplate , TagsYaml , tagsYaml , unTagsYaml@@ -39,6 +49,7 @@ import qualified Data.Aeson.KeyMap as KeyMap import Data.Aeson.Types (typeMismatch) import qualified Data.HashMap.Strict as HashMap+import Data.List.Extra (dropSuffix) import Data.Monoid (Last (..)) import qualified Data.Text as T import Stackctl.AWS@@ -117,31 +128,81 @@ parameterYamlPair :: KeyValue kv => ParameterYaml -> kv parameterYamlPair ParameterYaml {..} = pyKey .= pyValue -mkParameterYaml :: Text -> Maybe ParameterValue -> ParameterYaml-mkParameterYaml k = ParameterYaml (Key.fromText k) . Last- parameterYaml :: Parameter -> Maybe ParameterYaml parameterYaml p = do k <- p ^. parameter_parameterKey let mv = p ^. parameter_parameterValue- pure $ mkParameterYaml k $ ParameterValue <$> mv+ pure $ mkParameterYaml k $ parameterValueFromText <$> mv +mkParameterYaml :: Text -> Maybe ParameterValue -> ParameterYaml+mkParameterYaml k = ParameterYaml (Key.fromText k) . Last+ unParameterYaml :: ParameterYaml -> Parameter unParameterYaml (ParameterYaml k v) =- makeParameter (Key.toText k) $ unParameterValue <$> getLast v+ makeParameter (Key.toText k) $ parameterValueToText <$> getLast v -newtype ParameterValue = ParameterValue- { unParameterValue :: Text- }+data ParameterValue+ = StringParameter Text+ | NumberParameter Double+ | -- | Encodes as String True|False+ BooleanParameter Bool deriving stock (Eq, Show)- deriving newtype (Semigroup, ToJSON) instance FromJSON ParameterValue where parseJSON = \case- String x -> pure $ ParameterValue x- Number x -> pure $ ParameterValue $ dropSuffix ".0" $ pack $ show x- x -> fail $ "Expected String or Number, got: " <> show x+ String t -> pure $ StringParameter t+ Number s -> pure $ NumberParameter $ realToFrac s+ Bool b -> pure $ BooleanParameter b+ x -> typeMismatch "String, Number or Bool" x +instance ToJSON ParameterValue where+ toJSON = \case+ StringParameter t -> toJSON t+ NumberParameter d -> toJSON d+ BooleanParameter b -> toJSON $ pack $ show b+ toEncoding = \case+ StringParameter t -> toEncoding t+ NumberParameter d -> toEncoding d+ BooleanParameter b -> toEncoding $ pack $ show b++parameterValueToText :: ParameterValue -> Text+parameterValueToText = \case+ StringParameter t -> t+ NumberParameter d -> pack $ dropSuffix ".0" $ show d+ BooleanParameter b -> pack $ show b++parameterValueFromText :: Text -> ParameterValue+parameterValueFromText = \case+ v | T.toLower v == "true" -> BooleanParameter True+ v | T.toLower v == "false" -> BooleanParameter False+ v | Just d <- readMaybe (unpack v) -> NumberParameter d+ v -> StringParameter v++-- | For use as the value in a @Parameters@ object of a CFN Template+parameterValueTemplate :: ParameterValue -> Value+parameterValueTemplate v =+ object+ $ catMaybes+ [ Just $ "Type" .= parameterValueType v+ , Just $ "Default" .= parameterValueDefault v+ , ("AllowedValues" .=) <$> parameterValueAllowedValues v+ ]++parameterValueType :: ParameterValue -> Text+parameterValueType = \case+ StringParameter {} -> "String"+ NumberParameter {} -> "Number"+ BooleanParameter {} -> "String"++parameterValueDefault :: ParameterValue -> Value+parameterValueDefault = toJSON++parameterValueAllowedValues :: ParameterValue -> Maybe Value+parameterValueAllowedValues = \case+ StringParameter {} -> Nothing+ NumberParameter {} -> Nothing+ BooleanParameter {} -> Just $ toJSON [String "True", String "False"]+ newtype TagsYaml = TagsYaml { unTagsYaml :: [TagYaml] }@@ -194,6 +255,3 @@ tagYamlPair :: KeyValue kv => TagYaml -> kv tagYamlPair (TagYaml t) = Key.fromText (t ^. tag_key) .= (t ^. tag_value)--dropSuffix :: Text -> Text -> Text-dropSuffix suffix t = fromMaybe t $ T.stripSuffix suffix t
stackctl.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: stackctl-version: 1.6.1.2+version: 1.7.0.0 license: MIT license-file: LICENSE copyright: 2022 Renaissance Learning Inc
test/Stackctl/StackSpecYamlSpec.hs view
@@ -80,28 +80,6 @@ param ^. parameter_parameterKey `shouldBe` Just "Pie" param ^. parameter_parameterValue `shouldBe` Just "3.14" - it "has informative errors" $ do- let Left ex =- Yaml.decodeEither' @StackSpecYaml- $ mconcat- [ "Template: foo.yaml\n"- , "Parameters:\n"- , " - ParameterKey: Norway\n"- , " ParameterValue: no\n"- ]-- show ex- `shouldBe` "AesonException \"Error in $.Parameters[0].ParameterValue: Expected String or Number, got: Bool False\""-- it "has informative errors in Object form" $ do- let Left ex =- Yaml.decodeEither' @StackSpecYaml- $ mconcat- ["Template: foo.yaml\n", "Parameters:\n", " Norway: no\n"]-- show ex- `shouldBe` "AesonException \"Error in $.Parameters.Norway: Expected String or Number, got: Bool False\""- it "handles null Value" $ do StackSpecYaml {..} <- Yaml.decodeThrow