/*
* The MIT License
*
* Copyright 2014 Jesse Glick.
*
* 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 jenkins.triggers;
import hudson.model.Cause;
import hudson.model.Computer;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.Result;
import hudson.model.Run;
import hudson.model.User;
import hudson.security.AuthorizationMatrixProperty;
import hudson.security.Permission;
import hudson.security.ProjectMatrixAuthorizationStrategy;
import hudson.tasks.BuildTrigger;
import hudson.tasks.BuildTriggerTest;
import hudson.triggers.Trigger;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import jenkins.model.Jenkins;
import jenkins.security.QueueItemAuthenticatorConfiguration;
import org.acegisecurity.Authentication;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockQueueItemAuthenticator;
public class ReverseBuildTriggerTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Test public void configRoundtrip() throws Exception {
r.createFreeStyleProject("upstream");
FreeStyleProject downstream = r.createFreeStyleProject("downstream");
FreeStyleProject wayDownstream = r.createFreeStyleProject("wayDownstream");
downstream.addTrigger(new ReverseBuildTrigger("upstream", Result.SUCCESS));
downstream.getPublishersList().add(new BuildTrigger(Collections.singleton(wayDownstream), Result.SUCCESS));
downstream.save();
r.configRoundtrip(downstream);
ReverseBuildTrigger rbt = downstream.getTrigger(ReverseBuildTrigger.class);
assertNotNull(rbt);
assertEquals("upstream", rbt.getUpstreamProjects());
assertEquals(Result.SUCCESS, rbt.getThreshold());
BuildTrigger bt = downstream.getPublishersList().get(BuildTrigger.class);
assertNotNull(bt);
assertEquals(Collections.singletonList(wayDownstream), bt.getChildProjects(downstream));
assertEquals(Result.SUCCESS, bt.getThreshold());
}
/** @see BuildTriggerTest#testDownstreamProjectSecurity */
@Test public void upstreamProjectSecurity() throws Exception {
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
ProjectMatrixAuthorizationStrategy auth = new ProjectMatrixAuthorizationStrategy();
auth.add(Jenkins.READ, "alice");
auth.add(Computer.BUILD, "alice");
auth.add(Jenkins.ADMINISTER, "admin");
auth.add(Jenkins.READ, "bob");
auth.add(Computer.BUILD, "bob");
r.jenkins.setAuthorizationStrategy(auth);
String upstreamName = "upstr3@m"; // do not clash with English messages!
final FreeStyleProject upstream = r.createFreeStyleProject(upstreamName);
String downstreamName = "d0wnstr3am";
FreeStyleProject downstream = r.createFreeStyleProject(downstreamName);
Map<Permission,Set<String>> perms = new HashMap<Permission,Set<String>>();
perms.put(Item.READ, Collections.singleton("alice"));
downstream.addProperty(new AuthorizationMatrixProperty(perms));
perms = new HashMap<Permission,Set<String>>();
perms.put(Item.READ, Collections.singleton("bob"));
upstream.addProperty(new AuthorizationMatrixProperty(perms));
@SuppressWarnings("rawtypes") Trigger<Job> t = new ReverseBuildTrigger(upstreamName, Result.SUCCESS);
downstream.addTrigger(t);
t.start(downstream, true); // as in AbstractProject.submit
r.jenkins.rebuildDependencyGraph(); // as in AbstractProject.doConfigSubmit
assertEquals(Collections.singletonList(downstream), upstream.getDownstreamProjects());
// TODO could check doCheckUpstreamProjects, though it is not terribly interesting
// Legacy mode: alice has no read permission on upstream but it works anyway
FreeStyleBuild b = r.buildAndAssertSuccess(upstream);
r.assertLogContains(downstreamName, b);
r.assertLogContains(hudson.tasks.Messages.BuildTrigger_warning_access_control_for_builds_in_glo(), b);
r.waitUntilNoActivity();
assertNotNull(JenkinsRule.getLog(b), downstream.getLastBuild());
assertEquals(1, downstream.getLastBuild().number);
// A QIA is configured but does not specify any authentication for downstream, so upstream should not trigger it:
QueueItemAuthenticatorConfiguration.get().getAuthenticators().add(new MockQueueItemAuthenticator(Collections.singletonMap(upstreamName, User.get("admin").impersonate())));
b = r.buildAndAssertSuccess(upstream);
r.assertLogContains(downstreamName, b);
r.assertLogContains(Messages.ReverseBuildTrigger_running_as_cannot_even_see_for_trigger_f("anonymous", upstreamName, downstreamName), b);
r.waitUntilNoActivity();
assertEquals(1, downstream.getLastBuild().number);
// Auth for upstream is defined but cannot see downstream, so no message is printed about it:
QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(Collections.singletonMap(upstreamName, User.get("bob").impersonate())));
b = r.buildAndAssertSuccess(upstream);
r.assertLogNotContains(downstreamName, b);
r.waitUntilNoActivity();
assertEquals(1, downstream.getLastBuild().number);
// Alice can see upstream, so downstream gets built, but the upstream build cannot see downstream:
perms = new HashMap<Permission,Set<String>>();
perms.put(Item.READ, new HashSet<String>(Arrays.asList("alice", "bob")));
upstream.removeProperty(AuthorizationMatrixProperty.class);
upstream.addProperty(new AuthorizationMatrixProperty(perms));
Map<String,Authentication> qiaConfig = new HashMap<String,Authentication>();
qiaConfig.put(upstreamName, User.get("bob").impersonate());
qiaConfig.put(downstreamName, User.get("alice").impersonate());
QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(qiaConfig));
b = r.buildAndAssertSuccess(upstream);
r.assertLogNotContains(downstreamName, b);
r.waitUntilNoActivity();
assertEquals(2, downstream.getLastBuild().number);
assertEquals(new Cause.UpstreamCause((Run) b), downstream.getLastBuild().getCause(Cause.UpstreamCause.class));
// Now if upstream build is permitted to report on downstream:
qiaConfig = new HashMap<String,Authentication>();
qiaConfig.put(upstreamName, User.get("admin").impersonate());
qiaConfig.put(downstreamName, User.get("alice").impersonate());
QueueItemAuthenticatorConfiguration.get().getAuthenticators().replace(new MockQueueItemAuthenticator(qiaConfig));
b = r.buildAndAssertSuccess(upstream);
r.assertLogContains(downstreamName, b);
r.waitUntilNoActivity();
assertEquals(3, downstream.getLastBuild().number);
assertEquals(new Cause.UpstreamCause((Run) b), downstream.getLastBuild().getCause(Cause.UpstreamCause.class));
}
@Issue("JENKINS-29876")
@Test
public void nullJobInTriggerNotCausesNPE() throws Exception {
final FreeStyleProject upstreamJob = r.createFreeStyleProject("upstream");
//job with trigger.job == null
final FreeStyleProject downstreamJob1 = r.createFreeStyleProject("downstream1");
final ReverseBuildTrigger reverseBuildTrigger = new ReverseBuildTrigger("upstream", Result.SUCCESS);
downstreamJob1.addTrigger(reverseBuildTrigger);
downstreamJob1.save();
//job with trigger.job != null
final FreeStyleProject downstreamJob2 = r.createFreeStyleProject("downstream2");
final ReverseBuildTrigger reverseBuildTrigger2 = new ReverseBuildTrigger("upstream", Result.SUCCESS);
downstreamJob2.addTrigger(reverseBuildTrigger2);
downstreamJob2.save();
r.configRoundtrip(downstreamJob2);
r.jenkins.rebuildDependencyGraph();
final FreeStyleBuild build = upstreamJob.scheduleBuild2(0).get();
r.waitUntilNoActivity();
r.assertLogNotContains("java.lang.NullPointerException", build);
assertThat("Build should be not triggered", downstreamJob1.getBuilds(), hasSize(0));
assertThat("Build should be triggered", downstreamJob2.getBuilds(), not(hasSize(0)));
}
}