hercules-ci-agent 0.9.0 → 0.9.1
raw patch · 6 files changed
+174/−32 lines, 6 filesdep ~hercules-ci-api-agentPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: hercules-ci-api-agent
API changes (from Hackage documentation)
+ Hercules.Agent.WorkerProtocol.Command.Eval: [isFlakeJob] :: Eval -> Bool
+ Hercules.Agent.WorkerProtocol.Command.Eval: [srcInput] :: Eval -> Maybe (ViaJSON ImmutableGitInput)
+ Paths_hercules_ci_agent: getBinDir :: IO FilePath
+ Paths_hercules_ci_agent: getDataDir :: IO FilePath
+ Paths_hercules_ci_agent: getDataFileName :: FilePath -> IO FilePath
+ Paths_hercules_ci_agent: getDynLibDir :: IO FilePath
+ Paths_hercules_ci_agent: getLibDir :: IO FilePath
+ Paths_hercules_ci_agent: getLibexecDir :: IO FilePath
+ Paths_hercules_ci_agent: getSysconfDir :: IO FilePath
+ Paths_hercules_ci_agent: version :: Version
- Hercules.Agent.WorkerProtocol.Command.Eval: Eval :: FilePath -> Text -> Map Text Arg -> [(Text, Text)] -> ViaJSON GitSource -> Text -> LogSettings -> ViaJSON Selector -> Maybe (Map Text ()) -> Bool -> [ByteString] -> Eval
+ Hercules.Agent.WorkerProtocol.Command.Eval: Eval :: FilePath -> Text -> Map Text Arg -> [(Text, Text)] -> ViaJSON GitSource -> Maybe (ViaJSON ImmutableGitInput) -> Text -> LogSettings -> ViaJSON Selector -> Bool -> Maybe (Map Text ()) -> Bool -> [ByteString] -> Eval
Files
- CHANGELOG.md +10/−0
- hercules-ci-agent-worker/Hercules/Agent/Worker.hs +27/−9
- hercules-ci-agent.cabal +2/−11
- hercules-ci-agent/Hercules/Agent/Evaluate.hs +116/−11
- src/Hercules/Agent/NixFile.hs +16/−1
- src/Hercules/Agent/WorkerProtocol/Command/Eval.hs +3/−0
CHANGELOG.md view
@@ -5,7 +5,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.1] - 2022-03-18 +### Added++ - The built-in flake support now has the `sourceInfo` attributes.++### Changed++ - Flakes are checked out by Nix rather than custom local checkout.+ ## [0.9.0] - 2022-03-15 This release comes with an [Upgrade Guide! ✨](https://docs.hercules-ci.com/hercules-ci/guides/upgrade-to-agent-0.9/)@@ -534,6 +543,7 @@ - Initial release +[0.9.1]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.9.0...hercules-ci-agent-0.9.1 [0.9.0]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.8.7...hercules-ci-agent-0.9.0 [0.8.7]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.8.6...hercules-ci-agent-0.8.7 [0.8.6]: https://github.com/hercules-ci/hercules-ci-agent/compare/hercules-ci-agent-0.8.5...hercules-ci-agent-0.8.6
hercules-ci-agent-worker/Hercules/Agent/Worker.hs view
@@ -35,6 +35,7 @@ import Hercules.API.Agent.Evaluate.EvaluateEvent.OnPushHandlerEvent (OnPushHandlerEvent (OnPushHandlerEvent)) import qualified Hercules.API.Agent.Evaluate.EvaluateEvent.OnPushHandlerEvent import qualified Hercules.API.Agent.Evaluate.EvaluateTask as EvaluateTask+import qualified Hercules.API.Agent.Evaluate.EvaluateTask.OnPush as OnPush import qualified Hercules.API.Agent.Evaluate.ImmutableGitInput as API.ImmutableGitInput import qualified Hercules.API.Agent.Evaluate.ImmutableInput as API.ImmutableInput import qualified Hercules.API.Agent.LifeCycle.ServiceInfo@@ -44,6 +45,7 @@ import Hercules.API.Logs.LogMessage (LogMessage) import qualified Hercules.API.Logs.LogMessage as LogMessage import Hercules.Agent.NixFile (HerculesCISchema, getHerculesCI, homeExprRawValue, loadNixFile, parseExtraInputs)+import qualified Hercules.Agent.NixFile as NixFile import Hercules.Agent.NixFile.HerculesCIArgs (CISystems (CISystems), HerculesCIMeta (HerculesCIMeta), fromGitSource) import qualified Hercules.Agent.NixFile.HerculesCIArgs import Hercules.Agent.Sensitive@@ -75,7 +77,7 @@ import qualified Hercules.Agent.WorkerProtocol.Event.AttributeError as AttributeError import qualified Hercules.Agent.WorkerProtocol.LogSettings as LogSettings import Hercules.CNix as CNix-import Hercules.CNix.Expr (Match (IsAttrs, IsString), NixAttrs, RawValue, addAllowedPath, addInternalAllowedPaths, autoCallFunction, evalArgs, getAttrBool, getAttrList, getAttrs, getDrvFile, getFlakeFromArchiveUrl, getFlakeFromGit, getRecurseForDerivations, getStringIgnoreContext, init, isDerivation, isFunctor, match, rawValueType, rtValue, toRawValue, withEvalStateConduit)+import Hercules.CNix.Expr (Match (IsAttrs, IsString), NixAttrs, RawValue, addAllowedPath, addInternalAllowedPaths, autoCallFunction, evalArgs, getAttrBool, getAttrList, getAttrs, getDrvFile, getFlakeFromArchiveUrl, getFlakeFromGit, getRecurseForDerivations, getStringIgnoreContext, init, isDerivation, isFunctor, match, rawValueType, rtValue, toRawValue, toValue, withEvalStateConduit) import Hercules.CNix.Expr.Context (EvalState) import qualified Hercules.CNix.Expr.Raw import Hercules.CNix.Expr.Schema (MonadEval, PSObject, dictionaryToMap, fromPSObject, requireDict, (#.), (#?), (#?!), ($?))@@ -481,6 +483,7 @@ for_ (Eval.extraNixOptions eval) $ liftIO . uncurry setGlobalOption for_ (Eval.extraNixOptions eval) $ liftIO . uncurry setOption let store = nixStore hStore+ isFlake = Eval.isFlakeJob eval s <- storeUri store UnliftIO unlift <- lift askUnliftIO let decode = decodeUtf8With lenientDecode@@ -556,7 +559,7 @@ evalArgs evalState (autoArgArgs (Eval.autoArguments eval)) Data.Conduit.handleC (yieldAttributeError []) $ do- homeExpr <- escalateAs UserException =<< liftIO (loadNixFile evalState (toS $ Eval.cwd eval) (coerce $ Eval.gitSource eval))+ homeExpr <- getHomeExpr evalState eval let hargs = fromGitSource (coerce $ Eval.gitSource eval) meta meta = HerculesCIMeta {apiBaseUrl = Eval.apiBaseUrl eval, ciSystems = CISystems (Eval.ciSystems eval)} liftIO (flip runReaderT evalState $ getHerculesCI homeExpr hargs) >>= \case@@ -567,17 +570,31 @@ case Event.fromViaJSON (Eval.selector eval) of EvaluateTask.ConfigOrLegacy -> do yield Event.JobConfig- sendConfig evalState herculesCI+ sendConfig evalState isFlake herculesCI EvaluateTask.OnPush onPush -> transPipe (`runReaderT` evalState) do walkOnPush store evalState onPush herculesCI yield Event.EvaluationDone -walkOnPush :: (MonadEval m, MonadUnliftIO m, KatipContext m, MonadThrow m) => Store -> Ptr EvalState -> EvaluateTask.OnPush -> PSObject HerculesCISchema -> ConduitT i Event m ()+getHomeExpr :: (MonadThrow m, MonadIO m) => Ptr EvalState -> Eval -> m NixFile.HomeExpr+getHomeExpr evalState eval =+ if Eval.isFlakeJob eval+ then+ NixFile.Flake <$> liftIO do+ srcInput <- case Eval.srcInput eval of+ Just x -> pure x+ Nothing -> panic "srcInput is required for flake job"+ raw <- mkImmutableGitInputFlakeThunk evalState (Event.fromViaJSON srcInput)+ let pso :: PSObject (Schema.Attrs '[])+ pso = Schema.PSObject {value = raw, provenance = Schema.Other "flake.nix"}+ toValue evalState pso+ else escalateAs UserException =<< liftIO (loadNixFile evalState (toS $ Eval.cwd eval) (coerce $ Eval.gitSource eval))++walkOnPush :: (MonadEval m, MonadUnliftIO m, KatipContext m, MonadThrow m) => Store -> Ptr EvalState -> OnPush.OnPush -> PSObject HerculesCISchema -> ConduitT i Event m () walkOnPush store evalState onPushParams herculesCI = do- onPushHandler <- herculesCI #?! #onPush >>= requireDict (EvaluateTask.name onPushParams)+ onPushHandler <- herculesCI #?! #onPush >>= requireDict (OnPush.name onPushParams) inputs <- liftIO $ do- inputs <- for (EvaluateTask.inputs onPushParams) \input -> do+ inputs <- for (OnPush.inputs onPushParams) \input -> do inputToValue evalState input toRawValue evalState inputs outputsFun <- onPushHandler #. #outputs@@ -597,8 +614,8 @@ (API.ImmutableGitInput.ref git) (API.ImmutableGitInput.rev git) -sendConfig :: MonadIO m => Ptr EvalState -> PSObject HerculesCISchema -> ConduitT i Event m ()-sendConfig evalState herculesCI = flip runReaderT evalState $ do+sendConfig :: MonadIO m => Ptr EvalState -> Bool -> PSObject HerculesCISchema -> ConduitT i Event m ()+sendConfig evalState isFlake herculesCI = flip runReaderT evalState $ do herculesCI #? #onPush >>= traverse_ \onPushes -> do attrs <- dictionaryToMap onPushes for_ (M.mapWithKey (,) attrs) \(name, onPush) -> do@@ -607,7 +624,8 @@ when enable . lift . yield . Event.OnPushHandler . ViaJSON $ OnPushHandlerEvent { handlerName = decodeUtf8 name,- handlerExtraInputs = M.mapKeys decodeUtf8 (fromMaybe mempty ei)+ handlerExtraInputs = M.mapKeys decodeUtf8 (fromMaybe mempty ei),+ isFlake = isFlake } -- | Documented in @docs/modules/ROOT/pages/evaluation.adoc@.
hercules-ci-agent.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.4 name: hercules-ci-agent-version: 0.9.0+version: 0.9.1 synopsis: Runs Continuous Integration tasks on your machines category: Nix, CI, Testing, DevOps homepage: https://docs.hercules-ci.com@@ -104,8 +104,6 @@ Hercules.UserException Data.Conduit.Extras Data.Conduit.Katip.Orphans-- other-modules: Paths_hercules_ci_agent autogen-modules: Paths_hercules_ci_agent@@ -195,9 +193,6 @@ Hercules.Agent.SecureDirectory Hercules.Agent.ServiceInfo Hercules.Agent.Token- Paths_hercules_ci_agent- autogen-modules:- Paths_hercules_ci_agent hs-source-dirs: hercules-ci-agent default-extensions: DeriveGeneric DeriveTraversable DisambiguateRecordFields FlexibleContexts InstanceSigs LambdaCase MultiParamTypeClasses NoImplicitPrelude OverloadedStrings RankNTypes TupleSections TypeApplications TypeOperators@@ -223,7 +218,7 @@ , filepath , hercules-ci-agent , hercules-ci-api-core == 0.1.4.0- , hercules-ci-api-agent == 0.4.2.0+ , hercules-ci-api-agent == 0.4.3.0 , hostname , http-client , http-client-tls@@ -274,13 +269,10 @@ Hercules.Agent.Worker.Build.Logger Hercules.Agent.Worker.Build.Logger.Context Hercules.Agent.Worker.Effect- Paths_hercules_ci_agent Hercules.Agent.Worker.HerculesStore Hercules.Agent.Worker.HerculesStore.Context Hercules.Agent.Worker.Logging Hercules.Agent.Worker.NixDaemon- autogen-modules:- Paths_hercules_ci_agent hs-source-dirs: hercules-ci-agent-worker @@ -368,7 +360,6 @@ Hercules.Agent.WorkerProcessSpec Hercules.Secrets Hercules.SecretsSpec- Paths_hercules_ci_agent Spec hs-source-dirs: src
hercules-ci-agent/Hercules/Agent/Evaluate.hs view
@@ -41,6 +41,9 @@ import qualified Hercules.API.Agent.Evaluate.EvaluateEvent.Message as Message import qualified Hercules.API.Agent.Evaluate.EvaluateEvent.PushedAll as PushedAll import qualified Hercules.API.Agent.Evaluate.EvaluateTask as EvaluateTask+import Hercules.API.Agent.Evaluate.ImmutableGitInput (ImmutableGitInput)+import qualified Hercules.API.Agent.Evaluate.ImmutableGitInput as ImmutableGitInput+import qualified Hercules.API.Agent.Evaluate.ImmutableInput as ImmutableInput import Hercules.API.Servant (noContent) import qualified Hercules.Agent.Cache as Agent.Cache import qualified Hercules.Agent.Cachix.Env as Cachix.Env@@ -100,11 +103,108 @@ fix $ \continue -> joinSTM $ listen batchProducer (\b -> withSync b (postBatch task' . catMaybes) *> continue) pure +getSrcInput :: MonadIO m => EvaluateTask.EvaluateTask -> m (Maybe ImmutableGitInput)+getSrcInput task = case M.lookup "src" (EvaluateTask.inputs task) of+ Just (ImmutableInput.Git x) ->+ purer x+ Nothing -> do+ throwIO $ FatalError $ "No src input provided" <> show task+ Just {} -> do+ pure Nothing+ produceEvaluationTaskEvents :: Store -> EvaluateTask.EvaluateTask -> (Syncing EvaluateEvent.EvaluateEvent -> App ()) -> App ()+produceEvaluationTaskEvents store task writeToBatch | EvaluateTask.isFlakeJob task = withWorkDir "eval" $ \tmpdir -> do+ logLocM DebugS "Retrieving evaluation task (flake)"+ let emitSingle = writeToBatch . Syncable+ sync = syncer writeToBatch+ msgCounter <- liftIO $ newIORef 0+ let fixIndex ::+ MonadIO m =>+ EvaluateEvent.EvaluateEvent ->+ m EvaluateEvent.EvaluateEvent+ fixIndex (EvaluateEvent.Message m) = do+ i <- liftIO $ atomicModifyIORef msgCounter (\i0 -> (i0 + 1, i0))+ pure $ EvaluateEvent.Message m {Message.index = i}+ fixIndex other = pure other+ eventCounter <- liftIO $ newIORef 0+ topDerivationPaths <- liftIO $ newIORef mempty+ let emit :: EvaluateEvent.EvaluateEvent -> App ()+ emit update = do+ n <- liftIO $ atomicModifyIORef eventCounter $ \n -> dup (n + 1)+ if n > eventLimit+ then do+ truncMsg <-+ fixIndex $+ EvaluateEvent.Message+ Message.Message+ { index = -1,+ typ = Message.Error,+ message =+ "Evaluation limit reached. Does your nix expression produce infinite attributes? Please make sure that your project is finite. If it really does require more than "+ <> show eventLimit+ <> " attributes or messages, please contact info@hercules-ci.com."+ }+ emitSingle truncMsg+ panic "Evaluation limit reached."+ else emitSingle =<< fixIndex update+ let allowedPaths = []++ TraversalQueue.with $ \(derivationQueue :: Queue StorePath) ->+ let doIt =+ ( do+ Async.Lifted.concurrently_ evaluation emitDrvs+ -- derivationInfo upload has finished+ -- allAttrPaths :: IORef has been populated+ )+ `finally` pushDrvs+ uploadDrvInfos drvPath = do+ TraversalQueue.enqueue derivationQueue drvPath+ TraversalQueue.waitUntilDone derivationQueue+ addTopDerivation drvPath = do+ TraversalQueue.enqueue derivationQueue drvPath+ liftIO $ atomicModifyIORef topDerivationPaths ((,()) . S.insert drvPath)+ evaluation = do+ let evalProc =+ do runEvalProcess+ tmpdir -- unused+ ""+ mempty+ mempty+ captureAttrDrvAndEmit+ uploadDrvInfos+ sync+ task+ allowedPaths+ evalProc `finally` do+ -- Always upload drv infos, even in case of a crash in the worker+ TraversalQueue.waitUntilDone derivationQueue+ `finally` TraversalQueue.close derivationQueue+ pushDrvs = do+ caches <- activePushCaches+ paths <- liftIO $ readIORef topDerivationPaths+ forM_ caches $ \cache -> do+ withNamedContext "cache" cache $ logLocM DebugS "Pushing derivations"+ Agent.Cache.push cache (toList paths) pushEvalWorkers+ emit $ EvaluateEvent.PushedAll $ PushedAll.PushedAll {cache = cache}+ captureAttrDrvAndEmit msg = do+ case msg of+ EvaluateEvent.Attribute ae -> do+ storePath <- liftIO $ parseStorePath store (encodeUtf8 $ AttributeEvent.derivationPath ae)+ addTopDerivation storePath+ _ -> pass+ emit msg+ emitDrvs =+ TraversalQueue.work derivationQueue $ \recurse drvPath -> do+ drvInfo <- retrieveDerivationInfo store drvPath+ for_ (M.keys $ DerivationInfo.inputDerivations drvInfo) \inp -> do+ inputStorePath <- liftIO $ parseStorePath store (encodeUtf8 inp)+ recurse inputStorePath -- asynchronously+ emit $ EvaluateEvent.DerivationInfo drvInfo+ in doIt produceEvaluationTaskEvents store task writeToBatch = withWorkDir "eval" $ \tmpdir -> do logLocM DebugS "Retrieving evaluation task" let emitSingle = writeToBatch . Syncable@@ -133,12 +233,6 @@ projectDir <- case M.lookup "src" inputLocations of Nothing -> panic "No primary source provided" Just x -> pure x- (ref, rev) <- case M.lookup "src" (EvaluateTask.inputMetadata task) of- Nothing -> do- panic $ "No primary source metadata provided" <> show task- Just meta -> pure $ fromMaybe (panic "no ref/rev in primary source metadata") do- (,) <$> (meta ^? at "ref" . traverse . _String)- <*> (meta ^? at "rev" . traverse . _String) nixPath <- EvaluateTask.nixPath task & ( traverse@@ -250,7 +344,6 @@ uploadDrvInfos sync task- (ref, rev) allowedPaths evalProc `finally` do -- Always upload drv infos, even in case of a crash in the worker@@ -301,16 +394,26 @@ (StorePath -> App ()) -> App () -> EvaluateTask.EvaluateTask ->- (Text, Text) -> [ByteString] -> App ()-runEvalProcess projectDir file autoArguments nixPath emit uploadDerivationInfos flush task (ref, rev) allowedPaths = do+runEvalProcess projectDir file autoArguments nixPath emit uploadDerivationInfos flush task allowedPaths = do extraOpts <- Nix.askExtraOptions bulkBaseURL <- asks (ServiceInfo.bulkSocketBaseURL . Env.serviceInfo) apiBaseUrl <- asks (toS . showBaseUrl . Env.herculesBaseUrl) cfg <- asks Env.config- let gitSource = fromRefRevPath ref rev (toS projectDir)- eval =+ srcInput <- getSrcInput task+ gitSource <-+ case srcInput of+ Just git -> pure $ fromRefRevPath (ImmutableGitInput.ref git) (ImmutableGitInput.rev git) (toS projectDir)+ Nothing -> do+ (ref, rev) <- case M.lookup "src" (EvaluateTask.inputMetadata task) of+ Nothing -> do+ panic $ "No primary source metadata provided" <> show task+ Just meta -> pure $ fromMaybe (panic "no ref/rev in primary source metadata") do+ (,) <$> (meta ^? at "ref" . traverse . _String)+ <*> (meta ^? at "rev" . traverse . _String)+ pure $ fromRefRevPath ref rev (toS projectDir)+ let eval = Eval.Eval { Eval.cwd = projectDir, Eval.file = toS file,@@ -323,9 +426,11 @@ baseURL = toS $ Network.URI.uriToString identity bulkBaseURL "" }, Eval.gitSource = ViaJSON gitSource,+ Eval.srcInput = ViaJSON <$> srcInput, Eval.apiBaseUrl = apiBaseUrl, Eval.ciSystems = EvaluateTask.ciSystems task, Eval.selector = ViaJSON $ EvaluateTask.selector task,+ Eval.isFlakeJob = EvaluateTask.isFlakeJob task, Eval.allowInsecureBuiltinFetchers = Config.allowInsecureBuiltinFetchers cfg, Eval.allowedPaths = allowedPaths }
src/Hercules/Agent/NixFile.hs view
@@ -50,6 +50,7 @@ match', toRawValue, unsafeAssertType,+ valueFromExpressionString, ) import Hercules.CNix.Expr.Raw (RawValue) import Hercules.CNix.Expr.Schema (Attrs, Dictionary, MonadEval, PSObject (PSObject), Provenance (Other), StringWithoutContext, basicAttrsWithProvenance, dictionaryToMap, fromPSObject, toPSObject, (#.), (#?), ($?), (.$), (>>$.), type (->.), type (->?), type (::.), type (::?))@@ -142,6 +143,12 @@ '[ "addDefaults" ::. Attrs '[] ->. Attrs '[] ->. HerculesCISchema ] +exprString :: forall a m. MonadEval m => ByteString -> m (PSObject a)+exprString bs = do+ evalState <- ask+ value <- liftIO $ valueFromExpressionString evalState bs "/var/lib/empty/hercules-ci-agent-builtin"+ pure PSObject {value = value, provenance = Schema.Other "hercules-ci-agent built-in expression"}+ getHerculesCI :: MonadEval m => HomeExpr -> HerculesCIArgs -> m (Maybe (PSObject HerculesCISchema)) getHerculesCI homeExpr args = do home <- getHomeExprObject homeExpr@@ -153,10 +160,18 @@ herculesCI $? args' Flake flake -> Just <$> do+ -- fixup primaryRepo.outPath, which we didn't set to the right value for+ -- flakes earlier, because we don't have a local checkout.+ args'' <-+ exprString @(Attrs _ ->. HomeSchema ->. Attrs _)+ "args': flake: args' // { primaryRepo = args'.primaryRepo // { outPath = flake.outPath; }; }"+ >>$. pure args'+ >>$. pure home+ dh <- loadDefaultHerculesCI fn <- dh #. #addDefaults let flakeObj = basicAttrsWithProvenance flake $ Schema.Other "your flake"- hci <- fn .$ flakeObj >>$. pure args'+ hci <- fn .$ flakeObj >>$. pure args'' pure hci {Schema.provenance = Other "the herculesCI attribute of your flake (after adding defaults)"} parseExtraInputs :: MonadEval m => PSObject ExtraInputsSchema -> m (Map ByteString InputDeclaration)
src/Hercules/Agent/WorkerProtocol/Command/Eval.hs view
@@ -4,6 +4,7 @@ import Data.Binary import qualified Hercules.API.Agent.Evaluate.EvaluateTask as EvaluateTask+import Hercules.API.Agent.Evaluate.ImmutableGitInput (ImmutableGitInput) import Hercules.Agent.NixFile.GitSource (GitSource) import Hercules.Agent.WorkerProtocol.Event (ViaJSON) import Hercules.Agent.WorkerProtocol.LogSettings@@ -19,9 +20,11 @@ -- (as of now, we use one worker process per evaluation) extraNixOptions :: [(Text, Text)], gitSource :: ViaJSON GitSource,+ srcInput :: Maybe (ViaJSON ImmutableGitInput), apiBaseUrl :: Text, logSettings :: LogSettings, selector :: ViaJSON EvaluateTask.Selector,+ isFlakeJob :: Bool, ciSystems :: Maybe (Map Text ()), allowInsecureBuiltinFetchers :: Bool, allowedPaths :: [ByteString]