Skip to content

Commit

Permalink
Support Inplace edit (#33)
Browse files Browse the repository at this point in the history
* Add a flag for in-place updates to confcrypt files
  • Loading branch information
ChrisCoffey authored Jul 1, 2019
1 parent dbff924 commit cceb0b3
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 33 deletions.
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,20 @@ brew cask install confcrypt
`confcrypt rsa read --key <filename> <filename>`
This command reads in the provided file, decrypts the configuration variables using the provided key, then prints them to stdout. This allows you to pipe the results to other utilities. Returns 0 on success.
- add a parameter
`confcrypt rsa add --key <filename> --name <String> --type <SchemaType> --vaue <String> <filename>
Adds a new confguration parameter to the file. `--name` and `--value` are required, while `--type` is optional. If `--type` is provided, the schema record will be added immediately before the config variable. In total this adds two lines to the file. Returns 0 on sccess.
`confcrypt rsa add --key <filename> --name <String> --type <SchemaType> --vaue <String> --in-place <filename>
Adds a new confguration parameter to the file. `--name` and `--value` are required, while `--type` and `--in-place`are optional.
If `--type` is provided, the schema record will be added immediately before the config variable.
`--in-place` toggles whether to overwrite the provided file or emit the results to stdout.
In total this adds two lines to the file. Returns 0 on sccess.
- remove a parameter
`confcrypt delete --name <filename>`
`confcrypt delete --name <String> --in-place <filename>`
Removes an existing config parameter & associated schema. Returns 0 on success or 1 if the parameter is not found in the file.
`--in-place` toggles whether to overwrite the provided file or emit the results to stdout.
- edit a parameter in-place
`confcrypt rsa edit --key <filename> --name <String> --value <String> --type <SchemaType> <filename>`
Modifies an existing configuration parameter in place, leaving all other lines unchanged. While this isn't how it's actually implemented, this operation is equivalent to piping `confcrypt read` to a new file, editing the parameter, then reencrypting it.
`confcrypt rsa edit --key <filename> --name <String> --value <String> --type <SchemaType> --in-place <filename>`
Modifies an existing configuration parameter in place, leaving all other lines unchanged.
While this isn't how it's actually implemented, this operation is equivalent to piping `confcrypt read` to a new file, editing the parameter, then reencrypting it.
`--in-place` toggles whether to overwrite the provided file or emit the results to stdout.
- validate a config
`confcrypt rsa validate --key <filename> <filename>`
Checks that each config parameter matches the type of its schema. All errors are accumulated and returned at the end, with a response code equal to the number of failures.
Expand Down
31 changes: 23 additions & 8 deletions app/ConfCrypt/CLI/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ module ConfCrypt.CLI.API (
KeyProvider(..),
AnyCommand(..),
ParsedKey(..),
InPlace(..),
cliParser
) where

import ConfCrypt.Types (SchemaType(..))
import ConfCrypt.Commands (GetConfCrypt(..), AddConfCrypt(..), EditConfCrypt(..), DeleteConfCrypt(..), ReadConfCrypt(..))

import Options.Applicative
(ParserInfo, Parser, progDesc, command, fullDesc, long, flag,
(ParserInfo, Parser, progDesc, command, fullDesc, long, switch,
metavar, maybeReader, help, strOption, short, info, header, footer,
strArgument, hsubparser, helper, (<**>), value, option, auto,
ReadM)
Expand All @@ -24,13 +25,17 @@ data KeyAndConf = KeyAndConf {key :: ParsedKey, provider :: KeyProvider, conf ::
deriving (Eq, Show)
newtype Conf = Conf FilePath
deriving (Eq, Show)
data InPlace
= Overwrite
| StdOut
deriving (Eq, Show)

data AnyCommand
= RC KeyAndConf ReadConfCrypt
| GC KeyAndConf GetConfCrypt
| AC KeyAndConf AddConfCrypt
| EC KeyAndConf EditConfCrypt
| DC Conf DeleteConfCrypt
| AC KeyAndConf AddConfCrypt InPlace
| EC KeyAndConf EditConfCrypt InPlace
| DC Conf DeleteConfCrypt InPlace
| VC KeyAndConf
| VER T.Text
| NC
Expand Down Expand Up @@ -110,19 +115,19 @@ vers = info (pure . VER . T.pack $ showVersion version)
add ::
KeyProvider
-> ParserInfo AnyCommand
add provider = info ( AC <$> keyAndConf provider True <*> (AddConfCrypt <$> onlyName <*> onlyValue <*> onlyType))
add provider = info ( AC <$> keyAndConf provider True <*> (AddConfCrypt <$> onlyName <*> onlyValue <*> onlyType) <*> inPlace)
(progDesc "Add a new parameter to the configuration file. New parameters are added to the end of the file." <>
fullDesc)

edit ::
KeyProvider
-> ParserInfo AnyCommand
edit provider = info ( EC <$> keyAndConf provider True <*> (EditConfCrypt <$> onlyName <*> onlyValue <*> onlyType))
edit provider = info ( EC <$> keyAndConf provider True <*> (EditConfCrypt <$> onlyName <*> onlyValue <*> onlyType) <*> inPlace)
(progDesc "Modify an existing parameter in-place. This should preserve a clean diff." <>
fullDesc)

delete :: ParserInfo AnyCommand
delete = info ( DC <$> getConf <*> (DeleteConfCrypt <$> onlyName))
delete = info ( DC <$> getConf <*> (DeleteConfCrypt <$> onlyName) <*> inPlace)
(progDesc "Removes an existing parameter from the configuration." <>
fullDesc)

Expand Down Expand Up @@ -186,6 +191,16 @@ getConf = Conf <$> onlyConf
onlyConf :: Parser FilePath
onlyConf = strArgument (metavar "CONFIG_FILE")

inPlace :: Parser InPlace
inPlace = toInPlace <$> switch (
long "in-place" <>
short 'p' <>
help "Allows overwriting the existing confcrypt file rather than emitting the results to stdOut."
)
where
toInPlace True = Overwrite
toInPlace False = StdOut

onlyName :: Parser T.Text
onlyName = strOption (
long "name" <>
Expand Down Expand Up @@ -227,4 +242,4 @@ onlyFormat = option maybeOptReader (
)

maybeOptReader :: ReadM (Maybe T.Text)
maybeOptReader = maybeReader $ Just. Just . T.pack
maybeOptReader = maybeReader $ Just. Just . T.pack
37 changes: 26 additions & 11 deletions app/ConfCrypt/CLI/Engine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import ConfCrypt.Parser (parseConfCrypt)
import ConfCrypt.Encryption
import ConfCrypt.Default (emptyConfCryptFile)
import ConfCrypt.Providers.AWS (AWSCtx, loadAwsCtx, KMSKeyId(..))
import ConfCrypt.CLI.API (AnyCommand(..), Conf(..), KeyAndConf(..), KeyProvider(..), ParsedKey(..))
import ConfCrypt.CLI.API (AnyCommand(..), Conf(..), KeyAndConf(..),
KeyProvider(..), ParsedKey(..), InPlace(..))

import Conduit (ResourceT, runResourceT)
import Control.Exception (catch)
Expand All @@ -29,17 +30,22 @@ import System.Exit (exitSuccess, exitFailure, exitWith, ExitCode(..))
-- This wraps and drives the core ConfCrypt library.
run ::
AnyCommand -- ^ Command line arguments
-> IO [T.Text]
-> IO ([T.Text], Maybe FilePath)
run NC = do
res <- runConfCrypt emptyConfCryptFile $ evaluate NewConfCrypt
either (\e -> print e *> exitFailure)
pure
(pure . (,Nothing))
res
run (VER version) = pure [version]
run (VER version) = pure ([version], Nothing)
run parsedArguments = do
let filePath = confFilePath parsedArguments
lines <- T.readFile filePath
configParsingResults <- parseConfCrypt filePath <$> pure lines
-- This allows threading the file path into the return, for use in
-- in-place edits
let overwritePath = if overwriteExisting parsedArguments
then Just filePath
else Nothing
case configParsingResults of
--TODO print errors to stdErr
Left err -> print err *> exitFailure
Expand All @@ -55,15 +61,17 @@ run parsedArguments = do
runConfCrypt parsedConfiguration $ runWithDecrypt key provider ValidateConfCrypt

-- Requires Encryption
AC KeyAndConf {key, provider} cmd ->
AC KeyAndConf {key, provider} cmd _ ->
runConfCrypt parsedConfiguration $ runWithEncrypt key provider cmd
EC KeyAndConf {key, provider} cmd ->
EC KeyAndConf {key, provider} cmd _ ->
runConfCrypt parsedConfiguration $ runWithEncrypt key provider cmd

-- Doesn't care about encryption
DC _ cmd ->
DC _ cmd _ ->
runConfCrypt parsedConfiguration $ evaluate cmd
either (\e -> print e *> exitFailure) pure result
either (\e -> print e *> exitFailure)
(pure . (, overwritePath))
result
where

-- Inject an encryption context into the currently loaded environment. This varies dependng
Expand Down Expand Up @@ -110,6 +118,13 @@ confFilePath :: AnyCommand -> FilePath
confFilePath (RC KeyAndConf {conf} _) = conf
confFilePath (GC KeyAndConf {conf} _) = conf
confFilePath (VC KeyAndConf {conf}) = conf
confFilePath (AC KeyAndConf {conf} _) = conf
confFilePath (EC KeyAndConf {conf} _) = conf
confFilePath (DC (Conf conf) _) = conf
confFilePath (AC KeyAndConf {conf} _ _) = conf
confFilePath (EC KeyAndConf {conf} _ _) = conf
confFilePath (DC (Conf conf) _ _) = conf

overwriteExisting :: AnyCommand -> Bool
overwriteExisting (AC _ _ Overwrite) = True
overwriteExisting (EC _ _ Overwrite) = True
overwriteExisting (DC _ _ Overwrite) = True
overwriteExisting _ = False

8 changes: 6 additions & 2 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import ConfCrypt.CLI.API (cliParser)
import System.Environment (getArgs)
import Data.Foldable (traverse_)
import Data.Text (Text, intercalate, unpack)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Options.Applicative (customExecParser, ParserPrefs, prefs, showHelpOnEmpty)

main :: IO ()
main = do
parsedArguments <- customExecParser (prefs showHelpOnEmpty) cliParser
results <- run parsedArguments
traverse_ (putStrLn . unpack) results
(results, outputPath) <- run parsedArguments
case outputPath of
Nothing -> traverse_ (putStrLn . unpack) results
Just fp -> T.writeFile fp $ T.intercalate "\n" results <> "\n"
-- The ^ `putStrLn` call is important to preserve the trailing newline. Consider
-- moving this into the library to make the code read more clearly.
-- There's no reason that `writeFullContentsToBuffer` can't tag each line with a trailing newline
2 changes: 1 addition & 1 deletion package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: confcrypt
version: 0.2.3.3
version: 0.2.4.0
github: collegevine/confcrypt
license: MIT
author: "Chris Coffey"
Expand Down
12 changes: 6 additions & 6 deletions test/ConfCrypt/CLI/API/Tests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ addCases = testGroup "add" [
let args = ["rsa", "add", "--key", "testKey", "--name", "test", "--type", "String", "--value", "foo", "test.econf"]
res = execParserPure defaultPrefs cliParser args
case res of
Success (AC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (AddConfCrypt "test" "foo" CString)) -> assertBool "can't fail" True
Success (AC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (AddConfCrypt "test" "foo" CString) StdOut) -> assertBool "can't fail" True
Success a -> assertFailure ("Incorrectly parsed: "<> show a)
Failure _ -> assertFailure "Should have parsed an AC"
CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
Expand All @@ -161,7 +161,7 @@ addCases = testGroup "add" [
let args = ["rsa", "add", "-k", "testKey", "-n", "test", "-t", "String", "-v", "foo", "test.econf"]
res = execParserPure defaultPrefs cliParser args
case res of
Success (AC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (AddConfCrypt "test" "foo" CString)) -> assertBool "can't fail" True
Success (AC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (AddConfCrypt "test" "foo" CString) StdOut) -> assertBool "can't fail" True
Success a -> assertFailure ("Incorrectly parsed: "<> show a)
Failure _ -> assertFailure "Should have parsed an AC"
CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
Expand Down Expand Up @@ -213,7 +213,7 @@ editCases = testGroup "edit" [
let args = ["rsa", "edit", "--key", "testKey", "--name", "test", "--type", "String", "--value", "foo", "test.econf"]
res = execParserPure defaultPrefs cliParser args
case res of
Success (EC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (EditConfCrypt "test" "foo" CString)) -> assertBool "can't fail" True
Success (EC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (EditConfCrypt "test" "foo" CString) StdOut) -> assertBool "can't fail" True
Success a -> assertFailure ("Incorrectly parsed: "<> show a)
Failure _ -> assertFailure "Should have parsed an AC"
CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
Expand All @@ -222,7 +222,7 @@ editCases = testGroup "edit" [
let args = ["rsa", "edit", "-k", "testKey", "-n", "test", "-t", "String", "-v", "foo", "test.econf"]
res = execParserPure defaultPrefs cliParser args
case res of
Success (EC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (EditConfCrypt "test" "foo" CString)) -> assertBool "can't fail" True
Success (EC (KeyAndConf (OnDisk "testKey") LocalRSA "test.econf") (EditConfCrypt "test" "foo" CString) StdOut) -> assertBool "can't fail" True
Success a -> assertFailure ("Incorrectly parsed: "<> show a)
Failure _ -> assertFailure "Should have parsed an AC"
CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
Expand Down Expand Up @@ -250,7 +250,7 @@ deleteCases = testGroup "delete" [
let args = ["delete", "--name", "test", "test.econf"]
res = execParserPure defaultPrefs cliParser args
case res of
Success (DC (Conf "test.econf") (DeleteConfCrypt "test")) -> assertBool "can't fail" True
Success (DC (Conf "test.econf") (DeleteConfCrypt "test") StdOut) -> assertBool "can't fail" True
Success a -> assertFailure ("Incorrectly parsed: "<> show a)
Failure _ -> assertFailure "Should have parsed a DC"
CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
Expand All @@ -259,7 +259,7 @@ deleteCases = testGroup "delete" [
let args = ["delete", "-n", "test", "test.econf"]
res = execParserPure defaultPrefs cliParser args
case res of
Success (DC (Conf "test.econf") (DeleteConfCrypt "test")) -> assertBool "can't fail" True
Success (DC (Conf "test.econf") (DeleteConfCrypt "test") StdOut) -> assertBool "can't fail" True
Success a -> assertFailure ("Incorrectly parsed: "<> show a)
Failure _ -> assertFailure "Should have parsed a DC"
CompletionInvoked _ -> assertFailure "Incorrectly triggered completion"
Expand Down

0 comments on commit cceb0b3

Please sign in to comment.