packages feed

aws 0.9.1 → 0.9.2

raw patch · 7 files changed

+112/−16 lines, 7 filesdep +networkdep ~aesondep ~basePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: network

Dependency ranges changed: aeson, base

API changes (from Hackage documentation)

+ Aws: iamToken :: Credentials -> Maybe ByteString
+ Aws: loadCredentialsFromEnvOrFileOrInstanceMetadata :: MonadIO io => FilePath -> Text -> io (Maybe Credentials)
+ Aws: loadCredentialsFromInstanceMetadata :: MonadIO io => io (Maybe Credentials)
+ Aws.Core: NoCredentialsException :: String -> NoCredentialsException
+ Aws.Core: iamToken :: Credentials -> Maybe ByteString
+ Aws.Core: instance Exception NoCredentialsException
+ Aws.Core: instance Show NoCredentialsException
+ Aws.Core: instance Typeable NoCredentialsException
+ Aws.Core: loadCredentialsFromEnvOrFileOrInstanceMetadata :: MonadIO io => FilePath -> Text -> io (Maybe Credentials)
+ Aws.Core: loadCredentialsFromInstanceMetadata :: MonadIO io => io (Maybe Credentials)
+ Aws.Core: newtype NoCredentialsException
+ Aws.Core: noCredentialsErrorMesage :: NoCredentialsException -> String
+ Aws.Network: hostAvailable :: String -> IO Bool
- Aws: Credentials :: ByteString -> ByteString -> IORef [V4Key] -> Credentials
+ Aws: Credentials :: ByteString -> ByteString -> IORef [V4Key] -> Maybe ByteString -> Credentials
- Aws.Core: Credentials :: ByteString -> ByteString -> IORef [V4Key] -> Credentials
+ Aws.Core: Credentials :: ByteString -> ByteString -> IORef [V4Key] -> Maybe ByteString -> Credentials
- Aws.S3.Core: ObjectInfo :: Text -> UTCTime -> Text -> Integer -> StorageClass -> UserInfo -> ObjectInfo
+ Aws.S3.Core: ObjectInfo :: Text -> UTCTime -> Text -> Integer -> StorageClass -> Maybe UserInfo -> ObjectInfo
- Aws.S3.Core: objectOwner :: ObjectInfo -> UserInfo
+ Aws.S3.Core: objectOwner :: ObjectInfo -> Maybe UserInfo

Files

Aws.hs view
@@ -53,7 +53,9 @@ , credentialsDefaultKey , loadCredentialsFromFile , loadCredentialsFromEnv+, loadCredentialsFromInstanceMetadata , loadCredentialsFromEnvOrFile+, loadCredentialsFromEnvOrFileOrInstanceMetadata , loadCredentialsDefault ) where
Aws/Aws.hs view
@@ -79,13 +79,14 @@ -- (see 'loadCredentialsDefault'). baseConfiguration :: MonadIO io => io Configuration baseConfiguration = liftIO $ do-  Just cr <- loadCredentialsDefault-  return Configuration {+  cr <- loadCredentialsDefault+  case cr of+    Nothing -> E.throw $ NoCredentialsException "could not locate aws credentials"+    Just cr' -> return Configuration {                       timeInfo = Timestamp-                    , credentials = cr+                    , credentials = cr'                     , logger = defaultLog Warning                     }--- TODO: better error handling when credentials cannot be loaded  -- | Debug configuration, which logs much more verbosely. dbgConfiguration :: MonadIO io => io Configuration
Aws/Core.hs view
@@ -21,6 +21,7 @@ , XmlException(..) , HeaderException(..) , FormException(..)+, NoCredentialsException(..)   -- ** Response deconstruction helpers , readHex2   -- *** XML@@ -73,7 +74,9 @@ , credentialsDefaultKey , loadCredentialsFromFile , loadCredentialsFromEnv+, loadCredentialsFromInstanceMetadata , loadCredentialsFromEnvOrFile+, loadCredentialsFromEnvOrFileOrInstanceMetadata , loadCredentialsDefault   -- * Service configuration , DefaultServiceConfiguration(..)@@ -85,6 +88,8 @@ ) where +import           Aws.Ec2.InstanceMetadata+import           Aws.Network import qualified Blaze.ByteString.Builder as Blaze import           Control.Applicative import           Control.Arrow@@ -93,6 +98,7 @@ import           Control.Monad.IO.Class import           Control.Monad.Trans.Resource (ResourceT, MonadThrow (throwM)) import           Crypto.Hash+import qualified Data.Aeson               as A import           Data.Byteable import           Data.ByteString          (ByteString) import qualified Data.ByteString          as B@@ -108,6 +114,7 @@ import           Data.Default             (def) import           Data.IORef import           Data.List+import qualified Data.Map                 as M import           Data.Maybe import           Data.Monoid import qualified Data.Text                as T@@ -232,9 +239,11 @@       , secretAccessKey :: B.ByteString         -- | Signing keys for signature version 4       , v4SigningKeys :: IORef [V4Key]+        -- | Signed IAM token+      , iamToken :: Maybe B.ByteString       } instance Show Credentials where-    show c = "Credentials{accessKeyID=" ++ show (accessKeyID c) ++ ",secretAccessKey=" ++ show (secretAccessKey c) ++ "}"+    show c = "Credentials{accessKeyID=" ++ show (accessKeyID c) ++ ",secretAccessKey=" ++ show (secretAccessKey c) ++ ",iamToken=" ++ show (iamToken c) ++ "}"  -- | The file where access credentials are loaded, when using 'loadCredentialsDefault'. --@@ -262,6 +271,7 @@     return Credentials { accessKeyID = T.encodeUtf8 keyID                        , secretAccessKey = T.encodeUtf8 secret                        , v4SigningKeys = ref+                       , iamToken = Nothing                        }       where         hasKey _ [] = False@@ -278,8 +288,37 @@       secret = lk "AWS_ACCESS_KEY_SECRET" `mplus` lk "AWS_SECRET_ACCESS_KEY"   return (Credentials <$> (T.encodeUtf8 . T.pack <$> keyID)                        <*> (T.encodeUtf8 . T.pack <$> secret)-                      <*> return ref)+                      <*> return ref+                      <*> return Nothing) +loadCredentialsFromInstanceMetadata :: MonadIO io => io (Maybe Credentials)+loadCredentialsFromInstanceMetadata = liftIO $ HTTP.withManager $ \mgr ->+  do+    -- check if the path is routable+    avail <- liftIO $ hostAvailable "169.254.169.254"+    if not avail+      then return Nothing+      else do+        info <- liftIO $ E.catch (getInstanceMetadata mgr "latest/meta-data/iam" "info" >>= return . Just) (\(_ :: HTTP.HttpException) -> return Nothing)+        let infodict = info >>= A.decode :: Maybe (M.Map String String)+            info'    = infodict >>= M.lookup "InstanceProfileArn"+        case info' of+          Just name ->+            do+              let name' = drop 1 $ dropWhile (/= '/') $ name+              creds <- liftIO $ E.catch (getInstanceMetadata mgr "latest/meta-data/iam/security-credentials" name' >>= return . Just) (\(_ :: HTTP.HttpException) -> return Nothing)+              -- this token lasts ~6 hours+              let dict   = creds >>= A.decode :: Maybe (M.Map String String)+                  keyID  = dict  >>= M.lookup "AccessKeyId"+                  secret = dict  >>= M.lookup "SecretAccessKey"+                  token  = dict  >>= M.lookup "Token"+              ref <- liftIO $ newIORef []+              return (Credentials <$> (T.encodeUtf8 . T.pack <$> keyID)+                                  <*> (T.encodeUtf8 . T.pack <$> secret)+                                  <*> return ref+                                  <*> (Just . T.encodeUtf8 . T.pack <$> token))+          Nothing -> return Nothing+ -- | Load credentials from environment variables if possible, or alternatively from a file with a given key name. -- -- See 'loadCredentialsFromEnv' and 'loadCredentialsFromFile' for details.@@ -291,6 +330,22 @@       Just cr -> return (Just cr)       Nothing -> loadCredentialsFromFile file key +-- | Load credentials from environment variables if possible, or alternatively from the instance metadata store, or alternatively from a file with a given key name.+--+-- See 'loadCredentialsFromEnv', 'loadCredentialsFromFile' and 'loadCredentialsFromInstanceMetadata' for details.+loadCredentialsFromEnvOrFileOrInstanceMetadata :: MonadIO io => FilePath -> T.Text -> io (Maybe Credentials)+loadCredentialsFromEnvOrFileOrInstanceMetadata file key =+  do+    envcr <- loadCredentialsFromEnv+    case envcr of+      Just cr -> return (Just cr)+      Nothing ->+        do+          instcr <- loadCredentialsFromInstanceMetadata+          case instcr of+            Just cr -> return (Just cr)+            Nothing -> loadCredentialsFromFile file key+ -- | Load credentials from environment variables if possible, or alternative from the default file with the default -- key name. --@@ -301,7 +356,7 @@ loadCredentialsDefault :: MonadIO io => io (Maybe Credentials) loadCredentialsDefault = do   file <- credentialsDefaultFile-  loadCredentialsFromEnvOrFile file credentialsDefaultKey+  loadCredentialsFromEnvOrFileOrInstanceMetadata file credentialsDefaultKey  -- | Protocols supported by AWS. Currently, all AWS services use the HTTP or HTTPS protocols. data Protocol@@ -682,10 +737,16 @@ instance E.Exception HeaderException  -- | An error that occurred during form parsing / validation.-newtype FormException  = FormException { formErrorMesage :: String }+newtype FormException = FormException { formErrorMesage :: String }     deriving (Show, Typeable)  instance E.Exception FormException++-- | No credentials were found and an invariant was violated.+newtype NoCredentialsException = NoCredentialsException { noCredentialsErrorMesage :: String }+    deriving (Show, Typeable)++instance E.Exception NoCredentialsException   -- | A specific element (case-insensitive, ignoring namespace - sadly necessary), extracting only the textual contents.
+ Aws/Network.hs view
@@ -0,0 +1,20 @@+module Aws.Network where++import Data.Maybe+import Control.Exception+import Network.BSD (getProtocolNumber)+import Network.Socket+import System.Timeout++-- Make a good guess if a host is reachable.+hostAvailable :: String -> IO Bool+hostAvailable h = do+  sock <- getProtocolNumber "tcp" >>= socket AF_INET Stream+  addr <- (addrAddress . head) `fmap` getAddrInfo (Just (defaultHints { addrFlags = [ AI_PASSIVE ] } )) (Just h) (Just "80")+  case addr of+    remote@(SockAddrInet _ _) -> do+      v <- catch (timeout 100000 (connect sock remote) >>= return . isJust)+                 (\(_ :: SomeException) -> return False)+      sClose sock+      return v+    _ -> return False
Aws/S3/Core.hs view
@@ -177,7 +177,7 @@       , sqStringToSign = stringToSign       }     where-      amzHeaders = merge $ sortBy (compare `on` fst) s3QAmzHeaders+      amzHeaders = merge $ sortBy (compare `on` fst) (s3QAmzHeaders ++ (fmap (\(k, v) -> (CI.mk k, v)) iamTok))           where merge (x1@(k1,v1):x2@(k2,v2):xs) | k1 == k2  = merge ((k1, B8.intercalate "," [v1, v2]) : xs)                                                  | otherwise = x1 : merge (x2 : xs)                 merge xs = xs@@ -196,6 +196,7 @@              (True, AbsoluteTimestamp time) -> AbsoluteExpires $ s3DefaultExpiry `addUTCTime` time              (True, AbsoluteExpires time) -> AbsoluteExpires time       sig = signature signatureCredentials HmacSHA1 stringToSign+      iamTok = maybe [] (\x -> [("x-amz-security-token", x)]) (iamToken signatureCredentials)       stringToSign = Blaze.toByteString . mconcat . intersperse (Blaze8.fromChar '\n') . concat  $                        [[Blaze.copyByteString $ httpMethod s3QMethod]                        , [maybe mempty (Blaze.copyByteString . Base64.encode . toBytes) s3QContentMd5]@@ -214,7 +215,7 @@           = [("Expires" :: B8.ByteString, fmtTimeEpochSeconds time)             , ("AWSAccessKeyId", accessKeyID signatureCredentials)             , ("SignatureMethod", "HmacSHA256")-            , ("Signature", sig)]+            , ("Signature", sig)] ++ iamTok  s3ResponseConsumer :: HTTPResponseConsumer a                    -> IORef S3Metadata@@ -356,7 +357,7 @@       , objectETag         :: T.Text       , objectSize         :: Integer       , objectStorageClass :: StorageClass-      , objectOwner        :: UserInfo+      , objectOwner        :: Maybe UserInfo       }     deriving (Show) @@ -371,7 +372,9 @@          eTag <- force "Missing object ETag" $ el $/ elContent "ETag"          size <- forceM "Missing object Size" $ el $/ elContent "Size" &| textReadInt          storageClass <- forceM "Missing object StorageClass" $ el $/ elContent "StorageClass" &| parseStorageClass-         owner <- forceM "Missing object Owner" $ el $/ Cu.laxElement "Owner" &| parseUserInfo+         owner <- case el $/ Cu.laxElement "Owner" &| parseUserInfo of+                    (x:_) -> fmap' Just x+                    [] -> return Nothing          return ObjectInfo{                       objectKey          = key                     , objectLastModified = lastModified@@ -380,6 +383,9 @@                     , objectStorageClass = storageClass                     , objectOwner        = owner                     }+    where+      fmap' :: Monad m => (a -> b) -> m a -> m b+      fmap' f ma = ma >>= return . f  data ObjectMetadata     = ObjectMetadata {
README.org view
@@ -90,6 +90,10 @@  ** 0.9 series +- 0.9.2+  - Support for credentials from EC2 instance metadata (only S3 for now)+  - aeson 0.8 compatibility+ - 0.9.1   - Support for multi-page S3 GetBucket requests   - S3 GLACIER support
aws.cabal view
@@ -1,5 +1,5 @@ Name:                aws-Version:             0.9.1+Version:             0.9.2 Synopsis:            Amazon Web Services (AWS) for Haskell Description:         Bindings for Amazon Web Services (AWS), with the aim of supporting all AWS services. To see a high level overview of the library, see the README at <https://github.com/aristidb/aws/blob/master/README.org>. Homepage:            http://github.com/aristidb/aws@@ -20,7 +20,7 @@ Source-repository this   type: git   location: https://github.com/aristidb/aws.git-  tag: 0.9.1+  tag: 0.9.2  Source-repository head   type: git@@ -95,10 +95,11 @@                        Aws.Ses.Core,                        Aws.DynamoDb.Commands,                        Aws.DynamoDb.Commands.Table,-                       Aws.DynamoDb.Core+                       Aws.DynamoDb.Core,+                       Aws.Network    Build-depends:-                       aeson                >= 0.6     && < 0.8,+                       aeson                >= 0.6,                        base                 == 4.*,                        base16-bytestring    == 0.1.*,                        base64-bytestring    == 1.0.*,@@ -119,6 +120,7 @@                        lifted-base          >= 0.1     && < 0.3,                        monad-control        >= 0.3,                        mtl                  == 2.*,+                       network              == 2.*,                        old-locale           == 1.*,                        resourcet            >= 1.1     && < 1.2,                        text                 >= 0.11,