build: add dashboard firebase functions (#4806)

* build: add dashboard firebase functions

* Adds a new folder for the dashboard project that for now only contains firebase functions.
* The firebase functions will be used to securely make API calls to the Github API (e.g payload status)
* Functions will be automatically deployed as same as screenshot functions.

* Address feedback
This commit is contained in:
Paul Gschwendtner
2017-06-01 01:18:34 +02:00
committed by mmalerba
parent f169152638
commit 964033ce8d
13 changed files with 182 additions and 4 deletions
+5 -4
View File
@@ -22,10 +22,11 @@ $(npm bin)/gulp docs
# Run publishing of artifacts in parallel.
# This is possible because the output has been built before.
./scripts/release/publish-build-artifacts.sh --no-build &
./scripts/release/publish-docs-content.sh --no-build &
./scripts/deploy/publish-build-artifacts.sh --no-build &
./scripts/deploy/publish-docs-content.sh --no-build &
# Deploy the screenshot functions for each push build.
./scripts/release/deploy-screenshot-functions.sh &
# Deploy the screenshot and dashboard functions for each push build.
./scripts/deploy/deploy-screenshot-functions.sh &
./scripts/deploy/deploy-dashboard-functions.sh &
wait
+24
View File
@@ -0,0 +1,24 @@
#!/bin/bash
# Go to the project root directory
cd $(dirname ${0})/../..
# Paths to the dashboard and functions directories.
dashboardFolder=tools/dashboard
# Go to the dashboard folder because otherwise the Firebase CLI tries to deploy the wrong project.
cd ${dashboardFolder}
# Install node modules for dashboard functions. Firebase CLI needs to execute the functions
# before it can collect all functions and deploy them.
(cd functions; npm install)
if [ -z ${MATERIAL2_DASHBOARD_ACCESS_TOKEN} ]; then
echo "Error: No access token for firebase specified." \
"Please set the environment variable 'MATERIAL2_DASHBOARD_ACCESS_TOKEN'."
exit 1
fi
# Deploy the dashboard functions to Firebase. For now only the functions will be deployed.
$(npm bin)/firebase deploy \
--only functions --token ${MATERIAL2_DASHBOARD_ACCESS_TOKEN} --project material2-dashboard
+11
View File
@@ -0,0 +1,11 @@
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
+1
View File
@@ -0,0 +1 @@
export {payloadGithubStatus} from './payload-github-status';
@@ -0,0 +1,43 @@
import {config} from 'firebase-functions';
const request = require('request');
const {version, name} = require('../package.json');
/** API token for the Github repository. Required to set the github status on commits and PRs. */
const repoToken = config().repoToken;
/** Data that must be specified to set a Github PR status. */
export type GithubStatusData = {
result: boolean;
name: string;
description: string;
url: string;
};
/** Function that sets a Github commit status */
export function setGithubStatus(commitSHA: string, data: GithubStatusData) {
const state = data.result ? 'success' : 'failure';
const requestData = JSON.stringify({
state: state,
target_url: data.url,
context: data.name,
description: data.description
});
const headers = {
'Authorization': `token ${repoToken}`,
'User-Agent': `${name}/${version}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(requestData)
};
return new Promise((resolve) => {
request({
url: `https://api.github.com/repos/angular/material2/statuses/${commitSHA}`,
method: 'POST',
form: requestData,
headers: headers
}, (error: any, response: any) => resolve(response.statusCode));
});
}
+16
View File
@@ -0,0 +1,16 @@
/**
* Entry point for the Firebase functions of the dashboard app. Firebase functions only support
* JavaScript files and therefore the TypeScript files needs to be transpiled.
*/
'use strict';
const path = require('path');
// Enable TypeScript compilation at runtime using ts-node.
require('ts-node').register({
project: path.join(__dirname, 'tsconfig.json')
});
// Export all functions from the TypeScript source.
Object.assign(exports, require('./functions'));
@@ -0,0 +1,22 @@
import {verify} from 'jsonwebtoken';
import {config} from 'firebase-functions';
/** The JWT secret. This is used to validate JWT. */
const jwtSecret = config().jwtSecret;
/** The repo slug. This is used to validate the JWT is sent from correct repo. */
const repoSlug = config().repoSlug;
export function verifyToken(token: string): boolean {
try {
const tokenPayload = verify(token, jwtSecret, {issuer: 'Travis CI, GmbH'});
if (tokenPayload.slug !== repoSlug) {
console.log(`JWT slugs are not matching. Expected ${repoSlug}`);
}
return true;
} catch (e) {
return false;
}
}
+14
View File
@@ -0,0 +1,14 @@
{
"name": "material2-dashboard-functions",
"version": "0.0.1",
"main": "index.js",
"dependencies": {
"@types/jsonwebtoken": "^7.2.0",
"firebase-admin": "~4.2.1",
"firebase-functions": "^0.5.7",
"jsonwebtoken": "^7.4.1",
"request": "^2.81.0",
"ts-node": "^3.0.4",
"typescript": "^2.3.3"
}
}
@@ -0,0 +1,30 @@
import {https} from 'firebase-functions';
import {verifyToken} from './jwt/verify-token';
import {setGithubStatus} from './github/github-status';
export const payloadGithubStatus = https.onRequest(async (request, response) => {
const authToken = request.header('auth-token');
const commitSha = request.header('commit-sha');
const payloadDiff = parseInt(request.header('commit-payload-diff'));
if (!verifyToken(authToken)) {
return response.status(403).json({message: 'Auth token is not valid'});
}
if (!commitSha) {
return response.status(404).json({message: 'No commit has been specified'});
}
if (!payloadDiff || isNaN(payloadDiff)) {
return response.status(400).json({message: 'No valid payload diff has been specified.'});
}
await setGithubStatus(commitSha, {
result: true,
name: 'Library Payload',
url: `https://travis-ci.org/angular/material2/jobs/${process.env['TRAVIS_JOB_ID']}`,
description: `${payloadDiff > 0 ? `+` : ''} ${payloadDiff.toFixed(2)}KB`
});
response.json({message: 'Payload Github status successfully set.'});
});
+16
View File
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"lib": ["es2015", "es2016", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitAny": true,
"sourceMap": true,
"target": "es5",
"baseUrl": "",
"outDir": "../../../dist/dashboard-functions/"
},
"files": [
"./functions.ts"
]
}