module Data.Docker.Nix.FetchDocker where
import Control.Lens
import Control.Monad
import Control.Monad.Except as Except
import Data.Aeson.Lens
import qualified Data.Bifunctor as Bifunctor
import Data.Coerce
import Data.Fix
import Data.Maybe
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Text.Encoding (decodeUtf8')
import Data.Text.Encoding.Error
import Nix.Expr
import URI.ByteString
import Data.Docker.Image.Types
import Data.Docker.Nix.Lib as Nix.Lib
import Hocker.Lib
import Network.Wreq.Docker.Registry (pluckLayersFrom)
import Hocker.Types
import Hocker.Types.Exceptions
import Hocker.Types.ImageTag
constFetchdocker :: Text
constFetchdocker = "fetchdocker"
constFetchDockerConfig :: Text
constFetchDockerConfig = "fetchDockerConfig"
constFetchDockerLayer :: Text
constFetchDockerLayer = "fetchDockerLayer"
generate :: HockerImageMeta -> IO (Either HockerException NExpr)
generate dim@HockerImageMeta{..} = runExceptT $
case (manifestJSON ^? key "schemaVersion" . _Integer) of
Just 2 -> do
nixhash <- Hocker.Lib.findExec "nix-hash"
configDigest <- Nix.Lib.toBase32Nix nixhash . Base16Digest $ pluckedConfigDigest
layerDigests <- forM pluckedLayerDigests $ \d16 ->
(Base16Digest d16,) <$> (Nix.Lib.toBase32Nix nixhash $ Base16Digest d16)
ExceptT (pure $ generateFetchDockerExpr dim configDigest layerDigests)
Just v ->
throwError $ HockerException ("Expected a version 2 manifest but got version " <> (show v)) Nothing Nothing
Nothing ->
throwError $ HockerException "No key 'schemaVersion' in JSON object" Nothing Nothing
where
pluckedConfigDigest = Hocker.Lib.stripHashId $ manifestJSON ^. key "config" . key "digest" . _String
pluckedLayerDigests = Hocker.Lib.stripHashId <$> pluckLayersFrom manifestJSON
generateFetchDockerExpr :: HockerImageMeta -> ConfigDigest -> [(Base16Digest, Base32Digest)] -> Either HockerException NExpr
generateFetchDockerExpr dim@HockerImageMeta{..} configDigest layerDigests = do
let commonInherits =
[ StaticKey "registry"
, StaticKey "repository"
, StaticKey "imageName"
]
let genLayerId i = mkSym . Text.pack $ "layer" <> show i
let fetchconfig = mkFetchDockerConfig (inherit $ ((StaticKey "tag"):commonInherits)) configDigest
fetchlayers =
mkLets
(mkFetchDockerLayers (inherit commonInherits) layerDigests)
(mkList $ fmap genLayerId [0..(Prelude.length layerDigests)1])
fetchDockerExpr <- mkFetchDocker dim fetchconfig fetchlayers
pure
(mkFunction
(mkParamset
[ ("fetchdocker", Nothing)
, ("fetchDockerConfig", Nothing)
, ("fetchDockerLayer", Nothing)
]) fetchDockerExpr)
mkFetchDocker :: HockerImageMeta -> NExpr -> NExpr -> Either HockerException NExpr
mkFetchDocker HockerImageMeta{..} fetchconfig fetchlayers = do
registry <- Bifunctor.first mkHockerException serializedRegistry
pure
(mkApp (mkSym constFetchdocker)
(recAttrsE
[ ("name", mkStr $ fromMaybe imageName altImageName)
, ("registry", mkStr registry)
, ("repository", mkStr imageRepo)
, ("imageName", mkStr imageName)
, ("tag", mkStr (Text.pack $ coerce imageTag))
, ("imageConfig", fetchconfig)
, ("imageLayers", fetchlayers)
]))
where
serializedRegistry = decodeUtf8' (serializeURIRef' dockerRegistry)
mkHockerException (DecodeError err char) =
HockerException (err <> " " <> (show char)) Nothing Nothing
mkHockerException err =
HockerException (show err) Nothing Nothing
mkFetchDockerConfig :: Binding NExpr -> Base32Digest -> NExpr
mkFetchDockerConfig inherits (Base32Digest digest) =
mkApp (mkSym constFetchDockerConfig)
(Fix $ NSet [ inherits, "sha256" $= (mkStr digest) ])
mkFetchDockerLayers :: Binding NExpr -> [(Base16Digest, Base32Digest)] -> [Binding NExpr]
mkFetchDockerLayers inherits layerDigests =
fmap mkFetchLayer $ Prelude.zip [0..(Prelude.length layerDigests)] layerDigests
where
mkLayerId i = Text.pack $ "layer" <> show i
mkFetchLayer (i, ((Base16Digest d16), (Base32Digest d32))) =
(mkLayerId i) $= mkApp (mkSym constFetchDockerLayer)
(Fix $ NSet
[ inherits
, "layerDigest" $= (mkStr d16)
, "sha256" $= (mkStr d32)
])