packages feed

cloudy-0.1.0.0: src/Cloudy/Cmd/Scaleway/ListImages.hs

{-# LANGUAGE OverloadedRecordDot #-}

module Cloudy.Cmd.Scaleway.ListImages where

import Cloudy.Cli.Scaleway (ScalewayListImagesCliOpts (..))
import Cloudy.Cmd.Scaleway.Utils (createAuthReq, getZone, runScalewayClientM, fetchPagedApi)
import Cloudy.LocalConfFile (LocalConfFileOpts (..), LocalConfFileScalewayOpts (..))
import Cloudy.Scaleway (Zone (..), PerPage (PerPage), imagesGetApi, ImagesResp (ImagesResp), Image (..))
import Cloudy.Table (printTable, Table (..), Align (..))
import Data.List (sortOn)
import Data.List.NonEmpty (NonEmpty ((:|)), groupAllWith)
import Data.Text (Text, isInfixOf, pack, toLower)
import Servant.Client (ClientM)
import Data.Time (formatTime, defaultTimeLocale)
import qualified Data.List.NonEmpty as NE

data ScalewayListImagesSettings = ScalewayListImagesSettings
  { secretKey :: Text
  , zone :: Zone
  , arch :: Text
  , nameFilter :: Maybe Text
  , showAllVersions :: Bool
  }

mkSettings :: LocalConfFileOpts -> ScalewayListImagesCliOpts -> IO ScalewayListImagesSettings
mkSettings localConfFileOpts cliOpts = do
  let maybeSecretKey = localConfFileOpts.scaleway >>= \scale -> scale.secretKey :: Maybe Text
  secretKey <- getVal maybeSecretKey "Could not find scaleway.secret_key in config file"
  let maybeZoneFromConfFile = localConfFileOpts.scaleway >>= \scale -> scale.defaultZone
  zone <- getZone maybeZoneFromConfFile cliOpts.zone
  pure
    ScalewayListImagesSettings
      { secretKey
      , zone
      , arch = cliOpts.arch
      , nameFilter = cliOpts.nameFilter
      , showAllVersions = cliOpts.allVersions
      }
  where
    getVal :: Maybe a -> String -> IO a
    getVal mayVal errMsg = maybe (error errMsg) pure mayVal

runListImages :: LocalConfFileOpts -> ScalewayListImagesCliOpts -> IO ()
runListImages localConfFileOpts scalewayOpts = do
  settings <- mkSettings localConfFileOpts scalewayOpts
  imgs <-
    runScalewayClientM
      (\err -> error $ "Problem fetching instance types: " <> show err)
      (fetchImages settings)
  displayImages settings imgs

fetchImages :: ScalewayListImagesSettings -> ClientM [Image]
fetchImages settings = do
  let authReq = createAuthReq settings.secretKey
      numPerPage = 100
  ImagesResp imgs <-
    fetchPagedApi
      (imagesGetApi authReq settings.zone (Just settings.arch) (Just $ PerPage numPerPage))
      (\(ImagesResp images1) (ImagesResp images2) -> ImagesResp $ images1 <> images2)
      (\(ImagesResp imgs) -> length imgs)
  pure imgs

displayImages :: ScalewayListImagesSettings -> [Image] -> IO ()
displayImages settings imgs = do
  let nameFilteredImages =
        case settings.nameFilter of
          Nothing -> imgs
          Just name -> filter (\img -> isInfixOf (toLower name) (toLower img.name)) imgs
      volFilteredImages = filter (\img -> img.rootVolType == "unified") nameFilteredImages
      latestImages =
        if settings.showAllVersions
          then volFilteredImages
          else
            nubByNameArch volFilteredImages
      sortByModDateImages = sortOn (\img -> img.modificationDate) latestImages
  case sortByModDateImages of
    [] -> putStrLn "Found no images."
    (hImg : tImg) -> do
      let imgTable = mkTable (hImg :| tImg)
      printTable imgTable

nubByNameArch :: [Image] -> [Image]
nubByNameArch imgs =
  fmap getMostRecent $ groupAllWith (\img -> (img.name, img.arch)) imgs
  where
    getMostRecent :: NonEmpty Image -> Image
    getMostRecent = NE.head . NE.sortWith (\img -> img.modificationDate)

mkTable :: NonEmpty Image -> Table
mkTable images =
  Table
    { tableHeaders =
        (LeftJustified, "image id") :|
        [ (LeftJustified, "name")
        , (LeftJustified, "arch")
        , (LeftJustified, "modify date")
        -- , (LeftJustified, "create date")
        , (LeftJustified, "state")
        ]
    , tableBodyRows = fmap mkRow images
    }

mkRow :: Image -> NonEmpty Text
mkRow img =
  img.id :|
  [ img.name
  , img.arch
  , formatDate img.modificationDate
  -- , formatDate img.creationDate
  , img.state
  ]
  where
    formatDate = pack . formatTime defaultTimeLocale "%Y-%m-%d"