diff options
| author | Joey Hess <joeyh@debian.org> | 2014-07-09 22:11:31 -0400 |
|---|---|---|
| committer | Joey Hess <joeyh@debian.org> | 2014-07-09 22:11:31 -0400 |
| commit | f118c369d3240b656e4fef77b6efc758b3f714eb (patch) | |
| tree | 0a3e0c6e134680e35665364b2cd6895863bcc990 /src/Propellor/PrivData.hs | |
| parent | 17b21794a72f6cfaddda321d6f2cbdb87ce3dee0 (diff) | |
| parent | 82da31b3e0e9acdfbca4c48eb12ab1f28515ba10 (diff) | |
Record propellor (0.8.1) in archive suite sid
Diffstat (limited to 'src/Propellor/PrivData.hs')
| -rw-r--r-- | src/Propellor/PrivData.hs | 151 |
1 files changed, 113 insertions, 38 deletions
diff --git a/src/Propellor/PrivData.hs b/src/Propellor/PrivData.hs index 5ddbdcff..f85ded15 100644 --- a/src/Propellor/PrivData.hs +++ b/src/Propellor/PrivData.hs @@ -2,18 +2,23 @@ module Propellor.PrivData where -import qualified Data.Map as M import Control.Applicative import System.FilePath import System.IO import System.Directory import Data.Maybe +import Data.Monoid import Data.List import Control.Monad +import Control.Monad.IfElse import "mtl" Control.Monad.Reader +import qualified Data.Map as M +import qualified Data.Set as S import Propellor.Types +import Propellor.Types.Info import Propellor.Message +import Propellor.Info import Utility.Monad import Utility.PartialPrelude import Utility.Exception @@ -21,53 +26,123 @@ import Utility.Process import Utility.Tmp import Utility.SafeCommand import Utility.Misc +import Utility.FileMode +import Utility.Env +import Utility.Table + +-- | 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. +-- +-- The resulting Property includes Info about the PrivDataField +-- being used, which is necessary to ensure that the privdata is sent to +-- the remote host by propellor. +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) } } + +addPrivDataField :: (PrivDataField, Context) -> Property +addPrivDataField v = pureInfoProperty (show v) $ + mempty { _privDataFields = S.singleton v } + +{- 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 + where + localcache = catchDefaultIO Nothing $ readish <$> readFile privDataLocal + +type PrivMap = M.Map (PrivDataField, Context) PrivData --- | When the specified PrivDataField is available on the host Propellor --- is provisioning, it provies the data to the action. Otherwise, it prints --- a message to help the user make the necessary private data available. -withPrivData :: PrivDataField -> (String -> Propellor Result) -> Propellor Result -withPrivData field a = maybe missing a =<< liftIO (getPrivData field) +{- Get only the set of PrivData that the Host's Info says it uses. -} +filterPrivData :: Host -> PrivMap -> PrivMap +filterPrivData host = M.filterWithKey (\k _v -> S.member k used) where - missing = do - host <- asks hostName - let host' = if ".docker" `isSuffixOf` host - then "$parent_host" - else host - liftIO $ do - warningMessage $ "Missing privdata " ++ show field - putStrLn $ "Fix this by running: propellor --set "++host'++" '" ++ show field ++ "'" - return FailedChange + used = _privDataFields $ hostInfo host -getPrivData :: PrivDataField -> IO (Maybe String) -getPrivData field = do - m <- catchDefaultIO Nothing $ readish <$> readFile privDataLocal - return $ maybe Nothing (M.lookup field) m +getPrivData :: PrivDataField -> Context -> PrivMap -> Maybe PrivData +getPrivData field context = M.lookup (field, context) -setPrivData :: HostName -> PrivDataField -> IO () -setPrivData host field = do +setPrivData :: PrivDataField -> Context -> IO () +setPrivData field context = do putStrLn "Enter private data on stdin; ctrl-D when done:" - value <- chomp <$> hGetContentsStrict stdin + setPrivDataTo field context =<< hGetContentsStrict stdin + +dumpPrivData :: PrivDataField -> Context -> IO () +dumpPrivData field context = + maybe (error "Requested privdata is not set.") putStrLn + =<< (getPrivData field context <$> decryptPrivData) + +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 + setPrivDataTo field context v' + +listPrivDataFields :: [Host] -> IO () +listPrivDataFields hosts = do + m <- decryptPrivData + showtable "Currently set data:" $ + map mkrow (M.keys m) + showtable "Data that would be used if set:" $ + map mkrow (M.keys $ M.difference wantedmap m) + where + header = ["Field", "Context", "Used by"] + mkrow k@(field, (Context context)) = + [ shellEscape $ show field + , shellEscape context + , intercalate ", " $ sort $ fromMaybe [] $ M.lookup k usedby + ] + mkhostmap host = M.fromList $ map (\k -> (k, [hostName host])) $ + S.toList $ _privDataFields $ hostInfo host + usedby = M.unionsWith (++) $ map mkhostmap hosts + wantedmap = M.fromList $ zip (M.keys usedby) (repeat "") + showtable desc rows = do + putStrLn $ "\n" ++ desc + putStr $ unlines $ formatTable $ tableWithHeader header rows + +setPrivDataTo :: PrivDataField -> Context -> PrivData -> IO () +setPrivDataTo field context value = do makePrivDataDir - let f = privDataFile host - m <- decryptPrivData host - let m' = M.insert field value m - gpgEncrypt f (show m') + m <- decryptPrivData + let m' = M.insert (field, context) (chomp value) m + gpgEncrypt privDataFile (show m') putStrLn "Private data set." - void $ boolSystem "git" [Param "add", File f] + void $ boolSystem "git" [Param "add", File privDataFile] where chomp s | end s == "\n" = chomp (beginning s) | otherwise = s -dumpPrivData :: HostName -> PrivDataField -> IO () -dumpPrivData host field = go . M.lookup field =<< decryptPrivData host - where - go Nothing = error "Requested privdata is not set." - go (Just s) = putStrLn s - -decryptPrivData :: HostName -> IO (M.Map PrivDataField String) -decryptPrivData host = fromMaybe M.empty . readish - <$> gpgDecrypt (privDataFile host) +decryptPrivData :: IO PrivMap +decryptPrivData = fromMaybe M.empty . readish <$> gpgDecrypt privDataFile makePrivDataDir :: IO () makePrivDataDir = createDirectoryIfMissing False privDataDir @@ -75,8 +150,8 @@ makePrivDataDir = createDirectoryIfMissing False privDataDir privDataDir :: FilePath privDataDir = "privdata" -privDataFile :: HostName -> FilePath -privDataFile host = privDataDir </> host ++ ".gpg" +privDataFile :: FilePath +privDataFile = privDataDir </> "privdata.gpg" privDataLocal :: FilePath privDataLocal = privDataDir </> "local" |
