Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JENKINS-70066: Fix null check of parameters #134

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@
import java.io.IOException;
import java.io.Serial;
import java.io.Serializable;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -91,19 +96,19 @@
@CheckForNull String builderId,
@CheckForNull String scriptId,
boolean propagateParams,
Parameter[] parameters) {
this(builderId, scriptId, propagateParams, Arrays.asList(Objects.requireNonNull(parameters)));
@CheckForNull Parameter[] parameters) {
this(builderId, scriptId, propagateParams, parameters == null ? List.of() : List.of(parameters));

Check warning on line 100 in src/main/java/org/jenkinsci/plugins/scriptler/builder/ScriptlerBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 100 is not covered by tests
}

@DataBoundConstructor
public ScriptlerBuilder(
@CheckForNull String builderId,
@CheckForNull String scriptId,
boolean propagateParams,
@NonNull List<Parameter> parameters) {
@CheckForNull List<Parameter> parameters) {
this.builderId = builderId;
this.scriptId = scriptId;
this.parameters = new ArrayList<>(parameters);
this.parameters = parameters == null ? List.of() : List.copyOf(parameters);

Check warning on line 111 in src/main/java/org/jenkinsci/plugins/scriptler/builder/ScriptlerBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 111 is only partially covered, one branch is missing
Copy link
Member

Choose a reason for hiding this comment

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

Do not do this. Rather create a new ArrayList for the internal field used in the serial form.

this.propagateParams = propagateParams;
}

Expand Down Expand Up @@ -175,7 +180,7 @@

@NonNull
public List<Parameter> getParametersList() {
return Collections.unmodifiableList(parameters);
return parameters;
Copy link
Member

Choose a reason for hiding this comment

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

Whether you should revert this or not would depend on whether or not you want callers to modify the list. Probably you do not, so this should be reverted.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is currently safe as returning an unmodifiable view of an immutable list is a no-op.

}

public String getBuilderId() {
Expand Down Expand Up @@ -233,155 +238,154 @@

// expand the parameters before passing these to the execution, this is to allow any token macro to resolve
// parameter values
List<Parameter> expandedParams = new LinkedList<>();
List<Parameter> expandedParams = new ArrayList<>();

if (propagateParams) {
final ParametersAction paramsAction = build.getAction(ParametersAction.class);
if (paramsAction == null) {
listener.getLogger().println(Messages.no_parameters_defined());
} else {
final List<ParameterValue> jobParams = paramsAction.getParameters();
for (ParameterValue parameterValue : jobParams) {
// pass the params to the token expander in a way that these get expanded by environment
// variables (params are also environment variables)
String macro = "${" + parameterValue.getName() + "}";
String value = TokenMacro.expandAll(build, listener, macro, false, null);
expandedParams.add(new Parameter(parameterValue.getName(), value));
}
}
}
for (Parameter parameter : parameters) {
expandedParams.add(new Parameter(
parameter.getName(), TokenMacro.expandAll(build, listener, parameter.getValue())));
}
final Object output;
if (script.onlyBuiltIn) {
// When run on the built-in node, make build, launcher, listener available to script
output = FilePath.localChannel.call(new ControllerGroovyScript(
script.getScriptText(), expandedParams, true, listener, launcher, build));
} else {
VirtualChannel channel = launcher.getChannel();
if (channel == null) {
output = null;
listener.getLogger()
.println(Messages.scriptExecutionFailed(scriptId) + " - " + Messages.agent_no_channel());
} else {
output = channel.call(new GroovyScript(script.getScriptText(), expandedParams, true, listener));
}
}
isOk = !Boolean.FALSE.equals(output);
} catch (InterruptedException e) {
listener.getLogger().println(Messages.scriptExecutionFailed(scriptId) + " - " + e.getMessage());
e.printStackTrace(listener.getLogger());
Thread.currentThread().interrupt();
} catch (IOException | MacroEvaluationException e) {
listener.getLogger().println(Messages.scriptExecutionFailed(scriptId) + " - " + e.getMessage());
e.printStackTrace(listener.getLogger());
}

return isOk;
}

private static String generateBuilderId() {
return System.currentTimeMillis() + "_" + CURRENT_ID.addAndGet(1);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

ScriptlerBuilder that = (ScriptlerBuilder) o;

return Objects.equals(propagateParams, that.propagateParams)
&& Objects.equals(builderId, that.builderId)
&& Objects.equals(scriptId, that.scriptId)
&& Objects.equals(parameters, that.parameters);
}

@Override
public int hashCode() {
return Objects.hash(propagateParams, builderId, scriptId, parameters);
}

// Overridden for better type safety.
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}

/**
* Automatically registered by XStream2.AssociatedConverterImpl#findConverter(Class)
* Process the class regularly but add a check after that
*/
@SuppressWarnings("unused") // discovered dynamically
public static final class ConverterImpl extends XStream2.PassthruConverter<ScriptlerBuilder> {
public ConverterImpl(XStream2 xstream) {
super(xstream);
}

@Override
protected void callback(ScriptlerBuilder obj, UnmarshallingContext context) {
Map<String, String> errors = obj.checkGenericData();

if (!errors.isEmpty()) {
ConversionException conversionException = new ConversionException("Validation failed");
errors.forEach(conversionException::add);
throw new CriticalXStreamException(conversionException);
}
}
}

@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return Jenkins.get().hasPermission(ScriptlerPermissions.RUN_SCRIPTS);
}

@NonNull
@Override
public String getDisplayName() {
return Messages.builder_name();
}

// used by Jelly views
public Permission getRequiredPermission() {
return ScriptlerPermissions.RUN_SCRIPTS;
}

@Override
public ScriptlerBuilder newInstance(StaplerRequest2 req, JSONObject formData) {
ScriptlerBuilder builder = null;
String builderId = formData.optString(BUILDER_ID);
String id = formData.optString("scriptlerScriptId");

if (id != null && !id.isBlank()) {
boolean inPropagateParams = formData.getBoolean("propagateParams");
List<Parameter> params = UIHelper.extractParameters(formData);
builder = new ScriptlerBuilder(builderId, id, inPropagateParams, params);
}

if (builder != null) {
Map<String, String> errors = builder.checkGenericData();
if (!errors.isEmpty()) {
throw new MultipleErrorFormValidation(errors);
}
}

if (builder == null) {
builder = new ScriptlerBuilder(builderId, null, false, Collections.emptyList());
builder = new ScriptlerBuilder(builderId, null, false, List.of());
}

return builder.recreateBuilderWithBuilderIdIfRequired();
}

public List<Script> getScripts() {
// TODO currently only script for RUN_SCRIPT permissions are returned?

Check warning on line 386 in src/main/java/org/jenkinsci/plugins/scriptler/builder/ScriptlerBuilder.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: currently only script for RUN_SCRIPT permissions are returned?
Set<Script> scripts = getConfig().getScripts();
List<Script> scriptsForBuilder = new ArrayList<>();
for (Script script : scripts) {
for (Script script : getConfig().getScripts()) {

Check warning on line 388 in src/main/java/org/jenkinsci/plugins/scriptler/builder/ScriptlerBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 241-388 are not covered by tests
if (script.nonAdministerUsing) {
scriptsForBuilder.add(script);
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/META-INF/hudson.remoting.ClassFilter
Copy link
Member

Choose a reason for hiding this comment

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

Do not do this.

Copy link
Contributor Author

@mtughan mtughan Dec 9, 2024

Choose a reason for hiding this comment

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

This is only meant to be temporary until jenkinsci/jenkins#10026 is merged, as per the JEP-200 blog post:

  • If the class(es) are defined in a third-party library bundled in your plugin, create a resource file META-INF/hudson.remoting.ClassFilter listing them. (example)
    • You may also do this for Java or Jenkins core library classes, as a hotfix until your core baseline includes the whitelist entry proposed above.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
java.util.ImmutableCollections$ListN
java.util.ImmutableCollections$List12
Loading