packages feed

dnf-repo (empty) → 0.1

raw patch · 10 files changed

+557/−0 lines, 10 filesdep +basedep +directorydep +extra

Dependencies added: base, directory, extra, filepath, simple-cmd, simple-cmd-args

Files

+ ChangeLog.md view
@@ -0,0 +1,6 @@+# dnf-repo releases++## 0.1 (2022-06-20)+- initial version with basic functionality: --add-copr, --add-koji,+  --disable repo, --enable repo, --enable/disable-{testing,modular}+  and --save & --expire
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2022, 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,73 @@+# dnf-repo++A wrapper of the  dnf package manager for fine control of enabled/disabled yum repos (eg coprs).++DNF is not terribly fast at handling repos partly because it likes+to refresh the cached repodata frequently. So it can be advantageous+to disable smaller repos by default and only enable them as needed.++This tool can temporarily enable/disable repos selected by substring.+Repo states can also be saved or their cache individually expired.++There are options to enable/disable testing/modular repos,+and also to create a repo file for a Copr or Koji repo.++## Help++```shellsession+$ dnf-repo --version+0.1+$ dnf-repo --help+DNF wrapper repo tool++Usage: dnf-repo [--version] [-n|--dryrun] [-D|--debug] [-s|--save]+                [(-c|--add-copr COPR) | (-k|--add-koji REPO) |+                  (-d|--disable REPOPAT) | (-e|--enable REPOPAT) |+                  (-x|--expire REPOPAT) | (-E|--delete-repo REPOPAT)]+                [(-t|--enable-testing) | (-T|--disable-testing)]+                [(-m|--enable-modular) | (-M|--disable-modular)] [DNFARGS]+  see https://github.com/juhp/dnf-repo#readme++Available options:+  -h,--help                Show this help text+  --version                Show version+  -n,--dryrun              Dry run+  -D,--debug               Debug output+  -s,--save                Save the repo enable/disable state+  -c,--add-copr COPR       Create repo file for copr repo+  -k,--add-koji REPO       Create repo file for koji repo+  -d,--disable REPOPAT     Disable repos+  -e,--enable REPOPAT      Enable repos+  -x,--expire REPOPAT      Expire repo cache+  -E,--delete-repo REPOPAT Remove unwanted .repo file+  -t,--enable-testing      Enable testing repos+  -T,--disable-testing     Disable testing repos+  -m,--enable-modular      Enable modular repos+  -M,--disable-modular     Disable modular repos+```++## Usage+List repos:+```shellsession+$ dnf-repo+```++Update with --enable-testing:+```shellsession+$ sudo dnf-repo -t update+```++List disabled copr repos:+```shellsession+$ dnf-repo -e copr+```++Disable copr repos for update:+```shellsession+$ sudo dnf-repo -d copr update+```++Disable modular repos permanently:+```shellsession+$ sudo dnf-repo --disable-modular --save+```
+ data/copr.fedorainfracloud.orgCOLONOWNERCOLONREPO.repo view
@@ -0,0 +1,10 @@+[copr:copr.fedorainfracloud.org:@COPR_OWNER@:@COPR_REPO@]+name=Copr repo for @COPR_REPO@ owned by @COPR_OWNER@+baseurl=https://download.copr.fedorainfracloud.org/results/@COPR_OWNER@/@COPR_REPO@/fedora-$releasever-$basearch/+type=rpm-md+skip_if_unavailable=True+gpgcheck=1+gpgkey=https://download.copr.fedorainfracloud.org/results/@COPR_OWNER@/@COPR_REPO@/pubkey.gpg+repo_gpgcheck=0+enabled=0+enabled_metadata=1
+ data/koji-REPO.repo view
@@ -0,0 +1,10 @@+[koji-@REPO@]+name=Fedora - Koji @REPO@+failovermethod=priority+baseurl=https://kojipkgs.fedoraproject.org/repos/@REPO@/latest/x86_64/+enabled=0+metadata_expire=1h+repo_gpgcheck=0+type=rpm+gpgcheck=0+skip_if_unavailable=False
+ dnf-repo.cabal view
@@ -0,0 +1,60 @@+name:                dnf-repo+version:             0.1+synopsis:            DNF wrapper tool to control repos+description:+        A command-line wrapper of the dnf package manager to+        control the repos enabled for a dnf invocation.+license:             BSD3+license-file:        LICENSE+author:              Jens Petersen <juhpetersen@gmail.com>+maintainer:          Jens Petersen <juhpetersen@gmail.com>+copyright:           2022  Jens Petersen <juhpetersen@gmail.com>+category:            Utility+homepage:            https://github.com/juhp/dnf-repo+bug-reports:         https://github.com/juhp/dnf-repo/issues+build-type:          Simple+extra-doc-files:     README.md+                     ChangeLog.md+cabal-version:       2.0+tested-with:         GHC == 8.6.5+                     ||  == 8.8.4+                     ||  == 8.10.7+                     ||  == 9.0.2+                     ||  == 9.2.3+data-dir:            data+data-files:          copr.fedorainfracloud.orgCOLONOWNERCOLONREPO.repo+                     koji-REPO.repo++source-repository head+  type:                git+  location:            https://github.com/juhp/dnf-repo.git++executable dnf-repo+  main-is:             Main.hs+  other-modules:       Paths_dnf_repo+                       ExpireRepos+                       Sudo+                       YumRepoFile+  autogen-modules:     Paths_dnf_repo+  hs-source-dirs:      src+  build-depends:       base < 5,+                       directory,+                       extra,+                       filepath,+                       simple-cmd,+                       simple-cmd-args+  default-language:    Haskell2010+  ghc-options:         -Wall+  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
+ src/ExpireRepos.hs view
@@ -0,0 +1,27 @@+module ExpireRepos (expireRepos) where++import Data.List (nub)+import SimpleCmd (error')++import Sudo++expiredFile :: FilePath+expiredFile = "/var/cache/dnf/expired_repos.json"++expireRepos :: Bool -> [String] -> IO ()+expireRepos _ [] = error' "no repos to expire given"+expireRepos dryrun repos = do+  old <- read <$> readFile expiredFile :: IO [String]+  let expired = nub $ old ++ repos+  doSudo dryrun "sed" ["-i", "-e",+                       "s/" ++ renderShow old ++ "/" ++ renderShow expired ++ "/",+                       expiredFile]+  putStrLn $ "expired now: " ++ show expired+  where+    renderShow :: [String] -> String+    renderShow = render . show++    render :: String -> String+    render "" = ""+    render (c:cs) =+      (if c `elem` "[]" then ['\\',c] else [c]) ++ render cs
+ src/Main.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE CPP #-}++-- SPDX-License-Identifier: BSD-3-Clause++module Main (main) where++import Control.Monad.Extra+import Data.Bifunctor (bimap)+import Data.List.Extra+import Data.Maybe (mapMaybe)+import Data.Tuple.Extra (fst3,snd3)+import SimpleCmd+import SimpleCmdArgs+import System.Directory+import System.FilePath+import System.IO (hSetBuffering, stdout, BufferMode(NoBuffering))+import System.IO.Extra (withTempDir)+import System.Time.Extra (sleep)++import Paths_dnf_repo (getDataFileName, version)+import ExpireRepos (expireRepos)+import Sudo+import YumRepoFile++main :: IO ()+main = do+  simpleCmdArgs' (Just version)+    "DNF wrapper repo tool"+    "see https://github.com/juhp/dnf-repo#readme" $+    runMain+    <$> switchWith 'n' "dryrun" "Dry run"+    <*> switchWith 'D' "debug" "Debug output"+    <*> switchWith 's' "save" "Save the repo enable/disable state"+    <*> modeOpt+    <*> optional testingOpt+    <*> optional modularOpt+    <*> many (strArg "DNFARGS")+  where+    modeOpt =+      AddCopr . replace "/" ":" <$> strOptionWith 'c' "add-copr" "COPR" "Create repo file for copr repo" <|>+      AddKoji <$> strOptionWith 'k' "add-koji" "REPO" "Create repo file for koji repo" <|>+      DisableRepo <$> strOptionWith 'd' "disable" "REPOPAT" "Disable repos" <|>+      EnableRepo <$> strOptionWith 'e' "enable" "REPOPAT" "Enable repos" <|>+      ExpireRepo <$> strOptionWith 'x' "expire" "REPOPAT" "Expire repo cache" <|>+      DeleteRepo <$> strOptionWith 'E' "delete-repofile" "REPOPAT" "Remove unwanted .repo file" <|>+      pure Default++    testingOpt =+      flagWith' EnableTesting 't' "enable-testing" "Enable testing repos" <|>+      flagWith' DisableTesting 'T' "disable-testing" "Disable testing repos"++    modularOpt =+      flagWith' EnableModular 'm' "enable-modular" "Enable modular repos" <|>+      flagWith' DisableModular 'M' "disable-modular" "Disable modular repos"++coprRepoTemplate :: FilePath+coprRepoTemplate = "copr.fedorainfracloud.orgCOLONOWNERCOLONREPO.repo"++kojiRepoTemplate :: FilePath+kojiRepoTemplate = "koji-REPO.repo"++-- FIXME both enabling and disabled at the same time+-- FIXME --enable-all-coprs (for updating etc)+-- FIXME confirm repos if many+-- FIXME --disable-non-cores (modular,testing,cisco, etc)+runMain :: Bool -> Bool -> Bool -> Mode -> Maybe Testing -> Maybe Modular+        -> [String] -> IO ()+runMain dryrun debug save mode mtesting mmodular args = do+  hSetBuffering stdout NoBuffering+  withCurrentDirectory "/etc/yum.repos.d" $ do+    case mode of+      AddCopr copr -> addCoprRepo copr+      AddKoji repo -> addKojiRepo repo+      _ -> return ()+    repofiles <- filesWithExtension "." "repo"+--    when debug $ print repofiles+    nameStates <- sort <$> mapM readRepo repofiles+    let repoActs = mapMaybe (selectRepo debug mode mtesting mmodular) nameStates+    unless (null repoActs) $ do+      mapM_ print repoActs+      putStrLn ""+    case mode of+      ExpireRepo _ -> do+        putStrLn ""+        expireRepos dryrun $ mapMaybe expiring repoActs+      DeleteRepo _ ->+        mapM_ deleteRepos $ mapMaybe deleting repoActs+      _ -> return ()+    when save $+      if null repoActs+        then putStrLn "no changes to save\n"+        else do+        prompt_ "Press Enter to save repo enabled state"+        doSudo dryrun "dnf" $+          "config-manager" :+          concatMap saveRepo repoActs+    if null args+      then do+      listRepos $ map (updateState repoActs) nameStates+      else do+      sleep 1+      putStrLn ""+      let repoargs = concatMap changeRepo repoActs+        in doSudo dryrun "dnf" $ repoargs ++ args+    where+      -- FIXME pull non-fedora copr repo file+      -- FIXME delete created copr repo file if repo doesn't exist+      addCoprRepo :: String -> IO ()+      addCoprRepo repo = do+        case stripInfix ":" repo of+          Nothing -> error' $ "invalid copr: " ++ repo+          Just (copr_owner,copr_repo) -> do+            template <- getDataFileName coprRepoTemplate+            repodef <- cmd "sed" ["-e", "s/@COPR_OWNER@/" ++ copr_owner ++ "/g", "-e", "s/@COPR_REPO@/" ++ copr_repo ++ "/g", template]+            let repofile = ("_copr:" ++) $+                           replace "COLON" ":" $+                           replace "OWNER" copr_owner $+                           replace "REPO" copr_repo coprRepoTemplate+            exists <- doesFileExist repofile+            if exists+              then error' $ "repo already defined: " ++ repofile+              else putStrLn $ "Setting up copr repo " ++ repo+            withTempDir $ \ tmpdir -> do+              let tmpfile = tmpdir </> repofile+              unless dryrun $ writeFile tmpfile repodef+              doSudo dryrun "cp" [tmpfile, repofile]++      addKojiRepo :: String -> IO ()+      addKojiRepo repo = do+        template <- getDataFileName kojiRepoTemplate+        repodef <- cmd "sed" ["-e", "s/@REPO@/" ++ repo ++ "/g", template]+        let repofile = replace "REPO" repo kojiRepoTemplate+        exists <- doesFileExist repofile+        if exists+          then error' $ "repo already defined: " ++ repofile+          else putStrLn $ "Setting up koji repo " ++ repo+        withTempDir $ \ tmpdir -> do+          let tmpfile = tmpdir </> repofile+          unless dryrun $ writeFile tmpfile repodef+          doSudo dryrun "cp" [tmpfile, repofile]++      listRepos :: [RepoState] -> IO ()+      listRepos repoStates = do+        let (on,off) =+              -- can't this be simplified?+              bimap (map fst3) (map fst3) $ partition snd3 repoStates+        putStrLn "Enabled:"+        mapM_ putStrLn on+        putStrLn ""+        putStrLn "Disabled:"+        mapM_ putStrLn off++      deleteRepos :: FilePath -> IO ()+      deleteRepos repofile = do+        mowned <- cmdMaybe "rpm" ["-qf", "/etc/yum.repos.d" </> repofile]+        case mowned of+          Just owner -> error' $ repofile +-+ "owned by" +-+ owner+          Nothing -> do+            ok <- yesno $ "Remove " ++ takeFileName repofile+            when ok $ doSudo dryrun "rm" [repofile]++#if !MIN_VERSION_simple_cmd(0,2,4)+filesWithExtension :: FilePath -> String -> IO [FilePath]+filesWithExtension dir ext =+  filter (ext `isExtensionOf`) <$> listDirectory dir+#endif++prompt :: String -> IO String+prompt desc = do+  putStr $ desc ++ ": "+  getLine++prompt_ :: String -> IO ()+prompt_ desc = do+  void $ prompt desc++yesno :: String -> IO Bool+yesno desc = do+  inp <- prompt $ desc ++ "? [y/n]"+  case lower inp of+    "y" -> return True+    "yes" -> return True+    "n" -> return False+    "no" -> return False+    _ ->  yesno desc
+ src/Sudo.hs view
@@ -0,0 +1,9 @@+module Sudo (doSudo) where++import SimpleCmd (cmdN, sudo_)++-- FIXME make this silent (simple-cmd-0.2.7) unless debug+doSudo :: Bool -> String -> [String] -> IO ()+doSudo dryrun c args = do+  (if dryrun then cmdN else sudo_) c args+  putStrLn ""
+ src/YumRepoFile.hs view
@@ -0,0 +1,147 @@+{-# LANGUAGE OverloadedStrings #-}++module YumRepoFile (+  Mode(..),+  readRepo,+  RepoState,+  selectRepo,+  Modular(..),+  Testing(..),+  changeRepo,+  saveRepo,+  updateState,+  expiring,+  deleting+  )+where++import Data.List.Extra (isPrefixOf, isInfixOf, isSuffixOf, trim)+import SimpleCmd (error')++type RepoState = (String,Bool,FilePath)++data Mode = AddCopr String | AddKoji String+          | EnableRepo String | DisableRepo String+          | ExpireRepo String | DeleteRepo String | Default+  deriving Eq++data ChangeEnable = Disable String | Enable String | Expire String+                  | Delete FilePath+  deriving (Eq,Ord,Show)++changeRepo :: ChangeEnable -> [String]+changeRepo (Disable r) = ["--disablerepo", r]+changeRepo (Enable r) = ["--enablerepo", r]+changeRepo _ = []++saveRepo :: ChangeEnable -> [String]+saveRepo (Disable r) = ["--disable", r]+saveRepo (Enable r) = ["--enable", r]+saveRepo _ = []++expiring :: ChangeEnable -> Maybe String+expiring (Expire r) = Just r+expiring _ = Nothing++deleting :: ChangeEnable -> Maybe String+deleting (Delete r) = Just r+deleting _ = Nothing++updateState :: [ChangeEnable] -> RepoState -> RepoState+updateState [] rs = rs+updateState (ce:ces) re@(repo,enabled,file) =+  case ce of+    Disable r | r == repo && enabled -> (repo,False,file)+    Enable r | r == repo && not enabled -> (repo,True,file)+    _ -> updateState ces re++data Modular = EnableModular | DisableModular+  deriving Eq++data Testing = EnableTesting | DisableTesting+  deriving Eq++selectRepo :: Bool -> Mode -> Maybe Testing -> Maybe Modular+           -> RepoState -> Maybe ChangeEnable+selectRepo _debug mode mtesting mmodular (name,enabled,file) =+  case mode of+    AddCopr repo -> if repo `isSuffixOf` name && not enabled+                    then Just (Enable name)+                    else selectOther+    AddKoji repo -> if repo `isSuffixOf` name && not enabled+                    then Just (Enable name)+                    else selectOther+    EnableRepo pat -> if pat `isInfixOf` name && not enabled+                  then Just (Enable name)+                  else selectOther+    DisableRepo pat -> if pat `isInfixOf` name && enabled+                   then Just (Disable name)+                   else selectOther+    ExpireRepo pat -> if pat `isInfixOf` name+                  then Just (Expire name)+                  else Nothing+    DeleteRepo pat -> if pat `isInfixOf` name+                  then if enabled+                       then error' $ "disable repo before deleting: " ++ name+                       else Just (Delete file)+                  else Nothing+    _ -> selectOther+  where+    selectOther :: Maybe ChangeEnable+    selectOther+      | "modular" `isSuffixOf` name =+        case mmodular of+          Nothing -> Nothing+          Just include ->+            case include of+              EnableModular | not enabled ->+                               if "testing" `isInfixOf` name+                               then if mtesting == Just EnableTesting+                                    then Just (Enable name)+                                    else Nothing+                               else Just (Enable name)+              DisableModular | enabled -> Just (Disable name)+              _ -> Nothing+      | "testing" `isInfixOf` name =+          case mtesting of+            Nothing -> Nothing+            Just include ->+              case include of+                EnableTesting | not enabled -> Just (Enable name)+                DisableTesting | enabled -> Just (Disable name)+                _ -> Nothing+      | otherwise = Nothing++readRepo :: FilePath -> IO RepoState+readRepo file =+  parseRepo file . lines <$> readFile file++-- was called parseIni+parseRepo :: FilePath -> [String] -> RepoState+parseRepo file [] = error' $ "empty ini file: " ++ file+parseRepo file (l:ls) =+  case trim l of+    "" -> parseRepo file ls+    ('#':_) -> parseRepo file ls+    sec ->+      let section = secName sec+          enabled =+            case dropWhile (not . ("enabled=" `isPrefixOf`)) ls of+              [] -> error' $ "no enabled field for " ++ section+              (e:_) ->+                case trim e of+                  "enabled=1" -> True+                  "enabled=0" -> False+                  _ -> error' $ "unknown enabled state " ++ e ++ " for " ++ section+      in (section,enabled,file)+  where+    secName :: String -> String+    secName sec =+      if ' ' `elem` sec+      then error' $ "section contains space: " ++ sec+      else case sec of+        ('[' : rest) ->+          if last rest == ']'+          then init rest+          else error' $ "bad section " ++ sec ++ " in " ++ file+        _ -> error' $ "section not found in " ++ file