{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PartialTypeSignatures #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE ViewPatterns #-} {-# OPTIONS -fno-warn-orphans #-} ----------------------------------------------------------------------------- -- | -- Module : Hocker.Types -- Copyright : (C) 2016 Awake Networks -- License : Apache-2.0 -- Maintainer : Awake Networks -- Stability : stable ---------------------------------------------------------------------------- module Hocker.Types where import Control.Applicative import Control.Monad.Error.Class import qualified Control.Monad.Except as Except import Control.Monad.IO.Class import qualified Control.Monad.Reader as Reader import Control.Monad.Reader.Class import qualified Crypto.Hash as Hash import qualified Data.ByteString.Lazy import Data.Char (toUpper) import Data.Monoid import Data.Text (Text) import qualified Data.Text as Text import qualified Network.Wreq as Wreq import Network.Wreq.ErrorHandling import qualified Options.Applicative as Options import Options.Generic import URI.ByteString import Hocker.Types.Exceptions import Hocker.Types.Hash () import Hocker.Types.ImageName import Hocker.Types.ImageTag import Hocker.Types.URI () -- | Docker registry URI. type RegistryURI = (URIRef Absolute) -- | Docker registry username. type Username = Text -- | Docker registry user password. type Password = Text -- | Docker image layer sha256 hash digest. type Layer = Text -- | SHA256 hash digest with the hash algorithm identifier prefix, -- stripped type StrippedDigest = Text -- | Docker image manifest JSON. type Manifest = Data.ByteString.Lazy.ByteString -- | Docker image config JSON. type ImageConfigJSON = Data.ByteString.Lazy.ByteString -- | Wreq response type parameterized by the lazy bytestring type. type RspBS = Wreq.Response Data.ByteString.Lazy.ByteString -- | A file extension. type Extension = String -- | RepoName is the part before the forward slash in a docker image -- name, e.g: @library@ in @library/debian@ type RepoNamePart = Text -- | ImageName is the part after the forward slash in a docker image -- name, e.g: @library@ in @library/debian@ type ImageNamePart = Text -- | Docker image config JSON file's sha256 hash digest in Nix's -- base32 encoding. -- -- NB: it's very important to realize there's a significant difference -- between Nix's base32 encoding and the standard base32 encoding! -- (i.e, they're not compatible). type ConfigDigest = Base32Digest -- | Generic top-level optparse-generic CLI args data type and -- specification. -- -- NOTE: `hocker-layer` does not use this data type because it -- requires an additional layer sha256 hash digest argument. data Options w = Options { -- | URI for the registry, optional registry :: w ::: Maybe RegistryURI "URI of registry, defaults to the Docker Hub registry" , credentials :: Maybe Credentials -- | Filesystem path to write output to , out :: w ::: Maybe FilePath "Write content to location" -- | Docker image name (includes the reponame, e.g: library/debian) , imageName :: ImageName -- | Docker image tag , imageTag :: ImageTag } deriving (Generic) instance ParseRecord (Options Wrapped) deriving instance Show (Options Unwrapped) -- | Hocker 'ExceptT' and 'ReaderT' transformer stack threading a -- 'HockerMeta' data type. newtype Hocker a = Hocker { unHocker :: Reader.ReaderT HockerMeta (Except.ExceptT HockerException IO) a } deriving ( Functor , Applicative , Monad , MonadIO , MonadReader HockerMeta , MonadError HockerException ) runHocker :: Hocker a -> HockerMeta -> IO (Either HockerException a) runHocker (unHocker -> d) = Except.runExceptT . interceptHttpExc . Reader.runReaderT d -- | Red wagon record carrying around the environment as we fetch, -- transform, and assemble docker image artifacts. data HockerMeta = HockerMeta { dockerRegistry :: RegistryURI , auth :: Maybe Wreq.Auth , imageName :: ImageName , imageTag :: ImageTag , out :: Maybe FilePath , outDir :: Maybe FilePath , imageLayer :: Maybe (Hash.Digest Hash.SHA256) } deriving (Show) -- | Newtype base32 encoding of a hash digest. -- -- Please note, this base32 encoding is unique to Nix and not -- compatible with other base32 encodings. newtype Base32Digest = Base32Digest Text deriving (Show, Read, Eq) -- | Newtype base16 encoding of a hash digest. -- -- This encoding has no known idiosyncracies specific to Nix, it -- should be compatible with other tools and library's expectations. newtype Base16Digest = Base16Digest Text deriving (Show, Read, Eq) data Credentials = Basic Username Password | BearerToken Text deriving (Show) instance ParseField Credentials where readField = Options.readerError "Internal, fatal error: unexpected use of readField" parseField _ _ _ = (Basic <$> parseUsername <*> parsePassword) <|> (BearerToken <$> parseToken) where parseUsername = Text.pack <$> (Options.option Options.str $ ( Options.metavar "BASIC USERNAME" <> Options.long "username" <> Options.short 'u' <> Options.help "Username part of a basic auth credential" ) ) parsePassword = Text.pack <$> (Options.option Options.str $ ( Options.metavar "BASIC PASSWORD" <> Options.long "password" <> Options.short 'p' <> Options.help "Password part of a basic auth credential" ) ) parseToken = Text.pack <$> (Options.option Options.str $ ( Options.metavar "BEARER TOKEN" <> Options.long "token" <> Options.short 't' <> Options.help "Bearer token retrieved from a call to `docker login` (mutually exclusive to --username and --password)" ) ) instance ParseFields Credentials instance ParseRecord Credentials where parseRecord = fmap Options.Generic.getOnly parseRecord -- | @upperFirst@ uppercases the first letter of the string. upperFirst :: String -> String upperFirst [] = [] upperFirst (h:t) = toUpper h : t