cachix 0.3.4 → 0.3.5
raw patch · 25 files changed
+549/−466 lines, 25 files
Files
- CHANGELOG.md +11/−0
- cachix.cabal +2/−1
- src/Cachix/Client.hs +2/−2
- src/Cachix/Client/Commands.hs +52/−44
- src/Cachix/Client/Config.hs +9/−9
- src/Cachix/Client/Config/Orphans.hs +2/−4
- src/Cachix/Client/Env.hs +7/−7
- src/Cachix/Client/Exception.hs +0/−1
- src/Cachix/Client/HumanSize.hs +19/−0
- src/Cachix/Client/InstallationMode.hs +7/−7
- src/Cachix/Client/NetRc.hs +8/−8
- src/Cachix/Client/NixConf.hs +13/−14
- src/Cachix/Client/NixVersion.hs +2/−2
- src/Cachix/Client/OptionsParser.hs +64/−59
- src/Cachix/Client/Push.hs +107/−84
- src/Cachix/Client/Secrets.hs +16/−11
- src/Cachix/Client/Servant.hs +2/−3
- src/Cachix/Client/Store.hs +40/−24
- src/Cachix/Client/Store/Context.hs +4/−3
- src/Cachix/Client/URI.hs +2/−3
- test/InstallationModeSpec.hs +48/−48
- test/Main.hs +1/−1
- test/NetRcSpec.hs +3/−3
- test/NixConfSpec.hs +110/−110
- test/NixVersionSpec.hs +18/−18
CHANGELOG.md view
@@ -7,6 +7,17 @@ ## Unreleased +## [0.3.5] - 2020-01-10++### Added++- #265: print store path size before compressing/pushing++### Fixed++- #258: don't swallow exceptions while --watch-store+- NixOS: instruct to use sudo+ ## [0.3.4] - 2019-10-01 ### Fixed
cachix.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: cachix-version: 0.3.4+version: 0.3.5 license: Apache-2.0 license-file: LICENSE copyright: 2018 Domen Kožar@@ -47,6 +47,7 @@ Cachix.Client.Config.Orphans Cachix.Client.Env Cachix.Client.Exception+ Cachix.Client.HumanSize Cachix.Client.InstallationMode Cachix.Client.NetRc Cachix.Client.NixConf
src/Cachix/Client.hs view
@@ -1,6 +1,6 @@ module Cachix.Client- ( main- )+ ( main,+ ) where import Cachix.Client.Commands as Commands
src/Cachix/Client/Commands.hs view
@@ -8,8 +8,8 @@ create, generateKeypair, push,- use- )+ use,+ ) where import qualified Cachix.Api as Api@@ -18,29 +18,30 @@ ( BinaryCacheConfig (..), Config (..), mkConfig,- writeConfig- )+ writeConfig,+ ) import qualified Cachix.Client.Config as Config import Cachix.Client.Env (Env (..)) import Cachix.Client.Exception (CachixException (..))+import Cachix.Client.HumanSize (humanSize) import Cachix.Client.InstallationMode import qualified Cachix.Client.NixConf as NixConf import Cachix.Client.NixVersion (assertNixVersion) import Cachix.Client.OptionsParser ( CachixOptions (..), PushArguments (..),- PushOptions (..)- )+ PushOptions (..),+ ) import Cachix.Client.Push import Cachix.Client.Secrets ( SigningKey (SigningKey), exportSigningKey,- parseSigningKeyLenient- )+ parseSigningKeyLenient,+ ) import Cachix.Client.Servant import qualified Cachix.Types.SigningKeyCreate as SigningKeyCreate import Control.Concurrent (threadDelay)-import Control.Exception.Safe (throwM)+import Control.Exception.Safe (handle, throwM) import Control.Retry (RetryStatus (rsIterNumber)) import Crypto.Sign.Ed25519 import qualified Data.ByteString.Base64 as B64@@ -68,9 +69,9 @@ putStrLn ( [hereLit| Continue by creating a binary cache at https://cachix.org- |]- :: Text- )+ |] ::+ Text+ ) create :: Env -> Text -> IO () create _ _ =@@ -85,14 +86,12 @@ bcc = BinaryCacheConfig name signingKey -- we first validate if key can be added to the binary cache (_ :: NoContent) <-- escalate- =<< ( (`runClientM` clientenv env)- $ Api.createKey (cachixBCClient name) (authToken config) signingKeyCreate- )+ escalate <=< (`runClientM` clientenv env) $+ Api.createKey (cachixBCClient name) (authToken config) signingKeyCreate -- if key was successfully added, write it to the config -- TODO: warn if binary cache with the same key already exists- writeConfig (configPath (cachixoptions env))- $ config {binaryCaches = binaryCaches config <> [bcc]}+ writeConfig (configPath (cachixoptions env)) $+ config {binaryCaches = binaryCaches config <> [bcc]} putStrLn ( [iTrim| Secret signing key has been saved in the file above. To populate@@ -110,9 +109,9 @@ $ cachix use ${name} IMPORTANT: Make sure to make a backup for the signing key above, as you have the only copy.- |]- :: Text- )+ |] ::+ Text+ ) envToToken :: Env -> Token envToToken env =@@ -146,9 +145,9 @@ { isRoot = user == "root", isTrusted = isTrusted, isNixOS = isNixOS- }- addBinaryCache (config env) binaryCache useOptions- $ fromMaybe (getInstallationMode nixEnv) (useMode useOptions)+ }+ addBinaryCache (config env) binaryCache useOptions $+ fromMaybe (getInstallationMode nixEnv) (useMode useOptions) -- TODO: lots of room for performance improvements push :: Env -> PushArguments -> IO ()@@ -166,17 +165,18 @@ (_, paths) -> return paths sk <- findSigningKey env name store <- wait (storeAsync env)- void- $ pushClosure- (mapConcurrentlyBounded 4)- (clientenv env)- store PushCache+ void $+ pushClosure+ (mapConcurrentlyBounded 4)+ (clientenv env)+ store+ PushCache { pushCacheToken = envToToken env, pushCacheName = name, pushCacheSigningKey = sk- }- (pushStrategy env opts name)- inputStorePaths+ }+ (pushStrategy env opts name)+ inputStorePaths putText "All done." push env (PushWatchStore opts name) = withManager $ \mgr -> do _ <- watchDir mgr "/nix/store" filterF action@@ -184,9 +184,14 @@ putText "Watching /nix/store for new builds ..." forever $ threadDelay 1000000 where+ logErr :: FilePath -> SomeException -> IO ()+ logErr fp e = hPutStrLn stderr $ "Exception occured while pushing " <> fp <> ": " <> show e action :: Action action (Removed fp _ _) =- pushStorePath env opts name $ toS $ dropEnd 5 fp+ Control.Exception.Safe.handle (logErr fp)+ $ pushStorePath env opts name+ $ toS+ $ dropEnd 5 fp action _ = return () filterF :: ActionPredicate filterF (Removed fp _ _)@@ -196,10 +201,12 @@ dropEnd index xs = take (length xs - index) xs -- | Find a secret key in the 'Config' or environment variable-findSigningKey- :: Env- -> Text -- ^ Cache name- -> IO SigningKey -- ^ Secret key or exception+findSigningKey ::+ Env ->+ -- | Cache name+ Text ->+ -- | Secret key or exception+ IO SigningKey findSigningKey env name = do maybeEnvSK <- lookupEnv "CACHIX_SIGNING_KEY" -- we reverse list of caches to prioritize keys added as last@@ -224,12 +231,12 @@ then throwM $ accessDeniedBinaryCache name else throwM notAuthenticatedBinaryCache, onError = throwM,- onAttempt = \retrystatus ->+ onAttempt = \retrystatus size -> -- we append newline instead of putStrLn due to https://github.com/haskell/text/issues/242- putStr $ "pushing " <> retryText retrystatus <> storePath <> "\n",+ putStr $ retryText retrystatus <> "compressing and pushing " <> storePath <> " (" <> humanSize (fromIntegral size) <> ")\n", onDone = pass, withXzipCompressor = defaultWithXzipCompressorWithLevel (compressionLevel opts)- }+ } pushStorePath :: Env -> PushOptions -> Text -> Text -> IO () pushStorePath env opts name storePath = do@@ -239,10 +246,11 @@ store <- wait (storeAsync env) pushSingleStorePath (clientenv env)- store PushCache- { pushCacheToken = envToToken env,- pushCacheName = name,- pushCacheSigningKey = sk+ store+ PushCache+ { pushCacheToken = envToToken env,+ pushCacheName = name,+ pushCacheSigningKey = sk } (pushStrategy env opts name storePath) storePath
src/Cachix/Client/Config.hs view
@@ -7,8 +7,8 @@ writeConfig, getDefaultFilename, ConfigPath,- mkConfig- )+ mkConfig,+ ) where import Cachix.Client.Config.Orphans ()@@ -21,35 +21,35 @@ ( XdgDirectory (..), createDirectoryIfMissing, doesFileExist,- getXdgDirectory- )+ getXdgDirectory,+ ) import System.FilePath.Posix (takeDirectory) import System.Posix.Files ( ownerReadMode, ownerWriteMode, setFileMode,- unionFileModes- )+ unionFileModes,+ ) data BinaryCacheConfig = BinaryCacheConfig { name :: Text, secretKey :: Text- }+ } deriving (Show, Generic, Interpret, Inject) data Config = Config { authToken :: Token, binaryCaches :: [BinaryCacheConfig]- }+ } deriving (Show, Generic, Interpret, Inject) mkConfig :: Text -> Config mkConfig token = Config { authToken = Token (toS token), binaryCaches = []- }+ } type ConfigPath = FilePath
src/Cachix/Client/Config/Orphans.hs view
@@ -11,18 +11,16 @@ import Servant.Auth.Client instance Interpret Token where- autoWith _ = Type { extract = ex, expected = Text- }+ } where ex (TextLit (Chunks [] t)) = pure (Token (toS t)) ex _ = panic "Unexpected Dhall value. Did it typecheck?" instance Inject Token where- injectWith _ = InputType { embed = TextLit . Chunks [] . toS . getToken, declared = Text- }+ }
src/Cachix/Client/Env.hs view
@@ -1,8 +1,8 @@ module Cachix.Client.Env ( Env (..), mkEnv,- cachixVersion- )+ cachixVersion,+ ) where import Cachix.Client.Config (Config, readConfig)@@ -15,8 +15,8 @@ ( ManagerSettings, managerModifyRequest, managerResponseTimeout,- responseTimeoutNone- )+ responseTimeoutNone,+ ) import Network.HTTP.Client.TLS (newTlsManagerWith, tlsManagerSettings) import Network.HTTP.Simple (setRequestHeader) import Paths_cachix (version)@@ -30,7 +30,7 @@ clientenv :: ClientEnv, cachixoptions :: CachixOptions, storeAsync :: Async Store- }+ } mkEnv :: CachixOptions -> IO Env mkEnv rawcachixoptions = do@@ -46,7 +46,7 @@ clientenv = clientenv, cachixoptions = cachixoptions, storeAsync = store- }+ } customManagerSettings :: ManagerSettings customManagerSettings =@@ -54,7 +54,7 @@ { managerResponseTimeout = responseTimeoutNone, -- managerModifyRequest :: Request -> IO Request managerModifyRequest = return . setRequestHeader "User-Agent" [toS cachixVersion]- }+ } cachixVersion :: Text cachixVersion = "cachix " <> toS (showVersion version)
src/Cachix/Client/Exception.hs view
@@ -18,6 +18,5 @@ deriving (Show, Typeable) instance Exception CachixException where- displayException (NixOSInstructions txt) = toS txt displayException e = show e
+ src/Cachix/Client/HumanSize.hs view
@@ -0,0 +1,19 @@+module Cachix.Client.HumanSize where++import Protolude+import Text.Printf (printf)+import qualified Prelude++humanSize :: Double -> Text+humanSize size+ | size < unitstep 1 = render "B" size+ | size < unitstep 2 = render "KiB" $ size / unitstep 1+ | size < unitstep 3 = render "MiB" $ size / unitstep 2+ | size < unitstep 4 = render "GiB" $ size / unitstep 3+ | otherwise = render "TiB" $ size / unitstep 4+ where+ unitstep :: Int -> Double+ unitstep i = 1024.0 ^ i+ render :: Text -> Double -> Text+ render unit unitsize =+ toS (printf "%.2f" unitsize :: Prelude.String) <> " " <> unit
src/Cachix/Client/InstallationMode.hs view
@@ -9,8 +9,8 @@ getUser, fromString, toString,- UseOptions (..)- )+ UseOptions (..),+ ) where import Cachix.Api as Api@@ -31,7 +31,7 @@ { isTrusted :: Bool, isRoot :: Bool, isNixOS :: Bool- }+ } -- NOTE: update the list of options for --mode argument in OptionsParser.hs data InstallationMode@@ -44,7 +44,7 @@ = UseOptions { useMode :: Maybe InstallationMode, useNixOSFolder :: FilePath- }+ } deriving (Show) fromString :: String -> Maybe InstallationMode@@ -70,8 +70,8 @@ -- | Add a Binary cache to nix.conf, print nixos config or fail addBinaryCache :: Maybe Config -> Api.BinaryCache -> UseOptions -> InstallationMode -> IO () addBinaryCache _ _ _ UntrustedRequiresSudo =- throwIO- $ MustBeRoot "Run command as root OR execute: $ echo \"trusted-users = root $USER\" | sudo tee -a /etc/nix/nix.conf && sudo pkill nix-daemon"+ throwIO $+ MustBeRoot "Run command as root OR execute: $ echo \"trusted-users = root $USER\" | sudo tee -a /etc/nix/nix.conf && sudo pkill nix-daemon" addBinaryCache maybeConfig bc useOptions WriteNixOS = nixosBinaryCache maybeConfig bc useOptions addBinaryCache maybeConfig bc _ (Install ncl) = do@@ -140,7 +140,7 @@ Then run: - $ nixos-rebuild switch+ $ sudo nixos-rebuild switch |] glueModule :: Text glueModule =
src/Cachix/Client/NetRc.hs view
@@ -1,7 +1,7 @@ -- Deals with adding private caches to netrc module Cachix.Client.NetRc- ( add- )+ ( add,+ ) where import qualified Cachix.Api as Api@@ -20,11 +20,11 @@ -- | Add a list of binary caches to netrc under `filename`. -- Makes sure there are no duplicate entries (using domain as a key). -- If file under filename doesn't exist it's created.-add- :: Config- -> [Api.BinaryCache]- -> FilePath- -> IO ()+add ::+ Config ->+ [Api.BinaryCache] ->+ FilePath ->+ IO () add config binarycaches filename = do doesExist <- doesFileExist filename netrc <-@@ -51,7 +51,7 @@ nrhPassword = getToken (authToken config), nrhAccount = "", nrhMacros = []- }+ } where -- stripPrefix that either strips or returns the same string stripPrefix :: Text -> Text -> Text
src/Cachix/Client/NixConf.hs view
@@ -28,8 +28,8 @@ isTrustedUsers, defaultPublicURI, defaultSigningKey,- setNetRC- )+ setNetRC,+ ) where import Cachix.Api (BinaryCache (..))@@ -41,8 +41,8 @@ ( XdgDirectory (..), createDirectoryIfMissing, doesFileExist,- getXdgDirectory- )+ getXdgDirectory,+ ) import System.FilePath.Posix (takeDirectory) import qualified Text.Megaparsec as Mega import Text.Megaparsec.Char@@ -87,8 +87,8 @@ -- | Pure version of addIO add :: BinaryCache -> [NixConf] -> NixConf -> NixConf add bc toRead toWrite =- writeLines isPublicKey (TrustedPublicKeys $ nub publicKeys)- $ writeLines isSubstituter (Substituters $ nub substituters) 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) <> [uri bc]@@ -122,14 +122,13 @@ doesExist <- doesFileExist filename if not doesExist then return Nothing- else- do- result <- parse <$> readFile filename- case result of- 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+ else do+ result <- parse <$> readFile filename+ case result of+ 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
src/Cachix/Client/NixVersion.hs view
@@ -1,7 +1,7 @@ module Cachix.Client.NixVersion ( assertNixVersion,- parseNixVersion- )+ parseNixVersion,+ ) where import Data.Text as T
src/Cachix/Client/OptionsParser.hs view
@@ -4,8 +4,8 @@ PushArguments (..), PushOptions (..), BinaryCacheName,- getOpts- )+ getOpts,+ ) where import qualified Cachix.Client.Config as Config@@ -19,40 +19,41 @@ URIRef, parseURI, serializeURIRef',- strictURIParserOptions- )+ strictURIParserOptions,+ ) data CachixOptions = CachixOptions { host :: URIRef Absolute, configPath :: Config.ConfigPath, verbose :: Bool- }+ } deriving (Show) parserCachixOptions :: Config.ConfigPath -> Parser CachixOptions parserCachixOptions defaultConfigPath = CachixOptions- <$> option uriOption- ( long "host"- <> value defaultCachixURI- <> metavar "URI"- <> showDefaultWith (toS . serializeURIRef')- <> help "Host to connect to"- )+ <$> option+ uriOption+ ( long "host"+ <> value defaultCachixURI+ <> metavar "URI"+ <> showDefaultWith (toS . serializeURIRef')+ <> help "Host to connect to"+ ) <*> strOption- ( long "config"- <> short 'c'- <> value defaultConfigPath- <> metavar "CONFIGPATH"- <> showDefault- <> help "Cachix configuration file"- )+ ( long "config"+ <> short 'c'+ <> value defaultConfigPath+ <> metavar "CONFIGPATH"+ <> showDefault+ <> help "Cachix configuration file"+ ) <*> switch- ( long "verbose"- <> short 'v'- <> help "Verbose mode"- )+ ( long "verbose"+ <> short 'v'+ <> help "Verbose mode"+ ) uriOption :: ReadM (URIRef Absolute) uriOption = eitherReader $ \s ->@@ -77,17 +78,17 @@ data PushOptions = PushOptions { compressionLevel :: Int- }+ } deriving (Show) parserCachixCommand :: Parser CachixCommand parserCachixCommand =- subparser- $ command "authtoken" (infoH authtoken (progDesc "Configure token for authentication to cachix.org"))- <> command "create" (infoH create (progDesc "DEPRECATED: Go to https://cachix.org instead"))- <> command "generate-keypair" (infoH generateKeypair (progDesc "Generate keypair for a binary cache"))- <> command "push" (infoH push (progDesc "Upload Nix store paths to the binary cache"))- <> command "use" (infoH use (progDesc "Configure nix.conf to enable binary cache during builds"))+ subparser $+ command "authtoken" (infoH authtoken (progDesc "Configure token for authentication to cachix.org"))+ <> command "create" (infoH create (progDesc "DEPRECATED: Go to https://cachix.org instead"))+ <> command "generate-keypair" (infoH generateKeypair (progDesc "Generate keypair for a binary cache"))+ <> command "push" (infoH push (progDesc "Upload Nix store paths to the binary cache"))+ <> command "use" (infoH use (progDesc "Configure nix.conf to enable binary cache during builds")) where nameArg = strArgument (metavar "NAME") authtoken = AuthToken <$> strArgument (metavar "TOKEN")@@ -98,44 +99,47 @@ pushOptions :: Parser PushOptions pushOptions = PushOptions- <$> option (auto >>= validatedLevel)- ( long "compression-level"- <> metavar "[0..9]"- <> help- "The compression level for XZ compression.\- \ Take compressor *and* decompressor memory usage into account before using [7..9]!"- <> showDefault- <> value 2- )+ <$> option+ (auto >>= validatedLevel)+ ( long "compression-level"+ <> metavar "[0..9]"+ <> help+ "The compression level for XZ compression.\+ \ Take compressor *and* decompressor memory usage into account before using [7..9]!"+ <> showDefault+ <> value 2+ ) push = (\opts cache f -> Push $ f opts cache) <$> pushOptions <*> nameArg <*> (pushPaths <|> pushWatchStore) pushPaths = (\paths opts cache -> PushPaths opts cache paths) <$> many (strArgument (metavar "PATHS...")) pushWatchStore = (\() opts cache -> PushWatchStore opts cache)- <$> flag' ()- ( long "watch-store"- <> short 'w'- <> help "Run in daemon mode and push store paths as they are added to /nix/store"- )+ <$> flag'+ ()+ ( long "watch-store"+ <> short 'w'+ <> help "Run in daemon mode and push store paths as they are added to /nix/store"+ ) use = Use <$> nameArg <*> ( InstallationMode.UseOptions <$> optional- ( option (maybeReader InstallationMode.fromString)- ( long "mode"- <> short 'm'- <> help "Mode in which to configure binary caches for Nix. Supported values: nixos, root-nixconf, user-nixconf"- )- )+ ( option+ (maybeReader InstallationMode.fromString)+ ( long "mode"+ <> short 'm'+ <> help "Mode in which to configure binary caches for Nix. Supported values: nixos, root-nixconf, user-nixconf"+ )+ ) <*> strOption- ( long "nixos-folder"- <> short 'd'- <> help "Base directory for NixOS configuration"- <> value "/etc/nixos/"- <> showDefault- )- )+ ( long "nixos-folder"+ <> short 'd'+ <> help "Base directory for NixOS configuration"+ <> value "/etc/nixos/"+ <> showDefault+ )+ ) getOpts :: IO (CachixOptions, CachixCommand) getOpts = do@@ -148,11 +152,12 @@ parser = (,) <$> parserCachixOptions configpath <*> (parserCachixCommand <|> versionParser) versionParser :: Parser CachixCommand versionParser =- flag' Version+ flag'+ Version ( long "version" <> short 'V' <> help "Show cachix version"- )+ ) desc :: InfoMod a desc =
src/Cachix/Client/Push.hs view
@@ -10,10 +10,11 @@ PushStrategy (..), defaultWithXzipCompressor, defaultWithXzipCompressorWithLevel,+ -- * Pushing a closure of store paths pushClosure,- mapConcurrentlyBounded- )+ mapConcurrentlyBounded,+ ) where import qualified Cachix.Api as Api@@ -54,17 +55,18 @@ { pushCacheName :: Text, pushCacheSigningKey :: SigningKey, pushCacheToken :: Token- }+ } data PushStrategy m r = PushStrategy- { onAlreadyPresent :: m r, -- ^ Called when a path is already in the cache.- onAttempt :: RetryStatus -> m (),+ { -- | Called when a path is already in the cache.+ onAlreadyPresent :: m r,+ onAttempt :: RetryStatus -> Int64 -> m (), on401 :: m r, onError :: ClientError -> m r, onDone :: m r, withXzipCompressor :: forall a. (ConduitM ByteString ByteString (ResourceT IO) () -> m a) -> m a- }+ } defaultWithXzipCompressor :: forall m a. (ConduitM ByteString ByteString (ResourceT IO) () -> m a) -> m a defaultWithXzipCompressor = ($ compress (Just 2))@@ -72,25 +74,30 @@ defaultWithXzipCompressorWithLevel :: Int -> forall m a. (ConduitM ByteString ByteString (ResourceT IO) () -> m a) -> m a defaultWithXzipCompressorWithLevel l = ($ compress (Just l)) -pushSingleStorePath- :: (MonadMask m, MonadIO m)- => ClientEnv -- ^ cachix base url, connection manager, see 'Cachix.Client.URI.defaultCachixBaseUrl', 'Servant.Client.mkClientEnv'- -> Store- -> PushCache -- ^ details for pushing to cache- -> PushStrategy m r -- ^ how to report results, (some) errors, and do some things- -> Text -- ^ store path- -> m r -- ^ r is determined by the 'PushStrategy'+pushSingleStorePath ::+ (MonadMask m, MonadIO m) =>+ -- | cachix base url, connection manager, see 'Cachix.Client.URI.defaultCachixBaseUrl', 'Servant.Client.mkClientEnv'+ ClientEnv ->+ Store ->+ -- | details for pushing to cache+ PushCache ->+ -- | how to report results, (some) errors, and do some things+ PushStrategy m r ->+ -- | store path+ Text ->+ -- | r is determined by the 'PushStrategy'+ m r pushSingleStorePath clientEnv _store cache cb storePath = retryAll $ \retrystatus -> do let storeHash = fst $ splitStorePath $ toS storePath name = pushCacheName cache -- Check if narinfo already exists -- TODO: query also cache.nixos.org? server-side? res <-- liftIO $ (`runClientM` clientEnv)- $ Api.narinfoHead- (cachixBCClient name)- (pushCacheToken cache)- (Api.NarInfoC storeHash)+ liftIO $ (`runClientM` clientEnv) $+ Api.narinfoHead+ (cachixBCClient name)+ (pushCacheToken cache)+ (Api.NarInfoC storeHash) case res of Right NoContent -> onAlreadyPresent cb -- we're done as store path is already in the cache Left err@@ -98,25 +105,34 @@ | isErr err status401 -> on401 cb | otherwise -> onError cb err -uploadStorePath- :: (MonadMask m, MonadIO m)- => ClientEnv -- ^ cachix base url, connection manager, see 'Cachix.Client.URI.defaultCachixBaseUrl', 'Servant.Client.mkClientEnv'- -> Store- -> PushCache -- ^ details for pushing to cache- -> PushStrategy m r -- ^ how to report results, (some) errors, and do some things- -> Text -- ^ store path- -> RetryStatus- -> m r -- ^ r is determined by the 'PushStrategy'-uploadStorePath clientEnv _store cache cb storePath retrystatus = do+uploadStorePath ::+ (MonadMask m, MonadIO m) =>+ -- | cachix base url, connection manager, see 'Cachix.Client.URI.defaultCachixBaseUrl', 'Servant.Client.mkClientEnv'+ ClientEnv ->+ Store ->+ -- | details for pushing to cache+ PushCache ->+ -- | how to report results, (some) errors, and do some things+ PushStrategy m r ->+ -- | store path+ Text ->+ RetryStatus ->+ -- | r is determined by the 'PushStrategy'+ m r+uploadStorePath clientEnv store cache cb storePath retrystatus = do let (storeHash, storeSuffix) = splitStorePath $ toS storePath name = pushCacheName cache- onAttempt cb retrystatus narSizeRef <- liftIO $ newIORef 0 fileSizeRef <- liftIO $ newIORef 0 narHashRef <- liftIO $ newIORef ("" :: ByteString) fileHashRef <- liftIO $ newIORef ("" :: ByteString)+ normalized <- liftIO $ Store.followLinksToStorePath store $ toS storePath+ pathinfo <- liftIO $ Store.queryPathInfo store normalized -- stream store path as xz compressed nar file let cmd = proc "nix-store" ["--dump", toS storePath]+ storePathSize :: Int64+ storePathSize = Store.validPathInfoNarSize pathinfo+ onAttempt cb retrystatus storePathSize -- TODO: print process stderr? (ClosedStream, (stdoutStream, closeStdout), ClosedStream, cph) <- liftIO $ streamingProcess cmd withXzipCompressor cb $ \xzCompressor -> do@@ -127,50 +143,54 @@ .| xzCompressor .| passthroughSizeSink fileSizeRef .| passthroughHashSinkB16 fileHashRef- -- for now we need to use letsencrypt domain instead of cloudflare due to its upload limits- let newClientEnv =+ let subdomain =+ -- TODO: multipart+ if (fromIntegral storePathSize / (1024 * 1024)) > 100+ then "api"+ else toS name+ newClientEnv = clientEnv- { baseUrl = (baseUrl clientEnv) {baseUrlHost = toS name <> "." <> baseUrlHost (baseUrl clientEnv)}- }+ { baseUrl = (baseUrl clientEnv) {baseUrlHost = subdomain <> "." <> baseUrlHost (baseUrl clientEnv)}+ } (_ :: NoContent) <- liftIO $ (`withClientM` newClientEnv)- (Api.createNar (cachixBCStreamingClient name) stream')+ (Api.createNar (cachixBCStreamingClient name) stream') $ escalate- >=> \NoContent -> do- closeStdout- exitcode <- waitForStreamingProcess cph- when (exitcode /= ExitSuccess) $ throwM $ NarStreamingError exitcode $ show cmd- -- TODO: we're done, so we can leave withClientM. Doing so- -- will allow more concurrent requests when the number- -- of XZ compressors is limited- narSize <- readIORef narSizeRef- narHash <- ("sha256:" <>) . System.Nix.Base32.encode <$> readIORef narHashRef- fileHash <- readIORef fileHashRef- fileSize <- readIORef fileSizeRef- deriverRaw <- T.strip . toS <$> readProcess "nix-store" ["-q", "--deriver", toS storePath] mempty- let deriver =- if deriverRaw == "unknown-deriver"- then deriverRaw- else T.drop 11 deriverRaw- references <- sort . T.lines . T.strip . toS <$> readProcess "nix-store" ["-q", "--references", toS storePath] mempty- let fp = fingerprint storePath narHash narSize references- sig = dsign (signingSecretKey $ pushCacheSigningKey cache) fp- nic = Api.NarInfoCreate- { Api.cStoreHash = storeHash,- Api.cStoreSuffix = storeSuffix,- Api.cNarHash = narHash,- Api.cNarSize = narSize,- Api.cFileSize = fileSize,- Api.cFileHash = toS fileHash,- Api.cReferences = fmap (T.drop 11) references,- Api.cDeriver = deriver,- Api.cSig = toS $ B64.encode $ unSignature sig+ >=> \NoContent -> do+ closeStdout+ exitcode <- waitForStreamingProcess cph+ when (exitcode /= ExitSuccess) $ throwM $ NarStreamingError exitcode $ show cmd+ -- TODO: we're done, so we can leave withClientM. Doing so+ -- will allow more concurrent requests when the number+ -- of XZ compressors is limited+ narSize <- readIORef narSizeRef+ narHash <- ("sha256:" <>) . System.Nix.Base32.encode <$> readIORef narHashRef+ fileHash <- readIORef fileHashRef+ fileSize <- readIORef fileSizeRef+ deriverRaw <- T.strip . toS <$> readProcess "nix-store" ["-q", "--deriver", toS storePath] mempty+ let deriver =+ if deriverRaw == "unknown-deriver"+ then deriverRaw+ else T.drop 11 deriverRaw+ references <- sort . T.lines . T.strip . toS <$> readProcess "nix-store" ["-q", "--references", toS storePath] mempty+ let fp = fingerprint storePath narHash narSize references+ sig = dsign (signingSecretKey $ pushCacheSigningKey cache) fp+ nic = Api.NarInfoCreate+ { Api.cStoreHash = storeHash,+ Api.cStoreSuffix = storeSuffix,+ Api.cNarHash = narHash,+ Api.cNarSize = narSize,+ Api.cFileSize = fileSize,+ Api.cFileHash = toS fileHash,+ Api.cReferences = fmap (T.drop 11) references,+ Api.cDeriver = deriver,+ Api.cSig = toS $ B64.encode $ unSignature sig }- escalate $ Api.isNarInfoCreateValid nic- -- Upload narinfo with signature- escalate <=< (`runClientM` clientEnv)- $ Api.createNarinfo+ escalate $ Api.isNarInfoCreateValid nic+ -- Upload narinfo with signature+ escalate <=< (`runClientM` clientEnv) $+ Api.createNarinfo (cachixBCClient name) (Api.NarInfoC storeHash) nic@@ -187,18 +207,21 @@ -- | Push an entire closure -- -- Note: 'onAlreadyPresent' will be called less often in the future.-pushClosure- :: (MonadIO m, MonadMask m)- => (forall a b. (a -> m b) -> [a] -> m [b])- -- ^ Traverse paths, responsible for bounding parallel processing of paths+pushClosure ::+ (MonadIO m, MonadMask m) =>+ -- | Traverse paths, responsible for bounding parallel processing of paths -- -- For example: @'mapConcurrentlyBounded' 4@- -> ClientEnv -- ^ See 'pushSingleStorePath'- -> Store- -> PushCache- -> (Text -> PushStrategy m r)- -> [Text] -- ^ Initial store paths- -> m [r] -- ^ Every @r@ per store path of the entire closure of store paths+ (forall a b. (a -> m b) -> [a] -> m [b]) ->+ -- | See 'pushSingleStorePath'+ ClientEnv ->+ Store ->+ PushCache ->+ (Text -> PushStrategy m r) ->+ -- | Initial store paths+ [Text] ->+ -- | Every @r@ per store path of the entire closure of store paths+ m [r] pushClosure traversal clientEnv store pushCache pushStrategy inputStorePaths = do -- Get the transitive closure of dependencies paths <-@@ -215,12 +238,12 @@ retryAll $ \_ -> escalate =<< liftIO- ( (`runClientM` clientEnv)- $ Api.narinfoBulk- (cachixBCClient (pushCacheName pushCache))- (pushCacheToken pushCache)- (fst . splitStorePath <$> paths)- )+ ( (`runClientM` clientEnv) $+ Api.narinfoBulk+ (cachixBCClient (pushCacheName pushCache))+ (pushCacheToken pushCache)+ (fst . splitStorePath <$> paths)+ ) let missingHashes = Set.fromList missingHashesList missingPaths = filter (\path -> Set.member (fst (splitStorePath path)) missingHashes) paths -- TODO: make pool size configurable, on beefier machines this could be doubled
src/Cachix/Client/Secrets.hs view
@@ -4,8 +4,8 @@ ( -- * NAR signing SigningKey (..), parseSigningKeyLenient,- exportSigningKey- )+ exportSigningKey,+ ) where -- TODO: * Auth token@@ -18,9 +18,11 @@ -- | A secret key for signing nars. newtype SigningKey = SigningKey {signingSecretKey :: SecretKey} -parseSigningKeyLenientBS- :: ByteString -- ^ ASCII (Base64)- -> Either Text SigningKey -- ^ Error message or signing key+parseSigningKeyLenientBS ::+ -- | ASCII (Base64)+ ByteString ->+ -- | Error message or signing key+ Either Text SigningKey parseSigningKeyLenientBS raw = let bcDropWhileEnd f = BC.reverse . BC.dropWhile f . BC.reverse bcDropAround f = bcDropWhileEnd f . BC.dropWhile f@@ -28,14 +30,17 @@ nonNull = if BC.null stripped then Left "A signing key must not be empty" else pure stripped in SigningKey . SecretKey . B64.decodeLenient <$> nonNull -parseSigningKeyLenient- :: Text -- ^ Base64- -> Either Text SigningKey -- ^ Error message or signing key+parseSigningKeyLenient ::+ -- | Base64+ Text ->+ -- | Error message or signing key+ Either Text SigningKey parseSigningKeyLenient = parseSigningKeyLenientBS . toSL -exportSigningKeyBS- :: SigningKey- -> ByteString -- ^ ASCII (Base64)+exportSigningKeyBS ::+ SigningKey ->+ -- | ASCII (Base64)+ ByteString exportSigningKeyBS (SigningKey (SecretKey bs)) = B64.encode bs exportSigningKey :: SigningKey -> Text
src/Cachix/Client/Servant.hs view
@@ -59,6 +59,5 @@ runAuthenticatedClient env m = do config <- escalate $ maybeToEither (Exception.NoConfig "Start with visiting https://cachix.org and copying the token to $ cachix authtoken <token>") (Env.config env)- escalate- =<< ((`runClientM` Env.clientenv env)- $ m (Config.authToken config))+ escalate <=< (`runClientM` Env.clientenv env) $+ m (Config.authToken config)
src/Cachix/Client/Store.hs view
@@ -4,12 +4,16 @@ module Cachix.Client.Store ( Store,+ -- * Getting a Store openStore, releaseStore,+ -- * Query a path followLinksToStorePath, queryPathInfo,+ validPathInfoNarSize,+ -- * Get closures computeFSClosure, ClosureParams (..),@@ -18,9 +22,10 @@ newEmptyPathSet, addToPathSet, traversePathSet,+ -- * Miscellaneous- storeUri- )+ storeUri,+ ) where import Cachix.Client.Store.Context (NixStore, Ref, ValidPathInfo, context)@@ -85,17 +90,21 @@ return strdup(uri.c_str()); } |] --- TODO: test queryPathInfo-queryPathInfo :: Store -> ByteString -> IO (ForeignPtr (Ref ValidPathInfo))+queryPathInfo ::+ Store ->+ -- | Exact store path, not a subpath+ ByteString ->+ -- | ValidPathInfo or exception+ IO (ForeignPtr (Ref ValidPathInfo)) queryPathInfo (Store store) path = do vpi <-- [C.exp| refValidPathInfo* {- new refValidPathInfo((*$(refStore* store))->queryPathInfo($bs-cstr:path))- } |]+ [C.throwBlock| refValidPathInfo*+ {+ return new refValidPathInfo((*$(refStore* store))->queryPathInfo($bs-cstr:path));+ } |] newForeignPtr finalizeRefValidPathInfo vpi finalizeRefValidPathInfo :: FinalizerPtr (Ref ValidPathInfo)- {-# NOINLINE finalizeRefValidPathInfo #-} finalizeRefValidPathInfo = unsafePerformIO@@ -104,11 +113,19 @@ [](refValidPathInfo *v){ delete v; } } |] +-- | The narSize field of a ValidPathInfo struct. Source: store-api.hh+validPathInfoNarSize :: ForeignPtr (Ref ValidPathInfo) -> Int64+validPathInfoNarSize vpi =+ fromIntegral $+ toInteger+ [C.pure| long+ { (*$fptr-ptr:(refValidPathInfo* vpi))->narSize }+ |]+ ----- PathSet ----- newtype PathSet = PathSet (ForeignPtr (C.Set C.CxxString)) finalizePathSet :: FinalizerPtr C.PathSet- {-# NOINLINE finalizePathSet #-} finalizePathSet = unsafePerformIO@@ -143,21 +160,20 @@ delete $(PathSetIterator *i); delete $(PathSetIterator *end); }|]- flip finally cleanup- $ let go :: ([a] -> [a]) -> IO [a]- go acc = do- isDone <-- [C.exp| int {+ flip finally cleanup $+ let go :: ([a] -> [a]) -> IO [a]+ go acc = do+ isDone <-+ [C.exp| int { *$(PathSetIterator *i) == *$(PathSetIterator *end) }|]- if isDone /= 0- then pure $ acc []- else- do- somePath <- unsafePackMallocCString =<< [C.exp| const char *{ strdup((*$(PathSetIterator *i))->c_str()) } |]- a <- f somePath- [C.throwBlock| void { (*$(PathSetIterator *i))++; } |]- go (acc . (a :))+ if isDone /= 0+ then pure $ acc []+ else do+ somePath <- unsafePackMallocCString =<< [C.exp| const char *{ strdup((*$(PathSetIterator *i))->c_str()) } |]+ a <- f somePath+ [C.throwBlock| void { (*$(PathSetIterator *i))++; } |]+ go (acc . (a :)) in go identity ----- computeFSClosure -----@@ -166,14 +182,14 @@ { flipDirection :: Bool, includeOutputs :: Bool, includeDerivers :: Bool- }+ } defaultClosureParams :: ClosureParams defaultClosureParams = ClosureParams { flipDirection = False, includeOutputs = False, includeDerivers = False- }+ } computeFSClosure :: Store -> ClosureParams -> PathSet -> IO PathSet computeFSClosure (Store store) params startingSet_ = withPathSet startingSet_ $ \startingSet -> do
src/Cachix/Client/Store/Context.hs view
@@ -34,8 +34,9 @@ <> mempty { C.ctxTypesTable = M.singleton (C.TypeName "refStore") [t|Ref NixStore|]- <> M.singleton (C.TypeName "refValidPathInfo")- [t|Ref ValidPathInfo|]+ <> M.singleton+ (C.TypeName "refValidPathInfo")+ [t|Ref ValidPathInfo|] <> M.singleton (C.TypeName "PathSet") [t|PathSet|] <> M.singleton (C.TypeName "PathSetIterator") [t|Iterator PathSet|]- }+ }
src/Cachix/Client/URI.hs view
@@ -4,8 +4,8 @@ module Cachix.Client.URI ( getBaseUrl, defaultCachixURI,- defaultCachixBaseUrl- )+ defaultCachixBaseUrl,+ ) where import Protolude@@ -17,7 +17,6 @@ -- TODO: make getBaseUrl internal -- | Partial function from URI to BaseUrl--- getBaseUrl :: URIRef Absolute -> BaseUrl getBaseUrl uriref = case uriAuthority uriref of
test/InstallationModeSpec.hs view
@@ -7,59 +7,59 @@ spec :: Spec spec = describe "getInstallationMode" $ do- it "NixOS with root prints configuration"- $ let nixenv = NixEnv- { isTrusted = True, -- any- isRoot = True,- isNixOS = True- }+ it "NixOS with root prints configuration" $+ let nixenv = NixEnv+ { isTrusted = True, -- any+ isRoot = True,+ isNixOS = True+ } in getInstallationMode nixenv `shouldBe` WriteNixOS- it "NixOS without trust prints configuration plus trust"- $ let nixenv = NixEnv- { isTrusted = False,- isRoot = False,- isNixOS = True- }+ it "NixOS without trust prints configuration plus trust" $+ let nixenv = NixEnv+ { isTrusted = False,+ isRoot = False,+ isNixOS = True+ } in getInstallationMode nixenv `shouldBe` UntrustedRequiresSudo- it "NixOS without trust prints configuration on older Nix"- $ let nixenv = NixEnv- { isTrusted = False,- isRoot = False,- isNixOS = True- }+ it "NixOS without trust prints configuration on older Nix" $+ let nixenv = NixEnv+ { isTrusted = False,+ isRoot = False,+ isNixOS = True+ } in getInstallationMode nixenv `shouldBe` UntrustedRequiresSudo- it "NixOS non-root trusted results into local install"- $ let nixenv = NixEnv- { isTrusted = True,- isRoot = False,- isNixOS = True- }+ it "NixOS non-root trusted results into local install" $+ let nixenv = NixEnv+ { isTrusted = True,+ isRoot = False,+ isNixOS = True+ } in getInstallationMode nixenv `shouldBe` Install NixConf.Local- it "non-NixOS root results into global install"- $ let nixenv = NixEnv- { isTrusted = True,- isRoot = True,- isNixOS = False- }+ it "non-NixOS root results into global install" $+ let nixenv = NixEnv+ { isTrusted = True,+ isRoot = True,+ isNixOS = False+ } in getInstallationMode nixenv `shouldBe` Install NixConf.Global- it "non-NixOS with Nix 1.X root results into global install"- $ let nixenv = NixEnv- { isTrusted = True,- isRoot = True,- isNixOS = False -- any- }+ it "non-NixOS with Nix 1.X root results into global install" $+ let nixenv = NixEnv+ { isTrusted = True,+ isRoot = True,+ isNixOS = False -- any+ } in getInstallationMode nixenv `shouldBe` Install NixConf.Global- it "non-NixOS non-root trusted results into local install"- $ let nixenv = NixEnv- { isTrusted = True,- isRoot = False,- isNixOS = False- }+ it "non-NixOS non-root trusted results into local install" $+ let nixenv = NixEnv+ { isTrusted = True,+ isRoot = False,+ isNixOS = False+ } in getInstallationMode nixenv `shouldBe` Install NixConf.Local- it "non-NixOS non-root non-trusted results into required sudo"- $ let nixenv = NixEnv- { isTrusted = False,- isRoot = False,- isNixOS = False- }+ it "non-NixOS non-root non-trusted results into required sudo" $+ let nixenv = NixEnv+ { isTrusted = False,+ isRoot = False,+ isNixOS = False+ } in getInstallationMode nixenv `shouldBe` UntrustedRequiresSudo
test/Main.hs view
@@ -10,4 +10,4 @@ config = defaultConfig { configColorMode = ColorAlways- }+ }
test/NetRcSpec.hs view
@@ -15,7 +15,7 @@ publicSigningKeys = ["pub"], isPublic = False, githubUsername = "foobar"- }+ } bc2 :: BinaryCache bc2 = BinaryCache@@ -24,13 +24,13 @@ publicSigningKeys = ["pub2"], isPublic = False, githubUsername = "foobar2"- }+ } config :: Config config = Config { authToken = "token123", binaryCaches = []- }+ } -- TODO: poor man's golden tests, use https://github.com/stackbuilders/hspec-golden test :: [BinaryCache] -> Text -> Expectation
test/NixConfSpec.hs view
@@ -18,127 +18,127 @@ isPublic = True, publicSigningKeys = ["pub"], githubUsername = "foobar"- }+ } spec :: Spec spec = do describe "render . parse" $ do- it "handles single value substituters"- $ property "substituters = a\n"- it "handles multi value substituters"- $ 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 "random content"- $ property "blabla = foobar\nfoo = bar\n"+ it "handles single value substituters" $+ property "substituters = a\n"+ it "handles multi value substituters" $+ 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 "random content" $+ property "blabla = foobar\nfoo = bar\n" describe "add" $ do- it "merges binary caches from both files"- $ let globalConf =- NixConf- [ Substituters ["bc1"],- TrustedPublicKeys ["pub1"]- ]- localConf =- NixConf- [ Substituters ["bc2"],- TrustedPublicKeys ["pub2"]- ]- result =- NixConf- [ Substituters [defaultPublicURI, "bc1", "bc2", "https://name.cachix.org"],- TrustedPublicKeys [defaultSigningKey, "pub1", "pub2", "pub"]- ]+ it "merges binary caches from both files" $+ let globalConf =+ NixConf+ [ Substituters ["bc1"],+ TrustedPublicKeys ["pub1"]+ ]+ localConf =+ NixConf+ [ Substituters ["bc2"],+ TrustedPublicKeys ["pub2"]+ ]+ result =+ NixConf+ [ Substituters [defaultPublicURI, "bc1", "bc2", "https://name.cachix.org"],+ 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- [ Substituters [defaultPublicURI, "https://name.cachix.org"],- TrustedPublicKeys [defaultSigningKey, "pub"]- ]- localConf = NixConf []- result =- NixConf- [ Substituters [defaultPublicURI, "https://name.cachix.org"],- TrustedPublicKeys [defaultSigningKey, "pub"]- ]+ it "is noop if binary cache exists in one file" $+ let globalConf =+ NixConf+ [ Substituters [defaultPublicURI, "https://name.cachix.org"],+ TrustedPublicKeys [defaultSigningKey, "pub"]+ ]+ localConf = NixConf []+ result =+ NixConf+ [ Substituters [defaultPublicURI, "https://name.cachix.org"],+ TrustedPublicKeys [defaultSigningKey, "pub"]+ ] in add bc [globalConf, localConf] localConf `shouldBe` result- it "preserves other nixconf entries"- $ let globalConf =- NixConf- [ Substituters ["http"],- TrustedPublicKeys ["pub1"],- TrustedUsers ["user"],- Other "foo"- ]- localConf =- NixConf- [ TrustedUsers ["user2"],- Other "bar"- ]- result =- NixConf- [ TrustedUsers ["user2"],- Other "bar",- Substituters [defaultPublicURI, "http", "https://name.cachix.org"],- TrustedPublicKeys [defaultSigningKey, "pub1", "pub"]- ]+ it "preserves other nixconf entries" $+ let globalConf =+ NixConf+ [ Substituters ["http"],+ TrustedPublicKeys ["pub1"],+ TrustedUsers ["user"],+ Other "foo"+ ]+ localConf =+ NixConf+ [ TrustedUsers ["user2"],+ Other "bar"+ ]+ result =+ NixConf+ [ TrustedUsers ["user2"],+ Other "bar",+ Substituters [defaultPublicURI, "http", "https://name.cachix.org"],+ TrustedPublicKeys [defaultSigningKey, "pub1", "pub"]+ ] in add bc [globalConf, localConf] localConf `shouldBe` result- it "removed duplicates"- $ let globalConf =- NixConf- [ Substituters ["bc1", "bc1"],- TrustedPublicKeys ["pub1", "pub1"]- ]- localConf =- NixConf- [ Substituters ["bc2", "bc2"],- TrustedPublicKeys ["pub2", "pub2"]- ]- result =- NixConf- [ Substituters [defaultPublicURI, "bc1", "bc2", "https://name.cachix.org"],- TrustedPublicKeys [defaultSigningKey, "pub1", "pub2", "pub"]- ]+ it "removed duplicates" $+ let globalConf =+ NixConf+ [ Substituters ["bc1", "bc1"],+ TrustedPublicKeys ["pub1", "pub1"]+ ]+ localConf =+ NixConf+ [ Substituters ["bc2", "bc2"],+ TrustedPublicKeys ["pub2", "pub2"]+ ]+ result =+ NixConf+ [ Substituters [defaultPublicURI, "bc1", "bc2", "https://name.cachix.org"],+ 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 []- result =- NixConf- [ Substituters [defaultPublicURI, "https://name.cachix.org"],- TrustedPublicKeys [defaultSigningKey, "pub"]- ]+ it "adds binary cache and defaults if no existing entries exist" $+ let globalConf = NixConf []+ localConf = NixConf []+ result =+ NixConf+ [ Substituters [defaultPublicURI, "https://name.cachix.org"],+ TrustedPublicKeys [defaultSigningKey, "pub"]+ ] in add bc [globalConf, localConf] localConf `shouldBe` result 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 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 ""- ]- )+ 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 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 ""+ ]+ ) realExample :: Text realExample =
test/NixVersionSpec.hs view
@@ -6,21 +6,21 @@ spec :: Spec spec = describe "parseNixVersion" $ do- it "parses 'nix-env (Nix) 2.0' as Nix20"- $ parseNixVersion "nix-env (Nix) 2.0"- `shouldBe` Left "Nix 2.0.2 or lower is not supported. Please upgrade: https://nixos.org/nix/"- it "parses 'nix-env (Nix) 2.0.1' as Nix201"- $ parseNixVersion "nix-env (Nix) 2.0.1"- `shouldBe` Right ()- it "parses 'nix-env (Nix) 1.11.13' as Nix1XX"- $ parseNixVersion "nix-env (Nix) 1.11.13"- `shouldBe` Left "Nix 2.0.2 or lower is not supported. Please upgrade: https://nixos.org/nix/"- it "parses 'nix-env (Nix) 2.0.5' as Nix201"- $ parseNixVersion "nix-env (Nix) 2.0.5"- `shouldBe` Right ()- it "parses 'nix-env (Nix) 2.0.1pre6053_444b921' as Nix201"- $ parseNixVersion "nix-env (Nix) 2.0.1pre6053_444b921"- `shouldBe` Right ()- it "fails with unknown string 'foobar'"- $ parseNixVersion "foobar"- `shouldBe` Left "Couldn't parse 'nix-env --version' output: foobar"+ it "parses 'nix-env (Nix) 2.0' as Nix20" $+ parseNixVersion "nix-env (Nix) 2.0"+ `shouldBe` Left "Nix 2.0.2 or lower is not supported. Please upgrade: https://nixos.org/nix/"+ it "parses 'nix-env (Nix) 2.0.1' as Nix201" $+ parseNixVersion "nix-env (Nix) 2.0.1"+ `shouldBe` Right ()+ it "parses 'nix-env (Nix) 1.11.13' as Nix1XX" $+ parseNixVersion "nix-env (Nix) 1.11.13"+ `shouldBe` Left "Nix 2.0.2 or lower is not supported. Please upgrade: https://nixos.org/nix/"+ it "parses 'nix-env (Nix) 2.0.5' as Nix201" $+ parseNixVersion "nix-env (Nix) 2.0.5"+ `shouldBe` Right ()+ it "parses 'nix-env (Nix) 2.0.1pre6053_444b921' as Nix201" $+ parseNixVersion "nix-env (Nix) 2.0.1pre6053_444b921"+ `shouldBe` Right ()+ it "fails with unknown string 'foobar'" $+ parseNixVersion "foobar"+ `shouldBe` Left "Couldn't parse 'nix-env --version' output: foobar"