<commit_before>{-# LANGUAGE PackageImports #-}

module Propellor.PrivData where

import Control.Applicative
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.PrivData
import Propellor.Message
import Propellor.Info
import Propellor.Gpg
import Propellor.PrivData.Paths
import Utility.Monad
import Utility.PartialPrelude
import Utility.Exception
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 or HostContext.
--
-- 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
	:: (IsContext c, IsPrivDataSource s)
	=> s
	-> c
	-> (((PrivData -> Propellor Result) -> Propellor Result) -> Property)
	-> Property
withPrivData s = withPrivData' snd [s]

-- Like withPrivData, but here any one of a list of PrivDataFields can be used.
withSomePrivData
	:: (IsContext c, IsPrivDataSource s)
	=> [s]
	-> c
	-> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property)
	-> Property
withSomePrivData = withPrivData' id

withPrivData' 
	:: (IsContext c, IsPrivDataSource s)
	=> ((PrivDataField, PrivData) -> v)
	-> [s]
	-> c
	-> (((v -> Propellor Result) -> Propellor Result) -> Property)
	-> Property
withPrivData' feed srclist c mkprop = addinfo $ mkprop $ \a ->
	maybe missing (a . feed) =<< getM get fieldlist
  where
  	get field = do
		context <- mkHostContext hc <$> asks hostName
		maybe Nothing (\privdata -> Just (field, privdata))
			<$> liftIO (getLocalPrivData field context)
	missing = do
		Context cname <- mkHostContext hc <$> asks hostName
		warningMessage $ "Missing privdata " ++ intercalate " or " fieldnames ++ " (for " ++ cname ++ ")"
		liftIO $ putStrLn $ "Fix this by running:"
		liftIO $ forM_ srclist $ \src -> do
			putStrLn $ "  propellor --set '" ++ show (privDataField src) ++ "' '" ++ cname ++ "'"
			maybe noop (\d -> putStrLn $ "    " ++ d) (describePrivDataSource src)
			putStrLn ""
		return FailedChange
	addinfo p = p { propertyInfo = propertyInfo p <> mempty { _privDataFields = fieldset } }
	fieldnames = map show fieldlist
	fieldset = S.fromList $ zip fieldlist (repeat hc)
	fieldlist = map privDataField srclist
	hc = asHostContext c

addPrivDataField :: (PrivDataField, HostContext) -> 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<commit_msg>make both lines pastable together<commit_after>{-# LANGUAGE PackageImports #-}

module Propellor.PrivData where

import Control.Applicative
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.PrivData
import Propellor.Message
import Propellor.Info
import Propellor.Gpg
import Propellor.PrivData.Paths
import Utility.Monad
import Utility.PartialPrelude
import Utility.Exception
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 or HostContext.
--
-- 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
	:: (IsContext c, IsPrivDataSource s)
	=> s
	-> c
	-> (((PrivData -> Propellor Result) -> Propellor Result) -> Property)
	-> Property
withPrivData s = withPrivData' snd [s]

-- Like withPrivData, but here any one of a list of PrivDataFields can be used.
withSomePrivData
	:: (IsContext c, IsPrivDataSource s)
	=> [s]
	-> c
	-> ((((PrivDataField, PrivData) -> Propellor Result) -> Propellor Result) -> Property)
	-> Property
withSomePrivData = withPrivData' id

withPrivData' 
	:: (IsContext c, IsPrivDataSource s)
	=> ((PrivDataField, PrivData) -> v)
	-> [s]
	-> c
	-> (((v -> Propellor Result) -> Propellor Result) -> Property)
	-> Property
withPrivData' feed srclist c mkprop = addinfo $ mkprop $ \a ->
	maybe missing (a . feed) =<< getM get fieldlist
  where
  	get field = do
		context <- mkHostContext hc <$> asks hostName
		maybe Nothing (\privdata -> Just (field, privdata))
			<$> liftIO (getLocalPrivData field context)
	missing = do
		Context cname <- mkHostContext hc <$> asks hostName
		warningMessage $ "Missing privdata " ++ intercalate " or " fieldnames ++ " (for " ++ cname ++ ")"
		liftIO $ putStrLn $ "Fix this by running:"
		liftIO $ forM_ srclist $ \src -> do
			putStrLn $ "  propellor --set '" ++ show (privDataField src) ++ "' '" ++ cname ++ "' \\"
			maybe noop (\d -> putStrLn $ "    " ++ d) (describePrivDataSource src)
			putStrLn ""
		return FailedChange
	addinfo p = p { propertyInfo = propertyInfo p <> mempty { _privDataFields = fieldset } }
	fieldnames = map show fieldlist
	fieldset = S.fromList $ zip fieldlist (repeat hc)
	fieldlist = map privDataField srclist
	hc = asHostContext c

addPrivDataField :: (PrivDataField, HostContext) -> 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
{'fraction_non_alphanumeric': 0.10651372388365424, 'fraction_numerical': 0.0, 'mean_word_length': 3.8957219251336896, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 87, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3276401', 'n_tokens_mistral': 2337, 'n_tokens_neox': 2260, 'n_words': 785}