@adpt/cloud
Version:
AdaptJS cloud component library
193 lines (189 loc) • 7.42 kB
JavaScript
"use strict";
/*
* Copyright 2019 Unbounded Systems, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = tslib_1.__importStar(require("@adpt/core"));
const utils_1 = require("@adpt/utils");
const child_process_1 = require("child_process");
const fs_extra_1 = require("fs-extra");
const path = tslib_1.__importStar(require("path"));
const url_1 = require("url");
const util_1 = require("util");
const Container_1 = require("../Container");
const docker_1 = require("../docker");
const handles_1 = require("../handles");
const http_1 = require("../http");
const NetworkService_1 = require("../NetworkService");
const Service_1 = require("../Service");
const nginxImg = "nginx:latest";
function upstream(_r, i, _url) {
return `upstream_url_${i}`;
}
function getPort(lurl) {
if (lurl.port && lurl.port !== "") {
return lurl.port;
}
switch (lurl.protocol) {
case "http:": return 80;
case "https:": return 443;
default: return 80; //Throw here?
}
}
function useMakeNginxConf(props) {
const [resolvedRoutes, setResolvedRoutes] = core_1.useState([]);
setResolvedRoutes(() => props.routes.map((r) => {
let lurl;
if (util_1.isString(r.endpoint)) {
lurl = r.endpoint;
}
else {
const hostname = core_1.callInstanceMethod(r.endpoint, undefined, "hostname");
const port = core_1.callInstanceMethod(r.endpoint, undefined, "port");
if (!port || !hostname)
return undefined;
lurl = `http://${hostname}:${port}/`;
}
const upstreamPath = r.upstreamPath || "/";
return { path: r.path, upstreamPath, url: lurl };
}).filter(utils_1.notNull));
const locationBlocks = resolvedRoutes.map((r, i) => {
const lurl = new url_1.URL(r.url);
const varName = upstream(r, i, lurl);
const { hostname, protocol } = lurl;
const port = getPort(lurl);
const upPath = r.upstreamPath;
// NOTE: the set $encoded line is required because the captured
// portion of the regex ($1) has been URL-decoded. The set line
// re-encodes it. But using $1 directly on the proxy_pass line does not.
return `
set $${varName} ${protocol}//${hostname}:${port};
location ~ ^${r.path}(.*)$ {
set $encoded $${varName + upPath}$1;
proxy_pass $encoded;
proxy_set_header Host $host;
}
`;
});
let locationText = locationBlocks.join("\n");
if (locationBlocks.length === 0) {
locationText = `location / {
return ${(props.routes.length === 0) ? "404" : "503"};
}
`;
}
const mainConfig = [];
if (props.debug)
mainConfig.push("error_log stderr debug;");
return `
${mainConfig.join("\n")}
events {
worker_connections 1024;
}
http {
resolver_timeout 1s;
proxy_connect_timeout 11s;
include conf.d/*;
server {
listen ${props.port};
${locationText}
}
}
`;
}
const execP = util_1.promisify(child_process_1.exec);
async function checkNginxConf(conf) {
return utils_1.withTmpDir(async (dir) => {
const confPath = path.join(dir, "nginx.conf");
await fs_extra_1.writeFile(confPath, conf);
try {
// tslint:disable-next-line:max-line-length
await execP(`cat ${confPath} | docker run -i --rm ${nginxImg} bash -c "cat - > /nginx.conf && nginx -t -c /nginx.conf"`, { timeout: 3600 });
}
catch (e) {
if (e.signal !== null) {
throw new Error("Timeout trying to validate nginx configuration");
}
if ((e.code != null) && (e.code !== 0)) {
const errs = e.stdout.toString() + "\n" + e.stderr.toString();
throw new Error(`Internal Error: generated invalid nginx configuration:
${errs}
**Configuration**
${conf}
`);
}
throw e;
}
});
}
const defaultProps = Object.assign({}, http_1.UrlRouter.defaultProps, { debug: false });
/**
* {@link http.UrlRouter} implementation based on {@link https://nginx.org | nginx}
*
* @public
*/
function UrlRouter(propsIn) {
const props = propsIn;
const h = handles_1.handles();
http_1.checkUrlEndpoints(props.routes);
const nginxConf = useMakeNginxConf(props);
//FIXME(manishv) nginx config check will only pass if all hostnames can be resolved locally, how to fix?
if (false)
core_1.useAsync(async () => checkNginxConf(nginxConf), undefined);
const nginxExec = props.debug ? "nginx-debug" : "nginx";
const img = core_1.handle();
const externalPort = props.externalPort || props.port;
return core_1.default.createElement(core_1.Sequence, { key: props.key },
core_1.default.createElement(docker_1.LocalDockerImage, { handle: img, dockerfile: `
FROM ${nginxImg}
RUN apt-get update && \
apt-get install --no-install-recommends --no-install-suggests -y inotify-tools && \
apt-get clean
WORKDIR /router
COPY --from=files / .
RUN chmod a+x start_nginx.sh make_resolvers.sh
CMD [ "/router/start_nginx.sh" ]
`, files: [{
path: "start_nginx.sh",
contents: `#!/bin/sh
mkdir conf.d
./make_resolvers.sh
exec ${nginxExec} -g "daemon off;" -c /router/nginx.conf
`
},
{
path: "make_resolvers.sh",
contents: `#!/bin/sh
conf="resolver $(/usr/bin/awk 'BEGIN{ORS = " "} $1=="nameserver" {print $2}' /etc/resolv.conf);"
[ "$conf" = "resolver ;" ] && exit 0
confpath=conf.d/resolvers.conf
echo "$conf" > $confpath
`
},
{
path: "nginx.conf",
contents: nginxConf
}], options: {
imageName: "nginx-url-router",
uniqueTag: true
} }),
core_1.default.createElement(Service_1.Service, { key: props.key },
core_1.default.createElement(NetworkService_1.NetworkService, { key: props.key + "-netsvc", endpoint: h.create.nginx, port: externalPort, targetPort: props.port, scope: "external" }),
core_1.default.createElement(Container_1.Container, { key: props.key, handle: h.nginx, name: "nginx-url-router", image: img, ports: [props.port], imagePullPolicy: "Never" })));
}
exports.UrlRouter = UrlRouter;
exports.default = UrlRouter;
//# sourceMappingURL=UrlRouter.js.map