cachix 1.7.5 → 1.7.6
raw patch · 6 files changed
+323/−95 lines, 6 files
Files
- CHANGELOG.md +6/−0
- cachix.cabal +2/−1
- src/Cachix/Client/Exception.hs +4/−0
- src/Cachix/Client/InstallationMode.hs +36/−12
- src/Cachix/Client/NixConf.hs +176/−70
- test/NixConfSpec.hs +99/−12
CHANGELOG.md view
@@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.7.6] - 2025-01-23++### Fixed++- cachix use: support `include` statements when adding caches to `nix.conf`+ ## [1.7.5] - 2024-10-11 ### Fixed
cachix.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: cachix-version: 1.7.5+version: 1.7.6 synopsis: Command-line client for Nix binary cache hosting https://cachix.org @@ -263,6 +263,7 @@ , dhall , directory , extra+ , filepath , hercules-ci-cnix-store , here , hspec
src/Cachix/Client/Exception.hs view
@@ -12,6 +12,8 @@ | NoInput Text | NoConfig Text | NetRcParseError Text+ | IncludeNotFound Text+ | CircularInclude Text | NarStreamingError ExitCode Text | NarHashMismatch Text | DeprecatedCommand Text@@ -33,6 +35,8 @@ displayException (ArtifactNotFound s) = toS s displayException (NoSigningKey s) = toS s displayException (NetRcParseError s) = toS s+ displayException (IncludeNotFound s) = toS s+ displayException (CircularInclude s) = toS s displayException (NarStreamingError _ s) = toS s displayException (NarHashMismatch s) = toS s displayException (DeprecatedCommand s) = toS s
src/Cachix/Client/InstallationMode.hs view
@@ -81,8 +81,8 @@ getNixEnv :: IO NixEnv getNixEnv = do user <- getUser- nc <- NixConf.read NixConf.Global- isTrusted <- isTrustedUser $ NixConf.readLines (catMaybes [nc]) NixConf.isTrustedUsers+ ncs <- NixConf.resolveIncludes =<< NixConf.readWithDefault NixConf.Global+ isTrusted <- isTrustedUser $ concatMap (NixConf.readLines NixConf.isTrustedUsers) ncs isNixOS <- doesFileExist "/run/current-system/nixos-version" return $ NixEnv@@ -139,37 +139,55 @@ addBinaryCache config bc _ (Install ncl) = do (input, output) <- prepareNixConf ncl netrcLocMaybe <- forM (guard $ not (BinaryCache.isPublic bc)) $ const $ addPrivateBinaryCacheNetRC config bc ncl- let addNetRCLine :: NixConf.NixConf -> NixConf.NixConf+ let addNetRCLine :: NixConf.NixConfSource -> NixConf.NixConfSource addNetRCLine = fromMaybe identity $ do netrcLoc <- netrcLocMaybe :: Maybe FilePath -- We only add the netrc line for local user configs for now. -- On NixOS we assume it will be picked up from the default location. guard (ncl == NixConf.Local)- pure (NixConf.setNetRC $ toS netrcLoc)- NixConf.write ncl $ addNetRCLine $ NixConf.add bc input output+ pure $ setNetRC (toS netrcLoc)+ NixConf.write $ addNetRCLine $ NixConf.add bc input output filename <- NixConf.getFilename ncl putStrLn $ "Configured " <> BinaryCache.uri bc <> " binary cache in " <> toS filename -prepareNixConf :: NixConf.NixConfLoc -> IO ([NixConf.NixConf], NixConf.NixConf)+-- | Resolve and read the nix.conf.+--+-- Returns a set of "input" confs and the "output" conf.+--+-- The output is the parsed conf file at the location specified by the NixConfLoc.+--+-- Inputs are any confs included by the output conf, plus any additional external resolutions.+-- For example, for the local Nix conf, we also return the global one.+prepareNixConf :: NixConf.NixConfLoc -> IO ([NixConf.NixConfSource], NixConf.NixConfSource) prepareNixConf ncl = do+ outputPath <- NixConf.getFilename ncl -- TODO: might need locking one day gnc <- NixConf.read NixConf.Global+ gncInputs <- traverse NixConf.resolveIncludes gnc+ (input, output) <- case ncl of- NixConf.Global -> return ([gnc], gnc)+ NixConf.Global -> do+ return (gncInputs, gnc) NixConf.Local -> do lnc <- NixConf.read NixConf.Local- return ([gnc, lnc], lnc)+ lncInputs <- traverse NixConf.resolveIncludes lnc+ return (gncInputs <> lncInputs, lnc) NixConf.Custom _ -> do lnc <- NixConf.read ncl- return ([lnc], lnc)- return (catMaybes input, fromMaybe (NixConf.NixConf []) output)+ lncInputs <- traverse NixConf.resolveIncludes lnc+ return (lncInputs, lnc) + return+ ( fromMaybe [] input,+ fromMaybe (NixConf.new outputPath) output+ )+ removeBinaryCache :: URI.URI -> Text -> InstallationMode -> IO () removeBinaryCache uri name (Install ncl) = do- contents <- fromMaybe (NixConf.NixConf []) <$> NixConf.read ncl+ contents <- NixConf.readWithDefault ncl let (final, removed) = NixConf.remove uri name [contents] contents- NixConf.write ncl final+ NixConf.write final filename <- NixConf.getFilename ncl if removed then putStrLn $ "Removed " <> host <> " binary cache in " <> toS filename@@ -178,6 +196,12 @@ host = URI.toByteString (URI.appendSubdomain name uri) removeBinaryCache _ _ _ = do throwIO $ RemoveCacheUnsupported "Removing binary caches is only supported for nix.conf"++setNetRC :: Text -> NixConf.NixConfSource -> NixConf.NixConfSource+setNetRC netrc conf = (fmap . fmap) (\ls -> filter noNetRc ls ++ [NixConf.NetRcFile netrc]) conf+ where+ noNetRc (NixConf.NetRcFile _) = False+ noNetRc _ = True nixosBinaryCache :: Config -> BinaryCache.BinaryCache -> UseOptions -> IO () nixosBinaryCache config bc UseOptions {useNixOSFolder = baseDirectory} = do
src/Cachix/Client/NixConf.hs view
@@ -1,7 +1,6 @@ {-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{- (Very limited) parser, rendered and modifier of nix.conf+{- (Very limited) parser, renderer and modifier of nix.conf Supports subset of nix.conf given Nix 2.0 or Nix 1.0 @@ -14,12 +13,17 @@ ( NixConf, NixConfG (..), NixConfLine (..),+ NixConfSource,+ NixConfSourceG (..), NixConfLoc (..),+ IncludeType (..),+ new, render, add, remove, read,- update,+ readWithDefault,+ resolveIncludes, write, getFilename, parser,@@ -29,10 +33,10 @@ isTrustedUsers, defaultPublicURI, defaultSigningKey,- setNetRC, ) where +import Cachix.Client.Exception (CachixException (..)) import Cachix.Client.URI qualified as URI import Cachix.Types.BinaryCache qualified as BinaryCache import Data.List (nub)@@ -45,35 +49,114 @@ doesFileExist, getXdgDirectory, )-import System.FilePath.Posix (takeDirectory)+import System.FilePath (normalise)+import System.FilePath.Posix (takeDirectory, (</>)) import Text.Megaparsec qualified as Mega import Text.Megaparsec.Char +defaultPublicURI :: Text+defaultPublicURI = "https://cache.nixos.org"++defaultSigningKey :: Text+defaultSigningKey = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="+ data NixConfLine = Substituters [Text] | TrustedUsers [Text] | TrustedPublicKeys [Text] | NetRcFile Text+ | Include IncludeType | Other Text deriving (Show, Eq) -newtype NixConfG a = NixConf a- deriving (Show, Eq, Functor)+data IncludeType+ = RequiredInclude Text -- for "include"+ | OptionalInclude Text -- for "!include"+ deriving (Show, Eq) +-- | A list of conf lines type NixConf = NixConfG [NixConfLine] -readLines :: [NixConf] -> (NixConfLine -> Maybe [Text]) -> [Text]-readLines nixconfs predicate = concatMap f nixconfs- where- f (NixConf xs) = foldl foldIt [] xs- foldIt :: [Text] -> NixConfLine -> [Text]- foldIt prev new = prev <> fromMaybe [] (predicate new)+newtype NixConfG a = NixConf a+ deriving stock (Show, Eq, Functor) -writeLines :: (NixConfLine -> Maybe [Text]) -> NixConfLine -> NixConf -> NixConf-writeLines predicate addition = fmap f- where- f x = filter (isNothing . predicate) x <> [addition]+-- | A wrapper around NixConf that also tracks the path to the nix.conf file+type NixConfSource = NixConfSourceG NixConf +data NixConfSourceG a = NixConfSource+ { nixConfPath :: FilePath,+ nixConfLines :: a+ }+ deriving stock (Show, Eq, Functor)++-- | Operations on nix.conf.+-- Helps to work with both NixConf and NixConfSource.+class NixConfOps a where+ -- | Read the nix.conf lines that match the given predicate+ readLines :: (NixConfLine -> Maybe [Text]) -> a -> [Text]++ -- | Write the given lines to the nix.conf+ writeLines :: (NixConfLine -> Maybe [Text]) -> NixConfLine -> a -> a++ -- | Add the given binary cache to the nix.conf+ add :: BinaryCache.BinaryCache -> [a] -> a -> a++ -- | Remove the given binary cache from the nix.conf+ remove :: URI.URI -> Text -> [a] -> a -> (a, Bool)++ -- | Render the nix.conf to a Text+ render :: a -> Text++instance NixConfOps NixConf where+ readLines predicate (NixConf xs) = foldl f [] xs+ where+ f :: [Text] -> NixConfLine -> [Text]+ f prev next = prev <> fromMaybe [] (predicate next)++ writeLines predicate addition = fmap f+ where+ f x = filter (isNothing . predicate) x <> [addition]++ add bc toRead toWrite =+ writeLines isPublicKey (TrustedPublicKeys $ nub publicKeys) $+ writeLines isSubstituter (Substituters $ nub substituters) toWrite+ where+ -- Note: some defaults are always appended since overriding some setttings in nix.conf overrides defaults otherwise+ substituters = (defaultPublicURI : concatMap (readLines isSubstituter) toRead) <> [BinaryCache.uri bc]+ publicKeys = (defaultSigningKey : concatMap (readLines isPublicKey) toRead) <> BinaryCache.publicSigningKeys bc++ remove uri name toRead toWrite =+ (newconf, oldsubstituters /= substituters)+ where+ newconf =+ writeLines isPublicKey (TrustedPublicKeys $ nub publicKeys) $+ writeLines isSubstituter (Substituters $ nub substituters) toWrite+ oldsubstituters = concatMap (readLines isSubstituter) toRead+ substituters = filter (toS (URI.toByteString fulluri) /=) oldsubstituters+ oldpublicKeys = concatMap (readLines isPublicKey) toRead+ publicKeys = filter (not . T.isPrefixOf (toS $ URI.hostBS $ URI.getHostname fulluri)) oldpublicKeys+ fulluri = URI.appendSubdomain name uri++ render (NixConf ls) = T.unlines $ fmap go ls+ where+ go :: NixConfLine -> Text+ go (Substituters xs) = "substituters" <> " = " <> T.unwords xs+ go (TrustedUsers xs) = "trusted-users = " <> T.unwords xs+ go (TrustedPublicKeys xs) = "trusted-public-keys" <> " = " <> T.unwords xs+ go (NetRcFile filename) = "netrc-file = " <> filename+ go (Include (RequiredInclude path)) = "include " <> path+ go (Include (OptionalInclude path)) = "!include " <> path+ go (Other line) = line++instance NixConfOps NixConfSource where+ readLines f = readLines f . nixConfLines+ writeLines f = fmap . writeLines f+ add bc toRead toWrite = toWrite {nixConfLines = add bc (fmap nixConfLines toRead) (nixConfLines toWrite)}+ remove uri name toRead toWrite =+ let (newLines, changed) = remove uri name (fmap nixConfLines toRead) (nixConfLines toWrite)+ in (toWrite {nixConfLines = newLines}, changed)+ render = render . nixConfLines+ isSubstituter :: NixConfLine -> Maybe [Text] isSubstituter (Substituters xs) = Just xs isSubstituter _ = Nothing@@ -86,54 +169,75 @@ isTrustedUsers (TrustedUsers xs) = Just xs isTrustedUsers _ = Nothing --- | Pure version of addIO-add :: BinaryCache.BinaryCache -> [NixConf] -> NixConf -> NixConf-add bc toRead toWrite =- writeLines isPublicKey (TrustedPublicKeys $ nub publicKeys) $- writeLines isSubstituter (Substituters $ nub substituters) toWrite- where- -- Note: some defaults are always appended since overriding some setttings in nix.conf overrides defaults otherwise- substituters = (defaultPublicURI : readLines toRead isSubstituter) <> [BinaryCache.uri bc]- publicKeys = (defaultSigningKey : readLines toRead isPublicKey) <> BinaryCache.publicSigningKeys bc+-- | Create a new, empty NixConfSource with the given path+new :: FilePath -> NixConfSource+new path = NixConfSource path (NixConf []) -remove :: URI.URI -> Text -> [NixConf] -> NixConf -> (NixConf, Bool)-remove uri name toRead toWrite =- (newconf, oldsubstituters /= substituters)+write :: NixConfSource -> IO ()+write NixConfSource {nixConfPath, nixConfLines} = do+ createDirectoryIfMissing True (takeDirectory nixConfPath)+ writeFile nixConfPath $ render nixConfLines++-- | Resolves includes in the given NixConfSource, starting from the given source file.+resolveIncludes :: NixConfSource -> IO [NixConfSource]+resolveIncludes conf@NixConfSource {nixConfPath} =+ resolveIncludesWithStack [normalise nixConfPath] nixConfPath conf++resolveIncludesWithStack :: [FilePath] -> FilePath -> NixConfSource -> IO [NixConfSource]+resolveIncludesWithStack stack baseFile baseConf@(NixConfSource {nixConfLines = NixConf ls}) = do+ includedConfigs <- mapM resolveInclude [f | Include f <- ls]+ return $ baseConf : concat includedConfigs where- newconf =- writeLines isPublicKey (TrustedPublicKeys $ nub publicKeys) $- writeLines isSubstituter (Substituters $ nub substituters) toWrite- oldsubstituters = readLines toRead isSubstituter- substituters = filter (toS (URI.toByteString fulluri) /=) oldsubstituters- oldpublicKeys = readLines toRead isPublicKey- publicKeys = filter (not . T.isPrefixOf (toS $ URI.hostBS $ URI.getHostname fulluri)) oldpublicKeys- fulluri = URI.appendSubdomain name uri+ dir = takeDirectory baseFile -defaultPublicURI :: Text-defaultPublicURI = "https://cache.nixos.org"+ resolveInclude :: IncludeType -> IO [NixConfSource]+ resolveInclude includeType = do+ let path = case includeType of+ RequiredInclude p -> p+ OptionalInclude p -> p+ fullPath = normalise $ dir </> toS path -defaultSigningKey :: Text-defaultSigningKey = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="+ if fullPath `elem` stack+ then case includeType of+ RequiredInclude _ -> throwIO $ CircularInclude (formatCircularError fullPath)+ OptionalInclude _ -> return []+ else do+ exists <- doesFileExist fullPath+ if not exists && isRequired includeType+ then throwIO $ IncludeNotFound $ toS fullPath+ else do+ content <- readFile fullPath+ case parse content of+ Left _err -> return []+ Right conf -> resolveIncludesWithStack (fullPath : stack) baseFile (NixConfSource fullPath conf) -render :: NixConf -> Text-render (NixConf nixconflines) = T.unlines $ fmap go nixconflines- where- go :: NixConfLine -> Text- go (Substituters xs) = "substituters" <> " = " <> T.unwords xs- go (TrustedUsers xs) = "trusted-users = " <> T.unwords xs- go (TrustedPublicKeys xs) = "trusted-public-keys" <> " = " <> T.unwords xs- go (NetRcFile filename) = "netrc-file = " <> filename- go (Other line) = line+ isRequired (RequiredInclude _) = True+ isRequired (OptionalInclude _) = False -write :: NixConfLoc -> NixConf -> IO ()-write ncl nc = do- filename <- getFilename ncl- createDirectoryIfMissing True (takeDirectory filename)- writeFile filename $ render nc+ formatCircularError path =+ "Circular include detected:\n" <> T.intercalate "\n" (formatChain (reverse stack) path) -read :: NixConfLoc -> IO (Maybe NixConf)+ formatChain :: [FilePath] -> FilePath -> [Text]+ formatChain chain target =+ case chain of+ [] -> []+ (p : ps) ->+ format p+ : map ((" -> includes " <>) . format) ps+ ++ [" -> includes " <> format target <> " (circular reference)\n"]+ where+ format = toS . normalise :: FilePath -> Text++data NixConfLoc = Global | Local | Custom FilePath+ deriving stock (Show, Eq)++read :: NixConfLoc -> IO (Maybe NixConfSource) read ncl = do filename <- getFilename ncl+ read' filename++read' :: FilePath -> IO (Maybe NixConfSource)+read' filename = do doesExist <- doesFileExist filename if not doesExist then return Nothing@@ -143,21 +247,12 @@ Left err -> do putStrLn (Mega.errorBundlePretty err) panic $ toS filename <> " failed to parse, please copy the above error and contents of nix.conf and open an issue at https://github.com/cachix/cachix"- Right conf -> return $ Just conf--update :: NixConfLoc -> (Maybe NixConf -> NixConf) -> IO ()-update ncl f = do- nc <- f <$> read ncl- write ncl nc--setNetRC :: Text -> NixConf -> NixConf-setNetRC netrc (NixConf nc) = NixConf $ filter noNetRc nc ++ [NetRcFile netrc]- where- noNetRc (NetRcFile _) = False- noNetRc _ = True+ Right conf -> return (Just (NixConfSource filename conf)) -data NixConfLoc = Global | Local | Custom FilePath- deriving (Show, Eq)+readWithDefault :: NixConfLoc -> IO NixConfSource+readWithDefault ncl = do+ filename <- getFilename ncl+ fromMaybe (new filename) <$> read' filename getFilename :: NixConfLoc -> IO FilePath getFilename ncl = do@@ -183,6 +278,15 @@ _ <- many spaceChar return $ constr (fmap toS values) +parseInclude :: (Text -> IncludeType) -> Text -> Parser NixConfLine+parseInclude constr name = Mega.try $ do+ _ <- optional (some (char ' '))+ _ <- string name+ _ <- some (char ' ')+ path <- many (Mega.satisfy (not . isSpace))+ _ <- many spaceChar+ return $ Include (constr (toS path))+ parseOther :: Parser NixConfLine parseOther = Mega.try $ Other . toS <$> Mega.someTill Mega.anySingle (void eol <|> Mega.eof) @@ -196,6 +300,8 @@ <|> parseLine Substituters "binary-caches" -- NB: assume that space in this option means space in filename <|> parseLine (NetRcFile . T.concat) "netrc-file"+ <|> parseInclude RequiredInclude "include"+ <|> parseInclude OptionalInclude "!include" <|> parseOther parser :: Parser NixConf
test/NixConfSpec.hs view
@@ -2,11 +2,14 @@ module NixConfSpec where +import Cachix.Client.Exception (CachixException (CircularInclude)) import Cachix.Client.NixConf as NixConf import Cachix.Types.BinaryCache (BinaryCache (..), CompressionMethod (..)) import Cachix.Types.Permission (Permission (..)) import Data.String.Here import Protolude+import System.FilePath ((</>))+import System.IO.Temp (withTempDirectory) import Test.Hspec property :: Text -> Expectation@@ -33,8 +36,11 @@ property "substituters = a b c\n" it "handles all known keys" $ property "substituters = a b c\ntrusted-users = him me\ntrusted-public-keys = a\n"+ it "handles includes" $+ property "include /etc/nix/nix.conf\n!include /etc/nix/nix.conf\n" it "random content" $ property "blabla = foobar\nfoo = bar\n"+ describe "add" $ do it "merges binary caches from both files" $ let globalConf =@@ -53,6 +59,7 @@ TrustedPublicKeys [defaultSigningKey, "pub1", "pub2", "pub"] ] in add bc [globalConf, localConf] localConf `shouldBe` result+ it "is noop if binary cache exists in one file" $ let globalConf = NixConf@@ -66,6 +73,7 @@ TrustedPublicKeys [defaultSigningKey, "pub"] ] in add bc [globalConf, localConf] localConf `shouldBe` result+ it "preserves other nixconf entries" $ let globalConf = NixConf@@ -87,6 +95,7 @@ TrustedPublicKeys [defaultSigningKey, "pub1", "pub"] ] in add bc [globalConf, localConf] localConf `shouldBe` result+ it "removed duplicates" $ let globalConf = NixConf@@ -104,6 +113,7 @@ TrustedPublicKeys [defaultSigningKey, "pub1", "pub2", "pub"] ] in add bc [globalConf, localConf] localConf `shouldBe` result+ it "adds binary cache and defaults if no existing entries exist" $ let globalConf = NixConf [] localConf = NixConf []@@ -113,6 +123,7 @@ TrustedPublicKeys [defaultSigningKey, "pub"] ] in add bc [globalConf, localConf] localConf `shouldBe` result+ describe "remove" $ do it "removes a binary cache" $ let localConf =@@ -126,6 +137,7 @@ TrustedPublicKeys [defaultSigningKey] ] in remove "https://cachix.org" "name" [localConf] localConf `shouldBe` (result, True)+ it "removes nothing if the binary cache is missing" $ let globalConf = NixConf@@ -135,37 +147,99 @@ localConf = globalConf result = localConf in remove "https://cachix.org" "name" [globalConf, localConf] localConf `shouldBe` (result, False)+ describe "parse" $ do it "parses substituters" $ parse "substituters = a\n" `shouldBe` Right (NixConf [Substituters ["a"]])+ it "parses long key" $ parse "binary-caches-parallel-connections = 40\n" `shouldBe` Right (NixConf [Other "binary-caches-parallel-connections = 40"])+ it "parses substituters with multiple values" $ parse "substituters = a b c\n" `shouldBe` Right (NixConf [Substituters ["a", "b", "c"]])+ it "parses equal sign after the first key as literal" $ parse "substituters = a b c= d\n" `shouldBe` Right (NixConf [Substituters ["a", "b", "c=", "d"]])+ it "parses with missing endline" $ parse "allowed-users = *" `shouldBe` Right (NixConf [Other "allowed-users = *"])++ it "parses include" $+ parse "include /etc/nix/nix.conf\n"+ `shouldBe` Right (NixConf [Include (RequiredInclude "/etc/nix/nix.conf")])++ it "parses !include" $+ parse "!include /etc/nix/nix.conf\n"+ `shouldBe` Right (NixConf [Include (OptionalInclude "/etc/nix/nix.conf")])+ it "parses a complex example" $ parse realExample- `shouldBe` Right- ( NixConf- [ Other "",- Substituters ["a", "b", "c"],- TrustedUsers ["him", "me"],- TrustedPublicKeys ["a"],- Other "blabla = asd",- Other "# comment",- Other "",- Other ""- ]- )+ `shouldBe` Right parsedRealExample + describe "NixConfSource" $ do+ it "write . read" $ do+ withTempDirectory "/tmp" "nixconf" $ \temp -> do+ let confPath = temp </> "nix.conf"+ let subConfPath = temp </> "sub.conf"+ let confContents = "include " <> toS subConfPath <> "\n"+ let parsedConfContents = NixConf [Include (RequiredInclude (toS subConfPath))]+ writeFile confPath confContents+ writeFile subConfPath realExample++ Just conf <- NixConf.read (Custom temp)+ conf `shouldBe` NixConfSource confPath parsedConfContents++ NixConf.write conf+ readFile confPath `shouldReturn` confContents++ it "resolves includes" $ do+ withTempDirectory "/tmp" "nixconf" $ \temp -> do+ let confPath = temp </> "nix.conf"+ subConfPath = temp </> "sub.conf"+ confContents = "include " <> toS subConfPath <> "\n"+ parsedConfContents = NixConf [Include (RequiredInclude (toS subConfPath))]+ writeFile confPath confContents+ writeFile subConfPath realExample++ Just conf <- NixConf.read (Custom temp)+ NixConf.resolveIncludes conf+ `shouldReturn` [ NixConfSource confPath parsedConfContents,+ NixConfSource subConfPath parsedRealExample+ ]++ it "detects cycles" $ do+ withTempDirectory "/tmp" "nixconf" $ \temp -> do+ let confPath = temp </> "nix.conf"+ subConfPath = temp </> "sub.conf"+ confContents :: Text = "include " <> toS subConfPath <> "\n"+ subConfContents :: Text = "include " <> toS confPath <> "\n"+ writeFile confPath confContents+ writeFile subConfPath subConfContents++ Just conf <- NixConf.read (Custom temp)++ let isCircularInclude = \case+ CircularInclude _ -> True+ _ -> False+ NixConf.resolveIncludes conf `shouldThrow` isCircularInclude++ it "detects trusted-users through includes" $ do+ withTempDirectory "/tmp" "nixconf" $ \temp -> do+ let confPath = temp </> "nix.conf"+ subConfPath = temp </> "sub.conf"+ confContents :: Text = "include " <> toS subConfPath <> "\n"+ subConfContents :: Text = "trusted-users = @wheel"+ writeFile confPath confContents+ writeFile subConfPath subConfContents++ ncs <- NixConf.resolveIncludes =<< NixConf.readWithDefault (NixConf.Custom temp)+ concatMap (NixConf.readLines NixConf.isTrustedUsers) ncs `shouldBe` ["@wheel"]+ realExample :: Text realExample = [hereLit|@@ -177,3 +251,16 @@ |]++parsedRealExample :: NixConf+parsedRealExample =+ NixConf+ [ Other "",+ Substituters ["a", "b", "c"],+ TrustedUsers ["him", "me"],+ TrustedPublicKeys ["a"],+ Other "blabla = asd",+ Other "# comment",+ Other "",+ Other ""+ ]