Skip to content

Commit

Permalink
Merge pull request #148 from screwdriver-cd/syncPRs
Browse files Browse the repository at this point in the history
feat: Add syncPRs() for pipeline
  • Loading branch information
d2lam authored Jan 13, 2017
2 parents 8ed7d66 + ea9fad9 commit 824ad26
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 2 deletions.
121 changes: 121 additions & 0 deletions lib/pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,99 @@ class PipelineModel extends BaseModel {
.then(parser);
}

/**
* Get a list of PR jobs to create or update
* @method _checkPRState
* @param {Array} existingJobs List pipeline's existing jobs
* @param {Array} openedPRs List of opened PRs coming from SCM
* @return {Promise} Resolves to the list of jobs to archive, unarchive and create
* Note: toArchive and toUnarchive is an array of job objects; toCreate is an array of objects with name and ref.
*/
_checkPRState(existingJobs, openedPRs) {
const jobList = {
toCreate: [],
toArchive: [],
toUnarchive: []
};
const existingPRs = existingJobs.filter(j => j.isPR()); // list of PRs according to SD
const existingPRsNames = existingPRs.map(j => j.name);
const openedPRsNames = openedPRs.map(j => j.name);
const openedPRsRef = openedPRs.map(j => j.ref);

existingPRs.forEach((job) => {
// if PR is closed, add it to archive list
if (openedPRsNames.indexOf(job.name) < 0 && job.archived === false) {
jobList.toArchive.push(job);
}
});

openedPRsNames.forEach((name, i) => {
const index = existingPRsNames.indexOf(name);

if (index < 0) {
// if opened PR is not in the list of existingPRs, create it
jobList.toCreate.push({ name, ref: openedPRsRef[i] });
} else {
const job = existingPRs[index];

// if opened PR was previously archived, unarchive it
if (job.archived) {
jobList.toUnarchive.push(existingPRs[index]);
}
}
});

return jobList;
}

/**
* Go through the job list and archive/unarchive it
* @method _updateJobArchive
* @param {Array} jobList List of job objects
* @param {boolean} archived Archived value to update to
* @return {Promise}
*/
_updateJobArchive(jobList, archived) {
const jobsToUpdate = [];

jobList.forEach((j) => {
j.archived = archived;
jobsToUpdate.push(j.update());
});

return Promise.all(jobsToUpdate);
}

/**
* Go through the list of job names and create it
* @method _createJob
* @param {Array} jobList Array of job names and refs
* @param {Number} pipelineId Pipeline id that the job belongs to
* @return {Promise}
*/
_createJob(jobList) {
// Lazy load factory dependency to prevent circular dependency issues
// https://nodejs.org/api/modules.html#modules_cycles
/* eslint-disable global-require */
const JobFactory = require('./jobFactory');
/* eslint-enable global-require */

const factory = JobFactory.getInstance();

const jobsToCreate = jobList.map(j =>
this.getConfiguration(j.ref).then((config) => {
const jobConfig = {
pipelineId: this.id,
name: j.name,
permutations: config.jobs.main
};

return factory.create(jobConfig);
}));

return Promise.all(jobsToCreate);
}

/**
* Attach Screwdriver webhook to the pipeline's repository
* @param webhookUrl The webhook to be added
Expand All @@ -64,6 +157,34 @@ class PipelineModel extends BaseModel {
})
);
}

/**
* Sync the pull requests by checking against SCM
* Create or update PR jobs if necessary
* @method syncPRs
* @return {Promise}
*/
syncPRs() {
/* eslint-disable no-underscore-dangle */
return this.token.then(token =>
Promise.all([
this.jobs,
this.scm.getOpenedPRs({
scmUri: this.scmUri,
token
})
]).then(([existingJobs, openedPRs]) => {
const jobList = this._checkPRState(existingJobs, openedPRs);

return Promise.all([
this._createJob(jobList.toCreate),
this._updateJobArchive(jobList.toArchive, true),
this._updateJobArchive(jobList.toUnarchive, false)
]);
}));
/* eslint-enable no-understore-dangle */
}

/**
* Sync the pipeline by looking up screwdriver.yaml
* Create, update, or disable jobs if necessary.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "screwdriver-models",
"version": "20.0.0",
"version": "21.2.0",
"description": "Screwdriver models",
"main": "index.js",
"scripts": {
Expand Down
77 changes: 76 additions & 1 deletion test/lib/pipeline.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ describe('Pipeline Model', () => {
scmMock = {
addWebhook: sinon.stub(),
getFile: sinon.stub(),
decorateUrl: sinon.stub()
decorateUrl: sinon.stub(),
getOpenedPRs: sinon.stub()
};
parserMock = sinon.stub();

Expand Down Expand Up @@ -366,6 +367,80 @@ describe('Pipeline Model', () => {
});
});

describe('syncPRs', () => {
let prJob;

beforeEach(() => {
datastore.update.resolves(null);
scmMock.getFile.resolves('superyamlcontent');
parserMock.withArgs('superyamlcontent').resolves(PARSED_YAML);
userFactoryMock.get.withArgs({ username: 'batman' }).resolves({
unsealToken: sinon.stub().resolves('foo')
});
prJob = {
update: sinon.stub().resolves(null),
isPR: sinon.stub().returns(true),
name: 'PR-1',
state: 'ENABLED',
archived: false
};
jobs = [mainJob, prJob];
jobFactoryMock.list.resolves(jobs);
});

it('archive PR job if it is closed', () => {
scmMock.getOpenedPRs.resolves([]);

return pipeline.syncPRs()
.then(() => {
assert.calledOnce(prJob.update);
assert.equal(prJob.archived, true);
});
});

it('create PR job if it is opened and not in the existing jobs', () => {
prJob.archived = true;
const prJob2 = {
pipelineId: testId,
name: 'PR-2',
permutations: PARSED_YAML.jobs.main
};

scmMock.getOpenedPRs.resolves([{ name: 'PR-2', ref: 'abc' }]);
jobFactoryMock.create.resolves(prJob2);

return pipeline.syncPRs()
.then(() => {
assert.calledWith(jobFactoryMock.create, {
pipelineId: testId,
name: 'PR-2',
permutations: PARSED_YAML.jobs.main
});
});
});

it('unarchive PR job if it was previously archived', () => {
prJob.archived = true;
scmMock.getOpenedPRs.resolves([{ name: 'PR-1', ref: 'abc' }]);

return pipeline.syncPRs()
.then(() => {
assert.calledOnce(prJob.update);
assert.equal(prJob.archived, false);
});
});

it('does nothing if it PR is not archived', () => {
scmMock.getOpenedPRs.resolves([{ name: 'PR-1', ref: 'abc' }]);

return pipeline.syncPRs()
.then(() => {
assert.notCalled(prJob.update);
assert.notCalled(jobFactoryMock.create);
});
});
});

describe('get admin', () => {
it('has an admin getter', () => {
userFactoryMock.get.resolves(null);
Expand Down

0 comments on commit 824ad26

Please sign in to comment.