ci(docs-infra): rename 'upload-server' to 'preview-server'

The server no longer has files uploaded to it. Instead it is more
accurate to refer to it as dealing with "previews" of PRs.
This commit is contained in:
Pete Bacon Darwin 2018-08-15 13:47:45 +01:00 committed by Miško Hevery
parent 2f791ce68b
commit 1c34e02ae6
45 changed files with 270 additions and 267 deletions

View File

@ -38,12 +38,12 @@ ARG AIO_SIGNIFICANT_FILES_PATTERN='^(?:aio|packages)/(?!.*[._]spec\\.[jt]s$
ARG TEST_AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN ARG TEST_AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN
ARG AIO_TRUSTED_PR_LABEL="aio: preview" ARG AIO_TRUSTED_PR_LABEL="aio: preview"
ARG TEST_AIO_TRUSTED_PR_LABEL="aio: preview" ARG TEST_AIO_TRUSTED_PR_LABEL="aio: preview"
ARG AIO_UPLOAD_HOSTNAME=upload.localhost ARG AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost
ARG TEST_AIO_UPLOAD_HOSTNAME=upload.localhost ARG TEST_AIO_PREVIEW_SERVER_HOSTNAME=preview.localhost
ARG AIO_UPLOAD_MAX_SIZE=20971520 ARG AIO_ARTIFACT_MAX_SIZE=20971520
ARG TEST_AIO_UPLOAD_MAX_SIZE=200 ARG TEST_AIO_ARTIFACT_MAX_SIZE=200
ARG AIO_UPLOAD_PORT=3000 ARG AIO_PREVIEW_SERVER_PORT=3000
ARG TEST_AIO_UPLOAD_PORT=3001 ARG TEST_AIO_PREVIEW_SERVER_PORT=3001
ENV AIO_ARTIFACT_PATH=$AIO_ARTIFACT_PATH TEST_AIO_ARTIFACT_PATH=$TEST_AIO_ARTIFACT_PATH \ ENV AIO_ARTIFACT_PATH=$AIO_ARTIFACT_PATH TEST_AIO_ARTIFACT_PATH=$TEST_AIO_ARTIFACT_PATH \
AIO_BUILDS_DIR=$AIO_BUILDS_DIR TEST_AIO_BUILDS_DIR=$TEST_AIO_BUILDS_DIR \ AIO_BUILDS_DIR=$AIO_BUILDS_DIR TEST_AIO_BUILDS_DIR=$TEST_AIO_BUILDS_DIR \
@ -60,9 +60,9 @@ ENV AIO_ARTIFACT_PATH=$AIO_ARTIFACT_PATH TEST_AIO_ARTIF
AIO_SCRIPTS_SH_DIR=/usr/share/aio-scripts-sh \ AIO_SCRIPTS_SH_DIR=/usr/share/aio-scripts-sh \
AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN TEST_AIO_SIGNIFICANT_FILES_PATTERN=$TEST_AIO_SIGNIFICANT_FILES_PATTERN \ AIO_SIGNIFICANT_FILES_PATTERN=$AIO_SIGNIFICANT_FILES_PATTERN TEST_AIO_SIGNIFICANT_FILES_PATTERN=$TEST_AIO_SIGNIFICANT_FILES_PATTERN \
AIO_TRUSTED_PR_LABEL=$AIO_TRUSTED_PR_LABEL TEST_AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL \ AIO_TRUSTED_PR_LABEL=$AIO_TRUSTED_PR_LABEL TEST_AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL \
AIO_UPLOAD_HOSTNAME=$AIO_UPLOAD_HOSTNAME TEST_AIO_UPLOAD_HOSTNAME=$TEST_AIO_UPLOAD_HOSTNAME \ AIO_PREVIEW_SERVER_HOSTNAME=$AIO_PREVIEW_SERVER_HOSTNAME TEST_AIO_PREVIEW_SERVER_HOSTNAME=$TEST_AIO_PREVIEW_SERVER_HOSTNAME \
AIO_UPLOAD_MAX_SIZE=$AIO_UPLOAD_MAX_SIZE TEST_AIO_UPLOAD_MAX_SIZE=$TEST_AIO_UPLOAD_MAX_SIZE \ AIO_ARTIFACT_MAX_SIZE=$AIO_ARTIFACT_MAX_SIZE TEST_AIO_ARTIFACT_MAX_SIZE=$TEST_AIO_ARTIFACT_MAX_SIZE \
AIO_UPLOAD_PORT=$AIO_UPLOAD_PORT TEST_AIO_UPLOAD_PORT=$TEST_AIO_UPLOAD_PORT \ AIO_PREVIEW_SERVER_PORT=$AIO_PREVIEW_SERVER_PORT TEST_AIO_PREVIEW_SERVER_PORT=$TEST_AIO_PREVIEW_SERVER_PORT \
AIO_WWW_USER=www-data \ AIO_WWW_USER=www-data \
NODE_ENV=production NODE_ENV=production
@ -108,9 +108,9 @@ RUN printenv | grep AIO_ >> /etc/environment
# Set up dnsmasq # Set up dnsmasq
COPY dnsmasq/dnsmasq.conf /etc/ COPY dnsmasq/dnsmasq.conf /etc/
RUN sed -i "s|{{\$AIO_NGINX_HOSTNAME}}|$AIO_NGINX_HOSTNAME|g" /etc/dnsmasq.conf RUN sed -i "s|{{\$AIO_NGINX_HOSTNAME}}|$AIO_NGINX_HOSTNAME|g" /etc/dnsmasq.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$AIO_UPLOAD_HOSTNAME|g" /etc/dnsmasq.conf RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_HOSTNAME}}|$AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/dnsmasq.conf
RUN sed -i "s|{{\$TEST_AIO_NGINX_HOSTNAME}}|$TEST_AIO_NGINX_HOSTNAME|g" /etc/dnsmasq.conf RUN sed -i "s|{{\$TEST_AIO_NGINX_HOSTNAME}}|$TEST_AIO_NGINX_HOSTNAME|g" /etc/dnsmasq.conf
RUN sed -i "s|{{\$TEST_AIO_UPLOAD_HOSTNAME}}|$TEST_AIO_UPLOAD_HOSTNAME|g" /etc/dnsmasq.conf RUN sed -i "s|{{\$TEST_AIO_PREVIEW_SERVER_HOSTNAME}}|$TEST_AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/dnsmasq.conf
# Set up SSL/TLS certificates # Set up SSL/TLS certificates
@ -134,9 +134,9 @@ RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$AIO_LOCALCERTS_DIR|g" /etc/nginx/conf.d/
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$AIO_UPLOAD_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_HOSTNAME}}|$AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_ARTIFACT_MAX_SIZE}}|$AIO_ARTIFACT_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-prod.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$AIO_UPLOAD_PORT|g" /etc/nginx/conf.d/aio-builds-prod.conf RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_PORT}}|$AIO_PREVIEW_SERVER_PORT|g" /etc/nginx/conf.d/aio-builds-prod.conf
COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-test.conf COPY nginx/aio-builds.conf /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_BUILDS_DIR}}|$TEST_AIO_BUILDS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf
@ -145,9 +145,9 @@ RUN sed -i "s|{{\$AIO_LOCALCERTS_DIR}}|$TEST_AIO_LOCALCERTS_DIR|g" /etc/nginx/co
RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_NGINX_LOGS_DIR}}|$TEST_AIO_NGINX_LOGS_DIR|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTP}}|$TEST_AIO_NGINX_PORT_HTTP|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_NGINX_PORT_HTTPS}}|$TEST_AIO_NGINX_PORT_HTTPS|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_HOSTNAME}}|$TEST_AIO_UPLOAD_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_HOSTNAME}}|$TEST_AIO_PREVIEW_SERVER_HOSTNAME|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_MAX_SIZE}}|$TEST_AIO_UPLOAD_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_ARTIFACT_MAX_SIZE}}|$TEST_AIO_ARTIFACT_MAX_SIZE|g" /etc/nginx/conf.d/aio-builds-test.conf
RUN sed -i "s|{{\$AIO_UPLOAD_PORT}}|$TEST_AIO_UPLOAD_PORT|g" /etc/nginx/conf.d/aio-builds-test.conf RUN sed -i "s|{{\$AIO_PREVIEW_SERVER_PORT}}|$TEST_AIO_PREVIEW_SERVER_PORT|g" /etc/nginx/conf.d/aio-builds-test.conf
# Set up pm2 # Set up pm2

View File

@ -8,9 +8,9 @@ listen-address=127.0.0.1
# Force an IP address for these domains. # Force an IP address for these domains.
address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1 address=/{{$AIO_NGINX_HOSTNAME}}/127.0.0.1
address=/{{$AIO_UPLOAD_HOSTNAME}}/127.0.0.1 address=/{{$AIO_PREVIEW_SERVER_HOSTNAME}}/127.0.0.1
address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1 address=/{{$TEST_AIO_NGINX_HOSTNAME}}/127.0.0.1
address=/{{$TEST_AIO_UPLOAD_HOSTNAME}}/127.0.0.1 address=/{{$TEST_AIO_PREVIEW_SERVER_HOSTNAME}}/127.0.0.1
# Run as root (required from inside docker container). # Run as root (required from inside docker container).
user=root user=root

View File

@ -1,4 +1,4 @@
/var/log/aio/upload-server-*.log { /var/log/aio/preview-server-*.log {
compress compress
copytruncate copytruncate
delaycompress delaycompress

View File

@ -76,7 +76,7 @@ server {
proxy_pass_request_headers on; proxy_pass_request_headers on;
proxy_redirect off; proxy_redirect off;
proxy_method POST; proxy_method POST;
proxy_pass http://{{$AIO_UPLOAD_HOSTNAME}}:{{$AIO_UPLOAD_PORT}}$request_uri; proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri;
resolver 127.0.0.1; resolver 127.0.0.1;
} }
@ -91,7 +91,7 @@ server {
proxy_pass_request_headers on; proxy_pass_request_headers on;
proxy_redirect off; proxy_redirect off;
proxy_method POST; proxy_method POST;
proxy_pass http://{{$AIO_UPLOAD_HOSTNAME}}:{{$AIO_UPLOAD_PORT}}$request_uri; proxy_pass http://{{$AIO_PREVIEW_SERVER_HOSTNAME}}:{{$AIO_PREVIEW_SERVER_PORT}}$request_uri;
resolver 127.0.0.1; resolver 127.0.0.1;
} }

View File

@ -13,7 +13,7 @@ export const AIO_NGINX_PORT_HTTP = +getEnvVar('AIO_NGINX_PORT_HTTP');
export const AIO_NGINX_PORT_HTTPS = +getEnvVar('AIO_NGINX_PORT_HTTPS'); export const AIO_NGINX_PORT_HTTPS = +getEnvVar('AIO_NGINX_PORT_HTTPS');
export const AIO_SIGNIFICANT_FILES_PATTERN = getEnvVar('AIO_SIGNIFICANT_FILES_PATTERN'); export const AIO_SIGNIFICANT_FILES_PATTERN = getEnvVar('AIO_SIGNIFICANT_FILES_PATTERN');
export const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL'); export const AIO_TRUSTED_PR_LABEL = getEnvVar('AIO_TRUSTED_PR_LABEL');
export const AIO_UPLOAD_HOSTNAME = getEnvVar('AIO_UPLOAD_HOSTNAME'); export const AIO_PREVIEW_SERVER_HOSTNAME = getEnvVar('AIO_PREVIEW_SERVER_HOSTNAME');
export const AIO_UPLOAD_PORT = +getEnvVar('AIO_UPLOAD_PORT'); export const AIO_PREVIEW_SERVER_PORT = +getEnvVar('AIO_PREVIEW_SERVER_PORT');
export const AIO_UPLOAD_MAX_SIZE = +getEnvVar('AIO_UPLOAD_MAX_SIZE'); export const AIO_ARTIFACT_MAX_SIZE = +getEnvVar('AIO_ARTIFACT_MAX_SIZE');
export const AIO_WWW_USER = getEnvVar('AIO_WWW_USER'); export const AIO_WWW_USER = getEnvVar('AIO_WWW_USER');

View File

@ -1,5 +1,5 @@
import {assert, assertNotMissingOrEmpty} from '../common/utils';
import {GithubApi} from './github-api'; import {GithubApi} from './github-api';
import {assert, assertNotMissingOrEmpty} from './utils';
export interface PullRequest { export interface PullRequest {
number: number; number: number;

View File

@ -1,5 +1,5 @@
import {assertNotMissingOrEmpty} from '../common/utils';
import {GithubApi} from './github-api'; import {GithubApi} from './github-api';
import {assertNotMissingOrEmpty} from './utils';
export interface Team { export interface Team {
id: number; id: number;

View File

@ -7,7 +7,7 @@ import * as shell from 'shelljs';
import {HIDDEN_DIR_PREFIX} from '../common/constants'; import {HIDDEN_DIR_PREFIX} from '../common/constants';
import {assertNotMissingOrEmpty, computeShortSha, createLogger} from '../common/utils'; import {assertNotMissingOrEmpty, computeShortSha, createLogger} from '../common/utils';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events'; import {ChangedPrVisibilityEvent, CreatedBuildEvent} from './build-events';
import {UploadError} from './upload-error'; import {PreviewServerError} from './preview-error';
// Classes // Classes
export class BuildCreator extends EventEmitter { export class BuildCreator extends EventEmitter {
@ -36,7 +36,7 @@ export class BuildCreator extends EventEmitter {
then(([prDirExisted, shaDirExisted]) => { then(([prDirExisted, shaDirExisted]) => {
if (shaDirExisted) { if (shaDirExisted) {
const publicOrNot = isPublic ? 'public' : 'non-public'; const publicOrNot = isPublic ? 'public' : 'non-public';
throw new UploadError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); throw new PreviewServerError(409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
} }
dirToRemoveOnError = prDirExisted ? shaDir : prDir; dirToRemoveOnError = prDirExisted ? shaDir : prDir;
@ -52,8 +52,8 @@ export class BuildCreator extends EventEmitter {
shell.rm('-rf', dirToRemoveOnError); shell.rm('-rf', dirToRemoveOnError);
} }
if (!(err instanceof UploadError)) { if (!(err instanceof PreviewServerError)) {
err = new UploadError(500, `Error while uploading to directory: ${shaDir}\n${err}`); err = new PreviewServerError(500, `Error while creating preview at: ${shaDir}\n${err}`);
} }
throw err; throw err;
@ -71,7 +71,8 @@ export class BuildCreator extends EventEmitter {
return false; return false;
} else if (targetVisPrDirExisted) { } else if (targetVisPrDirExisted) {
// Error: Directories for both visibilities exist. // Error: Directories for both visibilities exist.
throw new UploadError(409, `Request to move '${otherVisPrDir}' to existing directory '${targetVisPrDir}'.`); throw new PreviewServerError(409,
`Request to move '${otherVisPrDir}' to existing directory '${targetVisPrDir}'.`);
} }
// Visibility change: Moving `otherVisPrDir` to `targetVisPrDir`. // Visibility change: Moving `otherVisPrDir` to `targetVisPrDir`.
@ -82,8 +83,8 @@ export class BuildCreator extends EventEmitter {
then(() => true); then(() => true);
}). }).
catch(err => { catch(err => {
if (!(err instanceof UploadError)) { if (!(err instanceof PreviewServerError)) {
err = new UploadError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`); err = new PreviewServerError(500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\n${err}`);
} }
throw err; throw err;

View File

@ -5,7 +5,7 @@ import {mkdir} from 'shelljs';
import {promisify} from 'util'; import {promisify} from 'util';
import {CircleCiApi} from '../common/circle-ci-api'; import {CircleCiApi} from '../common/circle-ci-api';
import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, createLogger} from '../common/utils'; import {assert, assertNotMissingOrEmpty, computeArtifactDownloadPath, createLogger} from '../common/utils';
import {UploadError} from '../upload-server/upload-error'; import {PreviewServerError} from './preview-error';
export interface GithubInfo { export interface GithubInfo {
org: string; org: string;
@ -58,7 +58,7 @@ export class BuildRetriever {
const url = await this.api.getBuildArtifactUrl(buildNum, artifactPath); const url = await this.api.getBuildArtifactUrl(buildNum, artifactPath);
const response = await fetch(url, {size: this.downloadSizeLimit}); const response = await fetch(url, {size: this.downloadSizeLimit});
if (response.status !== 200) { if (response.status !== 200) {
throw new UploadError(response.status, `Error ${response.status} - ${response.statusText}`); throw new PreviewServerError(response.status, `Error ${response.status} - ${response.statusText}`);
} }
const buffer = await response.buffer(); const buffer = await response.buffer();
mkdir('-p', dirname(outPath)); mkdir('-p', dirname(outPath));
@ -68,7 +68,7 @@ export class BuildRetriever {
} catch (error) { } catch (error) {
this.logger.warn(error); this.logger.warn(error);
const status = (error.type === 'max-size') ? 413 : 500; const status = (error.type === 'max-size') ? 413 : 500;
throw new UploadError(status, `CircleCI artifact download failed (${error.message || error})`); throw new PreviewServerError(status, `CircleCI artifact download failed (${error.message || error})`);
} }
} }
} }

View File

@ -1,6 +1,7 @@
// Imports // Imports
import {AIO_DOWNLOADS_DIR} from '../common/constants'; import {AIO_DOWNLOADS_DIR} from '../common/constants';
import { import {
AIO_ARTIFACT_MAX_SIZE,
AIO_ARTIFACT_PATH, AIO_ARTIFACT_PATH,
AIO_BUILDS_DIR, AIO_BUILDS_DIR,
AIO_CIRCLE_CI_TOKEN, AIO_CIRCLE_CI_TOKEN,
@ -9,26 +10,25 @@ import {
AIO_GITHUB_REPO, AIO_GITHUB_REPO,
AIO_GITHUB_TEAM_SLUGS, AIO_GITHUB_TEAM_SLUGS,
AIO_GITHUB_TOKEN, AIO_GITHUB_TOKEN,
AIO_PREVIEW_SERVER_HOSTNAME,
AIO_PREVIEW_SERVER_PORT,
AIO_SIGNIFICANT_FILES_PATTERN, AIO_SIGNIFICANT_FILES_PATTERN,
AIO_TRUSTED_PR_LABEL, AIO_TRUSTED_PR_LABEL,
AIO_UPLOAD_HOSTNAME,
AIO_UPLOAD_MAX_SIZE,
AIO_UPLOAD_PORT,
} from '../common/env-variables'; } from '../common/env-variables';
import {UploadServerFactory} from './upload-server-factory'; import {PreviewServerFactory} from './preview-server-factory';
// Run // Run
_main(); _main();
// Functions // Functions
function _main(): void { function _main(): void {
UploadServerFactory PreviewServerFactory
.create({ .create({
buildArtifactPath: AIO_ARTIFACT_PATH, buildArtifactPath: AIO_ARTIFACT_PATH,
buildsDir: AIO_BUILDS_DIR, buildsDir: AIO_BUILDS_DIR,
circleCiToken: AIO_CIRCLE_CI_TOKEN, circleCiToken: AIO_CIRCLE_CI_TOKEN,
domainName: AIO_DOMAIN_NAME, domainName: AIO_DOMAIN_NAME,
downloadSizeLimit: AIO_UPLOAD_MAX_SIZE, downloadSizeLimit: AIO_ARTIFACT_MAX_SIZE,
downloadsDir: AIO_DOWNLOADS_DIR, downloadsDir: AIO_DOWNLOADS_DIR,
githubOrg: AIO_GITHUB_ORGANIZATION, githubOrg: AIO_GITHUB_ORGANIZATION,
githubRepo: AIO_GITHUB_REPO, githubRepo: AIO_GITHUB_REPO,
@ -37,5 +37,5 @@ function _main(): void {
significantFilesPattern: AIO_SIGNIFICANT_FILES_PATTERN, significantFilesPattern: AIO_SIGNIFICANT_FILES_PATTERN,
trustedPrLabel: AIO_TRUSTED_PR_LABEL, trustedPrLabel: AIO_TRUSTED_PR_LABEL,
}) })
.listen(AIO_UPLOAD_PORT, AIO_UPLOAD_HOSTNAME); .listen(AIO_PREVIEW_SERVER_PORT, AIO_PREVIEW_SERVER_HOSTNAME);
} }

View File

@ -1,8 +1,8 @@
// Classes // Classes
export class UploadError extends Error { export class PreviewServerError extends Error {
// Constructor // Constructor
constructor(public status: number = 500, message?: string) { constructor(public status: number = 500, message?: string) {
super(message); super(message);
Object.setPrototypeOf(this, UploadError.prototype); Object.setPrototypeOf(this, PreviewServerError.prototype);
} }
} }

View File

@ -16,7 +16,7 @@ import {respondWithError, throwRequestError} from './utils';
const AIO_PREVIEW_JOB = 'aio_preview'; const AIO_PREVIEW_JOB = 'aio_preview';
// Interfaces - Types // Interfaces - Types
export interface UploadServerConfig { export interface PreviewServerConfig {
downloadsDir: string; downloadsDir: string;
downloadSizeLimit: number; downloadSizeLimit: number;
buildArtifactPath: string; buildArtifactPath: string;
@ -31,12 +31,12 @@ export interface UploadServerConfig {
trustedPrLabel: string; trustedPrLabel: string;
} }
const logger = createLogger('UploadServer'); const logger = createLogger('PreviewServer');
// Classes // Classes
export class UploadServerFactory { export class PreviewServerFactory {
// Methods - Public // Methods - Public
public static create(cfg: UploadServerConfig): http.Server { public static create(cfg: PreviewServerConfig): http.Server {
assertNotMissingOrEmpty('domainName', cfg.domainName); assertNotMissingOrEmpty('domainName', cfg.domainName);
const circleCiApi = new CircleCiApi(cfg.githubOrg, cfg.githubRepo, cfg.circleCiToken); const circleCiApi = new CircleCiApi(cfg.githubOrg, cfg.githubRepo, cfg.circleCiToken);
@ -46,9 +46,9 @@ export class UploadServerFactory {
const buildRetriever = new BuildRetriever(circleCiApi, cfg.downloadSizeLimit, cfg.downloadsDir); const buildRetriever = new BuildRetriever(circleCiApi, cfg.downloadSizeLimit, cfg.downloadsDir);
const buildVerifier = new BuildVerifier(prs, teams, cfg.githubTeamSlugs, cfg.trustedPrLabel); const buildVerifier = new BuildVerifier(prs, teams, cfg.githubTeamSlugs, cfg.trustedPrLabel);
const buildCreator = UploadServerFactory.createBuildCreator(prs, cfg.buildsDir, cfg.domainName); const buildCreator = PreviewServerFactory.createBuildCreator(prs, cfg.buildsDir, cfg.domainName);
const middleware = UploadServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator, cfg); const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator, cfg);
const httpServer = http.createServer(middleware as any); const httpServer = http.createServer(middleware as any);
httpServer.on('listening', () => { httpServer.on('listening', () => {
@ -60,7 +60,7 @@ export class UploadServerFactory {
} }
public static createMiddleware(buildRetriever: BuildRetriever, buildVerifier: BuildVerifier, public static createMiddleware(buildRetriever: BuildRetriever, buildVerifier: BuildVerifier,
buildCreator: BuildCreator, cfg: UploadServerConfig): express.Express { buildCreator: BuildCreator, cfg: PreviewServerConfig): express.Express {
const middleware = express(); const middleware = express();
const jsonParser = bodyParser.json(); const jsonParser = bodyParser.json();
@ -149,7 +149,7 @@ export class UploadServerFactory {
middleware.all('*', req => throwRequestError(404, 'Unknown resource', req)); middleware.all('*', req => throwRequestError(404, 'Unknown resource', req));
middleware.use((err: any, _req: any, res: express.Response, _next: any) => { middleware.use((err: any, _req: any, res: express.Response, _next: any) => {
const statusText = http.STATUS_CODES[err.status] || '???'; const statusText = http.STATUS_CODES[err.status] || '???';
logger.error(`Upload error: ${err.status} - ${statusText}:`, err.message); logger.error(`Preview server error: ${err.status} - ${statusText}:`, err.message);
respondWithError(res, err); respondWithError(res, err);
}); });

View File

@ -1,6 +1,6 @@
import * as express from 'express'; import * as express from 'express';
import {promisify} from 'util'; import {promisify} from 'util';
import {UploadError} from './upload-error'; import {PreviewServerError} from './preview-error';
/** /**
* Update the response to report that an error has occurred. * Update the response to report that an error has occurred.
@ -8,8 +8,8 @@ import {UploadError} from './upload-error';
* @param err The error that needs to be reported. * @param err The error that needs to be reported.
*/ */
export async function respondWithError(res: express.Response, err: any): Promise<void> { export async function respondWithError(res: express.Response, err: any): Promise<void> {
if (!(err instanceof UploadError)) { if (!(err instanceof PreviewServerError)) {
err = new UploadError(500, String((err && err.message) || err)); err = new PreviewServerError(500, String((err && err.message) || err));
} }
res.status(err.status); res.status(err.status);
@ -25,5 +25,5 @@ export async function respondWithError(res: express.Response, err: any): Promise
export function throwRequestError(status: number, error: string, req: express.Request): never { export function throwRequestError(status: number, error: string, req: express.Request): never {
const message = `${error} in request: ${req.method} ${req.originalUrl}` + const message = `${error} in request: ${req.method} ${req.originalUrl}` +
(!req.body ? '' : ` ${JSON.stringify(req.body)}`); (!req.body ? '' : ` ${JSON.stringify(req.body)}`);
throw new UploadError(status, message); throw new PreviewServerError(status, message);
} }

View File

@ -6,9 +6,9 @@ import {createLogger, getEnvVar} from '../common/utils';
import {BuildNums, PrNums, SHA} from './constants'; import {BuildNums, PrNums, SHA} from './constants';
// We are using the `nock` library to fake responses from REST requests, when testing. // We are using the `nock` library to fake responses from REST requests, when testing.
// This is necessary, because the test upload-server runs as a separate node process to // This is necessary, because the test preview-server runs as a separate node process to
// the test harness, so we do not have direct access to the code (e.g. for mocking). // the test harness, so we do not have direct access to the code (e.g. for mocking).
// (See also 'lib/verify-setup/start-test-upload-server.ts'.) // (See also 'lib/verify-setup/start-test-preview-server.ts'.)
// Each of the potential requests to an external API (e.g. Github or CircleCI) are mocked // Each of the potential requests to an external API (e.g. Github or CircleCI) are mocked
// below and return a suitable response. This is quite complicated to setup since the // below and return a suitable response. This is quite complicated to setup since the

View File

@ -266,7 +266,7 @@ describe(`nginx`, () => {
}); });
it('should pass requests through to the upload server', done => { it('should pass requests through to the preview server', done => {
h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`). h.runCmd(`curl -iLX POST ${scheme}://${host}/circle-build`).
then(h.verifyResponse(400, /Incorrect body content. Expected JSON/)). then(h.verifyResponse(400, /Incorrect body content. Expected JSON/)).
then(done); then(done);
@ -304,7 +304,7 @@ describe(`nginx`, () => {
}); });
it('should pass requests through to the upload server', done => { it('should pass requests through to the preview server', done => {
const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`; const cmdPrefix = `curl -iLX POST --header "Content-Type: application/json"`;
const cmd1 = `${cmdPrefix} ${url}`; const cmd1 = `${cmdPrefix} ${url}`;

View File

@ -1,16 +1,16 @@
// Imports // Imports
import * as fs from 'fs'; import * as fs from 'fs';
import {join} from 'path'; import {join} from 'path';
import {AIO_UPLOAD_HOSTNAME, AIO_UPLOAD_PORT, AIO_WWW_USER} from '../common/env-variables'; import {AIO_PREVIEW_SERVER_HOSTNAME, AIO_PREVIEW_SERVER_PORT, AIO_WWW_USER} from '../common/env-variables';
import {computeShortSha} from '../common/utils'; import {computeShortSha} from '../common/utils';
import {ALT_SHA, BuildNums, PrNums, SHA, SIMILAR_SHA} from './constants'; import {ALT_SHA, BuildNums, PrNums, SHA, SIMILAR_SHA} from './constants';
import {helper as h, makeCurl, payload} from './helper'; import {helper as h, makeCurl, payload} from './helper';
import {customMatchers} from './jasmine-custom-matchers'; import {customMatchers} from './jasmine-custom-matchers';
// Tests // Tests
describe('upload-server', () => { describe('preview-server', () => {
const hostname = AIO_UPLOAD_HOSTNAME; const hostname = AIO_PREVIEW_SERVER_HOSTNAME;
const port = AIO_UPLOAD_PORT; const port = AIO_PREVIEW_SERVER_PORT;
const host = `http://${hostname}:${port}`; const host = `http://${hostname}:${port}`;
beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000); beforeEach(() => jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000);
@ -122,7 +122,7 @@ describe('upload-server', () => {
describe(`for ${label} builds`, () => { describe(`for ${label} builds`, () => {
it('should extract the contents of the uploaded file', async () => { it('should extract the contents of the build artifact', async () => {
await curl(payload(build)) await curl(payload(build))
.then(h.verifyResponse(statusCode)); .then(h.verifyResponse(statusCode));
expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic)) expect(h.readBuildFile(prNum, SHA, 'index.html', isPublic))
@ -148,7 +148,7 @@ describe('upload-server', () => {
expect({ prNum, isPublic }).toExistAsABuild(); expect({ prNum, isPublic }).toExistAsABuild();
}); });
it('should delete the uploaded file', async () => { it('should delete the build artifact file', async () => {
await curl(payload(build)) await curl(payload(build))
.then(h.verifyResponse(statusCode)); .then(h.verifyResponse(statusCode));
expect({ prNum, SHA }).not.toExistAsAnArtifact(); expect({ prNum, SHA }).not.toExistAsAnArtifact();

View File

@ -25,7 +25,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
describe('for a new/non-existing PR', () => { describe('for a new/non-existing PR', () => {
it('should be able to upload and serve a public build', async () => { it('should be able to create and serve a public preview', async () => {
const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER;
const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER;
@ -44,7 +44,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
}); });
it('should be able to upload but not serve a hidden build', async () => { it('should be able to create but not serve a hidden preview', async () => {
const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED; const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED;
const PR = PrNums.TRUST_CHECK_UNTRUSTED; const PR = PrNums.TRUST_CHECK_UNTRUSTED;
@ -59,7 +59,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
}); });
it('should reject an upload if verification fails', async () => { it('should reject if verification fails', async () => {
const BUILD = BuildNums.TRUST_CHECK_ERROR; const BUILD = BuildNums.TRUST_CHECK_ERROR;
const PR = PrNums.TRUST_CHECK_ERROR; const PR = PrNums.TRUST_CHECK_ERROR;
@ -82,7 +82,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
describe('for an existing PR', () => { describe('for an existing PR', () => {
it('should be able to upload and serve a public build', async () => { it('should be able to create and serve a public preview', async () => {
const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER;
const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER;
@ -108,7 +108,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
}); });
it('should be able to upload but not serve a hidden build', async () => { it('should be able to create but not serve a hidden preview', async () => {
const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED; const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED;
const PR = PrNums.TRUST_CHECK_UNTRUSTED; const PR = PrNums.TRUST_CHECK_UNTRUSTED;
@ -129,7 +129,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
}); });
it('should reject an upload if verification fails', async () => { it('should reject if verification fails', async () => {
const BUILD = BuildNums.TRUST_CHECK_ERROR; const BUILD = BuildNums.TRUST_CHECK_ERROR;
const PR = PrNums.TRUST_CHECK_ERROR; const PR = PrNums.TRUST_CHECK_ERROR;
@ -144,7 +144,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
}); });
it('should not be able to overwrite an existing public build', async () => { it('should not be able to overwrite an existing public preview', async () => {
const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; const BUILD = BuildNums.TRUST_CHECK_ACTIVE_TRUSTED_USER;
const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER; const PR = PrNums.TRUST_CHECK_ACTIVE_TRUSTED_USER;
@ -165,7 +165,7 @@ h.runForAllSupportedSchemes((scheme, port) => describe(`integration (on ${scheme
}); });
it('should not be able to overwrite an existing hidden build', async () => { it('should not be able to overwrite an existing hidden preview', async () => {
const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED; const BUILD = BuildNums.TRUST_CHECK_UNTRUSTED;
const PR = PrNums.TRUST_CHECK_UNTRUSTED; const PR = PrNums.TRUST_CHECK_UNTRUSTED;
h.createDummyBuild(PR, SHA, false); h.createDummyBuild(PR, SHA, false);

View File

@ -1,2 +1,2 @@
import '../upload-server'; import '../preview-server';
import './mock-external-apis'; import './mock-external-apis';

View File

@ -5,10 +5,10 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as shell from 'shelljs'; import * as shell from 'shelljs';
import {SHORT_SHA_LEN} from '../../lib/common/constants'; import {SHORT_SHA_LEN} from '../../lib/common/constants';
import {BuildCreator} from '../../lib/upload-server/build-creator'; import {BuildCreator} from '../../lib/preview-server/build-creator';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events'; import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
import {UploadError} from '../../lib/upload-server/upload-error'; import {PreviewServerError} from '../../lib/preview-server/preview-error';
import {expectToBeUploadError} from './helpers'; import {expectToBePreviewServerError} from './helpers';
// Tests // Tests
describe('BuildCreator', () => { describe('BuildCreator', () => {
@ -134,7 +134,7 @@ describe('BuildCreator', () => {
it('should abort and skip further operations if changing the PR\'s visibility fails', done => { it('should abort and skip further operations if changing the PR\'s visibility fails', done => {
const mockError = new UploadError(543, 'Test'); const mockError = new PreviewServerError(543, 'Test');
bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject(mockError)); bcUpdatePrVisibilitySpy.and.callFake(() => Promise.reject(mockError));
bc.create(pr, sha, archive, isPublic).catch(err => { bc.create(pr, sha, archive, isPublic).catch(err => {
@ -154,7 +154,7 @@ describe('BuildCreator', () => {
existsValues[shaDir] = true; existsValues[shaDir] = true;
bc.create(pr, sha, archive, isPublic).catch(err => { bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public'; const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); expectToBePreviewServerError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
@ -171,7 +171,7 @@ describe('BuildCreator', () => {
bc.create(pr, sha, archive, isPublic).catch(err => { bc.create(pr, sha, archive, isPublic).catch(err => {
const publicOrNot = isPublic ? 'public' : 'non-public'; const publicOrNot = isPublic ? 'public' : 'non-public';
expectToBeUploadError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`); expectToBePreviewServerError(err, 409, `Request to overwrite existing ${publicOrNot} directory: ${shaDir}`);
expect(shellMkdirSpy).not.toHaveBeenCalled(); expect(shellMkdirSpy).not.toHaveBeenCalled();
expect(bcExtractArchiveSpy).not.toHaveBeenCalled(); expect(bcExtractArchiveSpy).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
@ -222,20 +222,20 @@ describe('BuildCreator', () => {
}); });
it('should reject with an UploadError', done => { it('should reject with an PreviewServerError', done => {
// tslint:disable-next-line: no-string-throw // tslint:disable-next-line: no-string-throw
shellMkdirSpy.and.callFake(() => { throw 'Test'; }); shellMkdirSpy.and.callFake(() => { throw 'Test'; });
bc.create(pr, sha, archive, isPublic).catch(err => { bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBeUploadError(err, 500, `Error while uploading to directory: ${shaDir}\nTest`); expectToBePreviewServerError(err, 500, `Error while creating preview at: ${shaDir}\nTest`);
done(); done();
}); });
}); });
it('should pass UploadError instances unmodified', done => { it('should pass PreviewServerError instances unmodified', done => {
shellMkdirSpy.and.callFake(() => { throw new UploadError(543, 'Test'); }); shellMkdirSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
bc.create(pr, sha, archive, isPublic).catch(err => { bc.create(pr, sha, archive, isPublic).catch(err => {
expectToBeUploadError(err, 543, 'Test'); expectToBePreviewServerError(err, 543, 'Test');
done(); done();
}); });
}); });
@ -376,7 +376,8 @@ describe('BuildCreator', () => {
it('should abort and skip further operations if both directories exist', done => { it('should abort and skip further operations if both directories exist', done => {
bcExistsSpy.and.returnValue(true); bcExistsSpy.and.returnValue(true);
bc.updatePrVisibility(pr, makePublic).catch(err => { bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 409, `Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`); expectToBePreviewServerError(err, 409,
`Request to move '${oldPrDir}' to existing directory '${newPrDir}'.`);
expect(shellMvSpy).not.toHaveBeenCalled(); expect(shellMvSpy).not.toHaveBeenCalled();
expect(bcListShasByDate).not.toHaveBeenCalled(); expect(bcListShasByDate).not.toHaveBeenCalled();
expect(bcEmitSpy).not.toHaveBeenCalled(); expect(bcEmitSpy).not.toHaveBeenCalled();
@ -407,20 +408,21 @@ describe('BuildCreator', () => {
}); });
it('should reject with an UploadError', done => { it('should reject with an PreviewServerError', done => {
// tslint:disable-next-line: no-string-throw // tslint:disable-next-line: no-string-throw
shellMvSpy.and.callFake(() => { throw 'Test'; }); shellMvSpy.and.callFake(() => { throw 'Test'; });
bc.updatePrVisibility(pr, makePublic).catch(err => { bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 500, `Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`); expectToBePreviewServerError(err, 500,
`Error while making PR ${pr} ${makePublic ? 'public' : 'hidden'}.\nTest`);
done(); done();
}); });
}); });
it('should pass UploadError instances unmodified', done => { it('should pass PreviewServerError instances unmodified', done => {
shellMvSpy.and.callFake(() => { throw new UploadError(543, 'Test'); }); shellMvSpy.and.callFake(() => { throw new PreviewServerError(543, 'Test'); });
bc.updatePrVisibility(pr, makePublic).catch(err => { bc.updatePrVisibility(pr, makePublic).catch(err => {
expectToBeUploadError(err, 543, 'Test'); expectToBePreviewServerError(err, 543, 'Test');
done(); done();
}); });
}); });
@ -528,7 +530,7 @@ describe('BuildCreator', () => {
}); });
it('should delete the uploaded file on success', done => { it('should delete the build artifact file on success', done => {
(bc as any).extractArchive('input/file', 'output/dir'). (bc as any).extractArchive('input/file', 'output/dir').
then(() => expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file')). then(() => expect(shellRmSpy).toHaveBeenCalledWith('-f', 'input/file')).
then(done); then(done);
@ -568,7 +570,7 @@ describe('BuildCreator', () => {
}); });
it('should abort and reject if it fails to remove the uploaded file', done => { it('should abort and reject if it fails to remove the build artifact file', done => {
(bc as any).extractArchive('foo', 'bar').catch((err: any) => { (bc as any).extractArchive('foo', 'bar').catch((err: any) => {
expect(shellChmodSpy).toHaveBeenCalled(); expect(shellChmodSpy).toHaveBeenCalled();
expect(shellRmSpy).toHaveBeenCalled(); expect(shellRmSpy).toHaveBeenCalled();

View File

@ -1,5 +1,5 @@
// Imports // Imports
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events'; import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
// Tests // Tests
describe('ChangedPrVisibilityEvent', () => { describe('ChangedPrVisibilityEvent', () => {

View File

@ -1,7 +1,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as nock from 'nock'; import * as nock from 'nock';
import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api'; import {BuildInfo, CircleCiApi} from '../../lib/common/circle-ci-api';
import {BuildRetriever} from '../../lib/upload-server/build-retriever'; import {BuildRetriever} from '../../lib/preview-server/build-retriever';
describe('BuildRetriever', () => { describe('BuildRetriever', () => {
const MAX_DOWNLOAD_SIZE = 10000; const MAX_DOWNLOAD_SIZE = 10000;

View File

@ -2,7 +2,7 @@
import {GithubApi} from '../../lib/common/github-api'; import {GithubApi} from '../../lib/common/github-api';
import {GithubPullRequests, PullRequest} from '../../lib/common/github-pull-requests'; import {GithubPullRequests, PullRequest} from '../../lib/common/github-pull-requests';
import {GithubTeams} from '../../lib/common/github-teams'; import {GithubTeams} from '../../lib/common/github-teams';
import {BuildVerifier} from '../../lib/upload-server/build-verifier'; import {BuildVerifier} from '../../lib/preview-server/build-verifier';
// Tests // Tests
describe('BuildVerifier', () => { describe('BuildVerifier', () => {

View File

@ -0,0 +1,11 @@
import {PreviewServerError} from '../../lib/preview-server/preview-error';
export const expectToBePreviewServerError = (actual: PreviewServerError, status?: number, message?: string) => {
expect(actual).toEqual(jasmine.any(PreviewServerError));
if (status != null) {
expect(actual.status).toBe(status);
}
if (message != null) {
expect(actual.message).toBe(message);
}
};

View File

@ -0,0 +1,39 @@
// Imports
import {PreviewServerError} from '../../lib/preview-server/preview-error';
// Tests
describe('PreviewServerError', () => {
let err: PreviewServerError;
beforeEach(() => err = new PreviewServerError(999, 'message'));
it('should extend Error', () => {
expect(err).toEqual(jasmine.any(PreviewServerError));
expect(err).toEqual(jasmine.any(Error));
expect(Object.getPrototypeOf(err)).toBe(PreviewServerError.prototype);
});
it('should have a \'status\' property', () => {
expect(err.status).toBe(999);
});
it('should have a \'message\' property', () => {
expect(err.message).toBe('message');
});
it('should have a 500 \'status\' by default', () => {
expect(new PreviewServerError().status).toBe(500);
});
it('should have an empty \'message\' by default', () => {
expect(new PreviewServerError().message).toBe('');
expect(new PreviewServerError(999).message).toBe('');
});
});

View File

@ -7,11 +7,11 @@ import {CircleCiApi} from '../../lib/common/circle-ci-api';
import {GithubApi} from '../../lib/common/github-api'; import {GithubApi} from '../../lib/common/github-api';
import {GithubPullRequests} from '../../lib/common/github-pull-requests'; import {GithubPullRequests} from '../../lib/common/github-pull-requests';
import {GithubTeams} from '../../lib/common/github-teams'; import {GithubTeams} from '../../lib/common/github-teams';
import {BuildCreator} from '../../lib/upload-server/build-creator'; import {BuildCreator} from '../../lib/preview-server/build-creator';
import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/upload-server/build-events'; import {ChangedPrVisibilityEvent, CreatedBuildEvent} from '../../lib/preview-server/build-events';
import {BuildRetriever, GithubInfo} from '../../lib/upload-server/build-retriever'; import {BuildRetriever, GithubInfo} from '../../lib/preview-server/build-retriever';
import {BuildVerifier} from '../../lib/upload-server/build-verifier'; import {BuildVerifier} from '../../lib/preview-server/build-verifier';
import {UploadServerConfig, UploadServerFactory} from '../../lib/upload-server/upload-server-factory'; import {PreviewServerConfig, PreviewServerFactory} from '../../lib/preview-server/preview-server-factory';
interface CircleCiWebHookPayload { interface CircleCiWebHookPayload {
payload: { payload: {
@ -23,8 +23,8 @@ interface CircleCiWebHookPayload {
} }
// Tests // Tests
describe('uploadServerFactory', () => { describe('PreviewServerFactory', () => {
const defaultConfig: UploadServerConfig = { const defaultConfig: PreviewServerConfig = {
buildArtifactPath: 'artifact/path.zip', buildArtifactPath: 'artifact/path.zip',
buildsDir: 'builds/dir', buildsDir: 'builds/dir',
circleCiToken: 'CIRCLE_CI_TOKEN', circleCiToken: 'CIRCLE_CI_TOKEN',
@ -40,8 +40,8 @@ describe('uploadServerFactory', () => {
}; };
// Helpers // Helpers
const createUploadServer = (partialConfig: Partial<UploadServerConfig> = {}) => const createPreviewServer = (partialConfig: Partial<PreviewServerConfig> = {}) =>
UploadServerFactory.create({...defaultConfig, ...partialConfig}); PreviewServerFactory.create({...defaultConfig, ...partialConfig});
beforeEach(() => { beforeEach(() => {
spyOn(console, 'error'); spyOn(console, 'error');
@ -53,64 +53,64 @@ describe('uploadServerFactory', () => {
let usfCreateMiddlewareSpy: jasmine.Spy; let usfCreateMiddlewareSpy: jasmine.Spy;
beforeEach(() => { beforeEach(() => {
usfCreateMiddlewareSpy = spyOn(UploadServerFactory, 'createMiddleware').and.callThrough(); usfCreateMiddlewareSpy = spyOn(PreviewServerFactory, 'createMiddleware').and.callThrough();
}); });
it('should throw if \'buildsDir\' is missing or empty', () => { it('should throw if \'buildsDir\' is missing or empty', () => {
expect(() => createUploadServer({buildsDir: ''})). expect(() => createPreviewServer({buildsDir: ''})).
toThrowError('Missing or empty required parameter \'buildsDir\'!'); toThrowError('Missing or empty required parameter \'buildsDir\'!');
}); });
it('should throw if \'domainName\' is missing or empty', () => { it('should throw if \'domainName\' is missing or empty', () => {
expect(() => createUploadServer({domainName: ''})). expect(() => createPreviewServer({domainName: ''})).
toThrowError('Missing or empty required parameter \'domainName\'!'); toThrowError('Missing or empty required parameter \'domainName\'!');
}); });
it('should throw if \'githubToken\' is missing or empty', () => { it('should throw if \'githubToken\' is missing or empty', () => {
expect(() => createUploadServer({githubToken: ''})). expect(() => createPreviewServer({githubToken: ''})).
toThrowError('Missing or empty required parameter \'githubToken\'!'); toThrowError('Missing or empty required parameter \'githubToken\'!');
}); });
it('should throw if \'githubOrg\' is missing or empty', () => { it('should throw if \'githubOrg\' is missing or empty', () => {
expect(() => createUploadServer({githubOrg: ''})). expect(() => createPreviewServer({githubOrg: ''})).
toThrowError('Missing or empty required parameter \'githubOrg\'!'); toThrowError('Missing or empty required parameter \'githubOrg\'!');
}); });
it('should throw if \'githubTeamSlugs\' is missing or empty', () => { it('should throw if \'githubTeamSlugs\' is missing or empty', () => {
expect(() => createUploadServer({githubTeamSlugs: []})). expect(() => createPreviewServer({githubTeamSlugs: []})).
toThrowError('Missing or empty required parameter \'allowedTeamSlugs\'!'); toThrowError('Missing or empty required parameter \'allowedTeamSlugs\'!');
}); });
it('should throw if \'githubRepo\' is missing or empty', () => { it('should throw if \'githubRepo\' is missing or empty', () => {
expect(() => createUploadServer({githubRepo: ''})). expect(() => createPreviewServer({githubRepo: ''})).
toThrowError('Missing or empty required parameter \'githubRepo\'!'); toThrowError('Missing or empty required parameter \'githubRepo\'!');
}); });
it('should throw if \'trustedPrLabel\' is missing or empty', () => { it('should throw if \'trustedPrLabel\' is missing or empty', () => {
expect(() => createUploadServer({trustedPrLabel: ''})). expect(() => createPreviewServer({trustedPrLabel: ''})).
toThrowError('Missing or empty required parameter \'trustedPrLabel\'!'); toThrowError('Missing or empty required parameter \'trustedPrLabel\'!');
}); });
it('should return an http.Server', () => { it('should return an http.Server', () => {
const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough(); const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
const server = createUploadServer(); const server = createPreviewServer();
expect(server).toBe(httpCreateServerSpy.calls.mostRecent().returnValue); expect(server).toBe(httpCreateServerSpy.calls.mostRecent().returnValue);
}); });
it('should create and use an appropriate BuildCreator', () => { it('should create and use an appropriate BuildCreator', () => {
const usfCreateBuildCreatorSpy = spyOn(UploadServerFactory, 'createBuildCreator').and.callThrough(); const usfCreateBuildCreatorSpy = spyOn(PreviewServerFactory, 'createBuildCreator').and.callThrough();
createUploadServer(); createPreviewServer();
const buildRetriever = jasmine.any(BuildRetriever); const buildRetriever = jasmine.any(BuildRetriever);
const buildVerifier = jasmine.any(BuildVerifier); const buildVerifier = jasmine.any(BuildVerifier);
const prs = jasmine.any(GithubPullRequests); const prs = jasmine.any(GithubPullRequests);
@ -124,7 +124,7 @@ describe('uploadServerFactory', () => {
it('should create and use an appropriate middleware', () => { it('should create and use an appropriate middleware', () => {
const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough(); const httpCreateServerSpy = spyOn(http, 'createServer').and.callThrough();
createUploadServer(); createPreviewServer();
const buildRetriever = jasmine.any(BuildRetriever); const buildRetriever = jasmine.any(BuildRetriever);
const buildVerifier = jasmine.any(BuildVerifier); const buildVerifier = jasmine.any(BuildVerifier);
@ -137,14 +137,14 @@ describe('uploadServerFactory', () => {
it('should log the server address info on \'listening\'', () => { it('should log the server address info on \'listening\'', () => {
const server = createUploadServer(); const server = createPreviewServer();
server.address = () => ({address: 'foo', family: '', port: 1337}); server.address = () => ({address: 'foo', family: '', port: 1337});
expect(console.info).not.toHaveBeenCalled(); expect(console.info).not.toHaveBeenCalled();
server.emit('listening'); server.emit('listening');
expect(console.info).toHaveBeenCalledWith( expect(console.info).toHaveBeenCalledWith(
jasmine.any(String), 'UploadServer: ', 'Up and running (and listening on foo:1337)...'); jasmine.any(String), 'PreviewServer: ', 'Up and running (and listening on foo:1337)...');
}); });
}); });
@ -158,7 +158,7 @@ describe('uploadServerFactory', () => {
beforeEach(() => { beforeEach(() => {
const api = new GithubApi(defaultConfig.githubToken); const api = new GithubApi(defaultConfig.githubToken);
const prs = new GithubPullRequests(api, defaultConfig.githubOrg, defaultConfig.githubRepo); const prs = new GithubPullRequests(api, defaultConfig.githubOrg, defaultConfig.githubRepo);
buildCreator = UploadServerFactory.createBuildCreator(prs, defaultConfig.buildsDir, defaultConfig.domainName); buildCreator = PreviewServerFactory.createBuildCreator(prs, defaultConfig.buildsDir, defaultConfig.domainName);
}); });
it('should pass the \'buildsDir\' to the BuildCreator', () => { it('should pass the \'buildsDir\' to the BuildCreator', () => {
@ -256,7 +256,7 @@ describe('uploadServerFactory', () => {
buildVerifier = new BuildVerifier(prs, teams, defaultConfig.githubTeamSlugs, defaultConfig.trustedPrLabel); buildVerifier = new BuildVerifier(prs, teams, defaultConfig.githubTeamSlugs, defaultConfig.trustedPrLabel);
buildCreator = new BuildCreator(defaultConfig.buildsDir); buildCreator = new BuildCreator(defaultConfig.buildsDir);
const middleware = UploadServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator, const middleware = PreviewServerFactory.createMiddleware(buildRetriever, buildVerifier, buildCreator,
defaultConfig); defaultConfig);
agent = supertest.agent(middleware); agent = supertest.agent(middleware);
}); });
@ -359,7 +359,7 @@ describe('uploadServerFactory', () => {
await agent.post(URL).send(BASIC_PAYLOAD).expect(204); await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
expect(getGithubInfoSpy).not.toHaveBeenCalled(); expect(getGithubInfoSpy).not.toHaveBeenCalled();
expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled(); expect(getSignificantFilesChangedSpy).not.toHaveBeenCalled();
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'UploadServer: ', expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.'); 'Build:12345, Job:lint -', 'Skipping preview processing because this is not the "aio_preview" job.');
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled(); expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
expect(getPrIsTrustedSpy).not.toHaveBeenCalled(); expect(getPrIsTrustedSpy).not.toHaveBeenCalled();
@ -371,7 +371,7 @@ describe('uploadServerFactory', () => {
await agent.post(URL).send(BASIC_PAYLOAD).expect(204); await agent.post(URL).send(BASIC_PAYLOAD).expect(204);
expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM); expect(getGithubInfoSpy).toHaveBeenCalledWith(BUILD_NUM);
expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp)); expect(getSignificantFilesChangedSpy).toHaveBeenCalledWith(PR, jasmine.any(RegExp));
expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'UploadServer: ', expect(console.log).toHaveBeenCalledWith(jasmine.any(String), 'PreviewServer: ',
'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.'); 'PR:777, Build:12345 - Skipping preview processing because this PR did not touch any significant files.');
expect(downloadBuildArtifactSpy).not.toHaveBeenCalled(); expect(downloadBuildArtifactSpy).not.toHaveBeenCalled();
expect(getPrIsTrustedSpy).not.toHaveBeenCalled(); expect(getPrIsTrustedSpy).not.toHaveBeenCalled();

View File

@ -1,8 +1,8 @@
import * as express from 'express'; import * as express from 'express';
import {UploadError} from '../../lib/upload-server/upload-error'; import {PreviewServerError} from '../../lib/preview-server/preview-error';
import {respondWithError, throwRequestError} from '../../lib/upload-server/utils'; import {respondWithError, throwRequestError} from '../../lib/preview-server/utils';
describe('upload-server/utils', () => { describe('preview-server/utils', () => {
describe('respondWithError', () => { describe('respondWithError', () => {
let endSpy: jasmine.Spy; let endSpy: jasmine.Spy;
let statusSpy: jasmine.Spy; let statusSpy: jasmine.Spy;
@ -15,12 +15,12 @@ describe('upload-server/utils', () => {
}); });
it('should set the status on the response', () => { it('should set the status on the response', () => {
respondWithError(response, new UploadError(505, 'TEST MESSAGE')); respondWithError(response, new PreviewServerError(505, 'TEST MESSAGE'));
expect(statusSpy).toHaveBeenCalledWith(505); expect(statusSpy).toHaveBeenCalledWith(505);
expect(endSpy).toHaveBeenCalledWith('TEST MESSAGE', jasmine.any(Function)); expect(endSpy).toHaveBeenCalledWith('TEST MESSAGE', jasmine.any(Function));
}); });
it('should convert non-UploadError errors to 500 UploadErrors', () => { it('should convert non-PreviewServerError errors to 500 PreviewServerErrors', () => {
respondWithError(response, new Error('OTHER MESSAGE')); respondWithError(response, new Error('OTHER MESSAGE'));
expect(statusSpy).toHaveBeenCalledWith(500); expect(statusSpy).toHaveBeenCalledWith(500);
expect(endSpy).toHaveBeenCalledWith('OTHER MESSAGE', jasmine.any(Function)); expect(endSpy).toHaveBeenCalledWith('OTHER MESSAGE', jasmine.any(Function));
@ -39,7 +39,7 @@ describe('upload-server/utils', () => {
throwRequestError(505, 'ERROR MESSAGE', request); throwRequestError(505, 'ERROR MESSAGE', request);
} catch (error) { } catch (error) {
caught = true; caught = true;
expect(error).toEqual(jasmine.any(UploadError)); expect(error).toEqual(jasmine.any(PreviewServerError));
expect(error.status).toEqual(505); expect(error.status).toEqual(505);
expect(error.message).toEqual(`ERROR MESSAGE in request: POST some.domain.com/path "The request body"`); expect(error.message).toEqual(`ERROR MESSAGE in request: POST some.domain.com/path "The request body"`);
} }

View File

@ -1,11 +0,0 @@
import {UploadError} from '../../lib/upload-server/upload-error';
export const expectToBeUploadError = (actual: UploadError, status?: number, message?: string) => {
expect(actual).toEqual(jasmine.any(UploadError));
if (status != null) {
expect(actual.status).toBe(status);
}
if (message != null) {
expect(actual.message).toBe(message);
}
};

View File

@ -1,39 +0,0 @@
// Imports
import {UploadError} from '../../lib/upload-server/upload-error';
// Tests
describe('UploadError', () => {
let err: UploadError;
beforeEach(() => err = new UploadError(999, 'message'));
it('should extend Error', () => {
expect(err).toEqual(jasmine.any(UploadError));
expect(err).toEqual(jasmine.any(Error));
expect(Object.getPrototypeOf(err)).toBe(UploadError.prototype);
});
it('should have a \'status\' property', () => {
expect(err.status).toBe(999);
});
it('should have a \'message\' property', () => {
expect(err.message).toBe('message');
});
it('should have a 500 \'status\' by default', () => {
expect(new UploadError().status).toBe(500);
});
it('should have an empty \'message\' by default', () => {
expect(new UploadError().message).toBe('');
expect(new UploadError(999).message).toBe('');
});
});

View File

@ -30,7 +30,7 @@ done
# Check servers # Check servers
origins=( origins=(
http://$AIO_UPLOAD_HOSTNAME:$AIO_UPLOAD_PORT http://$AIO_PREVIEW_SERVER_HOSTNAME:$AIO_PREVIEW_SERVER_PORT
http://$AIO_NGINX_HOSTNAME:$AIO_NGINX_PORT_HTTP http://$AIO_NGINX_HOSTNAME:$AIO_NGINX_PORT_HTTP
https://$AIO_NGINX_HOSTNAME:$AIO_NGINX_PORT_HTTPS https://$AIO_NGINX_HOSTNAME:$AIO_NGINX_PORT_HTTPS
) )

View File

@ -14,5 +14,5 @@ service cron start
service dnsmasq start service dnsmasq start
service nginx start service nginx start
service pm2-root start service pm2-root start
aio-upload-server-prod start aio-preview-server-prod start
echo [`date`] - Services started successfully. echo [`date`] - Services started successfully.

View File

@ -5,10 +5,10 @@ set -eu -o pipefail
export AIO_CIRCLE_CI_TOKEN=$(head -c -1 /aio-secrets/CIRCLE_CI_TOKEN 2>/dev/null || echo "MISSING_CIRCLE_CI_TOKEN") export AIO_CIRCLE_CI_TOKEN=$(head -c -1 /aio-secrets/CIRCLE_CI_TOKEN 2>/dev/null || echo "MISSING_CIRCLE_CI_TOKEN")
export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || echo "MISSING_GITHUB_TOKEN") export AIO_GITHUB_TOKEN=$(head -c -1 /aio-secrets/GITHUB_TOKEN 2>/dev/null || echo "MISSING_GITHUB_TOKEN")
# Start the upload-server instance # Start the preview-server instance
action=$([ "$1" == "stop" ] && echo "stop" || echo "start") action=$([ "$1" == "stop" ] && echo "stop" || echo "start")
pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/upload-server \ pm2 $action $AIO_SCRIPTS_JS_DIR/dist/lib/preview-server \
--uid $AIO_WWW_USER \ --uid $AIO_WWW_USER \
--log /var/log/aio/upload-server-prod.log \ --log /var/log/aio/preview-server-prod.log \
--name aio-upload-server-prod \ --name aio-preview-server-prod \
${@:2} ${@:2}

View File

@ -1,15 +1,15 @@
#!/bin/bash #!/bin/bash
set -eu -o pipefail set -eu -o pipefail
# Start the upload-server instance # Start the preview-server instance
appName=aio-upload-server-test appName=aio-preview-server-test
if [[ "$1" == "stop" ]]; then if [[ "$1" == "stop" ]]; then
pm2 delete $appName pm2 delete $appName
else else
source aio-test-env source aio-test-env
pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-upload-server.js \ pm2 start $AIO_SCRIPTS_JS_DIR/dist/lib/verify-setup/start-test-preview-server.js \
--uid $AIO_WWW_USER \ --uid $AIO_WWW_USER \
--log /var/log/aio/upload-server-test.log \ --log /var/log/aio/preview-server-test.log \
--name $appName \ --name $appName \
--no-autorestart \ --no-autorestart \
${@:2} ${@:2}

View File

@ -11,9 +11,9 @@
export AIO_GITHUB_TEAM_SLUGS=$TEST_AIO_GITHUB_TEAM_SLUGS export AIO_GITHUB_TEAM_SLUGS=$TEST_AIO_GITHUB_TEAM_SLUGS
export AIO_SIGNIFICANT_FILES_PATTERN=$TEST_AIO_SIGNIFICANT_FILES_PATTERN export AIO_SIGNIFICANT_FILES_PATTERN=$TEST_AIO_SIGNIFICANT_FILES_PATTERN
export AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL export AIO_TRUSTED_PR_LABEL=$TEST_AIO_TRUSTED_PR_LABEL
export AIO_UPLOAD_HOSTNAME=$TEST_AIO_UPLOAD_HOSTNAME export AIO_PREVIEW_SERVER_HOSTNAME=$TEST_AIO_PREVIEW_SERVER_HOSTNAME
export AIO_UPLOAD_PORT=$TEST_AIO_UPLOAD_PORT export AIO_PREVIEW_SERVER_PORT=$TEST_AIO_PREVIEW_SERVER_PORT
export AIO_UPLOAD_MAX_SIZE=$TEST_AIO_UPLOAD_MAX_SIZE export AIO_ARTIFACT_MAX_SIZE=$TEST_AIO_ARTIFACT_MAX_SIZE
export AIO_CIRCLE_CI_TOKEN=TEST_CIRCLE_CI_TOKEN export AIO_CIRCLE_CI_TOKEN=TEST_CIRCLE_CI_TOKEN
export AIO_GITHUB_TOKEN=TEST_GITHUB_TOKEN export AIO_GITHUB_TOKEN=TEST_GITHUB_TOKEN

View File

@ -1,2 +1,2 @@
aio-verify-setup aio-verify-setup
ls -t /var/log/aio/upload-server-verify* | head -1 | xargs cat ls -t /var/log/aio/preview-server-verify* | head -1 | xargs cat

View File

@ -2,7 +2,7 @@
set -eu -o pipefail set -eu -o pipefail
logFile=/var/log/aio/verify-setup.log logFile=/var/log/aio/verify-setup.log
uploadServerLogFile=/var/log/aio/upload-server-verify-setup.log previewServerLogFile=/var/log/aio/preview-server-verify-setup.log
exec 3>&1 exec 3>&1
exec >> $logFile exec >> $logFile
@ -24,18 +24,18 @@ function countdown {
function onExit { function onExit {
echo -e "Stopping Test Server" echo -e "Stopping Test Server"
aio-upload-server-test stop aio-preview-server-test stop
echo -e "Full logs in '$logFile'.\n" > /dev/fd/3 echo -e "Full logs in '$logFile'.\n" > /dev/fd/3
} }
# Setup EXIT trap # Setup EXIT trap
trap 'onExit' EXIT trap 'onExit' EXIT
# Start an upload-server instance for testing # Start an preview-server instance for testing
echo -e "Starting Test Server" echo -e "Starting Test Server"
aio-upload-server-test start --log $uploadServerLogFile aio-preview-server-test start --log $previewServerLogFile
# Give the upload-server some time to start :( # Give the preview-server some time to start :(
countdown "Starting" 5 > /dev/fd/3 countdown "Starting" 5 > /dev/fd/3
# Run the tests # Run the tests

View File

@ -54,14 +54,14 @@ you don't need to specify values for those.
build artifacts publicly served. This is useful for enabling previews for any PR (not only those build artifacts publicly served. This is useful for enabling previews for any PR (not only those
from trusted authors). from trusted authors).
- `AIO_UPLOAD_HOSTNAME`: - `AIO_PREVIEW_SERVER_HOSTNAME`:
The internal hostname for accessing the Node.js upload-server. This is used by nginx for The internal hostname for accessing the Node.js preview-server. This is used by nginx for
delegating upload requests and also for performing a periodic health-check. delegating web-hook requests and also for performing a periodic health-check.
- `AIO_UPLOAD_MAX_SIZE`: - `AIO_ARTIFACT_MAX_SIZE`:
The maximum allowed size for the uploaded gzip archive containing the build artifacts. Files The maximum allowed size for the gzip archive containing the build artifacts.
larger than this will be rejected. Files larger than this will be rejected.
- `AIO_UPLOAD_PORT`: - `AIO_PREVIEW_SERVER_PORT`:
The port number on which the Node.js upload-server listens for HTTP connections. This is used by The port number on which the Node.js preview-server listens for HTTP connections. This is used by
nginx for delegating upload requests and also for performing a periodic health-check. nginx for delegating web-hook requests and also for performing a periodic health-check.

View File

@ -5,14 +5,14 @@ TODO (gkalpak): Add docs. Mention:
- `aio-health-check` - `aio-health-check`
- `aio-verify-setup` - `aio-verify-setup`
- Test nginx accessible at: - Test nginx accessible at:
- `http://$TEST_AIO_NGINX_HOTNAME:$TEST_AIO_NGINX_PORT_HTTP` - `http://$TEST_AIO_NGINX_HOSTNAME:$TEST_AIO_NGINX_PORT_HTTP`
- `https://$TEST_AIO_NGINX_HOTNAME:$TEST_AIO_NGINX_PORT_HTTPS` - `https://$TEST_AIO_NGINX_HOSTNAME:$TEST_AIO_NGINX_PORT_HTTPS`
- Test upload-server accessible at: - Test preview-server accessible at:
- `http://$TEST_AIO_UPLOAD_HOTNAME:$TEST_AIO_UPLOAD_PORT` - `http://$TEST_AIO_PREVIEW_SERVER_HOSTNAME:$TEST_AIO_PREVIEW_SERVER_PORT`
- Local DNS (via dnsmasq) maps the above hostnames to 127.0.0.1 - Local DNS (via dnsmasq) maps the above hostnames to 127.0.0.1
## Developing the upload server TypeScript files ## Developing the preview server TypeScript files
If you are running Docker on OS/X then you can benefit from linking the built TypeScript If you are running Docker on OS/X then you can benefit from linking the built TypeScript
files (i.e. `script-js/dist`) to the JavaScript files inside the Docker container. files (i.e. `script-js/dist`) to the JavaScript files inside the Docker container.
@ -39,9 +39,9 @@ aio-verify-setup
``` ```
Sometimes, the errors in the unit test log are not enough to tell you what went wrong. Sometimes, the errors in the unit test log are not enough to tell you what went wrong.
In that case you can also look at the log of the upload-server itself. In that case you can also look at the log of the preview-server itself.
A helper script that runs the unit tests (i.e. `aio-verify-setup`) and displays the A helper script that runs the unit tests (i.e. `aio-verify-setup`) and displays the
last relevant test-upload-server log is: last relevant test-preview-server log is:
```bash ```bash
aio-verify-setup-and-log aio-verify-setup-and-log

View File

@ -3,7 +3,7 @@
## Objective ## Objective
Whenever a PR job is run on the CI infrastructure (e.g. CircleCI), we want to build `angular.io` Whenever a PR job is run on the CI infrastructure (e.g. CircleCI), we want to build `angular.io`
and upload the build artifacts to a publicly accessible server so that collaborators (developers, and host the build artifacts on a publicly accessible server so that collaborators (developers,
designers, authors, etc) can preview the changes without having to checkout and build the app designers, authors, etc) can preview the changes without having to checkout and build the app
locally. locally.
@ -40,31 +40,31 @@ container:
- The CI script checks whether the PR has touched any files that might affect the angular.io app - The CI script checks whether the PR has touched any files that might affect the angular.io app
(currently the `aio/` or `packages/` directories, ignoring spec files). (currently the `aio/` or `packages/` directories, ignoring spec files).
- The CI script gzips and stores the build artifacts in the CI infrastructure. - The CI script gzips and stores the build artifacts in the CI infrastructure.
- When the build completes CircleCI triggers a webhook on the upload-server. - When the build completes CircleCI triggers a webhook on the preview-server.
More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md). More info on how to set things up on CI can be found [here](misc--integrate-with-ci.md).
### Hosting build artifacts ### Hosting build artifacts
- nginx receives the webhook trigger and passes it through to the upload server. - nginx receives the webhook trigger and passes it through to the preview server.
- The upload-server makes a request to CircleCI for the URL of the AIO build artifacts. - The preview-server makes a request to CircleCI for the URL of the AIO build artifacts.
- The upload-server makes a request to this URL to receive the artifact - failing if the size - The preview-server makes a request to this URL to receive the artifact - failing if the size
exceeds the specified max file size - and stores it in a temporary location. exceeds the specified max file size - and stores it in a temporary location.
- The upload-server runs several checks to determine whether the request should be accepted and - The preview-server runs several checks to determine whether the request should be accepted and
whether it should be publicly accessible or stored for later verification (more details can be whether it should be publicly accessible or stored for later verification (more details can be
found [here](overview--security-model.md)). found [here](overview--security-model.md)).
- The upload-server changes the "visibility" of the associated PR, if necessary. For example, if - The preview-server changes the "visibility" of the associated PR, if necessary. For example, if
builds for the same PR had been previously deployed as non-public and the current build has been builds for the same PR had been previously deployed as non-public and the current build has been
automatically verified, all previous builds are made public as well. automatically verified, all previous builds are made public as well.
If the PR transitions from "non-public" to "public", the upload-server posts a comment on the If the PR transitions from "non-public" to "public", the preview-server posts a comment on the
corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found. corresponding PR on GitHub mentioning the SHAs and the links where the previews can be found.
- The upload-server verifies that the uploaded file is not trying to overwrite an existing build. - The preview-server verifies that it is not trying to overwrite an existing build.
- The upload-server deploys the artifacts to a sub-directory named after the PR number and the first - The preview-server deploys the artifacts to a sub-directory named after the PR number and the first
few characters of the SHA: `<PR>/<SHA>/` few characters of the SHA: `<PR>/<SHA>/`
(Non-publicly accessible PRs will be stored in a different location, but again derived from the PR (Non-publicly accessible PRs will be stored in a different location, but again derived from the PR
number and SHA.) number and SHA.)
- If the PR is publicly accessible, the upload-server posts a comment on the corresponding PR on - If the PR is publicly accessible, the preview-server posts a comment on the corresponding PR on
GitHub mentioning the SHA and the link where the preview can be found. GitHub mentioning the SHA and the link where the preview can be found.
More info on the possible HTTP status codes and their meaning can be found More info on the possible HTTP status codes and their meaning can be found
@ -73,24 +73,24 @@ More info on the possible HTTP status codes and their meaning can be found
### Updating PR visibility ### Updating PR visibility
- nginx receives a natification that a PR has been updated and passes it through to the - nginx receives a natification that a PR has been updated and passes it through to the
upload-server. This could, for example, be sent by a GitHub webhook every time a PR's labels preview-server. This could, for example, be sent by a GitHub webhook every time a PR's labels
change. change.
E.g.: `ngbuilds.io/pr-updated` (payload: `{"number":<PR>,"action":"labeled"}`) E.g.: `ngbuilds.io/pr-updated` (payload: `{"number":<PR>,"action":"labeled"}`)
- The request contains the PR number (as `number`) and optionally the action that triggered the - The request contains the PR number (as `number`) and optionally the action that triggered the
request (as `action`) in the payload. request (as `action`) in the payload.
- The upload-server verifies the payload and determines whether the `action` (if specified) could - The preview-server verifies the payload and determines whether the `action` (if specified) could
have led to PR visibility changes. Only requests that omit the `action` field altogether or have led to PR visibility changes. Only requests that omit the `action` field altogether or
specify an action that can affect visibility are further processed. specify an action that can affect visibility are further processed.
(Currently, the only actions that are considered capable of affecting visibility are `labeled` and (Currently, the only actions that are considered capable of affecting visibility are `labeled` and
`unlabeled`.) `unlabeled`.)
- The upload-server re-checks and if necessary updates the PR's visibility. - The preview-server re-checks and if necessary updates the PR's visibility.
More info on the possible HTTP status codes and their meaning can be found More info on the possible HTTP status codes and their meaning can be found
[here](overview--http-status-codes.md). [here](overview--http-status-codes.md).
### Serving build artifacts ### Serving build artifacts
- nginx receives a request for an uploaded resource on a subdomain corresponding to the PR and SHA. - nginx receives a request for a hosted preview resource on a subdomain corresponding to the PR and SHA.
E.g.: `pr<PR>-<SHA>.ngbuilds.io/path/to/resource` E.g.: `pr<PR>-<SHA>.ngbuilds.io/path/to/resource`
- nginx maps the subdomain to the correct sub-directory and serves the resource. - nginx maps the subdomain to the correct sub-directory and serves the resource.
E.g.: `/<PR>/<SHA>/path/to/resource` E.g.: `/<PR>/<SHA>/path/to/resource`
@ -108,4 +108,4 @@ that do not correspond with an open PR.
### Health-check ### Health-check
The docker service runs a periodic health-check that verifies the running conditions of the The docker service runs a periodic health-check that verifies the running conditions of the
container. This includes verifying the status of specific system services, the responsiveness of container. This includes verifying the status of specific system services, the responsiveness of
nginx and the upload-server and internet connectivity. nginx and the preview-server and internet connectivity.

View File

@ -1,8 +1,8 @@
# Overview - HTTP Status Codes # Overview - HTTP Status Codes
This is a list of all the possible HTTP status codes returned by the nginx anf upload servers, along This is a list of all the possible HTTP status codes returned by the nginx and preview servers, along
with a bried explanation of what they mean: with a brief explanation of what they mean:
## `http://*.ngbuilds.io/*` ## `http://*.ngbuilds.io/*`

View File

@ -40,23 +40,23 @@ purposes. Each command is backed by a corresponding script inside
Initializes the container (mainly by starting the necessary services). Initializes the container (mainly by starting the necessary services).
_It is run (by default) when starting the container._ _It is run (by default) when starting the container._
- `aio-upload-server-prod`: - `aio-preview-server-prod`:
Spins up a Node.js upload-server instance. Spins up a Node.js preview-server instance.
_It is used in `aio-init` (see above) during initialization._ _It is used in `aio-init` (see above) during initialization._
## Developer Commands ## Developer Commands
- `aio-upload-server-test`: - `aio-preview-server-test`:
Spins up a Node.js upload-server instance for tests. Spins up a Node.js preview-server instance for tests.
_It is used in `aio-verify-setup` (see below) for running tests._ _It is used in `aio-verify-setup` (see below) for running tests._
- `aio-verify-setup`: - `aio-verify-setup`:
Runs a suite of e2e-like tests, mainly verifying the correct (inter)operation of nginx and the Runs a suite of e2e-like tests, mainly verifying the correct (inter)operation of nginx and the
Node.js upload-server. Node.js preview-server.
- `aio-verify-setup-and-log`: - `aio-verify-setup-and-log`:
Runs the `aio-verify-setup` command but also then dumps the logs from the upload server, which Runs the `aio-verify-setup` command but also then dumps the logs from the preview server, which
gives additional useful debugging information. See the [debugging docs](misc--debug-docker-container.md) gives additional useful debugging information. See the [debugging docs](misc--debug-docker-container.md)
for more info. for more info.

View File

@ -48,7 +48,7 @@ The implemented approach can be broken up to the following sub-tasks:
5. Deploy the artifacts to the corresponding PR's directory. 5. Deploy the artifacts to the corresponding PR's directory.
6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established 6. Prevent overwriting previously deployed artifacts (which ensures that the guarantees established
during deployment will remain valid until the artifacts are removed). during deployment will remain valid until the artifacts are removed).
7. Prevent uploaded files from accessing anything outside their directory. 7. Prevent hosted preview files from accessing anything outside their directory.
### Implementation details ### Implementation details
@ -56,7 +56,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
0. **Receive notification from CircleCI of a completed build** 0. **Receive notification from CircleCI of a completed build**
CircleCI is configured to trigger a webhook on our upload-server whenever a build completes. CircleCI is configured to trigger a webhook on our preview-server whenever a build completes.
The payload contains the number of the build that completed. The payload contains the number of the build that completed.
1. **Verify that the build is valid and download the artifact.** 1. **Verify that the build is valid and download the artifact.**
@ -71,7 +71,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
Next we make another call to the CircleCI API to get a list of the URLS for artifacts of that Next we make another call to the CircleCI API to get a list of the URLS for artifacts of that
build. If there is one that matches the configured artifact path, we download the contents of the build. If there is one that matches the configured artifact path, we download the contents of the
build artifact and store it in a local folder. This download has a maximum size limit to prevent build artifact and store it in a local folder. This download has a maximum size limit to prevent
PRs from producing artifacts that are so large they would cause the upload server to crash. PRs from producing artifacts that are so large they would cause the preview server to crash.
2. **Fetch the PR's metadata, including author and labels**. 2. **Fetch the PR's metadata, including author and labels**.
@ -98,7 +98,7 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e. Once we have determined whether the PR is considered "trusted", we update its "visibility" (i.e.
whether it is publicly accessible or not), based on the new verification status. For example, if whether it is publicly accessible or not), based on the new verification status. For example, if
a PR was initially considered "not trusted" but the check triggered by a new build determined a PR was initially considered "not trusted" but the check triggered by a new build determined
otherwise, the PR (and all the previously uploaded previews) are made public. It works the same otherwise, the PR (and all the previously hosted previews) are made public. It works the same
way if a PR has gone from "trusted" to "not trusted". way if a PR has gone from "trusted" to "not trusted".
5. **Deploy the artifacts to the corresponding PR's directory.** 5. **Deploy the artifacts to the corresponding PR's directory.**
@ -117,23 +117,23 @@ This section describes how each of the aforementioned sub-tasks is accomplished:
Express server) rejects builds that have already been handled. Express server) rejects builds that have already been handled.
_Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._ _Note: A PR can contain multiple builds; one for each SHA that was built on CircleCI._
7. **Prevent uploaded files from accessing anything outside their directory.** 7. **Prevent hosted preview files from accessing anything outside their directory.**
Nginx (which is used to serve the uploaded artifacts) has been configured to not follow symlinks Nginx (which is used to serve the hosted preview) has been configured to not follow symlinks
outside of the directory where the build artifacts are stored. outside of the directory where the preview files are stored.
## Assumptions / Things to keep in mind ## Assumptions / Things to keep in mind
- Other than the initial webhook trigger, which provides a build number, all requests for data come - Other than the initial webhook trigger, which provides a build number, all requests for data come
from the upload-server making requests to well defined API endpoints (e.g. CircleCI and Github). from the preview-server making requests to well defined API endpoints (e.g. CircleCI and Github).
This means that any secret access keys need only be stored on the upload-server and not on any of This means that any secret access keys need only be stored on the preview-server and not on any of
the CI build infrastructure (e.g. CircleCI). the CI build infrastructure (e.g. CircleCI).
- Each trusted PR author has full control over the content that is uploaded for their PRs. Part of - Each trusted PR author has full control over the content that is hosted as a preview for their PRs.
the security model relies on the trustworthiness of these authors. Part of the security model relies on the trustworthiness of these authors.
- Adding the specified label on a PR and marking it as trusted, gives the author full control over - Adding the specified label on a PR to mark it as trusted, gives the author full control over
the content that is uploaded for the specific PR (e.g. by pushing more commits to it). The user the content that is hosted for the specific PR preview (e.g. by pushing more commits to it).
adding the label is responsible for ensuring that this control is not abused and that the PR is The user adding the label is responsible for ensuring that this control is not abused and that
either closed (one way of another) or the access is revoked. the PR is either closed (one way of another) or the access is revoked.

View File

@ -12,8 +12,8 @@ More info on how to create `secrets` directory and files can be found
## Create directory for build artifacts ## Create directory for build artifacts
The uploaded build artifacts should be kept on a directory outside the docker container, so it is The build artifacts should be kept on a directory outside the docker container, so it is
easier to replace the container without losing the uploaded builds. For portability across VMs a easier to replace the container without losing the builds. For portability across VMs a
persistent disk can be used (as described [here](vm-setup--attach-persistent-disk.md)). persistent disk can be used (as described [here](vm-setup--attach-persistent-disk.md)).
**Note:** The directories created inside that directory will be owned by user `www-data`. **Note:** The directories created inside that directory will be owned by user `www-data`.
@ -21,7 +21,7 @@ persistent disk can be used (as described [here](vm-setup--attach-persistent-dis
## Create SSL certificates (Optional for dev) ## Create SSL certificates (Optional for dev)
The host VM can attach a directory containing the SSL certificate and key to be used by the nginx The host VM can attach a directory containing the SSL certificate and key to be used by the nginx
server for serving the uploaded build artifacts. More info on how to attach the directory when server for serving the hosted previews. More info on how to attach the directory when
starting the container can be found [here](vm-setup--start-docker-container.md). starting the container can be found [here](vm-setup--start-docker-container.md).
In order for the container to be able to find the certificate and key, they should be named In order for the container to be able to find the certificate and key, they should be named
@ -61,15 +61,15 @@ The following log files are kept in this directory:
used when running tests locally from inside the container, e.g. with the `aio-verify-setup` used when running tests locally from inside the container, e.g. with the `aio-verify-setup`
command. (See [here](overview--scripts-and-commands.md) for more info.) command. (See [here](overview--scripts-and-commands.md) for more info.)
- `upload-server-{prod,test,verify-setup}-*.log`: - `preview-server-{prod,test,verify-setup}-*.log`:
The logs produced by the Node.js upload-server while serving either: The logs produced by the Node.js preview-server while serving either:
- `-prod`: "Production" files (g.g during normal operation). - `-prod`: "Production" files (g.g during normal operation).
- `-test`: "Test" files (e.g. when a test instance is started with the `aio-upload-server-test` - `-test`: "Test" files (e.g. when a test instance is started with the `aio-preview-server-test`
command). command).
- `-verify-setup`: "Test" files, but while running `aio-verify-setup`. - `-verify-setup`: "Test" files, but while running `aio-verify-setup`.
(See [here](overview--scripts-and-commands.md) for more info the commands mentioned above.) (See [here](overview--scripts-and-commands.md) for more info the commands mentioned above.)
- `verify-setup.log`: - `verify-setup.log`:
The output of the `aio-verify-setup` command (e.g. Jasmine output), except for upload-server The output of the `aio-verify-setup` command (e.g. Jasmine output), except for preview-server
output which is logged to `upload-server-verify-setup-*.log` (see above). output which is logged to `preview-server-verify-setup-*.log` (see above).

View File

@ -31,7 +31,7 @@ sudo docker run \
--detach \ --detach \
# Use the local DNS server. # Use the local DNS server.
# (This is necessary for mapping internal URLs, e.g. for the Node.js upload-server.) # (This is necessary for mapping internal URLs, e.g. for the Node.js preview-server.)
--dns 127.0.0.1 \ --dns 127.0.0.1 \
# USe `<instance-name>` as an alias for the container. # USe `<instance-name>` as an alias for the container.
@ -50,7 +50,7 @@ sudo docker run \
# (See [here](vm-setup--set-up-secrets.md) for more info.) # (See [here](vm-setup--set-up-secrets.md) for more info.)
--volume <host-secrets-dir>:/aio-secrets:ro \ --volume <host-secrets-dir>:/aio-secrets:ro \
# The uploaded build artifacts will stored to and served from this directory. # The build artifacts and hosted previews will stored to and served from this directory.
# (If you are using a persistent disk - as described [here](vm-setup--attach-persistent-disk.md) - # (If you are using a persistent disk - as described [here](vm-setup--attach-persistent-disk.md) -
# this will be a directory inside the disk.) # this will be a directory inside the disk.)
--volume <host-builds-dir>:/var/www/aio-builds \ --volume <host-builds-dir>:/var/www/aio-builds \