Skip to content

Conversation

@jglick
Copy link
Member

@jglick jglick commented Jul 11, 2023

Amends #312 by @alextu. While that appears to have worked, the way in which it worked was not reassuring—an exception is still thrown, but it gets caught and does not break withCredentials masking. Better to avoid the exception to begin with. jenkinsci/workflow-durable-task-step-plugin#323 proposes to enable USE_WATCHING by default, in which case the scenario would apply to all gradle plugin users, not only those who are also running opentelemetry.

Running

git checkout gradle-2.8 -- src/main/java/hudson/plugins/gradle/injection/GradleEnterpriseExceptionTaskListenerDecoratorFactory.java
./gradlew -i :test --tests hudson.plugins.gradle.injection.BuildScanInjectionGradleWithDurableTaskStepUseWatchingIntegrationTest."credentials are always masked in logs"

you can see the problem:

… org.jenkinsci.plugins.workflow.log.TaskListenerDecorator$DecoratedTaskListener getLogger
WARNING: null
java.lang.IllegalStateException: Expected 1 instance of hudson.plugins.gradle.injection.InjectionConfig but got 0
    at hudson.ExtensionList.lookupSingleton(ExtensionList.java:452)
    at hudson.plugins.gradle.injection.InjectionConfig.get(InjectionConfig.java:81)
    at hudson.plugins.gradle.injection.GradleEnterpriseExceptionTaskListenerDecoratorFactory$GradleEnterpriseExceptionTaskListenerDecorator.decorate(GradleEnterpriseExceptionTaskListenerDecoratorFactory.java:43)
    at org.jenkinsci.plugins.workflow.log.TaskListenerDecorator$MergedTaskListenerDecorator.decorate(TaskListenerDecorator.java:176)
    at org.jenkinsci.plugins.workflow.log.TaskListenerDecorator$DecoratedTaskListener.getLogger(TaskListenerDecorator.java:240)
    at org.jenkinsci.plugins.workflow.log.TaskListenerDecorator$CloseableTaskListener.getLogger(TaskListenerDecorator.java:283)
    at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution$NewlineSafeTaskListener.getLogger(DurableTaskStep.java:455)
    at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$HandlerImpl.output(DurableTaskStep.java:720)
    at org.jenkinsci.plugins.durabletask.FileMonitoringTask$Watcher.run(FileMonitoringTask.java:616)

As Javadoc says, trying to access controller-only facilities such as ExtensionList from within decorate is not permitted. In fact the current GradleEnterpriseExceptionTaskListenerDecorator could not possibly work remotely because GradleEnterpriseExceptionLogProcessor uses DefaultBuildAgentErrorListener which can only run inside the controller as currently designed (accessing Run objects and so forth). So unless and until that is redesigned to permit it to run inside the agent—ideally by streaming events directly to Gradle Enterprise, but failing that by calling back to the controller somewhat like TimeoutStep does (discussion)—this feature just needs to be suppressed on the log of sh steps running remotely.

CC @daniel-beck

public OutputStream decorateLogger(Run build, OutputStream logger) {
InjectionConfig injectionConfig = InjectionConfig.get();
if (injectionConfig.isEnabled() && injectionConfig.isCheckForBuildAgentErrors()) {
if (injectionConfig.isEnabled() && injectionConfig.isCheckForBuildAgentErrors() && build != null) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably unnecessary, but core method does not specify nullability of the build argument, so being defensive.

@Override
public OutputStream decorate(@Nonnull OutputStream logger) {
if (isBuildAgentErrorsEnabled()) {
if (run != null) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix. We cannot call isBuildAgentErrorsEnabled from the agent side, and even if this value were computed on the controller side and serialized, it would not help because run would still have been left null by deserialization and could not be used here. So simply disable the decorator when remoted.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a more expressive condition to "know" we're on the controller ? Maybe jenkins.util.JenkinsJVM#isJenkinsJVM ?
As an additional safety net, I think we also need to combine the condition with isBuildAgentErrorsEnabled() (so only executed on the controller) even if it's already called upfront in hudson.plugins.gradle.injection.GradleEnterpriseExceptionTaskListenerDecoratorFactory#of.
Btw, org.jenkinsci.plugins.workflow.log.TaskListenerDecorator.Factory#of implementations are executed only on the controller, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a more expressive condition to "know" we're on the controller ? Maybe jenkins.util.JenkinsJVM#isJenkinsJVM ?

We could, though it should not matter—if running on an agent, certainly run == null. A checkJenkinsJVM call could be added (inside the run != null block) as a matter of documentation, though it would perhaps just confuse the situation if later you do implement remote decoration (using some data structure that is safe in the agent JVM unlike Run).

we also need to combine the condition with isBuildAgentErrorsEnabled()

Seems redundant since of already runs this check.

#of implementations are executed only on the controller, right?

Right.

Comment on lines +79 to +82
cleanup:
System.err.println('---%<--- agent logs')
agent.computer.logText.writeLogTo(0, System.err)
System.err.println('--->%---')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise you would not notice the errors. Currently JenkinsRule.create[Online]Slave does not stream logs. (InboundAgentRule does.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh I see, I wondered as well why I could not see the error

withCredentials([string(credentialsId: 'my-creds', variable: 'PASSWORD')]) {
if (isUnix()) {
sh "echo password=\$PASSWORD"
sh 'echo password=$PASSWORD'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original test was improperly interpolating the password in Groovy code on the controller, rather than using the environment variable, and so actually running the shell with e.g.

echo password=actual secret here

Pipeline prints a warning about this to the build log. Does not make much difference for purposes of the masking issue (since either way the shell step output contains the secret), but better to test the script that users are actually supposed to write.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see this warning in the test logs 🤔 but I understand the issue, quite sneaky!

…ond constructor was used from `GradleEnterpriseExceptionLogProcessorTest` mocks
@jglick
Copy link
Member Author

jglick commented Jul 12, 2023

@alextu there are ten test failures here, but these seem to be failing already in trunk.

@alextu
Copy link
Contributor

alextu commented Jul 12, 2023

@alextu there are ten test failures here, but these seem to be failing already in trunk.

Thanks for your contribution! We will review it tomorrow, yes we have flakiness on ci.jenkins.io that we did not get a chance to tackle yet (we also build internally on our CI to make sure those are not "real" failures)

Copy link
Contributor

@alextu alextu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, @c00ler could you take a look next week ?

@Override
public OutputStream decorate(@Nonnull OutputStream logger) {
if (isBuildAgentErrorsEnabled()) {
if (run != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use a more expressive condition to "know" we're on the controller ? Maybe jenkins.util.JenkinsJVM#isJenkinsJVM ?
As an additional safety net, I think we also need to combine the condition with isBuildAgentErrorsEnabled() (so only executed on the controller) even if it's already called upfront in hudson.plugins.gradle.injection.GradleEnterpriseExceptionTaskListenerDecoratorFactory#of.
Btw, org.jenkinsci.plugins.workflow.log.TaskListenerDecorator.Factory#of implementations are executed only on the controller, right?

Comment on lines +79 to +82
cleanup:
System.err.println('---%<--- agent logs')
agent.computer.logText.writeLogTo(0, System.err)
System.err.println('--->%---')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh I see, I wondered as well why I could not see the error

withCredentials([string(credentialsId: 'my-creds', variable: 'PASSWORD')]) {
if (isUnix()) {
sh "echo password=\$PASSWORD"
sh 'echo password=$PASSWORD'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see this warning in the test logs 🤔 but I understand the issue, quite sneaky!

@c00ler c00ler added the bugfix label Jul 19, 2023
@c00ler c00ler merged commit 003c8b7 into jenkinsci:master Jul 21, 2023
@jglick jglick deleted the GradleEnterpriseExceptionTaskListenerDecorator-JENKINS-71509 branch July 21, 2023 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants