Skip to content

Commit

Permalink
JENKINS-57252: log to build log instead of system log, delete folders…
Browse files Browse the repository at this point in the history
… separately

Folders fail get removed when they still contain jobs, so we can only remove them
when the jobs are gone. This means that we have to wait for the shelve task to finish
before attempting to remove the folders.
  • Loading branch information
cpfeiffer committed Aug 15, 2019
1 parent 81186ed commit 87d894d
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 13 deletions.
Expand Up @@ -21,10 +21,13 @@
import hudson.model.ItemGroup;
import hudson.model.Items;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.View;
import hudson.model.ViewGroup;
import hudson.model.queue.QueueTaskFuture;
import hudson.tasks.Builder;
import javaposse.jobdsl.dsl.DslException;
import javaposse.jobdsl.dsl.GeneratedConfigFile;
Expand Down Expand Up @@ -55,19 +58,20 @@
import org.jvnet.hudson.plugins.shelveproject.ShelveProjectTask;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

import static hudson.Util.fixEmptyAndTrim;
import static java.lang.String.format;
import static javaposse.jobdsl.plugin.actions.GeneratedObjectsAction.extractGeneratedObjects;
Expand Down Expand Up @@ -369,7 +373,7 @@ public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnul
addJobAction(run, new GeneratedUserContentsBuildAction(freshUserContents));

updateTemplates(run.getParent(), listener, new HashSet<GeneratedJob>(run.getAction(GeneratedJobsBuildAction.class).getModifiedObjects()));
updateGeneratedJobs(run.getParent(), listener, new HashSet<GeneratedJob>(run.getAction(GeneratedJobsBuildAction.class).getModifiedObjects()));
updateGeneratedJobs(run, run.getParent(), listener, new HashSet<GeneratedJob>(run.getAction(GeneratedJobsBuildAction.class).getModifiedObjects()));
updateGeneratedViews(run.getParent(), listener, new HashSet<GeneratedView>(run.getAction(GeneratedViewsBuildAction.class).getModifiedObjects()));
updateGeneratedConfigFiles(run.getParent(), listener, new HashSet<GeneratedConfigFile>(run.getAction(GeneratedConfigFilesBuildAction.class).getModifiedObjects()));
updateGeneratedUserContents(run.getParent(), listener, new HashSet<GeneratedUserContent>(run.getAction(GeneratedUserContentsBuildAction.class).getModifiedObjects()));
Expand Down Expand Up @@ -453,7 +457,7 @@ private Set<String> updateTemplates(Job seedJob, TaskListener listener,
return freshTemplates;
}

private void updateGeneratedJobs(final Job seedJob, TaskListener listener,
private void updateGeneratedJobs(final Run<?, ?> run, final Job seedJob, TaskListener listener,
Set<GeneratedJob> freshJobs) throws IOException, InterruptedException {
// Update Project
Set<GeneratedJob> generatedJobs = extractGeneratedObjects(seedJob, GeneratedJobsAction.class);
Expand All @@ -463,6 +467,7 @@ private void updateGeneratedJobs(final Job seedJob, TaskListener listener,
Set<GeneratedJob> removed = new HashSet<>();
Set<GeneratedJob> shelved = new HashSet<>();
Set<GeneratedJob> disabled = new HashSet<>();
Map<Item,GeneratedJob> folders = new IdentityHashMap<>();

logItems(listener, "Added items", added);
logItems(listener, "Existing items", existing);
Expand All @@ -472,11 +477,16 @@ private void updateGeneratedJobs(final Job seedJob, TaskListener listener,
for (GeneratedJob unreferencedJob : unreferenced) {
Item removedItem = getLookupStrategy().getItem(seedJob, unreferencedJob.getJobName(), Item.class);
if (removedItem != null && removedJobAction != RemovedJobAction.IGNORE) {
if ("com.cloudbees.hudson.plugins.folder.Folder".equals(removedItem.getClass().getName())) {
folders.put(removedItem, unreferencedJob);
continue;
}

if (removedJobAction == RemovedJobAction.DELETE) {
removedItem.delete();
removed.add(unreferencedJob);
} else if (removedJobAction == RemovedJobAction.SHELVE) {
shelve(removedItem);
shelve(run, removedItem, listener);
shelved.add(unreferencedJob);
} else {
if (removedItem instanceof ParameterizedJob) {
Expand All @@ -489,6 +499,14 @@ private void updateGeneratedJobs(final Job seedJob, TaskListener listener,
}
}

// remove extraneous folders after jobs have been deleted/shelved
if (removedJobAction == RemovedJobAction.DELETE || removedJobAction == RemovedJobAction.SHELVE) {
for (Map.Entry<Item, GeneratedJob> folder : folders.entrySet()) {
folder.getKey().delete();
removed.add(folder.getValue());
}
}

// print what happened with unreferenced jobs
logItems(listener, "Disabled items", disabled);
logItems(listener, "Removed items", removed);
Expand All @@ -497,18 +515,33 @@ private void updateGeneratedJobs(final Job seedJob, TaskListener listener,
updateGeneratedJobMap(seedJob, Sets.union(added, existing), unreferenced);
}

private void shelve(Item project) {
private void shelve(Run<?,?> run, Item project, TaskListener listener) throws InterruptedException {
Jenkins jenkins = Jenkins.get();
if (! (project instanceof BuildableItem)) {
LOGGER.log(Level.WARNING, "Unable to shelve " + project + " since it is not a BuildableItem");
failBuild(run, "Unable to shelve " + project + " since it is not a BuildableItem", listener, null);
return;
}
BuildableItem item = (BuildableItem) project;
if (jenkins.getPlugin(SHELVE_PLUGIN_ID) == null) {
LOGGER.log(Level.WARNING, "Unable to shelve project " + item + " since the " + SHELVE_PLUGIN_ID + " plugin is not installed.");
failBuild(run, "Unable to shelve project " + item + " since the " + SHELVE_PLUGIN_ID + " plugin is not installed.", listener, null);
return;
}
jenkins.getQueue().schedule(new ShelveProjectTask(item), 0);

Queue.WaitingItem waitingItem = jenkins.getQueue().schedule(new ShelveProjectTask(item), 0);
QueueTaskFuture<Queue.Executable> future = waitingItem.getFuture();
try {
future.get(); // wait for completion so that upper folders can be deleted
} catch (ExecutionException ex) {
failBuild(run, "Error shelving project " + project, listener, ex);
}
}

private void failBuild(Run<?,?> run, String message, TaskListener listener, @Nullable Exception ex) {
listener.error(message);
if (ex != null) {
ex.printStackTrace(listener.getLogger());
}
run.setResult(Result.UNSTABLE);
}

private void updateGeneratedJobMap(Job seedJob, Set<GeneratedJob> createdOrUpdatedJobs,
Expand Down
Expand Up @@ -33,6 +33,7 @@ import org.acegisecurity.Authentication
import org.jenkinsci.plugins.configfiles.GlobalConfigFiles
import org.jenkinsci.plugins.configfiles.custom.CustomConfig
import org.jenkinsci.plugins.managedscripts.PowerShellConfig
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage
import org.junit.ClassRule
import org.junit.Rule
Expand All @@ -44,8 +45,6 @@ import org.jvnet.hudson.test.WithoutJenkins
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval

import static hudson.model.Result.FAILURE
import static hudson.model.Result.SUCCESS
import static hudson.model.Result.UNSTABLE
Expand Down Expand Up @@ -601,7 +600,7 @@ folder('folder-a/folder-b') {
when:
String script1 = 'job("test-job")'
ExecuteDslScripts builder1 = new ExecuteDslScripts(script1)
builder1.removedJobAction = RemovedJobAction.DELETE
builder1.removedJobAction = RemovedJobAction.SHELVE
runBuild(job, builder1)
then:
Expand All @@ -615,7 +614,34 @@ folder('folder-a/folder-b') {
then:
jenkinsRule.jenkins.getItemByFullName('different-job') instanceof FreeStyleProject
// jenkinsRule.jenkins.getItemByFullName('test-job') == null
jenkinsRule.jenkins.getItemByFullName('test-job') == null
}
def shelveJobInFolder() {
setup:
jenkinsRule.jenkins.createProject(Folder, 'folder')
FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
when:
String script1 = 'job("/folder/test-job")'
ExecuteDslScripts builder1 = new ExecuteDslScripts(script1)
builder1.removedJobAction = RemovedJobAction.DELETE
runBuild(job, builder1)
then:
jenkinsRule.jenkins.getItemByFullName('/folder/test-job') instanceof FreeStyleProject
when:
String script2 = 'job("/folder/different-job")'
ExecuteDslScripts builder2 = new ExecuteDslScripts(script2)
builder2.removedJobAction = RemovedJobAction.SHELVE
runBuild(job, builder2)
then:
jenkinsRule.jenkins.getItemByFullName('/folder/different-job') instanceof FreeStyleProject
jenkinsRule.jenkins.getItemByFullName('/folder/test-job') == null
// how to make sure there are no further jobs in /folder so that we can test that /folder gets deleted?
//jenkinsRule.jenkins.getItemByFullName('/folder') == null
}
def 'only use last build to calculate items to be deleted'() {
Expand Down

0 comments on commit 87d894d

Please sign in to comment.