dnf-repo 0.1 → 0.2
raw patch · 7 files changed
+142/−110 lines, 7 filesdep ~simple-cmd-args
Dependency ranges changed: simple-cmd-args
Files
- ChangeLog.md +6/−0
- README.md +31/−11
- dnf-repo.cabal +3/−3
- src/ExpireRepos.hs +4/−4
- src/Main.hs +27/−24
- src/Sudo.hs +8/−3
- src/YumRepoFile.hs +63/−65
ChangeLog.md view
@@ -1,5 +1,11 @@ # dnf-repo releases +## 0.2 (2022-08-12)+- support multiple repos per repo file (eg needed for eln)+- support multiple repo actions (eg "-d rawhide -e fedora")+- --exact repo match option (eg for 'fedora')+- --debug now shows what sudo does, like --dryrun+ ## 0.1 (2022-06-20) - initial version with basic functionality: --add-copr, --add-koji, --disable repo, --enable repo, --enable/disable-{testing,modular}
README.md view
@@ -16,14 +16,14 @@ ```shellsession $ dnf-repo --version-0.1+0.2 $ dnf-repo --help DNF wrapper repo tool -Usage: dnf-repo [--version] [-n|--dryrun] [-D|--debug] [-s|--save]+Usage: dnf-repo [--version] [-n|--dryrun] [-D|--debug] [--exact] [-s|--save] [(-c|--add-copr COPR) | (-k|--add-koji REPO) | (-d|--disable REPOPAT) | (-e|--enable REPOPAT) |- (-x|--expire REPOPAT) | (-E|--delete-repo REPOPAT)]+ (-x|--expire REPOPAT) | (-E|--delete-repofile REPOPAT)] [(-t|--enable-testing) | (-T|--disable-testing)] [(-m|--enable-modular) | (-M|--disable-modular)] [DNFARGS] see https://github.com/juhp/dnf-repo#readme@@ -33,41 +33,61 @@ --version Show version -n,--dryrun Dry run -D,--debug Debug output+ --exact Match repo names exactly -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+ -E,--delete-repofile 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+## Usage examples List repos: ```shellsession $ dnf-repo ``` -Update with --enable-testing:+Update with testing repos: ```shellsession-$ sudo dnf-repo -t update+$ dnf-repo -t update ``` -List disabled copr repos:+List disabled copr repos (actually shows "copr" repos that would be enabled): ```shellsession $ dnf-repo -e copr ``` -Disable copr repos for update:+Disable all copr repos for update: ```shellsession-$ sudo dnf-repo -d copr update+$ dnf-repo -d copr update ``` Disable modular repos permanently: ```shellsession-$ sudo dnf-repo --disable-modular --save+$ dnf-repo --disable-modular --save ```+(or equivalently `dnf-repo -M -s`).++Switch a system from Rawhide to F37:+```shellsession+$ dnf-repo --exact -d rawhide -e fedora distrosync --releasever 37 fedora\*+```++Note that sudo is used implicitly when needed:+there is no need to run dnf-repo with sudo.++## Installation++A copr repo is available:+<https://copr.fedorainfracloud.org/coprs/petersen/dnf-repo/>++## Building++Use {cabal,stack,cabal-rpm} install.
dnf-repo.cabal view
@@ -1,5 +1,5 @@ name: dnf-repo-version: 0.1+version: 0.2 synopsis: DNF wrapper tool to control repos description: A command-line wrapper of the dnf package manager to@@ -20,7 +20,7 @@ || == 8.8.4 || == 8.10.7 || == 9.0.2- || == 9.2.3+ || == 9.2.4 data-dir: data data-files: copr.fedorainfracloud.orgCOLONOWNERCOLONREPO.repo koji-REPO.repo@@ -42,7 +42,7 @@ extra, filepath, simple-cmd,- simple-cmd-args+ simple-cmd-args >= 0.1.8 default-language: Haskell2010 ghc-options: -Wall if impl(ghc >= 8.0)
src/ExpireRepos.hs view
@@ -8,12 +8,12 @@ expiredFile :: FilePath expiredFile = "/var/cache/dnf/expired_repos.json" -expireRepos :: Bool -> [String] -> IO ()-expireRepos _ [] = error' "no repos to expire given"-expireRepos dryrun repos = do+expireRepos :: Bool -> Bool -> [String] -> IO ()+expireRepos _ _ [] = error' "no repos to expire given"+expireRepos dryrun debug repos = do old <- read <$> readFile expiredFile :: IO [String] let expired = nub $ old ++ repos- doSudo dryrun "sed" ["-i", "-e",+ doSudo dryrun debug "sed" ["-i", "-e", "s/" ++ renderShow old ++ "/" ++ renderShow expired ++ "/", expiredFile] putStrLn $ "expired now: " ++ show expired
src/Main.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP, LambdaCase #-} -- SPDX-License-Identifier: BSD-3-Clause @@ -30,8 +30,9 @@ runMain <$> switchWith 'n' "dryrun" "Dry run" <*> switchWith 'D' "debug" "Debug output"+ <*> switchLongWith "exact" "Match repo names exactly" <*> switchWith 's' "save" "Save the repo enable/disable state"- <*> modeOpt+ <*> many modeOpt <*> optional testingOpt <*> optional modularOpt <*> many (strArg "DNFARGS")@@ -42,8 +43,7 @@ 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+ DeleteRepo <$> strOptionWith 'E' "delete-repofile" "REPOPAT" "Remove unwanted .repo file" testingOpt = flagWith' EnableTesting 't' "enable-testing" "Enable testing repos" <|>@@ -63,35 +63,38 @@ -- 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+runMain :: Bool -> Bool -> Bool -> Bool -> [Mode]+ -> Maybe Testing -> Maybe Modular -> [String] -> IO ()-runMain dryrun debug save mode mtesting mmodular args = do+runMain dryrun debug exact save modes 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 ()+ forM_ modes $+ \case+ 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+ nameStates <- sort <$> concatMapM readRepos repofiles+ let repoActs = concatMap (selectRepo exact modes 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 ()+ forM_ modes $+ \case+ ExpireRepo _ -> do+ putStrLn ""+ expireRepos dryrun debug $ 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" $+ doSudo dryrun debug "dnf" $ "config-manager" : concatMap saveRepo repoActs if null args@@ -101,7 +104,7 @@ sleep 1 putStrLn "" let repoargs = concatMap changeRepo repoActs- in doSudo dryrun "dnf" $ repoargs ++ args+ in doSudo dryrun debug "dnf" $ repoargs ++ args where -- FIXME pull non-fedora copr repo file -- FIXME delete created copr repo file if repo doesn't exist@@ -123,7 +126,7 @@ withTempDir $ \ tmpdir -> do let tmpfile = tmpdir </> repofile unless dryrun $ writeFile tmpfile repodef- doSudo dryrun "cp" [tmpfile, repofile]+ doSudo dryrun debug "cp" [tmpfile, repofile] addKojiRepo :: String -> IO () addKojiRepo repo = do@@ -137,7 +140,7 @@ withTempDir $ \ tmpdir -> do let tmpfile = tmpdir </> repofile unless dryrun $ writeFile tmpfile repodef- doSudo dryrun "cp" [tmpfile, repofile]+ doSudo dryrun debug "cp" [tmpfile, repofile] listRepos :: [RepoState] -> IO () listRepos repoStates = do@@ -157,7 +160,7 @@ Just owner -> error' $ repofile +-+ "owned by" +-+ owner Nothing -> do ok <- yesno $ "Remove " ++ takeFileName repofile- when ok $ doSudo dryrun "rm" [repofile]+ when ok $ doSudo dryrun debug "rm" [repofile] #if !MIN_VERSION_simple_cmd(0,2,4) filesWithExtension :: FilePath -> String -> IO [FilePath]
src/Sudo.hs view
@@ -1,9 +1,14 @@ module Sudo (doSudo) where +import Control.Monad (when) 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+doSudo :: Bool -> Bool -> String -> [String] -> IO ()+doSudo dryrun debug c args = do+ if dryrun+ then cmdN c args+ else do+ when debug $ cmdN c args+ sudo_ c args putStrLn ""
src/YumRepoFile.hs view
@@ -2,7 +2,7 @@ module YumRepoFile ( Mode(..),- readRepo,+ readRepos, RepoState, selectRepo, Modular(..),@@ -22,7 +22,7 @@ data Mode = AddCopr String | AddKoji String | EnableRepo String | DisableRepo String- | ExpireRepo String | DeleteRepo String | Default+ | ExpireRepo String | DeleteRepo String deriving Eq data ChangeEnable = Disable String | Enable String | Expire String@@ -61,87 +61,85 @@ 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+selectRepo :: Bool -> [Mode] -> Maybe Testing -> Maybe Modular+ -> RepoState -> [ChangeEnable]+selectRepo exact modes mtesting mmodular (name,enabled,file) =+ case modes of+ [] -> selectOther+ (mode:modes') ->+ case mode of+ AddCopr repo -> if repo `isSuffixOf` name && not enabled+ then [Enable name]+ else selectOther+ AddKoji repo -> if repo `isSuffixOf` name && not enabled+ then [Enable name]+ else selectOther+ EnableRepo pat -> if pat `matchesRepo` name && not enabled+ then [Enable name]+ else selectOther+ DisableRepo pat -> if pat `matchesRepo` name && enabled+ then [Disable name]+ else selectOther+ ExpireRepo pat -> [Expire name | pat `matchesRepo` name]+ DeleteRepo pat -> if pat `matchesRepo` name+ then if enabled+ then error' $ "disable repo before deleting: " ++ name+ else [Delete file]+ else []+ ++ selectRepo exact modes' mtesting mmodular (name,enabled,file) where- selectOther :: Maybe ChangeEnable+ matchesRepo = if exact then (==) else isInfixOf++ selectOther :: [ChangeEnable] selectOther | "modular" `isSuffixOf` name = case mmodular of- Nothing -> 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+ then+ [Enable name | mtesting == Just EnableTesting]+ else [Enable name]+ DisableModular | enabled -> [Disable name]+ _ -> [] | "testing" `isInfixOf` name = case mtesting of- Nothing -> Nothing+ Nothing -> [] Just include -> case include of- EnableTesting | not enabled -> Just (Enable name)- DisableTesting | enabled -> Just (Disable name)- _ -> Nothing- | otherwise = Nothing+ EnableTesting | not enabled -> [Enable name]+ DisableTesting | enabled -> [Disable name]+ _ -> []+ | otherwise = [] -readRepo :: FilePath -> IO RepoState-readRepo file =- parseRepo file . lines <$> readFile file+readRepos :: FilePath -> IO [RepoState]+readRepos file =+ parseRepos 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+parseRepos :: FilePath -> [String] -> [RepoState]+parseRepos file ls =+ case nextSection ls of+ Nothing -> []+ Just (section,rest) ->+ let (enabled,more) =+ case dropWhile (not . ("enabled=" `isPrefixOf`)) rest of [] -> error' $ "no enabled field for " ++ section- (e:_) ->+ (e:more') -> case trim e of- "enabled=1" -> True- "enabled=0" -> False+ "enabled=1" -> (True,more')+ "enabled=0" -> (False,more') _ -> error' $ "unknown enabled state " ++ e ++ " for " ++ section- in (section,enabled,file)+ in (section,enabled,file) : parseRepos file more where- secName :: String -> String- secName sec =- if ' ' `elem` sec- then error' $ "section contains space: " ++ sec- else case sec of+ nextSection :: [String] -> Maybe (String,[String])+ nextSection [] = Nothing+ nextSection (l:ls') =+ case l of ('[' : rest) -> if last rest == ']'- then init rest- else error' $ "bad section " ++ sec ++ " in " ++ file- _ -> error' $ "section not found in " ++ file+ then Just (init rest, ls')+ else error' $ "bad section " ++ l ++ " in " ++ file+ _ -> nextSection ls'