packages feed

stackctl 1.3.0.2 → 1.4.0.0

raw patch · 6 files changed

+110/−41 lines, 6 files

Files

CHANGELOG.md view
@@ -1,4 +1,10 @@-## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.3.0.2...main)+## [_Unreleased_](https://github.com/freckle/stackctl/compare/v1.4.0.0...main)++## [v1.4.0.0](https://github.com/freckle/stackctl/compare/v1.3.0.2...v1.4.0.0)++- Add `awsAssumeRole` for running an action as an assumed role+- Refactor `Generate` interface to better support generating stacks with+  pre-existing templates  ## [v1.3.0.2](https://github.com/freckle/stackctl/compare/v1.3.0.1...v1.3.0.2) 
src/Stackctl/AWS/Core.hs view
@@ -6,6 +6,7 @@   , awsSend   , awsPaginate   , awsAwait+  , awsAssumeRole    -- * Modifiers on 'AwsEnv'   , awsWithin@@ -25,6 +26,8 @@  import Amazonka hiding (LogLevel(..)) import qualified Amazonka as AWS+import Amazonka.Auth.Keys (fromSession)+import Amazonka.STS.AssumeRole import Conduit (ConduitM) import Control.Monad.Logger (defaultLoc, toLogStr) import Control.Monad.Trans.Resource (MonadResource)@@ -103,6 +106,30 @@ awsAwait w req = do   AwsEnv env <- view awsEnvL   await env w req++awsAssumeRole+  :: (MonadResource m, MonadReader env m, HasAwsEnv env)+  => Text+  -- ^ Role ARN+  -> Text+  -- ^ Session name+  -> m a+  -- ^ Action to run as the assumed role+  -> m a+awsAssumeRole role sessionName f = do+  let req = newAssumeRole role sessionName++  assumeEnv <- awsSimple "sts:AssumeRole" req $ \resp -> do+    creds <- resp ^. assumeRoleResponse_credentials+    token <- creds ^. authSessionToken++    let+      accessKeyId = creds ^. authAccessKeyId+      secretAccessKey = creds ^. authSecretAccessKey++    pure $ fromSession accessKeyId secretAccessKey token++  local (awsEnvL . unL %~ assumeEnv) f  awsWithin :: (MonadReader env m, HasAwsEnv env) => Region -> m a -> m a awsWithin r = local $ over (awsEnvL . unL) (within r)
src/Stackctl/Spec/Capture.hs view
@@ -10,7 +10,7 @@ import Stackctl.AWS import Stackctl.AWS.Scope import Stackctl.Config (HasConfig)-import Stackctl.DirectoryOption (HasDirectoryOption(..), unDirectoryOption)+import Stackctl.DirectoryOption (HasDirectoryOption) import Stackctl.Spec.Generate import Stackctl.StackSpec import System.FilePath.Glob@@ -74,26 +74,29 @@   => CaptureOptions   -> m () runCapture CaptureOptions {..} = do-  dir <- unDirectoryOption <$> view directoryOptionL-   let     setScopeName scope =       maybe scope (\name -> scope { awsAccountName = name }) scoAccountName      generate' stack template path templatePath = do+      let+        stackName = StackName $ stack ^. stack_stackName+        templateBody = templateBodyFromValue template+       void $ local (awsScopeL %~ setScopeName) $ generate Generate-        { gOutputDirectory = dir-        , gTemplatePath = templatePath-        , gTemplateFormat = scoTemplateFormat-        , gStackPath = path-        , gStackName = StackName $ stack ^. stack_stackName-        , gDescription = stackDescription stack+        { gDescription = stackDescription stack         , gDepends = scoDepends         , gActions = Nothing         , gParameters = parameters stack         , gCapabilities = capabilities stack         , gTags = tags stack-        , gTemplateBody = templateBodyFromValue template+        , 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         }    results <- awsCloudFormationGetStackNamesMatching scoStackName
src/Stackctl/Spec/Generate.hs view
@@ -1,5 +1,7 @@ module Stackctl.Spec.Generate   ( Generate(..)+  , GenerateSpec(..)+  , GenerateTemplate(..)   , generate   , TemplateFormat(..)   ) where@@ -10,29 +12,38 @@ import Stackctl.AWS import Stackctl.AWS.Scope import Stackctl.Config (HasConfig)+import Stackctl.DirectoryOption import Stackctl.Spec.Discover (buildSpecPath) import Stackctl.StackSpec import Stackctl.StackSpecPath import Stackctl.StackSpecYaml  data Generate = Generate-  { gOutputDirectory :: FilePath-  , gTemplatePath :: Maybe FilePath-  -- ^ 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@-  , gStackName :: StackName-  , gDescription :: Maybe StackDescription+  { gDescription :: Maybe StackDescription   , gDepends :: Maybe [StackName]   , gActions :: Maybe [Action]   , gParameters :: Maybe [Parameter]   , gCapabilities :: Maybe [Capability]   , gTags :: Maybe [Tag]-  , gTemplateBody :: TemplateBody+  , gSpec :: GenerateSpec+  , gTemplate :: GenerateTemplate+  , gOverwrite :: Bool   } +data GenerateSpec+  = GenerateSpec StackName+  -- ^ Generate at an inferred name+  | GenerateSpecTo StackName FilePath+  -- ^ Generate to a given path++data GenerateTemplate+  = GenerateTemplate TemplateBody TemplateFormat+  -- ^ Generate at an inferred name+  | GenerateTemplateTo TemplateBody FilePath+  -- ^ Generate to the given path+  | UseExistingTemplate FilePath+  -- ^ Assume template exists+ data TemplateFormat   = TemplateFormatYaml   | TemplateFormatJson@@ -44,23 +55,26 @@      , MonadReader env m      , HasConfig env      , HasAwsScope env+     , HasDirectoryOption env      )   => Generate   -> m FilePath generate Generate {..} = do   let-    defaultStackPath = unpack (unStackName gStackName) <.> "yaml"-    defaultTemplatePath =-      unpack (unStackName gStackName) <.> case gTemplateFormat of-        TemplateFormatYaml -> "yaml"-        TemplateFormatJson -> "json"--    stackPath = fromMaybe defaultStackPath gStackPath+    (stackName, stackPath) = case gSpec of+      GenerateSpec name -> (name, unpack (unStackName name) <> ".yaml")+      GenerateSpecTo name path -> (name, path) -  specPath <- buildSpecPath gStackName stackPath+    (mTemplateBody, templatePath) = case gTemplate of+      GenerateTemplate body format ->+        ( Just body+        , case format of+          TemplateFormatYaml -> unpack (unStackName stackName) <> ".yaml"+          TemplateFormatJson -> unpack (unStackName stackName) <> ".json"+        )+      GenerateTemplateTo body path -> (Just body, path)+      UseExistingTemplate path -> (Nothing, path) -  let-    templatePath = fromMaybe defaultTemplatePath gTemplatePath     specYaml = StackSpecYaml       { ssyDescription = gDescription       , ssyTemplate = templatePath@@ -71,9 +85,10 @@       , ssyTags = tagsYaml . map TagYaml <$> gTags       } -  stackSpec <- buildStackSpec gOutputDirectory specPath specYaml+  dir <- view $ directoryOptionL . to unDirectoryOption+  specPath <- buildSpecPath stackName stackPath+  stackSpec <- buildStackSpec dir specPath specYaml    withThreadContext ["stackName" .= stackSpecStackName stackSpec] $ do-    logInfo "Generating specification"-    writeStackSpec stackSpec gTemplateBody+    writeStackSpec gOverwrite stackSpec mTemplateBody     pure $ stackSpecPathFilePath specPath
src/Stackctl/StackSpec.hs view
@@ -34,7 +34,7 @@ import Stackctl.StackSpecYaml import qualified System.FilePath as FilePath import System.FilePath (takeExtension)-import UnliftIO.Directory (createDirectoryIfMissing)+import UnliftIO.Directory (createDirectoryIfMissing, doesFileExist)  data StackSpec = StackSpec   { ssSpecRoot :: FilePath@@ -135,11 +135,29 @@   dir = takeDirectory path   ext = takeExtension path -writeStackSpec :: MonadUnliftIO m => StackSpec -> TemplateBody -> m ()-writeStackSpec stackSpec templateBody = do-  writeTemplateBody templatePath templateBody-  createDirectoryIfMissing True $ takeDirectory specPath-  liftIO $ Yaml.encodeFile specPath $ stackSpecSpecBody stackSpec+writeStackSpec+  :: (MonadUnliftIO m, MonadLogger m)+  => Bool+  -> StackSpec+  -> Maybe TemplateBody+  -> m ()+writeStackSpec overwrite stackSpec mTemplateBody = do+  for_ mTemplateBody $ \templateBody -> do+    logInfo $ "Writing template" :# ["path" .= templatePath]+    writeTemplateBody templatePath templateBody++  exists <- doesFileExist specPath++  if exists && not overwrite+    then do+      let+        reason :: Text+        reason = "file exists and overwrite not set"+      logInfo $ "Skipping" :# ["path" .= specPath, "reason" .= reason]+    else do+      logInfo $ "Writing specification" :# ["path" .= specPath]+      createDirectoryIfMissing True $ takeDirectory specPath+      liftIO $ Yaml.encodeFile specPath $ stackSpecSpecBody stackSpec  where   templatePath = unStackTemplate $ stackSpecTemplate stackSpec   specPath =
stackctl.cabal view
@@ -1,6 +1,6 @@ cabal-version:   1.18 name:            stackctl-version:         1.3.0.2+version:         1.4.0.0 license:         MIT license-file:    LICENSE copyright:       2022 Renaissance Learning Inc