Skip to content

Commit

Permalink
[FIXED JENKINS-27182]: Allow to test Job DSL scripts (dry run)
Browse files Browse the repository at this point in the history
  • Loading branch information
arcivanov committed Oct 5, 2015
1 parent dcd97d2 commit 8f545f8
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public ScriptLocation(String value, String targets, String scriptText) {

private final boolean ignoreExisting;

private final boolean dryRun;

private final RemovedJobAction removedJobAction;

private final RemovedViewAction removedViewAction;
Expand All @@ -105,7 +107,7 @@ public ScriptLocation(String value, String targets, String scriptText) {
@DataBoundConstructor
public ExecuteDslScripts(ScriptLocation scriptLocation, boolean ignoreExisting, RemovedJobAction removedJobAction,
RemovedViewAction removedViewAction, LookupStrategy lookupStrategy,
String additionalClasspath) {
String additionalClasspath, boolean dryRun) {
// Copy over from embedded object
this.usingScriptText = scriptLocation == null || scriptLocation.usingScriptText;
this.targets = scriptLocation == null ? null : scriptLocation.targets;
Expand All @@ -115,6 +117,13 @@ public ExecuteDslScripts(ScriptLocation scriptLocation, boolean ignoreExisting,
this.removedViewAction = removedViewAction;
this.lookupStrategy = lookupStrategy == null ? LookupStrategy.JENKINS_ROOT : lookupStrategy;
this.additionalClasspath = additionalClasspath;
this.dryRun = dryRun;
}

public ExecuteDslScripts(ScriptLocation scriptLocation, boolean ignoreExisting, RemovedJobAction removedJobAction,
RemovedViewAction removedViewAction, LookupStrategy lookupStrategy,
String additionalClasspath) {
this(scriptLocation, ignoreExisting, removedJobAction, removedViewAction, lookupStrategy, additionalClasspath, false);
}

public ExecuteDslScripts(ScriptLocation scriptLocation, boolean ignoreExisting, RemovedJobAction removedJobAction,
Expand All @@ -140,6 +149,7 @@ public ExecuteDslScripts(ScriptLocation scriptLocation, boolean ignoreExisting,
this.removedViewAction = RemovedViewAction.IGNORE;
this.lookupStrategy = LookupStrategy.JENKINS_ROOT;
this.additionalClasspath = null;
this.dryRun = false;
}

ExecuteDslScripts() {
Expand All @@ -162,6 +172,10 @@ public boolean isIgnoreExisting() {
return ignoreExisting;
}

public boolean isDryRun() {
return dryRun;
}

public RemovedJobAction getRemovedJobAction() {
return removedJobAction;
}
Expand Down Expand Up @@ -199,9 +213,9 @@ public boolean perform(final AbstractBuild<?, ?> build, final Launcher launcher,
EnvVars env = build.getEnvironment(listener);
env.putAll(build.getBuildVariables());

JobManagement jm = new InterruptibleJobManagement(
new JenkinsJobManagement(listener.getLogger(), env, build, getLookupStrategy())
);
JenkinsJobManagement jjm = new JenkinsJobManagement(listener.getLogger(), env, build, getLookupStrategy());
jjm.setDryRun(dryRun);
JobManagement jm = new InterruptibleJobManagement(jjm);

ScriptRequestGenerator generator = new ScriptRequestGenerator(build, env);
try {
Expand Down Expand Up @@ -272,8 +286,10 @@ private Set<String> updateTemplates(AbstractBuild<?, ?> build, BuildListener lis
Collection<SeedReference> seedJobReferences = descriptor.getTemplateJobMap().get(templateName);
Collection<SeedReference> matching = Collections2.filter(seedJobReferences, new SeedNamePredicate(seedJobName));
if (!matching.isEmpty()) {
seedJobReferences.removeAll(matching);
descriptorMutated = true;
if (!isDryRun()) {
seedJobReferences.removeAll(matching);
descriptorMutated = true;
}
}
}

Expand All @@ -289,16 +305,20 @@ private Set<String> updateTemplates(AbstractBuild<?, ?> build, BuildListener lis
// Just update digest
SeedReference ref = Iterables.get(matching, 0);
if (digest.equals(ref.getDigest())) {
ref.setDigest(digest);
descriptorMutated = true;
if (!isDryRun()) {
ref.setDigest(digest);
descriptorMutated = true;
}
}
} else {
if (matching.size() > 1) {
// Not sure how there could be more one, throw it all away and start over
seedJobReferences.removeAll(matching);
if (!isDryRun()) {
if (matching.size() > 1) {
// Not sure how there could be more one, throw it all away and start over
seedJobReferences.removeAll(matching);
}
seedJobReferences.add(new SeedReference(templateName, seedJobName, digest));
descriptorMutated = true;
}
seedJobReferences.add(new SeedReference(templateName, seedJobName, digest));
descriptorMutated = true;
}
}

Expand Down Expand Up @@ -328,11 +348,15 @@ private void updateGeneratedJobs(final AbstractBuild<?, ?> build, BuildListener
Item removedItem = getLookupStrategy().getItem(build.getProject(), unreferencedJob.getJobName(), Item.class);
if (removedItem != null && removedJobAction != RemovedJobAction.IGNORE) {
if (removedJobAction == RemovedJobAction.DELETE) {
removedItem.delete();
if (!isDryRun()) {
removedItem.delete();
}
removed.add(unreferencedJob);
} else {
if (removedItem instanceof AbstractProject) {
((AbstractProject) removedItem).disable();
if (!isDryRun()) {
((AbstractProject) removedItem).disable();
}
disabled.add(unreferencedJob);
}
}
Expand Down Expand Up @@ -366,17 +390,21 @@ private void updateGeneratedJobMap(AbstractProject<?, ?> seedJob, Set<GeneratedJ

SeedReference oldSeedReference = generatedJobMap.get(item.getFullName());
if (!newSeedReference.equals(oldSeedReference)) {
generatedJobMap.put(item.getFullName(), newSeedReference);
descriptorMutated = true;
if (!isDryRun()) {
generatedJobMap.put(item.getFullName(), newSeedReference);
descriptorMutated = true;
}
}
}
}

for (GeneratedJob removedJob : removedJobs) {
Item removedItem = getLookupStrategy().getItem(seedJob, removedJob.getJobName(), Item.class);
if (removedItem != null) {
generatedJobMap.remove(removedItem.getFullName());
descriptorMutated = true;
if (!isDryRun()) {
generatedJobMap.remove(removedItem.getFullName());
descriptorMutated = true;
}
}
}

Expand Down Expand Up @@ -405,7 +433,9 @@ private void updateGeneratedViews(AbstractBuild<?, ?> build, BuildListener liste
if (parent instanceof ViewGroup) {
View view = ((ViewGroup) parent).getView(FilenameUtils.getName(viewName));
if (view != null) {
((ViewGroup) parent).deleteView(view);
if(!isDryRun()) {
((ViewGroup) parent).deleteView(view);
}
removed.add(unreferencedView);
}
} else if (parent == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public final class JenkinsJobManagement extends AbstractJobManagement {
private final Map<javaposse.jobdsl.dsl.Item, DslEnvironment> environments =
new HashMap<javaposse.jobdsl.dsl.Item, DslEnvironment>();

private boolean dryRun;

public JenkinsJobManagement(PrintStream outputLogger, EnvVars envVars, AbstractBuild<?, ?> build,
LookupStrategy lookupStrategy) {
super(outputLogger);
Expand Down Expand Up @@ -143,6 +145,11 @@ public void createOrUpdateView(String path, String config, boolean ignoreExistin
validateUpdateArgs(path, config);
String viewBaseName = FilenameUtils.getName(path);
Jenkins.checkGoodName(viewBaseName);
if (isDryRun()) {
getOutputStream().format("DRY RUN: Would create or update view '%s' on path '%s' with config:%n%s%n%n",
viewBaseName, path, config);
return;
}
try {
InputStream inputStream = new ByteArrayInputStream(config.getBytes("UTF-8"));

Expand Down Expand Up @@ -213,6 +220,11 @@ public void createOrUpdateUserContent(UserContent userContent, boolean ignoreExi
try {
FilePath file = Jenkins.getInstance().getRootPath().child("userContent").child(userContent.getPath());
if (!(file.exists() && ignoreExisting)) {
if (isDryRun()) {
getOutputStream().format("DRY RUN: Would create or update user content on '%s' from '%s'%n", file,
userContent.getPath());
return;
}
file.getParent().mkdirs();
file.copyFrom(userContent.getContent());
}
Expand Down Expand Up @@ -252,6 +264,11 @@ public void queueJob(String path) throws NameNotProvidedException {

BuildableItem project = lookupStrategy.getItem(build.getParent(), path, BuildableItem.class);

if (isDryRun()) {
getOutputStream().format("DRY RUN: Would schedule build of %s from %s%n", path,
build.getParent().getName());
return;
}
LOGGER.log(Level.INFO, format("Scheduling build of %s from %s", path, build.getParent().getName()));
project.scheduleBuild(new Cause.UpstreamCause((Run) build));
}
Expand Down Expand Up @@ -450,7 +467,9 @@ private boolean updateExistingItem(AbstractItem item, javaposse.jobdsl.dsl.Item
diff = XMLUnit.compareXML(oldJob, config);
if (diff.identical()) {
LOGGER.log(Level.FINE, format("Item %s is identical", item.getName()));
notifyItemUpdated(item, dslItem);
if (!isDryRun()) {
notifyItemUpdated(item, dslItem);
}
return false;
}
} catch (Exception e) {
Expand All @@ -463,8 +482,12 @@ private boolean updateExistingItem(AbstractItem item, javaposse.jobdsl.dsl.Item
LOGGER.log(Level.FINE, format("Updating item %s as %s", item.getName(), config));
Source streamSource = new StreamSource(new StringReader(config));
try {
item.updateByXml(streamSource);
notifyItemUpdated(item, dslItem);
if (!isDryRun()) {
item.updateByXml(streamSource);
notifyItemUpdated(item, dslItem);
} else {
getOutputStream().format("DRY RUN: Would update item '%s' as:%n%s%n%n", item.getName(), config);
}
created = true;
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Error writing updated item to file.", e);
Expand Down Expand Up @@ -505,8 +528,12 @@ private boolean createNewItem(String path, javaposse.jobdsl.dsl.Item dslItem) {
ItemGroup parent = lookupStrategy.getParent(build.getProject(), path);
String itemName = FilenameUtils.getName(path);
if (parent instanceof ModifiableTopLevelItemGroup) {
Item project = ((ModifiableTopLevelItemGroup) parent).createProjectFromXML(itemName, is);
notifyItemCreated(project, dslItem);
if (!isDryRun()) {
Item project = ((ModifiableTopLevelItemGroup) parent).createProjectFromXML(itemName, is);
notifyItemCreated(project, dslItem);
} else {
getOutputStream().format("DRY RUN: Would create item as '%s' as:%n%s%n%n", itemName, config);
}
created = true;
} else if (parent == null) {
throw new DslException(format(Messages.CreateItem_UnknownParent(), path));
Expand Down Expand Up @@ -555,8 +582,13 @@ private void renameJob(Job from, String to) throws IOException {
if (fromParent != toParent) {
LOGGER.info(format("Moving Job %s to folder %s", fromParent.getFullName(), toParent.getFullName()));
if (toParent instanceof DirectlyModifiableTopLevelItemGroup) {
DirectlyModifiableTopLevelItemGroup itemGroup = (DirectlyModifiableTopLevelItemGroup) toParent;
move(from, itemGroup);
if (!isDryRun()) {
DirectlyModifiableTopLevelItemGroup itemGroup = (DirectlyModifiableTopLevelItemGroup) toParent;
move(from, itemGroup);
} else {
getOutputStream().format("DRY RUN: Would move job '%s' to folder '%s'%n", fromParent.getFullName(),
toParent.getFullName());
}
} else {
throw new DslException(format(
Messages.RenameJobMatching_DestinationNotFolder(),
Expand All @@ -565,11 +597,23 @@ private void renameJob(Job from, String to) throws IOException {
));
}
}
from.renameTo(FilenameUtils.getName(to));
if (!isDryRun()) {
from.renameTo(FilenameUtils.getName(to));
} else {
getOutputStream().format("DRY RUN: Would rename job '%s' to '%s'%n", from.getFullName(), to);
}
}

@SuppressWarnings("unchecked")
private static <I extends AbstractItem & TopLevelItem> I move(Item item, DirectlyModifiableTopLevelItemGroup destination) throws IOException {
return Items.move((I) item, destination);
}

boolean isDryRun() {
return dryRun;
}

void setDryRun(boolean dryRun) {
this.dryRun = dryRun;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<f:entry title="Action for removed views:" field="removedViewAction">
<f:select/>
</f:entry>
<f:entry title="Dry run:" field="dryRun">
<f:checkbox name="dryRun" title="Dry run" checked="${instance.dryRun}"
description="Test specified DSL script by generating configuration but do not actually modify anything"/>
</f:entry>

<f:advanced>
<f:entry title="Context to use for relative job names:" field="lookupStrategy">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div>
This option forces a dry run that does everything but modify existing configuration and rather prints XML diffs to the console logs
to be inspected.
This mode is useful with pull request/feature branch building and staging to automatically validate and inspect the proposed change
in the actual deployment environment. The latter allows to validate that external references (Credential IDs, Secret Files etc) are
properly found and resolved.
</div>
Loading

0 comments on commit 8f545f8

Please sign in to comment.