Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
19 changes: 16 additions & 3 deletions plugin/src/main/java/io/jenkins/plugins/casc/Attribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,14 @@ public Type getValue(Owner target) throws Exception {
public CNode describe(Owner instance, ConfigurationContext context) throws ConfiguratorException {
final Configurator c = context.lookup(type);
if (c == null) {
return new Scalar("FAILED TO EXPORT\n" + instance.getClass().getName() + "#" + name
+ ": No configurator found for type " + type);
String errorMessage = "FAILED TO EXPORT\n" + instance.getClass().getName() + "#" + name
+ ": No configurator found for type " + type;

if (context.isStrictExport()) {
throw new ConfiguratorException(errorMessage);
}

return new Scalar(errorMessage);
}
try {
Object o = getValue(instance);
Expand All @@ -267,7 +273,14 @@ public CNode describe(Owner instance, ConfigurationContext context) throws Confi
return seq;
}
return _describe(c, context, o, shouldBeMasked);
} catch (Exception | /* Jenkins.getDescriptorOrDie */ AssertionError e) {
} catch (Exception | AssertionError e) {
if (context.isStrictExport()) {
if (e instanceof ConfiguratorException) {
throw (ConfiguratorException) e;
}
throw new ConfiguratorException(
"Failed to export " + instance.getClass().getName() + "#" + name, e);
}
// Don't fail the whole export, prefer logging this error
LOGGER.log(Level.WARNING, "Failed to export", e);
return new Scalar(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,10 +595,15 @@ public void doReference(StaplerRequest2 req, StaplerResponse2 res) throws Except

@Restricted(NoExternalUse.class)
public void export(OutputStream out) throws Exception {
export(out, false);
}

@Restricted(NoExternalUse.class)
public void export(OutputStream out, boolean strict) throws Exception {
Comment thread
somiljain2006 marked this conversation as resolved.
Outdated
final List<NodeTuple> tuples = new ArrayList<>();

final ConfigurationContext context = new ConfigurationContext(registry);
context.setStrictExport(strict);
for (RootElementConfigurator root : RootElementConfigurator.all()) {
final CNode config = root.describe(root.getTargetComponent(context), context);
final Node valueNode = toYaml(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class ConfigurationContext implements ConfiguratorRegistry {

private transient SecretSourceResolver secretSourceResolver;

private boolean strictExport = false;

public ConfigurationContext(ConfiguratorRegistry registry) {
this(registry, null);
}
Expand Down Expand Up @@ -126,6 +128,14 @@ public int getYamlCodePointLimit() {
return yamlCodePointLimit;
}

public boolean isStrictExport() {
return strictExport;
}

public void setStrictExport(boolean strictExport) {
this.strictExport = strictExport;
}

// --- delegate methods for ConfigurationContext

@Override
Expand Down
53 changes: 53 additions & 0 deletions plugin/src/test/java/io/jenkins/plugins/casc/AttributeTest.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package io.jenkins.plugins.casc;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.util.Secret;
import io.jenkins.plugins.casc.model.CNode;
import io.jenkins.plugins.casc.model.Scalar;
import java.lang.reflect.Type;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.jupiter.api.AfterEach;
Expand Down Expand Up @@ -230,4 +236,51 @@ void checkCalculationIsIdempotent() {
assertFalse(firstUnknown);
assertFalse(secondUnknown, "Subsequent calls should return the same fallback FALSE result");
}

@Test
@SuppressWarnings("ExtractMethodRecommender")
void describeHandlesMissingConfiguratorCorrectly() throws Exception {
ConfiguratorRegistry dummyRegistry = new ConfiguratorRegistry() {
@Override
public RootElementConfigurator<?> lookupRootElement(String name) {
return null;
}

@Override
@NonNull
public <T> Configurator<T> lookupOrFail(Type type) throws ConfiguratorException {
throw new ConfiguratorException("Not found");
}

@Override
public <T> Configurator<T> lookup(Type type) {
return null;
}
};

ConfigurationContext context = new ConfigurationContext(dummyRegistry);

Attribute<NonSecretField, String> attr = new Attribute<>("passwordPath", String.class);
NonSecretField dummyInstance = new NonSecretField("my-dummy-path");

context.setStrictExport(false);
CNode node = attr.describe(dummyInstance, context);

assertInstanceOf(Scalar.class, node, "Should return a Scalar node on failure in non-strict mode");
assertTrue(
((Scalar) node).getValue().contains("FAILED TO EXPORT"),
"Scalar should contain the fallback failure message");

context.setStrictExport(true);
ConfiguratorException exception = assertThrows(
ConfiguratorException.class,
() -> {
attr.describe(dummyInstance, context);
},
"Should completely abort and throw ConfiguratorException in strict mode");

assertTrue(
Comment thread
somiljain2006 marked this conversation as resolved.
Outdated
exception.getMessage().contains("No configurator found"),
"Exception message should accurately reflect the missing configurator");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertFalse;

import java.io.ByteArrayOutputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
Expand Down Expand Up @@ -185,4 +188,18 @@ public void testDoReplace_ValidSource() throws Exception {
assertThat(j.jenkins.getSystemMessage(), is("Hello Replace"));
}
}

@Test
public void testExportStrictMode_SuccessOnCleanJenkins() throws Exception {
configureAdminSecurity();

ByteArrayOutputStream out = new ByteArrayOutputStream();

ConfigurationAsCode.get().export(out, true);

String exportedYaml = out.toString(StandardCharsets.UTF_8);

assertThat(exportedYaml, containsString("jenkins:"));
assertFalse(exportedYaml.contains("FAILED TO EXPORT"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,18 @@

public class JenkinsConfiguredRule extends JenkinsRule {

// TODO: Looks like API defect, exception should be thrown
/**
* Exports the Jenkins configuration to a string.
* @return YAML as string
* @param strict Fail if any export operation returns error
* @throws Exception Export error
* @throws AssertionError Failed to export the configuration
* @since 1.25
*/
public String exportToString(boolean strict) throws Exception {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
ConfigurationAsCode.get().export(out);
final String s = out.toString(StandardCharsets.UTF_8.name());
if (strict && s.contains("Failed to export")) {
throw new AssertionError("Failed to export the configuration: " + s);
}
return s;

ConfigurationAsCode.get().export(out, strict);

return out.toString(StandardCharsets.UTF_8);
}
}
Loading