koji-install (empty) → 0.2.0
raw patch · 7 files changed
+480/−0 lines, 7 filesdep +basedep +directorydep +extra
Dependencies added: base, directory, extra, filepath, koji, optparse-applicative, rpm-nvr, simple-cmd, simple-cmd-args, xdg-userdirs
Files
- ChangeLog.md +15/−0
- LICENSE +30/−0
- README.md +62/−0
- koji-install.cabal +71/−0
- src/DownloadDir.hs +43/−0
- src/Main.hs +239/−0
- test/tests.hs +20/−0
+ ChangeLog.md view
@@ -0,0 +1,15 @@+# Release history for koji-install++## 0.2.0 (2021-12-03)+- initial Hackage release+- `--hub` to select hub+- `--pkgsurl` to override kojifiles url+- override `--disttag`+- select specific build with `--nvr` or `--nv`+- `--debug` output++## 0.1.0 (2021-08-12)+- pre-release on copr+- initial options:+ --all/--ask/--base-only/--exclude-devel --dry-run+- only supports Fedora Koji
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2021, Jens Petersen++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Jens Petersen nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,62 @@+# koji-install++A CLI tool to download and install rpms from a Koji build.++Koji is a package buildsystem used by Fedora, Centos, and some other projects.++## Usage++By default it uses Fedora Koji.++$ koji-install podman++Will download the latest build for your Fedora version,+and try to install it.++You can specify a different hub using `--hub`.++### Selecting subpackages++By default only installed subpackages are and downloaded and updated,+but the following options change the behavior:++`--all`: install all subpackages++`--ask`: ask about each subpackage++`--base-only`: only install base package++`--exclude-devel`: skip devel subpackages++### Help+```shellsession+$ koji-install+Install latest build from Koji++Usage: koji-install [--version] [-n|--dry-run] [-D|--debug] [-H|--hub HUB]+ [-P|--packages-url URL]+ [(-a|--all) | (-A|--ask) | (-b|--base-only) |+ (-D|--exclude-devel)] [-d|--disttag DISTTAG]+ [(-R|--nvr) | (-V|--nv)] PACKAGE+ Download and install latest package build from Koji tag.+ HUB = fedora, stream, mbox, rpmfusion, URL++Available options:+ -h,--help Show this help text+ --version Show version+ -n,--dry-run Don't actually download anything+ -D,--debug More detailed output+ -H,--hub HUB KojiHub shortname or url [default: fedora]+ -P,--packages-url URL KojiFiles packages url [default: fedora]+ -a,--all all subpackages+ -A,--ask ask for each subpackge+ -b,--base-only only base package+ -D,--exclude-devel Skip devel packages+ -d,--disttag DISTTAG Use a different disttag [default: .fc35]+ -R,--nvr Give an N-V-R instead of package name+ -V,--nv Give an N-V instead of package name+```++## Installation++`cabal-rpm builddep && cabal install` or `stack install`.
+ koji-install.cabal view
@@ -0,0 +1,71 @@+name: koji-install+version: 0.2.0+synopsis: CLI tool for installing rpms directly from Fedora Koji+description:+ koji-install can install the latest koji build of a package locally.+ By default it only downloads newer binaries of the subpackages+ already installed, but there are options to override that.+license: BSD3+license-file: LICENSE+author: Jens Petersen <juhpetersen@gmail.com>+maintainer: Jens Petersen <juhpetersen@gmail.com>+copyright: 2021 Jens Petersen <juhpetersen@gmail.com>+category: Utility+homepage: https://github.com/juhp/koji-install+bug-reports: https://github.com/juhp/koji-install/issues+build-type: Simple+extra-doc-files: README.md+ ChangeLog.md+cabal-version: 1.18+tested-with: GHC== 8.4.4+ || == 8.6.5+ || == 8.8.4+ || == 8.10.5+ || == 9.0.1++source-repository head+ type: git+ location: https://github.com/juhp/koji-install.git++executable koji-install+ main-is: Main.hs+ other-modules: Paths_koji_install+ DownloadDir+ hs-source-dirs: src+ build-depends: base < 5,+ directory,+ extra,+ filepath,+ koji >= 0.0.2,+ optparse-applicative,+ rpm-nvr,+ simple-cmd,+ simple-cmd-args,+ xdg-userdirs+ default-language: Haskell2010+ ghc-options: -Wall -threaded+ 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++test-suite test+ main-is: tests.hs+ type: exitcode-stdio-1.0+ hs-source-dirs: test++ default-language: Haskell2010++ ghc-options: -Wall+ build-depends: base >= 4 && < 5+ , simple-cmd+ build-tools: koji-install
+ src/DownloadDir.hs view
@@ -0,0 +1,43 @@+module DownloadDir (+ setDownloadDir)+where++import Control.Monad+import SimpleCmd (error')+import System.Directory (createDirectoryIfMissing,+ doesDirectoryExist, getHomeDirectory,+ setCurrentDirectory)+import System.Environment.XDG.UserDir (getUserDir)+import System.FilePath++-- FIXME check writeable+setDownloadDir :: Bool -> String -> IO FilePath+setDownloadDir dryrun subdir = do+ home <- getHomeDirectory+ dlDir <- getUserDir "DOWNLOAD"+ dirExists <- doesDirectoryExist dlDir+ -- is this really necessary?+ unless (dryrun || dirExists) $+ when (home == dlDir) $+ error' "HOME directory does not exist!"+ let filesDir = dlDir </> subdir+ filesExists <- doesDirectoryExist filesDir+ dir <-+ if filesExists+ then setCWD filesDir+ else+ if dirExists+ then setCWD dlDir+ else do+ if dryrun+ then return dlDir+ else do+ createDirectoryIfMissing True dlDir+ setCWD dlDir+ let path = makeRelative home dir+ return $ if isRelative path then "~" </> path else path+ where+ setCWD :: FilePath -> IO FilePath+ setCWD dir = do+ setCurrentDirectory dir+ return dir
+ src/Main.hs view
@@ -0,0 +1,239 @@+-- SPDX-License-Identifier: BSD-3-Clause++module Main (main) where++import Control.Monad.Extra+import Data.Char+import Data.List.Extra+import Data.Maybe+import Data.RPM+import Distribution.Koji+import qualified Distribution.Koji.API as Koji+import Options.Applicative (fullDesc, header, progDescDoc)+import qualified Options.Applicative.Help.Pretty as P+import SimpleCmd+import SimpleCmdArgs+import System.Directory+import System.FilePath ((<.>))+import System.IO++import DownloadDir+import Paths_koji_install (version)++data InstallMode = Update | All | Ask | Base | NoDevel++data Request = ReqName | ReqNV | ReqNVR++-- FIXME --include devel, --exclude *+-- FIXME specify tag or task+-- FIXME support enterprise builds+-- FIXME --arch (including src)+-- FIXME --debuginfo+-- FIXME --delete after installing++main :: IO ()+main = do+ sysdisttag <- cmd "rpm" ["--eval", "%{dist}"]+ let pdoc = Just $ P.vcat+ [ P.text "Download and install latest package build from Koji tag.",+ P.text ("HUB = " <> intercalate ", " knownHubs)+ ]+ simpleCmdArgsWithMods (Just Paths_koji_install.version)+ (fullDesc <> header "Install latest build from Koji" <> progDescDoc pdoc) $+ program+ <$> switchWith 'n' "dry-run" "Don't actually download anything"+ <*> switchWith 'D' "debug" "More detailed output"+ <*> optional (strOptionWith 'H' "hub" "HUB"+ "KojiHub shortname or url [default: fedora]")+ <*> optional (strOptionWith 'P' "packages-url" "URL"+ "KojiFiles packages url [default: fedora]")+ <*> modeOpt+ <*> disttagOpt sysdisttag+ <*> (flagWith' ReqNVR 'R' "nvr" "Give an N-V-R instead of package name"+ <|> flagWith ReqName ReqNVR 'V' "nv" "Give an N-V instead of package name")+ <*> some (strArg "PACKAGE")+ where+ modeOpt :: Parser InstallMode+ modeOpt =+ flagWith' All 'a' "all" "all subpackages" <|>+ flagWith' Ask 'A' "ask" "ask for each subpackge" <|>+ flagWith' Base 'b' "base-only" "only base package" <|>+ flagWith' NoDevel 'D' "exclude-devel" "Skip devel packages" <|>+ pure Update++ disttagOpt :: String -> Parser String+ disttagOpt disttag = startingDot <$> strOptionalWith 'd' "disttag" "DISTTAG" ("Use a different disttag [default: " ++ disttag ++ "]") disttag++ startingDot cs =+ case cs of+ "" -> error' "empty disttag"+ (c:_) -> if c == '.' then cs else '.' : cs++knownHubs :: [String]+knownHubs = ["fedora","stream","mbox","rpmfusion", "URL"]++hubURL :: String -> String+hubURL "fedora" = fedoraKojiHub+-- later use centosKojiHub+hubURL "stream" = "https://kojihub.stream.centos.org/kojihub"+hubURL "mbox" = "https://koji.mbox.centos.org/kojihub"+hubURL "rpmfusion" = "https://koji.rpmfusion.org/kojihub"+hubURL "fusion" = "https://koji.rpmfusion.org/kojihub"+hubURL hub =+ if "http" `isPrefixOf` hub+ then hub+ else error' $ "unknown hub: try " ++ show knownHubs++defaultPkgsURL :: Maybe String -> String+defaultPkgsURL Nothing =+ "https://kojipkgs.fedoraproject.org/packages"+defaultPkgsURL (Just url) =+ case url of+ "https://kojihub.stream.centos.org/kojihub" ->+ "https://kojihub.stream.centos.org/kojifiles/packages"+ _ ->+ if "kojihub" `isSuffixOf` url+ then replace "kojihub" "kojifiles" url+ else error' "use --files-url to specify kojifiles url for this kojihub"++program :: Bool -> Bool -> Maybe String -> Maybe String -> InstallMode+ -> String -> Request -> [String] -> IO ()+program dryrun debug mhuburl mpkgsurl mode disttag request pkgs = do+ let huburl = maybe fedoraKojiHub hubURL mhuburl+ pkgsurl = fromMaybe (defaultPkgsURL mhuburl) mpkgsurl+ when debug $ do+ putStrLn huburl+ putStrLn pkgsurl+ -- FIXME use this?+ dlDir <- setDownloadDir dryrun "rpms"+ when debug $ putStrLn dlDir+ setNoBuffering+ mapM (kojiLatestRPMs huburl pkgsurl dlDir) pkgs+ >>= installRPMs dryrun . mconcat+ where+ kojiLatestRPMs :: String -> String -> String -> String -> IO [String]+ kojiLatestRPMs huburl pkgsurl dlDir pkg = do+ mnvr <- kojiLatestOSBuild huburl disttag request pkg+ case mnvr of+ Nothing -> error' $ "latest " ++ pkg ++ " not found"+ Just nvr -> do+ putStrLn $ nvr ++ "\n"+ allRpms <- map (<.> "rpm") . sort . filter (not . debugPkg) <$> kojiGetBuildRPMs huburl nvr+ dlRpms <- decideRpms mode allRpms+ unless (dryrun || null dlRpms) $ do+ mapM_ (downloadRpm pkgsurl (readNVR nvr)) dlRpms+ -- FIXME once we check file size - can skip if no downloads+ putStrLn $ "Packages downloaded to " ++ dlDir+ return dlRpms+ where+ decideRpms :: InstallMode -> [String] -> IO [String]+ decideRpms mode' allRpms =+ case mode' of+ All -> return allRpms+ Ask -> mapMaybeM rpmPrompt allRpms+ Base -> return $ pure $ minimumOn length $ filter (pkg `isPrefixOf`) allRpms+ Update -> do+ rpms <- filterM (isInstalled . rpmName . readNVRA) allRpms+ if null rpms+ then decideRpms Ask allRpms+ else return rpms+ NoDevel -> return $ filter (not . ("-devel-" `isInfixOf`)) allRpms++ rpmPrompt :: String -> IO (Maybe String)+ rpmPrompt rpm = do+ putStr $ rpm ++ " [y/n]: "+ c <- getChar+ putStrLn ""+ case toLower c of+ 'y' -> return $ Just rpm+ 'n' -> return Nothing+ _ -> rpmPrompt rpm++ isInstalled :: String -> IO Bool+ isInstalled rpm = cmdBool "rpm" ["--quiet", "-q", rpm]++ debugPkg :: String -> Bool+ debugPkg p = "-debuginfo-" `isInfixOf` p || "-debugsource-" `isInfixOf` p++kojiLatestOSBuild :: String -> String -> Request -> String -> IO (Maybe String)+kojiLatestOSBuild hub disttag request pkgpat = do+ let (pkg,full) = packageOfPattern pkgpat+ mpkgid <- Koji.getPackageID hub pkg+ case mpkgid of+ Nothing -> error $ "package not found: " ++ pkg+ Just pkgid -> do+ let opts = [("packageID", ValueInt pkgid),+ ("state", ValueInt (fromEnum BuildComplete)),+ ("queryOpts",ValueStruct [("limit",ValueInt 1),+ ("order",ValueString "-build_id")])]+ res <- Koji.listBuilds hub $+ ("pattern", ValueString (if full then pkgpat else dropSuffix "*" pkgpat ++ "*" ++ disttag)) : opts+ case res of+ [] -> return Nothing+ [bld] -> return $ lookupStruct "nvr" bld+ _ -> error $ "more than one latest build found for " ++ pkg+ where+ packageOfPattern :: String -> (String, Bool)+ packageOfPattern pat =+ case request of+ ReqName -> (dropSuffix "-" $ takeWhile (/= '*') pat, False)+ ReqNV ->+ case readNV pat of+ NV n _ -> (n, False)+ ReqNVR ->+ case readNVR pat of+ NVR n _ -> (n, True)++kojiGetBuildRPMs :: String -> String -> IO [String]+kojiGetBuildRPMs huburl nvr = do+ mbid <- kojiGetBuildID huburl nvr+ case mbid of+ Nothing -> error $ "Build id not found for " ++ nvr+ Just (BuildId bid) -> do+ rpms <- Koji.listBuildRPMs huburl bid+ return $ map getNVRA $ filter (forArch "x86_64") rpms+ where+ forArch :: String -> Struct -> Bool+ forArch sysarch st =+ case lookupStruct "arch" st of+ Just arch -> arch `elem` [sysarch, "noarch"]+ Nothing -> error $ "No arch found for rpm for: " ++ nvr++ getNVRA :: Struct -> String+ getNVRA st =+ case lookupStruct "nvr" st of+ Nothing -> error' "NVR not found"+ Just pnvr ->+ case lookupStruct "arch" st of+ Nothing -> error "arch not found"+ Just arch ->+ pnvr <.> arch++setNoBuffering :: IO ()+setNoBuffering = do+ hSetBuffering stdin NoBuffering+ hSetBuffering stdout NoBuffering++installRPMs :: Bool -> [FilePath] -> IO ()+installRPMs _ [] = return ()+installRPMs dryrun pkgs =+ unless dryrun $+ sudo_ "dnf" ("install" : map ("./" ++) pkgs)++-- FIXME check file size+downloadRpm :: String -> NVR -> String -> IO ()+downloadRpm pkgsurl (NVR n (VerRel v r)) rpm = do+ unlessM (doesFileExist rpm) $ do+ let arch = rpmArch (readNVRA rpm)+ url = pkgsurl +/+ n +/+ v +/+ r +/+ arch +/+ rpm+ putStrLn $ "Downloading " ++ rpm+ cmd_ "curl" ["--fail", "--silent", "-C-", "--show-error", "--remote-name", url]++-- from next http-directory or http-query+infixr 5 +/++(+/+) :: String -> String -> String+"" +/+ s = s+s +/+ "" = s+s +/+ t | last s == '/' = init s +/+ t+ | head t == '/' = s +/+ tail t+s +/+ t = s ++ "/" ++ t
+ test/tests.hs view
@@ -0,0 +1,20 @@+import SimpleCmd+import System.IO++program :: [String] -> IO ()+program args =+ putStrLn "" >> cmdLog "koji-install" ("-n" : "-b" : args)++tests :: [[String]]+tests =+ [["podman"]+ ,["-H", "https://kojihub.stream.centos.org/kojihub", "-d", "el9", "bash"]+ ,["-H", "stream", "-d", "el9", "kernel"]+-- ,["-H", "rpmfusion", "ffmpeg"]+ ]++main :: IO ()+main = do+ hSetBuffering stdout NoBuffering+ mapM_ program tests+ putStrLn $ show (length tests) ++ " tests run"