Details
-
New Feature
-
Status: Closed (View Workflow)
-
Minor
-
Resolution: Fixed
-
None
Description
having agent at the global level is nice, but it has come up a few times in conversations that having agents on a per-stage level is worth considering.
This makes the "blocking for input" case a lot nicer and less wasteful (without the user needing to learn additional node syntax).
CC jamesdumay hrmpw
Attachments
Issue Links
- is duplicated by
-
JENKINS-37779 Pipeline Config: Add support for overriding "agent" per-stage
-
- Closed
-
- relates to
-
JENKINS-33510 dir('foo') inside "docker.image().inside{}" does not affect CWD of launched processes
-
- Resolved
-
- links to
Activity
hrmpw the most common use for input is blocking for approval, which does work well at least (that seems the vast majority) - but putting aside input utility for now (it will need to set env vars as it claims to)...
Yes, I can understand the setting an agent as a "default" for a block, but we can't just automatically yield an agent because we use input, if a user is using input they need to "opt in" to handling the flow of built artifacts between long lived stages (ie they need to know to stash/unstash, and what) - that can't be done automatically for them. Hence my comment on having per stage agents.
Or perhaps the user choses to opt out for a stage by saying "agent none" - they still need to ensure they are stashing/unstashing (ie you can't transparently drop and agent and resume)
Currently they have to use the node syntax and nest the steps inside it (which isn't terrible, but is jarring).
I would argue that it is terrible, to the point of unusable. I would revert to Pipeline Script if I wanted to use Input in a Pipeline.
Not sure what you mean, michaelneale. What is the same?
Right now to use an input to block for approval, or for any other reason, it is more complicated to use Declarative syntax than it is use to Pipeline Script. Not only do you have to use node blocks and docker.inside for all of your stages but you have to create a superfluous stage for your input step.
Having people use agent on each stage improves that experience slightly but only to the point that it is on par with with using Pipeline Script. Input is the biggest gap in Declarative functionality.
In the case of using one single node (ie keeping it alive) and blocking for input there is no difference (try it).
otherwise:
stage "build" node { sh "echo 42" } stage "Approval" input "Go ahead?" stage "End" node { sh "echo done" }
becomes:
pipeline { agent none stages { stage("build") { node { sh "echo 42" } } stage("Approval") { input "Go ahead?" } stage("End") { node { sh "echo done" } } } }
is this what you mean? as it looks very similar
You wouldn't have the input inside a node in the former.
My point is that in your example, there is no advantage of using Declarative. In fact, if you have to go to the trouble of setting up all your nodes or images manually anyway it is much simpler and more flexible to use Script:
stage "build" { node { sh "echo 42" } } stage "End" { input "Go ahead?" node { sh "echo done" } }
I don't need to create a superfluous stage just to break out of the node. In this case it is easier and more flexible to not use Declarative.
This will be especially true for things like Checkpoints. If I create 2 or 3 Checkpoints in my pipeline that is 2-3 additional Stages that shouldn't necessarily be visualized. Why force users to create Stages when they aren't necessary?
The goal should be to make things simpler in Declarative. We are trying to trade off flexibility for opinionated simplicity. Having it opinionated (limited) but not simpler is just wrong.
hrmpw yes that was my typo - intended it to not be in a node (corrected).
But I still don't get your point - is input a reasonable thing people would want to do in a declarative way or not? There are clearly advantages to the model - as there is a model, better error messages, editor round tripping. Inputs today need to be in a stage anyway (even for stage view) or they won't show up (other than in the log, in some cases)
Do you mean that input should be a first class thing in pipeline model instead of "working around" the agents like this?
michaelneale I assumed it was a typo. Just pointing it out for anyone else reading.
There were a few things. First, at a bare minimum we need per-stage agent directives when agent none is used but we should also allow per-stage directives that override any global agent.
pipeline{ agent docker:"ubuntu" stages{ stage("Foo"){ agent none echo "Foo" } stage("Bar"){ echo "Bar" } } }
and
pipeline{ agent none stages{ stage("Foo"){ echo "Foo" } stage("Bar"){ agent docker:"ubuntu" echo "Bar" } } }
Should both produce the same results and setup.
The second point was whether we should consider not forcing things like Input (and Checkpoints or anything else that shouldn't be run inside a node) to run in an isolated Stage that then has to be visualized in your Pipeline.
btw, we should probably also flag things like "input inside a node" as part of our pre-compilation syntax checking.
hrmpw yes your last proposal sounds ok.
I am wary of making it unclear when it switches node, but by having agent there, it is clear I think.
For input - even in a GUI it would be represented as a stage (I think that is current intention and practice) - so it could certainly act as a stage (ie wrap itself in a stage)? (I would be interested what jdumay thinks) - would make some things easier/clearer.
However, I would want to let people have the option of having an input that keeps a node alive at least
So fyi, I'm going to take advantage of this opportunity (and the fact that GroovyStep is close to landing, which will allow me to implement the pluggable agent backend) to completely revisit what the agent config looks like. The map approach is going to become unwieldy very quickly, so I think something more like:
agent { docker { label "foo" image "ubuntu" } }
...would be preferable. This would ideally still have backwards compatibility for the existing possible configurations. Thoughts?
Would this also let us incorporate what docker args as requested in PR-17 ?
Do we want to support docker args?
What would this look like with a Kubernetes Pod?
What would this look like with just a node label?
Yes, it would, and yes we do want to support that.
For Kubernetes...well, I don't know the config enough to say exactly what it would look like, but...similar. For just a label, the existing agent label:'foo' would still work, hopefully.
I'm fine with this approach. It gives us a lot more room to grow instead of an every growing map that gets unwieldy.
I can't see how a label would be useful in this context, as the docker-based agent would be dedicated to this build (aka "one-shot").
I also suggest to consider during design a docker-based executor is not a single container, as demonstrated by docker-slaves-plugin : one can compose a build container with "sidecars" container (typically for test database, selenium browser, etc) and jenkins plumbing container to support agent slave.jar
we don't have one-shot right now. Docker pipeline needs a node with docker engine running on it. Until we have deprecated all physical agents and everything is running one-shot then label is a must have here.
ndeloof - the agent section is going to be pluggable (see JENKINS-38433) so if you want to implement one for Docker Slaves (which, mind you, should be renamed to Docker Agents!), you'll be able to, but the out-of-the-box implementations will be "label", Docker Pipeline, and Dockerfile->Docker Pipeline.
First tentative PR is up at https://github.com/jenkinsci/pipeline-model-definition-plugin/pull/19 wooo
Code changed in jenkins
User: Andrew Bayer
Path:
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/ast/ModelASTStage.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/ast/ModelASTStageConfig.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/ClosureContentsChecker.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/Stage.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/StageConfig.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/JSONParser.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ModelParser.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/validator/ModelValidator.groovy
src/main/java/org/jenkinsci/plugins/pipeline/modeldefinition/model/MethodMissingWrapperWhitelist.java
src/main/resources/ast-schema.json
src/main/resources/org/jenkinsci/plugins/pipeline/modeldefinition/ClosureModelTranslator.groovy
src/main/resources/org/jenkinsci/plugins/pipeline/modeldefinition/ModelInterpreter.groovy
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AbstractModelDefTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AgentTest.java
src/test/resources/json/perStageConfigAgent.json
src/test/resources/perStageConfigAgent.groovy
http://jenkins-ci.org/commit/pipeline-model-definition-plugin/385940d33c4184cb5f5947c00c45d7ed57566c47
Log:
JENKINS-38331 Very preliminary version of per-stage agent config
Honestly, I'm doing this for JENKINS-38284 more, but I needed some
section to actually have be per-stage to test it, so...tada?
This is literally the result of three hours of slamming my head
against the wall over stupid mistakes, so I'm fairly sure there are
still a plethora of such mistakes in here. But I'm pretty sure it
works - I just added the JSON version and conversion for
perStageAgentConfig to this and the tests are actively running as I
type this, but I got impatient and made a commit now. So ha.
Code changed in jenkins
User: Andrew Bayer
Path:
SYNTAX.md
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/ast/ModelASTStage.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/ClosureContentsChecker.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/model/Stage.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/JSONParser.groovy
src/main/groovy/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ModelParser.groovy
src/main/resources/ast-schema.json
src/main/resources/org/jenkinsci/plugins/pipeline/modeldefinition/ClosureModelTranslator.groovy
src/main/resources/org/jenkinsci/plugins/pipeline/modeldefinition/ModelInterpreter.groovy
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AbstractModelDefTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/AgentTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/BasicModelDefTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/EnvironmentTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ScriptStepTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ToolsTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/ValidatorTest.java
src/test/java/org/jenkinsci/plugins/pipeline/modeldefinition/parser/ExecuteConvertedTest.java
src/test/resources/abortedNotification.groovy
src/test/resources/agentAny.groovy
src/test/resources/agentDocker.groovy
src/test/resources/agentDockerWithEmptyDockerArgs.groovy
src/test/resources/agentDockerWithNullDockerArgs.groovy
src/test/resources/agentLabel.groovy
src/test/resources/agentNone.groovy
src/test/resources/agentNoneWithNode.groovy
src/test/resources/allStagesExist.groovy
src/test/resources/buildPluginParentPOM.groovy
src/test/resources/dockerGlobalVariable.groovy
src/test/resources/dockerGlobalVariableInScript.groovy
src/test/resources/errors/blockInJobProperties.groovy
src/test/resources/errors/blockInParameters.groovy
src/test/resources/errors/blockInTriggers.groovy
src/test/resources/errors/closureAsMethodCallArg.groovy
src/test/resources/errors/duplicateEnvironment.groovy
src/test/resources/errors/duplicateNotificationConditions.groovy
src/test/resources/errors/duplicatePostBuildConditions.groovy
src/test/resources/errors/duplicateStageNames.groovy
src/test/resources/errors/duplicateStepParameter.groovy
src/test/resources/errors/emptyAgent.groovy
src/test/resources/errors/emptyEnvironment.groovy
src/test/resources/errors/emptyJobProperties.groovy
src/test/resources/errors/emptyNotifications.groovy
src/test/resources/errors/emptyParallel.groovy
src/test/resources/errors/emptyParameters.groovy
src/test/resources/errors/emptyPostBuild.groovy
src/test/resources/errors/emptyTriggers.groovy
src/test/resources/errors/globalLibraryNonStepBody.groovy
src/test/resources/errors/globalLibraryObjectMethodCall.groovy
src/test/resources/errors/importAndFunctionShouldNotSkipParsing.groovy
src/test/resources/errors/invalidBuildCondition.groovy
src/test/resources/errors/invalidMetaStepSyntax.groovy
src/test/resources/errors/invalidParameterTypeMethodCall.groovy
src/test/resources/errors/invalidStepParameterType.groovy
src/test/resources/errors/missingAgent.groovy
src/test/resources/errors/missingRequiredStepParameters.groovy
src/test/resources/errors/mixedMethodArgs.groovy
src/test/resources/errors/notInstalledToolType.groovy
src/test/resources/errors/notInstalledToolVersion.groovy
src/test/resources/errors/packageShouldNotSkipParsing.groovy
src/test/resources/errors/perStageConfigEmptyAgent.groovy
src/test/resources/errors/perStageConfigEmptySteps.groovy
src/test/resources/errors/perStageConfigMissingSteps.groovy
src/test/resources/errors/perStageConfigUnknownSection.groovy
src/test/resources/errors/rejectMapsForTriggerDefinition.groovy
src/test/resources/errors/rejectParallelInNotifications.groovy
src/test/resources/errors/rejectParallelMixedInSteps.groovy
src/test/resources/errors/rejectPropertiesStepInMethodCall.groovy
src/test/resources/errors/rejectStageInSteps.groovy
src/test/resources/errors/stageWithoutName.groovy
src/test/resources/errors/tooFewMethodCallArgs.groovy
src/test/resources/errors/unknownStepParameter.groovy
src/test/resources/errors/unlistedToolType.groovy
src/test/resources/errors/wrongParameterNameMethodCall.groovy
src/test/resources/executionModelAction.groovy
src/test/resources/failingNotifications.groovy
src/test/resources/failingPipeline.groovy
src/test/resources/failingPostBuild.groovy
src/test/resources/globalLibrarySuccess.groovy
src/test/resources/globalLibrarySuccessInScript.groovy
src/test/resources/json/errors/perStageConfigEmptySteps.json
src/test/resources/json/errors/perStageConfigMissingSteps.json
src/test/resources/json/errors/perStageConfigUnknownSection.json
src/test/resources/json/perStageConfigAgent.json
src/test/resources/legacyMetaStepSyntax.groovy
src/test/resources/metaStepSyntax.groovy
src/test/resources/multipleProperties.groovy
src/test/resources/noCheckoutScmInWrongContext.groovy
src/test/resources/nonLiteralEnvironment.groovy
src/test/resources/notificationOnChangeChanged.groovy
src/test/resources/notificationOnChangeFailed.groovy
src/test/resources/parallelPipeline.groovy
src/test/resources/perStageConfigAgent.groovy
src/test/resources/postBuildAndNotifications.groovy
src/test/resources/shInNotification.groovy
src/test/resources/simpleEnvironment.groovy
src/test/resources/simpleJobProperties.groovy
src/test/resources/simpleNotification.groovy
src/test/resources/simpleParameters.groovy
src/test/resources/simplePipeline.groovy
src/test/resources/simplePostBuild.groovy
src/test/resources/simpleScript.groovy
src/test/resources/simpleTools.groovy
src/test/resources/simpleTriggers.groovy
src/test/resources/stringsNeedingEscapeLogic.groovy
src/test/resources/twoStagePipeline.groovy
src/test/resources/unstableNotification.groovy
src/test/resources/validStepParameters.groovy
http://jenkins-ci.org/commit/pipeline-model-definition-plugin/b7a3ec68779e2b8edc4da667cf9d42551d067fc6
Log:
Merge pull request #19 from abayer/jenkins-38331
JENKINS-38331 Per-stage configuration for agent
Compare: https://github.com/jenkinsci/pipeline-model-definition-plugin/compare/263a17adae4a...b7a3ec68779e
I think this ticket might be a duplicate but we should discuss how Input (and other steps that should be run outside of a node) should behave.
Right now it is easier to use Input in Pipeline Script than it is in Declarative Pipeline. Using Input in Declarative is pretty much a non-starter right now.
2 questions come to mind.
1. Do we force input to be in its own stage entirely or do we want to create a withoutAgent section that can be used inside a stage ?
2. Assuming it is in its own agent if I set a a global agent it makes sense that I can override that default on a stage rather than being forced to assign an agent on every stage just to use an input in one stage
versus