Skip to content

Commit 4b44001

Browse files
committed
feat: Add webhook to create an editor site
Signed-off-by: Andrew Burnes <[email protected]>
1 parent bf7c59d commit 4b44001

File tree

29 files changed

+587
-106
lines changed

29 files changed

+587
-106
lines changed

.cloudgov/manifest.yml

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,31 @@ applications:
3030
- mailer
3131
- slack
3232
env:
33-
NODE_ENV: ((node_env))
34-
APP_ENV: ((env))
3533
ADMIN_HOSTNAME: https://admin.((domain))
34+
APP_ENV: ((env))
3635
APP_HOSTNAME: https://((domain))
36+
CF_CDN_SPACE_NAME: ((cf_cdn_space_name))
37+
CF_DOMAIN_WITH_CDN_PLAN_GUID: 440cce96-8989-428b-a60e-91c1393bf3f2
3738
CLOUD_FOUNDRY_API_HOST: https://api.fr.cloud.gov
3839
CLOUD_FOUNDRY_OAUTH_TOKEN_URL: https://login.fr.cloud.gov/oauth/token
3940
DOMAIN: ((domain))
41+
FEATURE_FILE_STORAGE_SERVICE: ((feature_file_storage_service))
4042
HOMEPAGE_URL: https://cloud.gov/pages
4143
LOG_LEVEL: ((log_level))
42-
NPM_CONFIG_PRODUCTION: true
43-
NODE_MODULES_CACHE: false
44-
USER_AUDITOR: federalist
45-
S3_SERVICE_PLAN_ID: F36820DC-FDB6-496C-9D96-68861F5D0D95
46-
UAA_HOST: ((uaa_host))
4744
NEW_RELIC_APP_NAME: ((new_relic_app_name))
48-
PROXY_DOMAIN: ((proxy_domain))
49-
PRODUCT: pages
50-
CF_CDN_SPACE_NAME: ((cf_cdn_space_name))
51-
CF_DOMAIN_WITH_CDN_PLAN_GUID: 440cce96-8989-428b-a60e-91c1393bf3f2
5245
NEW_RELIC_ERROR_COLLECTOR_ENABLED: false
46+
NODE_ENV: ((node_env))
47+
NODE_MODULES_CACHE: false
48+
NPM_CONFIG_PRODUCTION: true
49+
OPS_EMAIL: [email protected]
50+
PAGES_EDITOR_HOST: https://pages-editor((env_postfix)).app.cloud.gov
51+
PRODUCT: pages
52+
PROXY_DOMAIN: ((proxy_domain))
5353
QUEUES_BUILD_TASKS_CONCURRENCY: ((queues_build_tasks_concurrency))
5454
QUEUES_SITE_BUILDS_CONCURRENCY: ((queues_site_builds_concurrency))
55-
FEATURE_FILE_STORAGE_SERVICE: ((feature_file_storage_service))
55+
S3_SERVICE_PLAN_ID: F36820DC-FDB6-496C-9D96-68861F5D0D95
56+
UAA_HOST: ((uaa_host))
57+
USER_AUDITOR: federalist
5658
- name: pages-admin((env_postfix))
5759
buildpack: staticfile_buildpack
5860
path: ../admin-client

.cloudgov/vars/pages-dev.yml

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
product: pages
2-
name: pages-dev
1+
cf_cdn_space_name: sites-dev
32
domain: pages-dev.cloud.gov
4-
instances: 2
53
env: dev
64
env_postfix: -dev
5+
feature_file_storage_service: "true"
6+
instances: 2
77
log_level: verbose
8-
uaa_host: https://uaa.fr-stage.cloud.gov
9-
uaa_login_host: https://login.fr-stage.cloud.gov
8+
name: pages-dev
109
new_relic_app_name: web-pages-dev
11-
proxy_domain: sites.pages-dev.cloud.gov
12-
cf_cdn_space_name: sites-dev
1310
node_env: development
11+
proxy_domain: sites.pages-dev.cloud.gov
1412
queues_build_tasks_concurrency: 2
1513
queues_site_builds_concurrency: 3
16-
feature_file_storage_service: "true"
14+
uaa_host: https://uaa.fr-stage.cloud.gov
15+
uaa_login_host: https://login.fr-stage.cloud.gov

.cloudgov/vars/pages-production.yml

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
product: pages
2-
name: pages-production
1+
cf_cdn_space_name: sites
32
domain: pages.cloud.gov
4-
instances: 4
53
env: production
64
env_postfix: -production
5+
feature_file_storage_service: "false"
6+
instances: 4
77
log_level: info
8-
uaa_host: https://uaa.fr.cloud.gov
9-
uaa_login_host: https://login.fr.cloud.gov
8+
name: pages-production
109
new_relic_app_name: web-pages-production
11-
proxy_domain: sites.pages.cloud.gov
12-
cf_cdn_space_name: sites
1310
node_env: production
11+
proxy_domain: sites.pages.cloud.gov
1412
queues_build_tasks_concurrency: 10
1513
queues_site_builds_concurrency: 10
16-
feature_file_storage_service: "false"
14+
uaa_host: https://uaa.fr.cloud.gov
15+
uaa_login_host: https://login.fr.cloud.gov

.cloudgov/vars/pages-staging.yml

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
product: pages
2-
name: pages-staging
1+
cf_cdn_space_name: sites-staging
32
domain: pages-staging.cloud.gov
4-
instances: 2
53
env: staging
64
env_postfix: -staging
5+
feature_file_storage_service: "true"
6+
instances: 2
77
log_level: verbose
8-
uaa_host: https://uaa.fr-stage.cloud.gov
9-
uaa_login_host: https://login.fr-stage.cloud.gov
8+
name: pages-staging
109
new_relic_app_name: web-pages-staging
11-
proxy_domain: sites.pages-staging.cloud.gov
12-
cf_cdn_space_name: sites-staging
1310
node_env: production
11+
proxy_domain: sites.pages-staging.cloud.gov
1412
queues_build_tasks_concurrency: 4
1513
queues_site_builds_concurrency: 4
16-
feature_file_storage_service: "true"
14+
uaa_host: https://uaa.fr-stage.cloud.gov
15+
uaa_login_host: https://login.fr-stage.cloud.gov

api/bull-board/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const slowDown = require('express-slow-down');
1010
const {
1111
ArchiveBuildLogsQueue,
1212
BuildTasksQueue,
13+
CreateEditorSiteQueue,
1314
DomainQueue,
1415
FailStuckBuildsQueue,
1516
MailQueue,
@@ -38,6 +39,7 @@ createBullBoard({
3839
new BullMQAdapter(new SiteBuildsQueue(connection)),
3940
new BullMQAdapter(new ArchiveBuildLogsQueue(connection)),
4041
new BullMQAdapter(new BuildTasksQueue(connection)),
42+
new BullMQAdapter(new CreateEditorSiteQueue(connection)),
4143
new BullMQAdapter(new DomainQueue(connection)),
4244
new BullMQAdapter(new FailStuckBuildsQueue(connection)),
4345
new BullMQAdapter(new MailQueue(connection)),

api/controllers/webhook.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
const { wrapHandlers } = require('../utils');
22
const Webhooks = require('../services/Webhooks');
3+
const { decrypt } = require('../services/Encryptor');
4+
const { encryption } = require('../../config');
5+
const QueueJobs = require('../queue-jobs');
6+
const { createQueueConnection } = require('../utils/queues');
7+
8+
const connection = createQueueConnection();
9+
const queueJob = new QueueJobs(connection);
310

411
module.exports = wrapHandlers({
512
async github(req, res) {
@@ -17,4 +24,24 @@ module.exports = wrapHandlers({
1724

1825
res.ok();
1926
},
27+
28+
async site(req, res) {
29+
const { body } = req;
30+
31+
const userEmail = decrypt(body.userEmail, encryption.key);
32+
const apiKey = decrypt(body.apiKey, encryption.key);
33+
const siteId = decrypt(body.siteId, encryption.key);
34+
const siteName = decrypt(body.siteName, encryption.key);
35+
const orgName = decrypt(body.org, encryption.key);
36+
37+
await queueJob.startCreateEditorSiteTask({
38+
userEmail,
39+
apiKey,
40+
siteId,
41+
siteName,
42+
orgName,
43+
});
44+
45+
return res.ok();
46+
},
2047
});

api/queue-jobs/index.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
const moment = require('moment');
22
const PromisePool = require('@supercharge/promise-pool');
3-
const { BuildTasksQueue, MailQueue, SiteBuildsQueue } = require('../queues');
3+
const {
4+
BuildTasksQueue,
5+
CreateEditorSiteQueue,
6+
MailQueue,
7+
SiteBuildsQueue,
8+
} = require('../queues');
49
const Templates = require('../services/mailer/templates');
510
const { truncateString } = require('../utils');
611
const {
@@ -11,6 +16,7 @@ class QueueJobs {
1116
constructor(connection) {
1217
this.siteBuildsQueue = new SiteBuildsQueue(connection);
1318
this.buildTasksQueue = new BuildTasksQueue(connection);
19+
this.createEditorSiteQueue = new CreateEditorSiteQueue(connection);
1420
this.mailQueue = new MailQueue(connection);
1521
}
1622

@@ -27,7 +33,7 @@ class QueueJobs {
2733
await this.mailQueue.waitUntilReady();
2834

2935
return this.mailQueue.add('alert', {
30-
36+
to: [process.env.OPS_EMAIL],
3137
subject: `${appName} ${appEnv} Alert | ${reason}`,
3238
html: Templates.alert({ errors, reason }),
3339
});
@@ -147,6 +153,35 @@ class QueueJobs {
147153
return this.buildTasksQueue.add(name, { buildTaskId }, { priority });
148154
}
149155

156+
/**
157+
* Adds a create editor site task job to the Create Editor Site Queue
158+
* The editor sites's manager email, org name, site name, site id, and bot api key
159+
* are used to create the site job added to the queue
160+
* @async
161+
* @method startCreateEditorSiteTask
162+
* @param {Object} editorSite - An instance of the model Build Taks
163+
* * @param {number} editorSite.userEmail - The editor site's manager email for the org
164+
* * @param {Object} editorSite.orgName - The name of the org
165+
* * @param {Object} editorSite.siteName - The name of the site
166+
* * @param {Object} editorSite.siteName - The id of the site
167+
* * @param {Object} editorSite.apiKey - The bot api key
168+
* @return {Promise<{Object}>} The bullmq's queue add job response
169+
*/
170+
async startCreateEditorSiteTask(editorSite) {
171+
const { userEmail, orgName, siteName, siteId, apiKey } = editorSite;
172+
const jobName = `Creating editor site for org ${orgName}`;
173+
174+
await this.createEditorSiteQueue.waitUntilReady();
175+
176+
return this.createEditorSiteQueue.add(jobName, {
177+
userEmail,
178+
orgName,
179+
siteName,
180+
siteId,
181+
apiKey,
182+
});
183+
}
184+
150185
/**
151186
* Adds a site build job to the Site Builds Queue
152187
* The build's branch, site owner, and site repository attributes

api/queues/CreateEditorSiteQueue.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { Queue } = require('bullmq');
2+
3+
const CreateEditorSiteQueueName = 'create-editor-site';
4+
5+
class CreateEditorSiteQueue extends Queue {
6+
constructor(connection, { attempts = 1 } = {}) {
7+
super(CreateEditorSiteQueueName, {
8+
connection,
9+
defaultJobOptions: {
10+
attempts,
11+
},
12+
});
13+
}
14+
}
15+
16+
module.exports = {
17+
CreateEditorSiteQueue,
18+
CreateEditorSiteQueueName,
19+
};

api/queues/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ const {
33
ArchiveBuildLogsQueueName,
44
} = require('./ArchiveBuildLogsQueue');
55
const { BuildTasksQueue, BuildTasksQueueName } = require('./BuildTasksQueue');
6+
const {
7+
CreateEditorSiteQueue,
8+
CreateEditorSiteQueueName,
9+
} = require('./CreateEditorSiteQueue');
610
const { DomainQueue, DomainQueueName } = require('./DomainQueue');
711
const {
812
FailStuckBuildsQueue,
@@ -23,6 +27,8 @@ module.exports = {
2327
ArchiveBuildLogsQueueName,
2428
BuildTasksQueue,
2529
BuildTasksQueueName,
30+
CreateEditorSiteQueue,
31+
CreateEditorSiteQueueName,
2632
DomainQueue,
2733
DomainQueueName,
2834
FailStuckBuildsQueue,

api/routers/webhook.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,32 @@ function verifySignature(req, res, next) {
3232
next();
3333
}
3434

35+
function verifySiteRequest(req, res, next) {
36+
const { body: payload } = req;
37+
const expectedKeys = ['userEmail', 'apiKey', 'siteId', 'siteName', 'org'].sort();
38+
39+
// ToDo Add additional headers to check if request is legit
40+
41+
try {
42+
const payloadKeys = Object.keys(payload).sort();
43+
44+
if (payloadKeys.length !== expectedKeys.length) {
45+
throw new Error('Invalid request payload');
46+
}
47+
48+
const hasKeys = payloadKeys.every((value, index) => value === expectedKeys[index]);
49+
50+
if (!hasKeys) throw new Error('Invalid request payload');
51+
} catch (err) {
52+
res.badRequest();
53+
next(err);
54+
}
55+
56+
next();
57+
}
58+
3559
router.post('/webhook/github', verifySignature, WebhookController.github);
3660
router.post('/webhook/organization', verifySignature, WebhookController.organization);
61+
router.post('/webhook/site', verifySiteRequest, WebhookController.site);
3762

3863
module.exports = router;

api/services/SiteCreator.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const GitHub = require('./GitHub');
22
const TemplateResolver = require('./TemplateResolver');
3-
const { Build, Site, User } = require('../models');
3+
const { Build, Organization, Site, User } = require('../models');
44
const { generateS3ServiceName, generateSubdomain } = require('../utils');
55
const CloudFoundryAPIClient = require('../utils/cfApiClient');
66
const config = require('../../config');
@@ -44,12 +44,18 @@ function ownerIsFederalistUser(owner) {
4444
});
4545
}
4646

47-
function checkSiteExists({ owner, repository }) {
47+
function checkSiteExists({ owner, repository, organizationId }) {
4848
return Site.findOne({
4949
where: {
5050
owner,
5151
repository,
5252
},
53+
include: [
54+
{
55+
model: Organization,
56+
where: { id: organizationId },
57+
},
58+
],
5359
}).then((existingSite) => {
5460
if (existingSite) {
5561
const error = new Error(
@@ -183,11 +189,12 @@ async function saveAndBuildSite({ site, user }) {
183189
}
184190

185191
async function createSiteFromExistingRepo({ siteParams, user }) {
186-
const { owner, repository } = siteParams;
192+
const { owner, repository, organizationId } = siteParams;
187193

188194
await checkSiteExists({
189195
owner,
190196
repository,
197+
organizationId,
191198
});
192199
const repo = await checkGithubRepository({
193200
user,

api/services/Webhooks.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const EventCreator = require('./EventCreator');
66
const findSiteForWebhookRequest = (payload) => {
77
const [owner, repository] = payload.repository.full_name.split('/');
88

9-
return Site.findOne({
9+
return Site.findAll({
1010
where: {
1111
owner: owner.toLowerCase(),
1212
repository: repository.toLowerCase(),
@@ -101,12 +101,17 @@ const createBuildForWebhookRequest = async (payload, site) => {
101101

102102
const pushWebhookRequest = async (payload) => {
103103
if (payload.commits && payload.commits.length > 0) {
104-
const site = await findSiteForWebhookRequest(payload);
105-
if (shouldBuildForSite(site)) {
106-
const build = await createBuildForWebhookRequest(payload, site);
107-
await build.reload({ include: Site });
108-
await GithubBuildHelper.reportBuildStatus(build);
109-
}
104+
const sites = await findSiteForWebhookRequest(payload);
105+
106+
await Promise.all(
107+
sites.map(async (site) => {
108+
if (shouldBuildForSite(site)) {
109+
const build = await createBuildForWebhookRequest(payload, site);
110+
await build.reload({ include: Site });
111+
await GithubBuildHelper.reportBuildStatus(build);
112+
}
113+
}),
114+
);
110115
}
111116
};
112117

0 commit comments

Comments
 (0)