hercules-ci-cli 0.2.4 → 0.2.5
raw patch · 5 files changed
+60/−6 lines, 5 files
Files
- CHANGELOG.md +8/−0
- hercules-ci-cli.cabal +1/−1
- src/Hercules/CLI/JSON.hs +33/−2
- src/Hercules/CLI/Login.hs +1/−1
- src/Hercules/CLI/Secret.hs +17/−2
CHANGELOG.md view
@@ -5,6 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## 0.2.5 - 2022-03-07++### Added++ - `hci secret echo`: write secret data on stdout. A side-effect free version of `hci secret add`.++ - `hci secret echo/add --password`: another safe way of inputting a secret field+ ## 0.2.4 - 2021-11-17 ### Added
hercules-ci-cli.cabal view
@@ -1,7 +1,7 @@ cabal-version: 1.12 name: hercules-ci-cli-version: 0.2.4+version: 0.2.5 synopsis: The hci command for working with Hercules CI homepage: https://docs.hercules-ci.com bug-reports: https://github.com/hercules-ci/hercules-ci-agent/issues
src/Hercules/CLI/JSON.hs view
@@ -17,6 +17,7 @@ import Protolude import System.AtomicWrite.Writer.ByteString (atomicWriteFile) import System.Environment (getEnvironment, lookupEnv)+import System.IO (hGetEcho, hSetEcho) mergePaths :: [([Text], Value)] -> Either Text Value mergePaths = mergeLeafPaths [] . toLeafPaths@@ -48,7 +49,7 @@ toLeafPaths [(path ++ [subpath], subitem)] _ -> [(path, item)] -options :: Optparse.Parser (IO Value)+options :: Optparse.Parser (Maybe Text -> IO Value) options = do strings <- many@@ -61,6 +62,15 @@ <> Optparse.metavar2 "STRING" ) )+ stringPasswords <-+ many+ ( Optparse.option+ Optparse.str+ ( Optparse.long "password"+ <> Optparse.help "Define a string at dot-separated PATH in the secret data using password input on stdin"+ <> Optparse.metavar "PATH"+ )+ ) jsons <- many ( Optparse.biOption@@ -120,7 +130,7 @@ <> Optparse.completer2 envCompleter ) )- pure do+ pure \maybeSecretName -> do fileStrings <- for stringFiles readFileWithKey fileJsons <- for jsonFiles readJsonFileWithKey envStrings <- for stringEnvs readEnvWithKey@@ -133,11 +143,13 @@ Left e -> throwIO $ UserException $ "Value for key " <> key <> " is not valid JSON: " <> show e Right r -> pure (key, r :: Value) )+ passwordStrings <- for stringPasswords (askPasswordWithKey maybeSecretName) let items = map (first split) $ (fmap String <$> strings) <> (fmap String <$> envStrings) <> (fmap String <$> fileStrings)+ <> (fmap String <$> passwordStrings) <> validJsons <> envJsons <> fileJsons@@ -194,6 +206,9 @@ writeJsonFile filePath v = atomicWriteFile filePath $ BL.toStrict $ encodePretty' prettyConf v +printJson :: ToJSON a => a -> IO ()+printJson = BS.putStr . BL.toStrict . (<> "\n") . encodePretty' prettyConf+ prettyConf :: Data.Aeson.Encode.Pretty.Config prettyConf = defConfig@@ -202,3 +217,19 @@ -- UNIX convention confTrailingNewline = True }++askPasswordWithKey :: Maybe Text -> Text -> IO (Text, Text)+askPasswordWithKey secretNameMaybe key = do+ case secretNameMaybe of+ Nothing -> putErrText $ "Enter value for " <> key <> ":"+ Just secretName -> putErrText $ "Enter value for " <> key <> " in secret " <> secretName <> ":"+ s <- T.strip <$> withEcho False getLine+ putErrText ""+ case s of+ "" -> throwIO $ UserException $ "Value must not be empty for " <> show key+ _ -> pure (key, s)++withEcho :: Bool -> IO a -> IO a+withEcho echo action = do+ old <- hGetEcho stdin+ bracket_ (hSetEcho stdin echo) (hSetEcho stdin old) action
src/Hercules/CLI/Login.hs view
@@ -34,7 +34,7 @@ } putErrText "Please confirm your login at " putErrText $ " " <> CLIAuthorizationRequestCreateResponse.browserURL r- putErrText "Waiting for confirmation..."+ putErrText "Waiting for you to confirm using the link..." let tmpTok = CLIAuthorizationRequestCreateResponse.temporaryCLIToken r -- TODO do something pretty with 404 pollLoop = do
src/Hercules/CLI/Secret.hs view
@@ -4,6 +4,7 @@ module Hercules.CLI.Secret where import qualified Data.Aeson as A+import qualified Data.Aeson.Types as A import qualified Data.Map as M import qualified Data.Text as T import Hercules.CLI.Common (exitMsg, runAuthenticated)@@ -15,7 +16,7 @@ import System.FilePath (takeDirectory, (</>)) import UnliftIO.Directory (XdgDirectory (XdgConfig), createDirectoryIfMissing, doesFileExist, getXdgDirectory) -commandParser, initLocal, add :: Optparse.Parser (IO ())+commandParser, initLocal, add, echo :: Optparse.Parser (IO ()) commandParser = subparser ( mkCommand@@ -26,6 +27,10 @@ "add" (Optparse.progDesc "Insert a secret into the local secrets file") add+ <> mkCommand+ "echo"+ (Optparse.progDesc "Assemble a secret for stdout")+ echo ) initLocal = do projectOptionMaybe <- optional projectOption@@ -44,7 +49,7 @@ mkJson <- JSON.options projectOptionMaybe <- optional projectOption pure $ runAuthenticated do- secretData <- liftIO mkJson+ secretData <- liftIO (mkJson (Just secretName)) projectPath <- getProjectPath projectOptionMaybe secretsFilePath <- liftIO $ getSecretsFilePath projectPath liftIO $@@ -60,6 +65,16 @@ liftIO $ writeJsonFile secretsFilePath secrets' putErrText $ "hci: successfully wrote " <> secretName <> " to " <> toS secretsFilePath putErrText "NOTE: Remember to synchronize this file with your agents!"+echo = do+ mkJson <- JSON.options+ pure do+ secretDataValue <- liftIO (mkJson Nothing)+ secretData <- case A.parse A.parseJSON secretDataValue of+ A.Error e -> exitMsg $ "The secret data must be an object. " <> toS e+ A.Success a -> pure a+ let secret =+ A.object ["kind" A..= A.String "Secret", "data" A..= (secretData :: Map Text A.Value)]+ liftIO $ JSON.printJson secret getSecretsFilePath :: ProjectPath -> IO FilePath getSecretsFilePath projectPath = do