Skip to content

Commit

Permalink
fix(3242): Unable to force start a frozen job that is part of a pipel…
Browse files Browse the repository at this point in the history
…ine stage
  • Loading branch information
sagar1312 committed Nov 25, 2024
1 parent 18f1e79 commit 65a1936
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 7 deletions.
14 changes: 10 additions & 4 deletions plugins/builds/triggers/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ async function createExternalEvent(config) {
* @param {Boolean} config.start Whether to start the build or not
* @param {Number|undefined} config.jobId Job ID
* @param {EventModel} config.event Event build belongs to
* @param {String} config.causeMessage Reason the event is run
* @returns {Promise<BuildModel|null>}
*/
async function createInternalBuild(config) {
Expand All @@ -309,7 +310,8 @@ async function createInternalBuild(config) {
start,
baseBranch,
parentBuildId,
jobId
jobId,
causeMessage
} = config;
const { ref = '', prSource = '', prBranchName = '', url = '' } = event.pr || {};
const prInfo = prBranchName ? { url, prBranchName } : '';
Expand All @@ -334,7 +336,8 @@ async function createInternalBuild(config) {
prSource,
prInfo,
start: start !== false,
baseBranch
baseBranch,
causeMessage
};

let jobState = job.state;
Expand Down Expand Up @@ -609,9 +612,10 @@ async function getParentBuildStatus({ newBuild, joinListNames, pipelineId, build
* @param {String|undefined} arg.pipelineId Pipeline ID
* @param {String|undefined} arg.stageName Stage name
* @param {Boolean} arg.isVirtualJob If the job is virtual or not
* @param {Event} arg.event Event
* @returns {Promise<Build|null>} The newly updated/created build
*/
async function handleNewBuild({ done, hasFailure, newBuild, job, pipelineId, stageName, isVirtualJob }) {
async function handleNewBuild({ done, hasFailure, newBuild, job, pipelineId, stageName, isVirtualJob, event }) {
if (!done || Status.isStarted(newBuild.status)) {
return null;
}
Expand Down Expand Up @@ -647,7 +651,9 @@ async function handleNewBuild({ done, hasFailure, newBuild, job, pipelineId, sta
newBuild.status = Status.QUEUED;
await newBuild.update();

return newBuild.start();
const causeMessage = job.name === event.startFrom ? event.causeMessage : '';

return newBuild.start({ causeMessage });
}

/**
Expand Down
3 changes: 2 additions & 1 deletion plugins/builds/triggers/joinBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ class JoinBase {
job: nextJob,
pipelineId,
isVirtualJob: isNextJobVirtual,
stageName: nextJobStageName
stageName: nextJobStageName,
event
});
}
}
Expand Down
7 changes: 5 additions & 2 deletions plugins/builds/triggers/orBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class OrBase {
const hasFreezeWindows =
nextJob.permutations[0].freezeWindows && nextJob.permutations[0].freezeWindows.length > 0;

const causeMessage = nextJob.name === event.startFrom ? event.causeMessage : '';

if (nextBuild !== null) {
if (Status.isStarted(nextBuild.status)) {
return nextBuild;
Expand All @@ -67,7 +69,7 @@ class OrBase {
nextBuild.status = Status.QUEUED;
await nextBuild.update();

return nextBuild.start();
return nextBuild.start({ causeMessage });
}

nextBuild = await createInternalBuild({
Expand All @@ -82,7 +84,8 @@ class OrBase {
baseBranch: event.baseBranch || null,
parentBuilds,
parentBuildId: this.currentBuild.id,
start: hasFreezeWindows || !isNextJobVirtual
start: hasFreezeWindows || !isNextJobVirtual,
causeMessage
});

// Bypass execution of the build if the job is virtual
Expand Down
99 changes: 99 additions & 0 deletions test/plugins/builds.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,105 @@ describe('build plugin test', () => {
});
});

it('triggers the startFrom job after the stage setup job even if the startFrom job is within freeze window', () => {
const status = 'SUCCESS';
const options = {
method: 'PUT',
url: `/builds/${id}`,
auth: {
credentials: {
username: id,
scope: ['build']
},
strategy: ['token']
},
payload: {
status
}
};

jobMock = {
id: 110,
name: 'stage@alpha:setup',
pipelineId,
permutations: [
{
settings: {
email: '[email protected]'
}
}
],
pipeline: sinon.stub().resolves(pipelineMock)(),
getLatestBuild: sinon.stub().resolves(buildMock)
};
buildMock.job = sinon.stub().resolves(jobMock)();
buildMock.parentBuilds = {
123: { eventId: '8888', jobs: { '~commit': 7777, C: 7778, D: 7779 } }
};
eventMock.getBuilds.resolves([
{
id: 1,
eventId: '8888',
jobId: 1,
status: 'FAILURE'
},
{
id: 7777,
eventId: '8888',
jobId: 4,
status: 'SUCCESS'
},
{
id: 7778,
eventId: '8888',
jobId: 5,
status: 'SUCCESS'
},
{
id: 7779,
eventId: '8888',
jobId: 6,
status: 'SUCCESS'
}
]);

const causeMessage = '[force start] Starting frozen build from the middle of a stage';

eventMock.workflowGraph = testWorkflowGraphWithStages;
eventMock.startFrom = 'alpha-certify';
eventMock.causeMessage = causeMessage;
eventFactoryMock.get.resolves(eventMock);

const jobAlphaCertify = {
id: 113,
name: 'alpha-certify',
pipelineId,
state: 'ENABLED',
permutations: [
{
settings: {
email: '[email protected]'
}
}
]
};

jobFactoryMock.get.withArgs(jobAlphaCertify.id).resolves(jobAlphaCertify);
jobFactoryMock.get.withArgs({ pipelineId, name: 'alpha-certify' }).resolves(jobAlphaCertify);

buildFactoryMock.get.withArgs({ eventId: eventMock.id, jobId: jobAlphaCertify.id }).returns(null);

return server.inject(options).then(reply => {
assert.equal(reply.statusCode, 200);
assert.calledWith(jobFactoryMock.get, {
name: eventMock.startFrom,
pipelineId
});
assert.calledOnce(buildFactoryMock.create);
assert.strictEqual(buildFactoryMock.create.getCall(0).args[0].causeMessage, causeMessage);
});
});

it('triggers the startFrom job after the stage setup job if the startFrom job is a non-setup job in a different stage', () => {
const status = 'SUCCESS';
const options = {
Expand Down

0 comments on commit 65a1936

Please sign in to comment.