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

Add support for running promotions on inheritance projects. #75

Merged
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
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@
<version>1.10</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>hudson.plugins</groupId>
<artifactId>project-inheritance</artifactId>
<version>1.5.3</version>
<optional>true</optional>
</dependency>
</dependencies>

<build>
Expand Down
46 changes: 29 additions & 17 deletions src/main/java/hudson/plugins/promoted_builds/JobPropertyImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,21 +64,18 @@ public final class JobPropertyImpl extends JobProperty<AbstractProject<?,?>> imp
* These {@link PromotionProcess}es are active.
*/
private final Set<String> activeProcessNames = new HashSet<String>();

// /**
// * Names of the processes that are configured.
// * Used to construct {@link #processes}.
// */
// private final List<String> names = new ArrayList<String>();

/**
* Programmatic construction.
*/
public JobPropertyImpl(AbstractProject<?,?> owner) throws Descriptor.FormException, IOException {
this.owner = owner;
init();
}

public JobPropertyImpl(JobPropertyImpl other, AbstractProject<?,?> owner) throws Descriptor.FormException, IOException {
this.owner = owner;
this.activeProcessNames.addAll(other.activeProcessNames);
loadAllProcesses(other.getRootDir());
}
private JobPropertyImpl(StaplerRequest req, JSONObject json) throws Descriptor.FormException, IOException {
// a hack to get the owning AbstractProject.
// this is needed here so that we can load items
Expand Down Expand Up @@ -112,14 +109,25 @@ private JobPropertyImpl(StaplerRequest req, JSONObject json) throws Descriptor.F
}
init();
}
private void loadAllProcesses(File rootDir) throws IOException {
File[] subdirs = rootDir.listFiles(new FileFilter() {
public boolean accept(File child) {
return child.isDirectory();
}
});

loadProcesses(subdirs);
}
Copy link
Member

Choose a reason for hiding this comment

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

Mostly duplicates init(). Would be great to merge methods somehow

private void init() throws IOException {
// load inactive processes
File[] subdirs = getRootDir().listFiles(new FileFilter() {
public boolean accept(File child) {
return child.isDirectory() && !isActiveProcessNameIgnoreCase(child.getName());
}
});
loadProcesses(subdirs);
}
private void loadProcesses(File[] subdirs) throws IOException {
if(subdirs!=null) {
for (File subdir : subdirs) {
try {
Expand Down Expand Up @@ -179,7 +187,7 @@ protected synchronized void setOwner(AbstractProject<?,?> owner) {
// so use this as the initialization opportunity.
// CopyListener is also using setOwner to re-init after copying config from another job.
processes = new ArrayList<PromotionProcess>(ItemGroupMixIn.<String,PromotionProcess>loadChildren(
this,getRootDir(),ItemGroupMixIn.KEYED_BY_NAME).values());
this,getRootDir(),ItemGroupMixIn.KEYED_BY_NAME).values());
try {
buildActiveProcess();
} catch (IOException e) {
Expand All @@ -201,7 +209,11 @@ private void buildActiveProcess() throws IOException {
// ensure that the name casing matches what's given in the activeProcessName
// this is because in case insensitive file system, we may end up resolving
// to a directory name that differs only in their case.
p.renameTo(getActiveProcessName(p.getName()));
String processName = p.getName();
String activeProcessName = getActiveProcessName(processName);
if (!activeProcessName.equals(processName)){
p.renameTo(activeProcessName);
}
Copy link
Member

Choose a reason for hiding this comment

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

This change seems to be unrelated to the issue, but seems to be reasonable. Anyway, the plugin would benefit from a configurable case sensitivity handling like it happens with User IDs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is actually related to the issue. Unfortunately p.renameTo results in an attempt to lock the promotion process and ultimately a deadlock.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed

}
}

Expand Down Expand Up @@ -352,16 +364,16 @@ public Action getJobAction(AbstractProject<?,?> job) {

@Extension
public static final class DescriptorImpl extends JobPropertyDescriptor {

public DescriptorImpl() {
super();
}
super();
}

public DescriptorImpl(Class<? extends JobProperty<?>> clazz) {
super(clazz);
}
public DescriptorImpl(Class<? extends JobProperty<?>> clazz) {
super(clazz);
}

public String getDisplayName() {
public String getDisplayName() {
return "Promote Builds When...";
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,7 @@ public boolean scheduleBuild(AbstractBuild<?,?> build, Cause cause) {
}

public Future<Promotion> scheduleBuild2(AbstractBuild<?,?> build, Cause cause, List<ParameterValue> params) {
assert build.getProject()==getOwner();


List<Action> actions = new ArrayList<Action>();
Promotion.buildParametersAction(actions, build, params);
actions.add(new PromotionTargetAction(build));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public AbstractBuild<?,?> resolve() {
}

public AbstractBuild<?,?> resolve(PromotionProcess parent) {
AbstractBuild<?,?> build = this.resolve();
if (build !=null){
return build;
}
//In case of project renamed.
AbstractProject<?,?> j = parent.getOwner();
if (j==null) return null;
return j.getBuildByNumber(number);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

package hudson.plugins.promoted_builds.inheritance;

import org.apache.log4j.Logger;

import hudson.Extension;
import hudson.model.JobProperty;

import hudson.plugins.project_inheritance.projects.InheritanceProject;
import hudson.plugins.project_inheritance.projects.inheritance.InheritanceSelector;

import hudson.plugins.promoted_builds.JobPropertyImpl;

/**
*
* @author Jacek Tomaka
Copy link
Member

Choose a reason for hiding this comment

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

A good practice is to add @since TODO to the Javadoc. Then it will be possible to resolve these TODOs before the relelase

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok. Will add this. Thanks.

* @since TODO
*/
@Extension(optional=true)
public class JobPropertyImplSelector extends InheritanceSelector<JobProperty<?>> {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(JobPropertyImplSelector.class);

@Override
public boolean isApplicableFor(Class<?> clazz){
return JobProperty.class.isAssignableFrom(clazz);
}

@Override
public InheritanceSelector.MODE getModeFor(Class<?> clazz){
if (JobPropertyImpl.class.isAssignableFrom(clazz)) return MODE.USE_LAST;
return MODE.NOT_RESPONSIBLE;
}

@Override
public String getObjectIdentifier(JobProperty<?> obj){
if ( obj!=null && JobPropertyImpl.class.getName().equals(obj.getClass().getName())){
return JobPropertyImplSelector.class.getName();
}
return null;
}

@Override
public JobPropertyImpl merge(JobProperty<?> prior, JobProperty<?> latter, InheritanceProject caller){
return null;
}

@Override
public JobProperty<?> handleSingleton(JobProperty<?> jobProperty, InheritanceProject caller){
if (jobProperty == null || caller == null) return jobProperty;
if (caller.isAbstract) return jobProperty;

if (!JobPropertyImpl.class.isAssignableFrom(jobProperty.getClass())) return jobProperty;


JobPropertyImpl jobPropertyImpl = (JobPropertyImpl)jobProperty;

try {
JobPropertyImpl newJobProperty = new JobPropertyImpl(jobPropertyImpl, caller);
return newJobProperty;
} catch (Exception ex){
logger.error("Error during hacking up JobPropertyImpl", ex );
}
return jobProperty;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* The MIT License
*
* Copyright 2015 Franta Mejta
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.plugins.promoted_builds.conditions.inheritance;

import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Bug;

import hudson.model.Result;
import hudson.plugins.project_inheritance.projects.InheritanceBuild;
import hudson.plugins.project_inheritance.projects.InheritanceProject.IMode;
import hudson.plugins.promoted_builds.JobPropertyImpl;
import hudson.plugins.promoted_builds.PromotedBuildAction;
import hudson.plugins.promoted_builds.PromotionProcess;
import hudson.plugins.promoted_builds.Status;
import hudson.plugins.promoted_builds.conditions.DownstreamPassCondition;
import hudson.plugins.promoted_builds.inheritance.helpers.InheritanceProjectRule;
import hudson.plugins.promoted_builds.inheritance.helpers.InheritanceProjectsPair;
import hudson.tasks.BuildTrigger;
import jenkins.model.Jenkins;

public final class DownstreamPassConditionInheritanceTest {

@Rule
public InheritanceProjectRule j = new InheritanceProjectRule();

@Test
@Bug(7739)
public void shouldEvaluateUpstreamRecursively() throws Exception {
final InheritanceProjectsPair pair1 = j.createInheritanceProjectDerivedWithBase();
final InheritanceProjectsPair pair2 = j.createInheritanceProjectDerivedWithBase();
final InheritanceProjectsPair pair3 = j.createInheritanceProjectDerivedWithBase();


final JobPropertyImpl property = new JobPropertyImpl(pair1.getBase());
pair1.getBase().addProperty(property);

final PromotionProcess process = property.addProcess("promotion");
process.conditions.add(new DownstreamPassCondition(pair3.getDerived().getFullName()));

pair1.getDerived().getPublishersList().add(new BuildTrigger(pair2.getDerived().getFullName(), Result.SUCCESS));
pair2.getDerived().getPublishersList().add(new BuildTrigger(pair3.getDerived().getFullName(), Result.SUCCESS));
Jenkins.getInstance().rebuildDependencyGraph();

final InheritanceBuild run1 = j.buildAndAssertSuccess(pair1.getDerived());
j.assertBuildStatusSuccess(run1);
j.waitUntilNoActivity();
j.assertBuildStatusSuccess(pair2.getDerived().getLastBuild());
j.waitUntilNoActivity();
final InheritanceBuild run3 = j.assertBuildStatusSuccess(pair3.getDerived().getLastBuild());
j.waitUntilNoActivity();

//We cannot assume that the process will contain builds because the process added to base project is different to the one in derived.
JobPropertyImpl jobProperty = pair1.getDerived().getProperty(JobPropertyImpl.class,
/*Forcing inheritance as temporary hack for inheritance plugin 1.53
because that version of the plugin uses inheritance only for certain predefined cases:
-specific methods on the call stack
-url paths.
This has been changed as pull request https://github.com/i-m-c/jenkins-inheritance-plugin/pull/40
*/
IMode.INHERIT_FORCED);

assertNotNull("derived jobProperty is null", jobProperty);
PromotionProcess processDerived = jobProperty.getItem("promotion");

assertEquals("fingerprint relation", run3.getUpstreamRelationship(pair1.getDerived()), -1);
assertFalse("no promotion process", processDerived.getBuilds().isEmpty());

final PromotedBuildAction action = run1.getAction(PromotedBuildAction.class);
assertNotNull("no promoted action", action);

final Status promotion = action.getPromotion("promotion");
assertNotNull("promotion not found", promotion);
assertTrue("promotion not successful", promotion.isPromotionSuccessful());
}

}
Loading