propellor/src/Propellor/PrivData.hs

142 lines
4.3 KiB
Haskell
Raw Normal View History

{-# LANGUAGE PackageImports #-}
2014-03-31 03:37:54 +00:00
module Propellor.PrivData where
2014-03-30 23:11:24 +00:00
import Control.Applicative
import System.FilePath
import System.IO
import System.Directory
import Data.Maybe
2014-07-06 19:56:56 +00:00
import Data.Monoid
2014-03-30 23:11:24 +00:00
import Control.Monad
import Control.Monad.IfElse
import "mtl" Control.Monad.Reader
2014-07-06 19:56:56 +00:00
import qualified Data.Map as M
import qualified Data.Set as S
2014-03-30 23:11:24 +00:00
2014-03-31 03:37:54 +00:00
import Propellor.Types
2014-07-06 19:56:56 +00:00
import Propellor.Types.Info
2014-03-31 22:31:08 +00:00
import Propellor.Message
2014-03-30 23:11:24 +00:00
import Utility.Monad
import Utility.PartialPrelude
import Utility.Exception
import Utility.Process
import Utility.Tmp
import Utility.SafeCommand
2014-03-31 01:01:18 +00:00
import Utility.Misc
import Utility.FileMode
import Utility.Env
2014-03-30 23:11:24 +00:00
2014-07-06 19:56:56 +00:00
-- | Allows a Property to access the value of a specific PrivDataField,
-- for use in a specific Context.
--
-- Example use:
--
-- > withPrivData (PrivFile pemfile) (Context "joeyh.name") $ \getdata ->
-- > property "joeyh.name ssl cert" $ getdata $ \privdata ->
-- > liftIO $ writeFile pemfile privdata
-- > where pemfile = "/etc/ssl/certs/web.pem"
--
-- Note that if the value is not available, the action is not run
-- and instead it prints a message to help the user make the necessary
-- private data available.
withPrivData
:: PrivDataField
-> Context
-> (((PrivData -> Propellor Result) -> Propellor Result) -> Property)
-> Property
withPrivData field context@(Context cname) mkprop = addinfo $ mkprop $ \a ->
maybe missing a =<< liftIO (getLocalPrivData field context)
where
missing = liftIO $ do
warningMessage $ "Missing privdata " ++ show field ++ " (for " ++ cname ++ ")"
putStrLn $ "Fix this by running: propellor --set '" ++ show field ++ "' '" ++ cname ++ "'"
return FailedChange
addinfo p = p { propertyInfo = propertyInfo p <> mempty { _privDataFields = S.singleton (field, context) } }
{- Gets the requested field's value, in the specified context if it's
- available, from the host's local privdata cache. -}
getLocalPrivData :: PrivDataField -> Context -> IO (Maybe PrivData)
getLocalPrivData field context =
getPrivData field context . fromMaybe M.empty <$> localcache
2014-03-30 23:11:24 +00:00
where
2014-07-06 19:56:56 +00:00
localcache = catchDefaultIO Nothing $ readish <$> readFile privDataLocal
getPrivData :: PrivDataField -> Context -> (M.Map (PrivDataField, Context) PrivData) -> Maybe PrivData
getPrivData field context = M.lookup (field, context)
setPrivData :: PrivDataField -> Context -> IO ()
setPrivData field context = do
2014-03-31 01:01:18 +00:00
putStrLn "Enter private data on stdin; ctrl-D when done:"
2014-07-06 19:56:56 +00:00
setPrivDataTo field context =<< hGetContentsStrict stdin
2014-07-06 19:56:56 +00:00
dumpPrivData :: PrivDataField -> Context -> IO ()
dumpPrivData field context =
maybe (error "Requested privdata is not set.") putStrLn
2014-07-06 19:56:56 +00:00
=<< (getPrivData field context <$> decryptPrivData)
2014-07-06 19:56:56 +00:00
editPrivData :: PrivDataField -> Context -> IO ()
editPrivData field context = do
v <- getPrivData field context <$> decryptPrivData
v' <- withTmpFile "propellorXXXX" $ \f h -> do
hClose h
maybe noop (writeFileProtected f) v
editor <- getEnvDefault "EDITOR" "vi"
unlessM (boolSystem editor [File f]) $
error "Editor failed; aborting."
readFile f
2014-07-06 19:56:56 +00:00
setPrivDataTo field context v'
2014-07-06 19:56:56 +00:00
listPrivDataFields :: IO ()
listPrivDataFields = do
putStrLn ("All currently set privdata fields:")
mapM_ list . M.keys =<< decryptPrivData
where
list = putStrLn . ("\t" ++) . shellEscape . show
2014-07-06 19:56:56 +00:00
setPrivDataTo :: PrivDataField -> Context -> PrivData -> IO ()
setPrivDataTo field context value = do
2014-03-30 23:19:29 +00:00
makePrivDataDir
2014-07-06 19:56:56 +00:00
m <- decryptPrivData
let m' = M.insert (field, context) (chomp value) m
gpgEncrypt privDataFile (show m')
2014-03-31 01:01:18 +00:00
putStrLn "Private data set."
2014-07-06 19:56:56 +00:00
void $ boolSystem "git" [Param "add", File privDataFile]
2014-04-01 22:42:32 +00:00
where
chomp s
| end s == "\n" = chomp (beginning s)
| otherwise = s
2014-03-30 23:11:24 +00:00
2014-07-06 19:56:56 +00:00
decryptPrivData :: IO (M.Map (PrivDataField, Context) PrivData)
decryptPrivData = fromMaybe M.empty . readish <$> gpgDecrypt privDataFile
2014-03-30 23:19:29 +00:00
makePrivDataDir :: IO ()
makePrivDataDir = createDirectoryIfMissing False privDataDir
2014-03-30 23:11:24 +00:00
privDataDir :: FilePath
privDataDir = "privdata"
2014-07-06 19:56:56 +00:00
privDataFile :: FilePath
privDataFile = privDataDir </> "privdata.gpg"
2014-03-30 23:11:24 +00:00
privDataLocal :: FilePath
privDataLocal = privDataDir </> "local"
gpgDecrypt :: FilePath -> IO String
gpgDecrypt f = ifM (doesFileExist f)
( readProcess "gpg" ["--decrypt", f]
, return ""
)
gpgEncrypt :: FilePath -> String -> IO ()
gpgEncrypt f s = do
encrypted <- writeReadProcessEnv "gpg"
[ "--default-recipient-self"
, "--armor"
, "--encrypt"
]
Nothing
(Just $ flip hPutStr s)
Nothing
viaTmp writeFile f encrypted