packages feed

aws-ec2-knownhosts-0.1.0.0: exec/aws-ec2-keysync/Main.hs

{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Control.Monad                ((<=<))
import           Data.Aeson                   (Result (..), fromJSON, json')
import           Data.Bool                    (bool)
import           Data.Function                (on)
import           Data.List                    (foldl')
import           Data.Monoid                  ((<>))
import           Filesystem.Path.CurrentOS    (encodeString, fromText)
import           Prelude                      hiding (FilePath)
import qualified System.IO.Streams            as Streams
import           System.IO.Streams.Attoparsec (parseFromStream)
import           Turtle                       (ExitCode (..), FilePath, Parser, Text, argPath, argText, exit, format,
                                               fp, isRegularFile, options, proc, rm, stat)

import           AWS.PubKeys                  (writePubKeys)
import           AWS.Types                    (Ec2Instance (..))


main :: IO ()
main = options "Synchronize pubkeys from EC2 to AWS S3" args >>= processPubKeys


args :: Parser (FilePath, FilePath, Text)
args = (,,)
    <$> argPath "local_pubkey_filename" "Local pubkey file to send to S3"
    <*> argPath "remote_pubkey_filename" "Destination pubkey file to send to S3"
    <*> argText "s3_bucket_name" "AWS S3 bucket name to store pubkeys"


processPubKeys :: (FilePath, FilePath, Text) -> IO ()
processPubKeys (localPubKeyFile, remotePubKeyFile, bucketName) = do
    newPubKeys <- readPubKeys localPubKeyFile
    remotePubKeys <- getFromS3 remotePubKeyFile tempRemoteFile bucketName
    writePubKeys (encodeString mergedKeyFile) $ merge remotePubKeys newPubKeys
    copyToS3 mergedKeyFile remotePubKeyFile bucketName
  where
    tempRemoteFile :: FilePath
    tempRemoteFile = "/tmp/" <> fromText bucketName <> "-" <> remotePubKeyFile
    mergedKeyFile = "/tmp/merged-keys.json"


readPubKeys :: FilePath -> IO [Ec2Instance]
readPubKeys pubKeyFile =
    Streams.withFileAsInput (encodeString pubKeyFile) (handleResult <=< parseFromStream pubKeyParser)
  where pubKeyParser = fmap fromJSON json'


merge :: [Ec2Instance] -> [Ec2Instance] -> [Ec2Instance]
merge = foldl' replace
  where
    replace instancePubKeys newInstancePubKey =
        newInstancePubKey : filter (compareFqdn newInstancePubKey) instancePubKeys


compareFqdn :: Ec2Instance -> Ec2Instance -> Bool
compareFqdn = (/=) `on` fqdn


handleResult :: Result a -> IO a
handleResult (Success a) = return a
handleResult (Error s)   = putStrLn ("Parse error: " <> s) >> exit (ExitFailure (-1))


getFromS3 :: FilePath -> FilePath -> Text -> IO [Ec2Instance]
getFromS3 remoteFile tempRemoteFile bucketName = do
    _ <- proc "aws" [ "s3", "cp" , source, format fp tempRemoteFile ] ""
    isRegularFile <$> stat tempRemoteFile >>= bool (return []) (readPubKeys tempRemoteFile)
  where
    source = "s3://" <> bucketName <> "/" <> format fp remoteFile


copyToS3 :: FilePath -> FilePath -> Text -> IO ()
copyToS3 localFile remoteFile bucketName =
    proc "aws" ["s3", "cp", format fp localFile, dest] "" >>= checkUpload
  where
    dest = "s3://" <> bucketName <> "/" <> format fp remoteFile
    checkUpload ExitSuccess = putStrLn "Upload successful"
    checkUpload _ = putStrLn "Failure uploading pubkey file. Check AWS credentials." >> exit (ExitFailure (-1))


cleanup :: FilePath -> FilePath -> IO ()
cleanup tempRemoteFile mergedKeyFile = rm tempRemoteFile >> rm mergedKeyFile