packages feed

cachix 0.3.4 → 0.3.5

raw patch · 25 files changed

+549/−466 lines, 25 files

Files

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"