git-annex/Remote/Glacier.hs

295 lines
8.1 KiB
Haskell
Raw Normal View History

{- Amazon Glacier remotes.
-
- Copyright 2012 Joey Hess <joey@kitenet.net>
-
- Licensed under the GNU GPL version 3 or higher.
-}
module Remote.Glacier (remote, jobList) where
import qualified Data.Map as M
import qualified Data.Text as T
import System.Environment
import Common.Annex
import Types.Remote
import Types.Key
import qualified Git
import Config
import Remote.Helper.Special
import Remote.Helper.Encryptable
import qualified Remote.Helper.AWS as AWS
import Crypto
import Creds
2012-11-25 17:27:20 +00:00
import Meters
import qualified Annex
import Annex.Content
2012-11-25 17:27:20 +00:00
import System.Process
type Vault = String
type Archive = FilePath
remote :: RemoteType
remote = RemoteType {
typename = "glacier",
enumerate = findSpecialRemotes "glacier",
generate = gen,
setup = glacierSetup
}
gen :: Git.Repo -> UUID -> RemoteConfig -> RemoteGitConfig -> Annex Remote
gen r u c gc = new <$> remoteCost gc veryExpensiveRemoteCost
2012-11-30 04:55:59 +00:00
where
new cst = encryptableRemote c
(storeEncrypted this)
(retrieveEncrypted this)
this
2012-11-30 04:55:59 +00:00
where
this = Remote {
uuid = u,
cost = cst,
name = Git.repoDescribe r,
2012-12-13 04:45:27 +00:00
storeKey = store this,
2012-11-30 04:55:59 +00:00
retrieveKeyFile = retrieve this,
retrieveKeyFileCheap = retrieveCheap this,
removeKey = remove this,
hasKey = checkPresent this,
hasKeyCheap = False,
whereisKey = Nothing,
config = c,
repo = r,
gitconfig = gc,
2012-11-30 04:55:59 +00:00
localpath = Nothing,
readonly = False,
remotetype = remote
}
glacierSetup :: UUID -> RemoteConfig -> Annex RemoteConfig
glacierSetup u c = do
c' <- encryptionSetup c
let fullconfig = c' `M.union` defaults
genVault fullconfig u
gitConfigSpecialRemote u fullconfig "glacier" "true"
setRemoteCredPair fullconfig (AWS.creds u)
where
remotename = fromJust (M.lookup "name" c)
defvault = remotename ++ "-" ++ fromUUID u
defaults = M.fromList
[ ("datacenter", T.unpack $ AWS.defaultRegion AWS.Glacier)
, ("vault", defvault)
]
store :: Remote -> Key -> AssociatedFile -> MeterUpdate -> Annex Bool
2012-11-25 17:27:20 +00:00
store r k _f m
| keySize k == Just 0 = do
warning "Cannot store empty files in Glacier."
return False
| otherwise = sendAnnex k (void $ remove r k) $ \src ->
2012-11-25 17:27:20 +00:00
metered (Just m) k $ \meterupdate ->
storeHelper r k $ streamMeteredFile src meterupdate
storeEncrypted :: Remote -> (Cipher, Key) -> Key -> MeterUpdate -> Annex Bool
storeEncrypted r (cipher, enck) k m = sendAnnex k (void $ remove r enck) $ \src -> do
2012-11-25 17:27:20 +00:00
metered (Just m) k $ \meterupdate ->
storeHelper r enck $ \h ->
encrypt cipher (feedFile src)
2012-11-25 17:27:20 +00:00
(readBytes $ meteredWrite meterupdate h)
2012-11-25 17:42:28 +00:00
retrieve :: Remote -> Key -> AssociatedFile -> FilePath -> Annex Bool
retrieve r k _f d = metered Nothing k $ \meterupdate ->
retrieveHelper r k $
readBytes $ meteredWriteFile meterupdate d
retrieveCheap :: Remote -> Key -> FilePath -> Annex Bool
retrieveCheap _ _ _ = return False
retrieveEncrypted :: Remote -> (Cipher, Key) -> Key -> FilePath -> Annex Bool
retrieveEncrypted r (cipher, enck) k d = metered Nothing k $ \meterupdate ->
retrieveHelper r enck $ readBytes $ \b ->
decrypt cipher (feedBytes b) $
readBytes $ meteredWriteFile meterupdate d
2012-11-25 17:27:20 +00:00
storeHelper :: Remote -> Key -> (Handle -> IO ()) -> Annex Bool
storeHelper r k feeder = go =<< glacierEnv c u
where
2012-11-30 04:55:59 +00:00
c = config r
2012-11-25 17:27:20 +00:00
u = uuid r
params = glacierParams c
[ Param "archive"
, Param "upload"
, Param "--name", Param $ archive r k
2012-11-30 04:55:59 +00:00
, Param $ getVault $ config r
2012-11-25 17:27:20 +00:00
, Param "-"
]
2012-11-25 17:27:20 +00:00
go Nothing = return False
go (Just e) = do
let p = (proc "glacier" (toCommand params)) { env = Just e }
liftIO $ catchBoolIO $
withHandle StdinHandle createProcessSuccess p $ \h -> do
feeder h
return True
2012-11-25 17:42:28 +00:00
retrieveHelper :: Remote -> Key -> (Handle -> IO ()) -> Annex Bool
retrieveHelper r k reader = go =<< glacierEnv c u
where
2012-11-30 04:55:59 +00:00
c = config r
2012-11-25 17:42:28 +00:00
u = uuid r
params = glacierParams c
[ Param "archive"
, Param "retrieve"
2012-11-25 17:42:28 +00:00
, Param "-o-"
2012-11-30 04:55:59 +00:00
, Param $ getVault $ config r
, Param $ archive r k
]
2012-11-25 17:42:28 +00:00
go Nothing = return False
go (Just e) = do
let p = (proc "glacier" (toCommand params)) { env = Just e }
ok <- liftIO $ catchBoolIO $
withHandle StdoutHandle createProcessSuccess p $ \h ->
ifM (hIsEOF h)
( return False
, do
reader h
return True
)
unless ok later
return ok
later = showLongNote "Recommend you wait up to 4 hours, and then run this command again."
remove :: Remote -> Key -> Annex Bool
remove r k = glacierAction r
[ Param "archive"
, Param "delete"
2012-11-30 04:55:59 +00:00
, Param $ getVault $ config r
, Param $ archive r k
]
checkPresent :: Remote -> Key -> Annex (Either String Bool)
checkPresent r k = do
showAction $ "checking " ++ name r
2012-11-30 04:55:59 +00:00
go =<< glacierEnv (config r) (uuid r)
where
go Nothing = return $ Left "cannot check glacier"
2012-11-25 17:27:20 +00:00
go (Just e) = do
{- glacier checkpresent outputs the archive name to stdout if
- it's present. -}
v <- liftIO $ catchMsgIO $
2012-11-25 17:27:20 +00:00
readProcessEnv "glacier" (toCommand params) (Just e)
case v of
Right s -> do
let probablypresent = key2file k `elem` lines s
if probablypresent
then ifM (Annex.getFlag "trustglacier")
( return $ Right True, untrusted )
else return $ Right False
2012-11-25 17:27:20 +00:00
Left err -> return $ Left err
params =
[ Param "archive"
, Param "checkpresent"
2012-11-30 04:55:59 +00:00
, Param $ getVault $ config r
2012-11-21 23:35:28 +00:00
, Param "--quiet"
, Param $ archive r k
]
untrusted = do
showLongNote $ unlines
[ "Glacier's inventory says it has a copy."
, "However, the inventory could be out of date, if it was recently removed."
, "(Use --trust-glacier if you're sure it's still in Glacier.)"
, ""
]
return $ Right False
glacierAction :: Remote -> [CommandParam] -> Annex Bool
2012-11-30 04:55:59 +00:00
glacierAction r params = runGlacier (config r) (uuid r) params
runGlacier :: RemoteConfig -> UUID -> [CommandParam] -> Annex Bool
runGlacier c u params = go =<< glacierEnv c u
where
go Nothing = return False
2012-11-25 17:27:20 +00:00
go (Just e) = liftIO $
boolSystemEnv "glacier" (glacierParams c params) (Just e)
2012-11-25 17:27:20 +00:00
glacierParams :: RemoteConfig -> [CommandParam] -> [CommandParam]
glacierParams c params = datacenter:params
where
datacenter = Param $ "--region=" ++
(fromJust $ M.lookup "datacenter" c)
glacierEnv :: RemoteConfig -> UUID -> Annex (Maybe [(String, String)])
glacierEnv c u = go =<< getRemoteCredPairFor "glacier" c creds
where
go Nothing = return Nothing
go (Just (user, pass)) = do
2012-11-25 17:27:20 +00:00
e <- liftIO getEnvironment
return $ Just $ (uk, user):(pk, pass):e
creds = AWS.creds u
(uk, pk) = credPairEnvironment creds
getVault :: RemoteConfig -> Vault
getVault = fromJust . M.lookup "vault"
archive :: Remote -> Key -> Archive
archive r k = fileprefix ++ key2file k
where
2012-11-30 04:55:59 +00:00
fileprefix = M.findWithDefault "" "fileprefix" $ config r
-- glacier vault create will succeed even if the vault already exists.
genVault :: RemoteConfig -> UUID -> Annex ()
genVault c u = unlessM (runGlacier c u params) $
error "Failed creating glacier vault."
where
params =
[ Param "vault"
, Param "create"
, Param $ getVault c
]
{- Partitions the input list of keys into ones which have
- glacier retieval jobs that have succeeded, or failed.
-
- A complication is that `glacier job list` will display the encrypted
- keys when the remote is encrypted.
-}
jobList :: Remote -> [Key] -> Annex ([Key], [Key])
2012-11-30 04:55:59 +00:00
jobList r keys = go =<< glacierEnv (config r) (uuid r)
where
params = [ Param "job", Param "list" ]
nada = ([], [])
2012-11-30 04:55:59 +00:00
myvault = getVault $ config r
go Nothing = return nada
go (Just e) = do
v <- liftIO $ catchMaybeIO $
readProcessEnv "glacier" (toCommand params) (Just e)
maybe (return nada) extract v
extract s = do
let result@(succeeded, failed) =
parse nada $ (map words . lines) s
if result == nada
then return nada
else do
enckeys <- forM keys $ \k ->
maybe k snd <$> cipherKey (config r) k
let keymap = M.fromList $ zip enckeys keys
let convert = catMaybes . map (`M.lookup` keymap)
return (convert succeeded, convert failed)
parse c [] = c
parse c@(succeeded, failed) ((status:_date:vault:key:[]):rest)
| vault == myvault =
case file2key key of
Nothing -> parse c rest
Just k
| "a/d" `isPrefixOf` status ->
parse (k:succeeded, failed) rest
| "a/e" `isPrefixOf` status ->
parse (succeeded, k:failed) rest
| otherwise ->
parse c rest
parse c (_:rest) = parse c rest