diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,14 @@
 
 `fedora-dists` uses [PVP Versioning](https://pvp.haskell.org).
 
+## 2.0.0 (2022-01-17)
+- Distribution.Fedora: new IO-based Dist API
+  without Fedora version hardcoding
+- caches current releases from
+  https://pdc.fedoraproject.org/rest_api/v1/product-versions/
+  under ~/.fedora/
+- Distribution.Fedora.Branch provides Branch type taken from juhp/fbrnch
+
 ## 1.1.2 (2020-01-24)
 - mockConfig rawhide version
 - drop F29 and EPEL6
diff --git a/FedoraDists.hs b/FedoraDists.hs
deleted file mode 100644
--- a/FedoraDists.hs
+++ /dev/null
@@ -1,135 +0,0 @@
--- |
--- Module      :  FedoraDists
--- Copyright   :  (C) 2014-2020  Jens Petersen
---
--- Maintainer  :  Jens Petersen <petersen@fedoraproject.org>
--- Stability   :  alpha
--- Portability :  portable
---
--- Explanation: Fedora dist metadata
-
--- This program is free software: you can redistribute it and/or modify
--- it under the terms of the GNU General Public License as published by
--- the Free Software Foundation, either version 3 of the License, or
--- (at your option) any later version.
-
-module FedoraDists
-  (Dist(..),
-   dists,
-   distBranch,
-   distContainer,
-   distRepo,
-   distUpdates,
-   distOverride,
-   kojicmd,
-   mockConfig,
-   releaseVersion,
-   rawhide,
-   rawhideRelease,
-   rpkg,
-   rpmDistTag) where
-
-import Data.Version
-import Text.Read
-import Text.ParserCombinators.ReadP (char, eof, string)
-
-#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,0))
-#else
-import Control.Applicative ((<$>), (*>))
-import Data.Traversable (traverse)
-#endif
-
--- | The `Dist` datatype specifies the target OS and version.
--- (roughly corresponds to a git branch)
-data Dist = Fedora Int -- ^ Fedora release
-          | EPEL Int -- ^ EPEL release
-          | RHEL Version -- ^ RHEL version
-  deriving (Eq)
-
-instance Show Dist where
-  show (Fedora n) = "f" ++ show n
-  show (EPEL n) = (if n <= 6 then "el" else "epel") ++ show n
-  show (RHEL v) = "rhel-" ++ showVersion v
-
--- | Read from eg "f29", "epel7"
-instance Read Dist where
-  readPrec = choice [pFedora, pEPEL, pRHEL] where
-    pFedora = Fedora <$> (lift (char 'f') *> readPrec)
-    pEPEL = EPEL <$> (lift (string "epel") *> readPrec)
-    pRHEL = RHEL <$> lift (do
-      v <- string "rhel-" >> parseVersion
-      eof
-      return v)
-
--- | Current maintained distribution releases.
-dists :: [Dist]
-dists = [rawhide, Fedora 31, Fedora 30, EPEL 8, EPEL 7]
-
--- | The Fedora release number corresponding to current Rawhide
-rawhideRelease :: Int
-rawhideRelease = 32
-
--- | The Fedora release corresponding to Rawhide
-rawhide :: Dist
-rawhide = Fedora rawhideRelease
-
--- | Maps `Dist` to package distgit branch
-distBranch :: Dist -> String
-distBranch (Fedora n) | n >= rawhideRelease = "master"
-distBranch d = show d
-
--- | Map `Dist` to DNF/YUM repo name
-distRepo :: Dist -> String
-distRepo (Fedora n) | n >= rawhideRelease = "rawhide"
-                    | otherwise = "fedora"
-distRepo (EPEL _) = "epel"
-distRepo (RHEL _) = "rhel"
-
--- | Map `Dist` to Maybe the DNF/YUM updates repo name
-distUpdates :: Dist -> Maybe String
-distUpdates (Fedora n) | n >= rawhideRelease = Nothing
-distUpdates (Fedora _) = Just "updates"
-distUpdates _ = Nothing
-
--- | Whether dist has overrides in Bodhi
-distOverride :: Dist -> Bool
-distOverride d = d `notElem` [rawhide, Fedora 32 , EPEL 9]
-
--- | OS release major version for `Dist`
-releaseVersion :: Dist -> String
-releaseVersion (Fedora n) | n >= rawhideRelease = "rawhide"
-releaseVersion (Fedora n) = show n
-releaseVersion (EPEL n) = show n
-releaseVersion (RHEL n) = show n
-
--- | `Dist` tag (appended to rpm package release field)
-rpmDistTag :: Dist -> String
-rpmDistTag (Fedora n) = ".fc" ++ show n
-rpmDistTag (RHEL v) = ".el" ++ (show . head . versionBranch) v
-rpmDistTag d = '.' : show d
-
--- | Command line tool for `Dist` (eg "koji")
-kojicmd :: Dist -> String
-kojicmd (RHEL _) = "brew"
-kojicmd _ =  "koji"
-
--- | rpkg command for `Dist` (eg "fedpkg")
-rpkg :: Dist -> String
-rpkg (RHEL _) = "rhpkg"
-rpkg _ = "fedpkg"
-
--- | Mock configuration for `Dist` and arch
-mockConfig :: Dist -> String -> String
-mockConfig dist arch =
-  let prefix =
-        case dist of
-          Fedora _ -> "fedora"
-          _ -> distRepo dist
-  in
-  prefix ++ "-" ++ releaseVersion dist ++ "-" ++ arch
-
--- | Map `Dist` to a container image
-distContainer :: Dist -> String
-distContainer (Fedora n) = "fedora:" ++ show n
-distContainer (EPEL n) = "centos:" ++ show n
-distContainer (RHEL n) = "ubi" ++ show n ++ "/ubi"
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
 [![GPL-3 license](https://img.shields.io/badge/license-GPL--3-blue.svg)](LICENSE)
 [![Stackage Lts](http://stackage.org/package/fedora-dists/badge/lts)](http://stackage.org/lts/package/fedora-dists)
 [![Stackage Nightly](http://stackage.org/package/fedora-dists/badge/nightly)](http://stackage.org/nightly/package/fedora-dists)
-[![Build status](https://secure.travis-ci.org/juhp/fedora-dists.svg)](https://travis-ci.org/juhp/fedora-dists)
+[![GitHub CI](https://github.com/juhp/fedora-dists/workflows/build/badge.svg)](https://github.com/juhp/fedora-dists/actions)
+
 
 Library for Fedora distribution versions
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/fedora-dists.cabal b/fedora-dists.cabal
--- a/fedora-dists.cabal
+++ b/fedora-dists.cabal
@@ -1,35 +1,58 @@
 cabal-version:       1.18
 name:                fedora-dists
-version:             1.1.2
+version:             2.0.0
 synopsis:            Library for Fedora distribution versions
 description:
             This library provides the Dist datatype and various associated
             metadata functions for Red Hat distributions (Fedora, EPEL, RHEL)
-            needed for packaging development and building.
+            needed for packaging development and building. It uses current
+            releasedata from Fedora PDC. Also provides a Branch type.
 homepage:            https://github.com/juhp/fedora-dists
 bug-reports:         https://github.com/juhp/fedora-dists/issues
 license:             GPL-3
 license-file:        LICENSE
 author:              Jens Petersen
 maintainer:          petersen@fedoraproject.org
-copyright:           2018-2020 Jens Petersen
+copyright:           2018-2022 Jens Petersen
 category:            Distribution
 build-type:          Simple
 extra-doc-files:     README.md
                    , CHANGELOG.md
-tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2,
-                     GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.2
+tested-with:         GHC == 7.10.3, GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4,
+                     GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.7
 
 source-repository head
   type:                git
   location:            https://github.com/juhp/fedora-dists.git
 
 library
-  exposed-modules:     FedoraDists
+  exposed-modules:     Distribution.Fedora
+                       Distribution.Fedora.Branch
+  other-modules:       Distribution.Fedora.Release
+                       Distribution.Fedora.Products
+  hs-source-dirs:      src
 
   build-depends:       base >= 4.6 && < 5
+                     , aeson
+                     , bytestring
+                     , pdc
+                     , cached-json-file
+                     , text
+                     , time
 
   ghc-options:         -Wall
-                       -fwarn-missing-signatures
-  default-extensions:  CPP
+  if impl(ghc >= 8.0)
+    ghc-options:       -Wcompat
+                       -Widentities
+                       -Wincomplete-uni-patterns
+                       -Wincomplete-record-updates
+                       -Wredundant-constraints
+  if impl(ghc >= 8.2)
+    ghc-options:       -fhide-source-paths
+  if impl(ghc >= 8.4)
+    ghc-options:       -Wmissing-export-lists
+                       -Wpartial-fields
+  if impl(ghc >= 8.10)
+    ghc-options:       -Wunused-packages
+
   default-language:    Haskell2010
diff --git a/src/Distribution/Fedora.hs b/src/Distribution/Fedora.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Fedora.hs
@@ -0,0 +1,215 @@
+{-# LANGUAGE CPP #-}
+
+-- |
+-- Module      :  Distribution.Fedora
+-- Copyright   :  (C) 2014-2021  Jens Petersen
+--
+-- Maintainer  :  Jens Petersen <petersen@fedoraproject.org>
+--
+-- Explanation: Fedora Dist type and functions
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+
+module Distribution.Fedora
+  (Dist(..),
+   getReleaseIds,
+   getFedoraReleaseIds,
+   getFedoraDists,
+   getEPELReleaseIds,
+   getRawhideDist,
+   getLatestFedoraDist,
+   getLatestEPELDist,
+   rawhideVersionId,
+   distBranch,
+   distRepo,
+   distUpdates,
+   distOverride,
+   mockConfig,
+   distVersion,
+   kojicmd,
+   rpkg,
+   rpmDistTag) where
+
+import Data.Maybe
+import qualified Data.Text as T
+import Data.Text (Text)
+import Data.Version
+import Text.Read
+import Text.ParserCombinators.ReadP (char, eof, string)
+
+#if !MIN_VERSION_base(4,8,0)
+import Control.Applicative ((<$>), (*>))
+import Data.Traversable (traverse)
+#endif
+
+import Distribution.Fedora.Products
+import Distribution.Fedora.Release
+
+-- | The `Dist` datatype specifies the target OS and version.
+-- (roughly corresponds to a git branch)
+data Dist = RHEL Version -- ^ RHEL version
+          | EPEL Int -- ^ EPEL release
+          | Fedora Int -- ^ Fedora release
+  deriving (Eq, Ord)
+
+instance Show Dist where
+  show (Fedora n) = "f" ++ show n
+  show (EPEL n) = (if n <= 6 then "el" else "epel") ++ show n
+  show (RHEL v) = "rhel-" ++ showVersion v
+
+-- | Read from eg "f35", "epel8"
+instance Read Dist where
+  readPrec = choice [pFedora, pEPEL, pRHEL] where
+    pFedora = Fedora <$> (lift (char 'f') *> readPrec)
+    pEPEL = EPEL <$> (lift (string "epel") *> readPrec)
+    pRHEL = RHEL <$> lift (do
+      v <- string "rhel-" >> parseVersion
+      eof
+      return v)
+
+getReleases :: IO [Release]
+getReleases = reverse . mapMaybe readRelease <$> getProducts
+
+-- | gets list of current releases (Fedora and EPEL)
+--
+-- The data is stored in ~/.fedora/product-versions.json
+-- and refreshed from Fedora PDC if older than 5.5 hours
+getReleaseIds :: IO [Text]
+getReleaseIds = map releaseProductVersionId <$> getReleases
+
+getProductReleases :: Text -> IO [Release]
+getProductReleases name =
+  filter (\p -> releaseProduct p == name) <$> getReleases
+
+-- getRelease :: Text -> IO (Maybe Release)
+-- getRelease pv =
+--   find (\p -> releaseProductVersionId p == pv) <$> getReleases
+
+getFedoraReleases :: IO [Release]
+getFedoraReleases =
+  getProductReleases (T.pack "fedora")
+
+-- | gets current Fedora releases
+getFedoraReleaseIds :: IO [Text]
+getFedoraReleaseIds =
+  map releaseProductVersionId <$> getFedoraReleases
+
+getEPELReleases :: IO [Release]
+getEPELReleases =
+  getProductReleases (T.pack "epel")
+
+-- | gets current EPEL releases
+getEPELReleaseIds :: IO [Text]
+getEPELReleaseIds =
+  map releaseProductVersionId <$> getEPELReleases
+
+-- | Rawhide version id
+rawhideVersionId :: Text
+rawhideVersionId = T.pack "fedora-rawhide"
+
+-- fails on rawhide - only use on other releases
+releaseMajorVersion :: Release -> Int
+releaseMajorVersion = read . T.unpack . releaseVersion
+
+releaseDist :: Release -> Dist
+releaseDist = Fedora . releaseMajorVersion
+
+releaseDists :: [Release] -> [Dist]
+releaseDists rels =
+  map mkDist rels
+  where
+    mkDist :: Release -> Dist
+    mkDist r | releaseProductVersionId r == rawhideVersionId = newerDist latestbranch
+             | otherwise = releaseDist r
+
+    latestbranch = maximum $ filter (\p -> releaseProductVersionId p /= rawhideVersionId) rels
+
+    newerDist = Fedora . (+ 1) . releaseMajorVersion
+
+-- | get list of current Fedora Dist's
+getFedoraDists :: IO [Dist]
+getFedoraDists = releaseDists <$> getFedoraReleases
+
+-- | get current Dist for Fedora Rawhide
+getRawhideDist :: IO Dist
+getRawhideDist =
+  head . releaseDists <$> getFedoraReleases
+
+-- | get newest Fedora branch
+getLatestFedoraDist :: IO Dist
+getLatestFedoraDist =
+  releaseDist . maximum . filter (\p -> releaseProductVersionId p /= rawhideVersionId) <$> getFedoraReleases
+
+-- | get newest EPEL release
+getLatestEPELDist :: IO Dist
+getLatestEPELDist =
+  EPEL . releaseMajorVersion . maximum <$> getEPELReleases
+
+-- activeRelease :: Text -> IO Bool
+-- activeRelease pv = do
+--   res <- filter (\p -> releaseProductVersionId p == pv) <$> getReleases
+--   return $ not (null res)
+
+-- | Maps `Dist` to package dist-git branch name, relative to latest branch
+--
+-- > distBranch (Fedora 35) (Fedora 36) == "rawhide"
+-- > distBranch (Fedora 35) (Fedora 34) == "f34"
+distBranch :: Dist -- ^ latest branch
+           -> Dist -> String
+distBranch branch (Fedora n) | Fedora n > branch = "rawhide"
+distBranch _ d = show d
+
+-- | Map `Dist` to DNF/YUM repo name, relative to latest branch
+distRepo :: Dist -> Dist -> String
+distRepo branched (Fedora n) | Fedora n > branched = "rawhide"
+                             | otherwise = "fedora"
+distRepo _ (EPEL _) = "epel"
+distRepo _ (RHEL _) = "rhel"
+
+-- | Map `Dist` to Maybe the DNF/YUM updates repo name, relative to latest branch
+distUpdates :: Dist -> Dist -> Maybe String
+distUpdates branched (Fedora n) | Fedora n > branched  = Nothing
+distUpdates _ (Fedora _) = Just "updates"
+distUpdates _ _ = Nothing
+
+-- | Whether dist has overrides in Bodhi, relative to latest branch
+distOverride :: Dist -> Dist -> Bool
+distOverride branch (Fedora n) = Fedora n <= branch
+distOverride _ (EPEL n) = n < 9
+distOverride _ _ = False
+
+-- | OS release major version for `Dist`, relative to latest branch
+distVersion :: Dist -> Dist -> String
+distVersion branch (Fedora n) | Fedora n > branch = "rawhide"
+distVersion _ (Fedora n) = show n
+distVersion _ (EPEL n) = show n
+distVersion _ (RHEL n) = show n
+
+-- | Mock configuration for `Dist` and arch, relative to latest branch
+mockConfig :: Dist -> Dist -> String -> String
+mockConfig branch dist arch =
+  let prefix =
+        case dist of
+          Fedora _ -> "fedora"
+          _ -> distRepo branch dist
+  in
+  prefix ++ "-" ++ distVersion branch dist ++ "-" ++ arch
+
+-- | `Dist` tag (appended to rpm package release field)
+rpmDistTag :: Dist -> String
+rpmDistTag (Fedora n) = ".fc" ++ show n
+rpmDistTag (EPEL n) = ".el" ++ show n
+rpmDistTag (RHEL v) = ".el" ++ (show . head . versionBranch) v
+
+-- | Command line tool for `Dist` (eg "koji")
+kojicmd :: Dist -> String
+kojicmd (RHEL _) = "brew"
+kojicmd _ =  "koji"
+
+-- | rpkg command for `Dist` (eg "fedpkg")
+rpkg :: Dist -> String
+rpkg (RHEL _) = "rhpkg"
+rpkg _ = "fedpkg"
diff --git a/src/Distribution/Fedora/Branch.hs b/src/Distribution/Fedora/Branch.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Fedora/Branch.hs
@@ -0,0 +1,185 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP               #-}
+
+-- |
+-- Module      :  Distribution.Fedora.Branch
+-- Copyright   :  (C) 2020-2022  Jens Petersen
+--
+-- Maintainer  :  Jens Petersen <petersen@fedoraproject.org>
+--
+-- Explanation: Fedora Branch type and functions
+
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+
+module Distribution.Fedora.Branch
+  ( Branch(..)
+  , readBranch
+  , readBranch'
+  , eitherBranch
+  , eitherBranch'
+  , readActiveBranch
+  , readActiveBranch'
+  , eitherActiveBranch
+  , newerBranch
+  , releaseBranch
+  , getFedoraBranches
+  , getFedoraBranched
+  , branchDestTag
+  , branchDist
+  , branchTarget
+  )
+where
+
+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,0))
+#else
+import Control.Applicative ((<$>))
+#endif
+
+import Data.Char (isDigit)
+import qualified Data.Text as T
+
+import qualified Distribution.Fedora as Dist
+
+-- | Branch datatype
+--
+-- Branch can be rawhide, or a fedora or epel branch
+data Branch = EPEL Int | Fedora Int | Rawhide
+  deriving (Eq, Ord)
+
+-- | Read a Fedora Branch name, otherwise return branch string
+eitherBranch :: String -> Either String Branch
+eitherBranch "rawhide" = Right Rawhide
+eitherBranch ('f':ns) | all isDigit ns = let br = Fedora (read ns) in Right br
+eitherBranch ('e':'p':'e':'l':n) | all isDigit n = let br = EPEL (read n) in Right br
+eitherBranch ('e':'l':n) | all isDigit n = let br = EPEL (read n) in Right br
+eitherBranch cs = Left cs
+
+-- | Read a Fedora Branch name, otherwise return an error message
+eitherBranch' :: String -> Either String Branch
+eitherBranch' cs = case eitherBranch cs of
+  Right br -> Right br
+  Left xs -> Left $ xs ++ " is not a known Fedora/EPEL branch"
+
+-- | Read a Fedora Branch name
+readBranch :: String -> Maybe Branch
+readBranch bs =
+  case eitherBranch bs of
+    Left _ -> Nothing
+    Right br -> Just br
+
+-- | Unsafely read a Fedora Branch name: errors for unknown branches
+readBranch' :: String -> Branch
+readBranch' bs =
+  case eitherBranch bs of
+    Left e -> error' $! "unknown Fedora branch: " ++ e
+    Right br -> br
+
+-- | Read a Branch name (one of the list of active branches)
+--
+-- Provides error strings for inactive or unknown branches.
+eitherActiveBranch :: [Branch] -> String -> Either String Branch
+eitherActiveBranch active bs =
+  case eitherBranch bs of
+    Left e -> Left e
+    Right br -> if br `elem` active
+                then Right br
+                else Left bs
+
+-- | Read a Branch name (one of the list of active branches)
+--
+-- Similar to eitherActiveBranch but ignores any error string
+readActiveBranch :: [Branch] -> String -> Maybe Branch
+readActiveBranch active cs =
+  case eitherActiveBranch active cs of
+    Left _ -> Nothing
+    Right br -> Just br
+
+-- | Read a Branch name (one of the list of active branches)
+--
+-- Like readActiveBranch, but errors for inactive or unknown branches.
+readActiveBranch' :: [Branch] -> String -> Branch
+readActiveBranch' active cs =
+  case eitherActiveBranch active cs of
+    Left e -> error' $ "inactive Fedora branch: " ++ e
+    Right br -> br
+
+instance Show Branch where
+  show Rawhide = "rawhide"
+  show (Fedora n) = "f" ++ show n
+  show (EPEL n) = (if n <= 6 then "el" else "epel") ++ show n
+
+-- | Map Branch to Koji destination tag
+branchDestTag :: Branch -> String
+branchDestTag Rawhide = "rawhide"
+branchDestTag (Fedora n) = show (Fedora n) ++ "-updates-candidate"
+branchDestTag (EPEL n) = show (EPEL n) ++ "-testing-candidate"
+
+-- | Default build target associated with a branch
+branchTarget :: Branch -> String
+branchTarget (Fedora n) = show (Fedora n)
+branchTarget (EPEL n) = show (EPEL n)
+branchTarget Rawhide = "rawhide"
+
+--getLatestBranch :: IO Branch
+
+-- | Returns newer branch than given one from supplied active branches.
+--
+-- Branches should be in descending order, eg from getFedoraBranches
+newerBranch :: Branch -> [Branch] -> Branch
+newerBranch Rawhide _ = Rawhide
+newerBranch (Fedora n) branches =
+  if Fedora n `elem` branches
+  then if Fedora (n+1) `elem` branches
+       then Fedora (n+1)
+       else Rawhide
+  else error' $ "Unsupported branch: " ++ show (Fedora n)
+newerBranch (EPEL n) branches =
+  if EPEL n `elem` branches
+  then if EPEL (n+1) `elem` branches
+       then EPEL (n+1)
+       else EPEL n
+  else error' $ "Unsupported branch: " ++ show (EPEL n)
+
+--olderBranch :: Branch -> Branch
+--olderBranch Rawhide = latestBranch
+--olderBranch (Fedora n) = Fedora (n-1)
+
+-- | Returns list of active Fedora branches, including rawhide and EPEL
+getFedoraBranches :: IO [Branch]
+getFedoraBranches = map releaseBranch <$> Dist.getReleaseIds
+
+-- | Maps release-id to Branch
+releaseBranch :: T.Text -> Branch
+releaseBranch "fedora-rawhide" = Rawhide
+releaseBranch rel | "fedora-" `T.isPrefixOf` rel =
+                      let (_,ver) = T.unpack <$> T.breakOnEnd "-" rel in
+                        if all isDigit ver
+                        then Fedora $ read ver
+                        else error' $ "Unsupport release: " ++ T.unpack rel
+                  | "epel-" `T.isPrefixOf` rel =
+                      let (_,ver) = T.unpack <$> T.breakOnEnd "-" rel in
+                        if all isDigit ver
+                        then EPEL $ read ver
+                        else error' $ "Unsupport release: " ++ T.unpack rel
+                  | otherwise = error' $ "Unsupport release: " ++ T.unpack rel
+
+-- | Returns list of active Fedora branches, excluding rawhide
+getFedoraBranched :: IO [Branch]
+getFedoraBranched = filter (/= Rawhide) <$> getFedoraBranches
+
+-- from simple-cmd
+error' :: String -> a
+#if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,9,0))
+error' = errorWithoutStackTrace
+#else
+error' = error
+#endif
+
+-- | Convert a Branch to a Dist
+branchDist :: Branch -> IO Dist.Dist
+branchDist (Fedora n) = return $ Dist.Fedora n
+branchDist (EPEL n) = return $ Dist.EPEL n
+branchDist Rawhide = Dist.getRawhideDist
diff --git a/src/Distribution/Fedora/Products.hs b/src/Distribution/Fedora/Products.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Fedora/Products.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE CPP                 #-}
+
+module Distribution.Fedora.Products
+  (getProducts)
+where
+
+import Data.Aeson (Object)
+import Fedora.PDC (makeKey, pdcProductVersions)
+import System.Cached.JSON
+
+getProducts :: IO [Object]
+getProducts =
+  getCachedJSONQuery "fedora" "product-versions.json"
+  (pdcProductVersions "pdc.fedoraproject.org" (makeKey "active" "true")) 300
diff --git a/src/Distribution/Fedora/Release.hs b/src/Distribution/Fedora/Release.hs
new file mode 100644
--- /dev/null
+++ b/src/Distribution/Fedora/Release.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE CPP                 #-}
+
+module Distribution.Fedora.Release
+  ( Release(releaseProductVersionId,releaseVersion,releaseProduct),
+    readRelease
+  )
+where
+
+import Data.Aeson(Object)
+import Data.Text (Text)
+import Fedora.PDC (lookupKey)
+
+data Release = Release {
+    releaseProductVersionId :: Text,
+--    releaseReleases :: [Text],
+--    releaseAllowedPushTargets :: [[Maybe Value]],
+--    releaseActive :: Bool,
+--    releaseName :: Text,
+    releaseVersion :: Text,
+--    releaseShort :: Text,
+    releaseProduct :: Text
+  } deriving (Show,Eq)
+
+instance Ord Release where
+  compare r1 r2 =
+    compare (releaseProductVersionId r1) (releaseProductVersionId r2)
+
+readRelease :: Object -> Maybe Release
+readRelease obj = do
+  pvid <- lookupKey "product_version_id" obj
+  ver <- lookupKey "version" obj
+  prod <- lookupKey "product" obj
+  return $ Release pvid ver prod
