2019-10-10 16:48:26 +00:00
|
|
|
{- git-annex special remote configuration
|
|
|
|
-
|
2020-01-14 16:35:08 +00:00
|
|
|
- Copyright 2019-2020 Joey Hess <id@joeyh.name>
|
2019-10-10 16:48:26 +00:00
|
|
|
-
|
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
|
|
|
-}
|
|
|
|
|
2020-01-14 16:35:08 +00:00
|
|
|
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}
|
|
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
|
|
|
2019-10-10 16:48:26 +00:00
|
|
|
module Annex.SpecialRemote.Config where
|
|
|
|
|
|
|
|
import Common
|
2019-10-10 19:31:10 +00:00
|
|
|
import Types.Remote (RemoteConfigField, RemoteConfig)
|
2019-10-11 16:45:30 +00:00
|
|
|
import Types.UUID
|
2020-01-10 18:10:20 +00:00
|
|
|
import Types.ProposedAccepted
|
2020-01-14 16:35:08 +00:00
|
|
|
import Types.RemoteConfig
|
|
|
|
import Config
|
|
|
|
import qualified Git.Config
|
2019-10-10 16:48:26 +00:00
|
|
|
|
|
|
|
import qualified Data.Map as M
|
2019-10-10 19:46:12 +00:00
|
|
|
import qualified Data.Set as S
|
2020-01-14 16:35:08 +00:00
|
|
|
import Data.Typeable
|
|
|
|
import GHC.Stack
|
2019-10-10 16:48:26 +00:00
|
|
|
|
2019-10-11 19:32:56 +00:00
|
|
|
newtype Sameas t = Sameas t
|
|
|
|
deriving (Show)
|
|
|
|
|
|
|
|
newtype ConfigFrom t = ConfigFrom t
|
|
|
|
deriving (Show)
|
|
|
|
|
2019-10-10 16:48:26 +00:00
|
|
|
{- The name of a configured remote is stored in its config using this key. -}
|
2019-10-10 19:31:10 +00:00
|
|
|
nameField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
nameField = Accepted "name"
|
2019-10-10 16:48:26 +00:00
|
|
|
|
|
|
|
{- The name of a sameas remote is stored using this key instead.
|
|
|
|
- This prevents old versions of git-annex getting confused. -}
|
2019-10-10 19:31:10 +00:00
|
|
|
sameasNameField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
sameasNameField = Accepted "sameas-name"
|
2019-10-10 16:48:26 +00:00
|
|
|
|
|
|
|
lookupName :: RemoteConfig -> Maybe String
|
2020-01-10 18:10:20 +00:00
|
|
|
lookupName c = fmap fromProposedAccepted $
|
|
|
|
M.lookup nameField c <|> M.lookup sameasNameField c
|
2019-10-10 16:48:26 +00:00
|
|
|
|
2020-01-14 16:35:08 +00:00
|
|
|
instance RemoteNameable RemoteConfig where
|
|
|
|
getRemoteName c = fromMaybe "" (lookupName c)
|
|
|
|
|
2019-10-10 16:48:26 +00:00
|
|
|
{- The uuid that a sameas remote is the same as is stored in this key. -}
|
2019-10-10 19:31:10 +00:00
|
|
|
sameasUUIDField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
sameasUUIDField = Accepted "sameas-uuid"
|
2019-10-10 16:48:26 +00:00
|
|
|
|
|
|
|
{- The type of a remote is stored in its config using this key. -}
|
2019-10-10 19:31:10 +00:00
|
|
|
typeField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
typeField = Accepted "type"
|
2019-10-10 16:48:26 +00:00
|
|
|
|
2019-10-10 19:31:10 +00:00
|
|
|
autoEnableField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
autoEnableField = Accepted "autoenable"
|
2019-10-10 16:48:26 +00:00
|
|
|
|
2019-10-10 19:46:12 +00:00
|
|
|
encryptionField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
encryptionField = Accepted "encryption"
|
2019-10-10 19:46:12 +00:00
|
|
|
|
2019-10-10 20:10:12 +00:00
|
|
|
macField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
macField = Accepted "mac"
|
2019-10-10 20:10:12 +00:00
|
|
|
|
|
|
|
cipherField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
cipherField = Accepted "cipher"
|
2019-10-10 20:10:12 +00:00
|
|
|
|
|
|
|
cipherkeysField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
cipherkeysField = Accepted "cipherkeys"
|
2019-10-10 20:10:12 +00:00
|
|
|
|
|
|
|
pubkeysField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
pubkeysField = Accepted "pubkeys"
|
2019-10-10 20:10:12 +00:00
|
|
|
|
2020-01-13 16:35:39 +00:00
|
|
|
chunkField :: RemoteConfigField
|
|
|
|
chunkField = Accepted "chunk"
|
|
|
|
|
2019-10-10 20:10:12 +00:00
|
|
|
chunksizeField :: RemoteConfigField
|
2020-01-10 18:10:20 +00:00
|
|
|
chunksizeField = Accepted "chunksize"
|
|
|
|
|
|
|
|
embedCredsField :: RemoteConfigField
|
|
|
|
embedCredsField = Accepted "embedcreds"
|
|
|
|
|
2020-01-15 15:22:36 +00:00
|
|
|
preferreddirField :: RemoteConfigField
|
|
|
|
preferreddirField = Accepted "preferreddir"
|
|
|
|
|
2020-01-10 18:10:20 +00:00
|
|
|
exportTreeField :: RemoteConfigField
|
|
|
|
exportTreeField = Accepted "exporttree"
|
|
|
|
|
|
|
|
importTreeField :: RemoteConfigField
|
|
|
|
importTreeField = Accepted "importtree"
|
2019-10-10 20:10:12 +00:00
|
|
|
|
2020-01-14 16:35:08 +00:00
|
|
|
exportTree :: ParsedRemoteConfig -> Bool
|
|
|
|
exportTree = fromMaybe False . getRemoteConfigValue exportTreeField
|
|
|
|
|
|
|
|
importTree :: ParsedRemoteConfig -> Bool
|
|
|
|
importTree = fromMaybe False . getRemoteConfigValue importTreeField
|
|
|
|
|
|
|
|
{- Parsers for fields that are common to all special remotes. -}
|
2020-01-14 17:18:15 +00:00
|
|
|
commonFieldParsers :: [RemoteConfigFieldParser]
|
|
|
|
commonFieldParsers =
|
2020-01-14 16:35:08 +00:00
|
|
|
[ optionalStringParser nameField
|
|
|
|
, optionalStringParser sameasNameField
|
|
|
|
, optionalStringParser sameasUUIDField
|
|
|
|
, optionalStringParser typeField
|
|
|
|
, trueFalseParser autoEnableField False
|
2020-01-15 15:22:36 +00:00
|
|
|
, optionalStringParser preferreddirField
|
2020-01-14 16:35:08 +00:00
|
|
|
]
|
|
|
|
|
2019-10-10 19:46:12 +00:00
|
|
|
{- A remote with sameas-uuid set will inherit these values from the config
|
2019-10-10 20:12:17 +00:00
|
|
|
- of that uuid. These values cannot be overridden in the remote's config. -}
|
2019-10-10 19:46:12 +00:00
|
|
|
sameasInherits :: S.Set RemoteConfigField
|
|
|
|
sameasInherits = S.fromList
|
2019-10-10 20:10:12 +00:00
|
|
|
-- encryption configuration is necessarily the same for two
|
|
|
|
-- remotes that access the same data store
|
2019-10-10 19:46:12 +00:00
|
|
|
[ encryptionField
|
2019-10-10 20:10:12 +00:00
|
|
|
, macField
|
|
|
|
, cipherField
|
|
|
|
, cipherkeysField
|
|
|
|
, pubkeysField
|
|
|
|
-- legacy chunking was either enabled or not, so has to be the same
|
|
|
|
-- across configs for remotes that access the same data
|
|
|
|
-- (new-style chunking does not have that limitation)
|
|
|
|
, chunksizeField
|
2019-10-10 19:46:12 +00:00
|
|
|
]
|
2019-10-11 16:45:30 +00:00
|
|
|
|
|
|
|
{- Each RemoteConfig that has a sameas-uuid inherits some fields
|
|
|
|
- from it. Such fields can only be set by inheritance; the RemoteConfig
|
|
|
|
- cannot provide values from them. -}
|
|
|
|
addSameasInherited :: M.Map UUID RemoteConfig -> RemoteConfig -> RemoteConfig
|
2019-10-11 19:32:56 +00:00
|
|
|
addSameasInherited m c = case findSameasUUID c of
|
2019-10-11 16:45:30 +00:00
|
|
|
Nothing -> c
|
2019-10-11 19:32:56 +00:00
|
|
|
Just (Sameas sameasuuid) -> case M.lookup sameasuuid m of
|
2019-10-11 16:45:30 +00:00
|
|
|
Nothing -> c
|
|
|
|
Just parentc ->
|
|
|
|
M.withoutKeys c sameasInherits
|
|
|
|
`M.union`
|
|
|
|
M.restrictKeys parentc sameasInherits
|
|
|
|
|
2019-10-11 19:32:56 +00:00
|
|
|
findSameasUUID :: RemoteConfig -> Maybe (Sameas UUID)
|
2020-01-10 18:10:20 +00:00
|
|
|
findSameasUUID c = Sameas . toUUID . fromProposedAccepted
|
|
|
|
<$> M.lookup sameasUUIDField c
|
2019-10-11 19:32:56 +00:00
|
|
|
|
2019-10-11 16:45:30 +00:00
|
|
|
{- Remove any fields inherited from a sameas-uuid. When storing a
|
|
|
|
- RemoteConfig, those fields don't get stored, since they were already
|
|
|
|
- inherited. -}
|
|
|
|
removeSameasInherited :: RemoteConfig -> RemoteConfig
|
|
|
|
removeSameasInherited c = case M.lookup sameasUUIDField c of
|
|
|
|
Nothing -> c
|
2019-10-11 17:10:07 +00:00
|
|
|
Just _ -> M.withoutKeys c sameasInherits
|
2019-11-18 20:09:09 +00:00
|
|
|
|
|
|
|
{- Finds remote uuids with matching RemoteConfig. -}
|
|
|
|
findByRemoteConfig :: (RemoteConfig -> Bool) -> M.Map UUID RemoteConfig -> [(UUID, RemoteConfig, Maybe (ConfigFrom UUID))]
|
|
|
|
findByRemoteConfig matching = map sameasuuid . filter (matching . snd) . M.toList
|
|
|
|
where
|
|
|
|
sameasuuid (u, c) = case M.lookup sameasUUIDField c of
|
|
|
|
Nothing -> (u, c, Nothing)
|
2020-01-10 18:10:20 +00:00
|
|
|
Just u' -> (toUUID (fromProposedAccepted u'), c, Just (ConfigFrom u))
|
2020-01-14 16:35:08 +00:00
|
|
|
|
|
|
|
{- Extracts a value from ParsedRemoteConfig. -}
|
|
|
|
getRemoteConfigValue :: HasCallStack => Typeable v => RemoteConfigField -> ParsedRemoteConfig -> Maybe v
|
|
|
|
getRemoteConfigValue f m = case M.lookup f m of
|
|
|
|
Just (RemoteConfigValue v) -> case cast v of
|
|
|
|
Just v' -> Just v'
|
|
|
|
Nothing -> error $ unwords
|
|
|
|
[ "getRemoteConfigValue"
|
|
|
|
, fromProposedAccepted f
|
|
|
|
, "found value of unexpected type"
|
|
|
|
, show (typeOf v) ++ "."
|
|
|
|
, "This is a bug in git-annex!"
|
|
|
|
]
|
|
|
|
Nothing -> Nothing
|
|
|
|
|
2020-01-14 19:41:34 +00:00
|
|
|
{- Gets all fields that remoteConfigRestPassthrough matched. -}
|
|
|
|
getRemoteConfigPassedThrough :: ParsedRemoteConfig -> M.Map RemoteConfigField String
|
2020-01-17 21:09:56 +00:00
|
|
|
getRemoteConfigPassedThrough = M.mapMaybe $ \(RemoteConfigValue v) ->
|
2020-01-14 19:41:34 +00:00
|
|
|
case cast v of
|
|
|
|
Just (PassedThrough s) -> Just s
|
|
|
|
Nothing -> Nothing
|
|
|
|
|
|
|
|
newtype PassedThrough = PassedThrough String
|
|
|
|
|
2020-01-14 17:18:15 +00:00
|
|
|
parseRemoteConfig :: RemoteConfig -> RemoteConfigParser -> Either String ParsedRemoteConfig
|
|
|
|
parseRemoteConfig c rpc =
|
2020-01-17 21:09:56 +00:00
|
|
|
go [] c (remoteConfigFieldParsers rpc ++ commonFieldParsers)
|
2020-01-14 16:35:08 +00:00
|
|
|
where
|
2020-01-14 19:41:34 +00:00
|
|
|
go l c' [] =
|
|
|
|
let (passover, leftovers) = partition
|
|
|
|
(remoteConfigRestPassthrough rpc . fst)
|
|
|
|
(M.toList c')
|
2020-01-17 21:09:56 +00:00
|
|
|
leftovers' = filter (notaccepted . fst) leftovers
|
|
|
|
in if not (null leftovers')
|
2020-01-15 18:07:05 +00:00
|
|
|
then Left $ "Unexpected parameters: " ++
|
2020-01-17 21:09:56 +00:00
|
|
|
unwords (map (fromProposedAccepted . fst) leftovers')
|
2020-01-14 19:41:34 +00:00
|
|
|
else Right $ M.fromList $
|
|
|
|
l ++ map (uncurry passthrough) passover
|
2020-01-20 17:49:30 +00:00
|
|
|
go l c' (p:rest) = do
|
|
|
|
let f = parserForField p
|
|
|
|
(valueParser p) (M.lookup f c) c >>= \case
|
|
|
|
Just v -> go ((f,v):l) (M.delete f c') rest
|
2020-01-14 16:35:08 +00:00
|
|
|
Nothing -> go l (M.delete f c') rest
|
2020-01-14 17:18:15 +00:00
|
|
|
|
2020-01-14 19:41:34 +00:00
|
|
|
passthrough f v = (f, RemoteConfigValue (PassedThrough (fromProposedAccepted v)))
|
2020-01-20 17:49:30 +00:00
|
|
|
|
2020-01-17 21:09:56 +00:00
|
|
|
notaccepted (Proposed _) = True
|
|
|
|
notaccepted (Accepted _) = False
|
2020-01-14 16:35:08 +00:00
|
|
|
|
2020-01-14 17:18:15 +00:00
|
|
|
optionalStringParser :: RemoteConfigField -> RemoteConfigFieldParser
|
2020-01-20 17:49:30 +00:00
|
|
|
optionalStringParser f = RemoteConfigFieldParser f p
|
2020-01-14 16:35:08 +00:00
|
|
|
where
|
|
|
|
p (Just v) _c = Right (Just (RemoteConfigValue (fromProposedAccepted v)))
|
|
|
|
p Nothing _c = Right Nothing
|
|
|
|
|
2020-01-14 17:18:15 +00:00
|
|
|
yesNoParser :: RemoteConfigField -> Bool -> RemoteConfigFieldParser
|
2020-01-14 16:35:08 +00:00
|
|
|
yesNoParser = genParser yesNo "yes or no"
|
|
|
|
|
2020-01-14 17:18:15 +00:00
|
|
|
trueFalseParser :: RemoteConfigField -> Bool -> RemoteConfigFieldParser
|
2020-01-14 16:35:08 +00:00
|
|
|
trueFalseParser = genParser Git.Config.isTrueFalse "true or false"
|
|
|
|
|
|
|
|
genParser
|
|
|
|
:: Typeable t
|
|
|
|
=> (String -> Maybe t)
|
|
|
|
-> String -- ^ description of the value
|
|
|
|
-> RemoteConfigField
|
|
|
|
-> t -- ^ fallback value
|
2020-01-14 17:18:15 +00:00
|
|
|
-> RemoteConfigFieldParser
|
2020-01-20 17:49:30 +00:00
|
|
|
genParser parse desc f fallback = RemoteConfigFieldParser f p
|
2020-01-14 16:35:08 +00:00
|
|
|
where
|
|
|
|
p Nothing _c = Right (Just (RemoteConfigValue fallback))
|
|
|
|
p (Just v) _c = case parse (fromProposedAccepted v) of
|
|
|
|
Just b -> Right (Just (RemoteConfigValue b))
|
|
|
|
Nothing -> case v of
|
|
|
|
Accepted _ -> Right (Just (RemoteConfigValue fallback))
|
|
|
|
Proposed _ -> Left $
|
|
|
|
"Bad value for " ++ fromProposedAccepted f ++
|
|
|
|
" (expected " ++ desc ++ ")"
|