packages feed

aws-kinesis-client-0.4.0.2: cli/CLI/Config.hs

-- Copyright (c) 2013-2015 PivotCloud, Inc.
--
-- CLI.Config
--
-- Please feel free to contact us at licensing@pivotmail.com with any
-- contributions, additions, or other feedback; we would love to hear from
-- you.
--
-- Licensed under the Apache License, Version 2.0 (the "License"); you may
-- not use this file except in compliance with the License. You may obtain a
-- copy of the License at http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-- WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-- License for the specific language governing permissions and limitations
-- under the License.

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE UnicodeSyntax #-}

-- |
-- Module: CLI.Config
-- Copyright: Copyright © 2013-2015 PivotCloud, Inc.
-- License: Apache-2.0
-- Maintainer: Jon Sterling <jsterling@alephcloud.com>
-- Stability: experimental
--
module CLI.Config
( -- * Configuration Type
  Config(..)
, defaultConfig
  -- ** Lenses
, configStreamName
, configRegion
, configLimit
, configTimeout
, configIteratorType
, configCredentialConfig
, configStateIn
, configStateOut
  -- ** Parser
, pConfig
  -- ** Validation
, validateConfig
) where

import Aws.General
import Aws.Kinesis
import Configuration.Utils hiding (Lens')
import Configuration.Utils.Aws.Credentials
import Control.Lens hiding ((.=))

import Control.Monad
import Control.Monad.Error.Class
import Control.Monad.Unicode
import Data.Monoid.Unicode
import qualified Data.Text as T
import qualified Data.Text.Lens as T
import Options.Applicative
import Options.Applicative.Types
import Prelude.Unicode

data Config
  = Config
  { _configStreamName ∷ !StreamName
  , _configRegion ∷ !Region
  , _configLimit ∷ !(Maybe Int)
  , _configTimeout ∷ !(Maybe Int)
  , _configIteratorType ∷ !ShardIteratorType
  , _configCredentialConfig ∷ !CredentialConfig
  , _configStateIn ∷ !(Maybe FilePath)
  , _configStateOut ∷ !(Maybe FilePath)
  } deriving Show

invalidStreamName ∷ StreamName
invalidStreamName = "NOT_A_VALID_STREAM_NAME"

defaultConfig ∷ Config
defaultConfig = Config
  { _configStreamName = invalidStreamName
  , _configRegion = UsWest2
  , _configLimit = Nothing
  , _configTimeout = Nothing
  , _configIteratorType = TrimHorizon
  , _configCredentialConfig = defaultCredentialConfig
  , _configStateIn = Nothing
  , _configStateOut = Nothing
  }

configStreamName ∷ Lens' Config StreamName
configStreamName = lens _configStreamName $ \x y → x { _configStreamName = y }

configRegion ∷ Lens' Config Region
configRegion = lens _configRegion $ \x y → x { _configRegion = y }

configLimit ∷ Lens' Config (Maybe Int)
configLimit = lens _configLimit $ \x y → x { _configLimit = y }

configTimeout ∷ Lens' Config (Maybe Int)
configTimeout = lens _configTimeout $ \x y → x { _configTimeout = y }

configIteratorType ∷ Lens' Config ShardIteratorType
configIteratorType = lens _configIteratorType $ \x y → x { _configIteratorType = y }

configCredentialConfig ∷ Lens' Config CredentialConfig
configCredentialConfig = lens _configCredentialConfig $ \x y → x { _configCredentialConfig = y }

configStateIn ∷ Lens' Config (Maybe FilePath)
configStateIn = lens _configStateIn $ \x y → x { _configStateIn = y }

configStateOut∷ Lens' Config (Maybe FilePath)
configStateOut = lens _configStateOut $ \x y → x { _configStateOut = y }

-- makeLenses ''Config

instance FromJSON (Config → Config) where
  parseJSON =
    withObject "Config" $ \o → id
      <$< configStreamName ..: "stream_name" % o
      <*< setProperty configRegion "region" (withText "Region" $ either fail return ∘ fromText) o
      <*< configLimit ..: "limit" % o
      <*< configTimeout ..: "timeout" % o
      <*< configIteratorType ..: "iterator_type" % o
      <*< configCredentialConfig %.: "aws_credentials" % o
      <*< configStateIn ..: "restore_state" % o
      <*< configStateOut ..: "save_state" % o

instance ToJSON Config where
  toJSON Config{..} = object
    [ "stream_name" .= _configStreamName
    , "region" .= String (toText _configRegion)
    , "limit" .= _configLimit
    , "timeout" .= _configTimeout
    , "iterator_type" .= _configIteratorType
    , "aws_credentials" .= _configCredentialConfig
    , "restore_state" .= _configStateIn
    , "save_state" .= _configStateOut
    ]


eitherTextReader
  ∷ (T.IsText i, T.IsText e)
  ⇒ (i → Either e a)
  → ReadM a
eitherTextReader f =
  eitherReader $
    (_Left %~ view T.unpacked) ∘ f ∘ view T.packed

streamNameParser ∷ Parser StreamName
streamNameParser =
  option (eitherTextReader streamName) $
    long "stream-name"
    ⊕ short 's'
    ⊕ metavar "SN"
    ⊕ help "Fetch from the Kinesis stream named `SN`"

limitParser ∷ Parser Int
limitParser =
  option auto $
    long "limit"
    ⊕ short 'l'
    ⊕ metavar "L"
    ⊕ help "Fetch `L` records. If a limit is provided, then the run will only be considered successful if it results in the CLI fetching `L` records; otherwise, a run is always considered successful."

timeoutParser ∷ Parser Int
timeoutParser =
  option auto $
    long "timeout"
    ⊕ short 't'
    ⊕ metavar "T"
    ⊕ help "Terminate the consumer after `T` seconds. Even if a limit has been provided, the consumer will terminate after at most `T` seconds."

regionParser ∷ Parser Region
regionParser =
  option regionReader $
    long "region"
    ⊕ value UsWest2
    ⊕ help "Choose an AWS Kinesis region (default: us-west-2)"

regionReader ∷ ReadM Region
regionReader = do
  fromText ∘ T.pack <$> readerAsk ≫=
    either readerError return

iteratorTypeReader ∷ ReadM ShardIteratorType
iteratorTypeReader = do
  readIteratorType ∘ T.pack <$> readerAsk ≫=
    either readerError return

  where
    readIteratorType = \case
      "LATEST" → Right Latest
      "Latest" → Right Latest
      "TRIM_HORIZON" → Right TrimHorizon
      "TrimHorizon" → Right TrimHorizon
      it → Left $ "Unsupported shard iterator type: " ⊕ T.unpack it

iteratorTypeParser ∷ Parser ShardIteratorType
iteratorTypeParser =
  option iteratorTypeReader $
    long "iterator-type"
    ⊕ short 'i'
    ⊕ metavar "IT"
    ⊕ help "Iterator type (LATEST|TRIM_HORIZON)"
    ⊕ value TrimHorizon
    ⊕ showDefault

stateOutParser ∷ Parser FilePath
stateOutParser =
  strOption $
    long "save-state"
    ⊕ help "Write the last known state of each shard to a file; this will only occur when the run has completed in a \"successful\" state."
    ⊕ metavar "FILE"

stateInParser ∷ Parser FilePath
stateInParser =
  strOption $
    long "restore-state"
    ⊕ help "Read a saved stream state from a file. For any shards whose state is restored, the 'AFTER_SEQUENCE_NUMBER' iterator type will be used; other shards will use the iterator type you have specified. Some shards may have been merged or closed between when the state was saved and restored; at this point, no effort has been made to do anything here beyond the obvious (shards are identified by their shard-id)."
    ⊕ metavar "FILE"

pConfig ∷ MParser Config
pConfig = id
  <$< configStreamName .:: streamNameParser
  <*< configRegion .:: regionParser
  <*< configLimit .:: optional limitParser
  <*< configTimeout .:: optional timeoutParser
  <*< configIteratorType .:: iteratorTypeParser
  <*< configCredentialConfig %:: pCredentialConfig "aws-"
  <*< configStateIn .:: optional stateInParser
  <*< configStateOut .:: optional stateOutParser

validateConfig ∷ ConfigValidation Config λ
validateConfig Config{..} = do
  when (_configStreamName == invalidStreamName) $
    throwError "stream name not configured"

  validateCredentialConfig _configCredentialConfig