Add datapaneall and fetchall for handling clusters with multiple servers

Better error handling on domain add
Better error handling (and clearing) in frontend api.js
develop
Thomas Lynch 1 year ago
parent 71195b808a
commit 216dd1f932
  1. 7
      api.js
  2. 2
      components/ErrorAlert.js
  3. 18
      controllers/account.js
  4. 59
      controllers/domains.js
  5. 23
      controllers/maps.js
  6. 24
      router.js

@ -82,6 +82,7 @@ export async function ApiCall(route, method='get', body, dispatch, errorCallback
// Make request, catch errors, and finally{} to always end progress bar
let response;
try {
errorCallback(null);
response = await fetch(route, requestOptions);
} catch(e) {
console.error(e);
@ -95,11 +96,17 @@ export async function ApiCall(route, method='get', body, dispatch, errorCallback
if (!response) {
errorCallback('An error occurred');
NProgress.done(true);
return;
}
// Process request response
const contentType = response.headers.get('Content-type');
if (!contentType) {
errorCallback('An error occurred');
NProgress.done(true);
return;
}
if (contentType.startsWith('application/json;')) {
response = await response.json();
if (response.redirect) {

@ -1,5 +1,5 @@
export default function ErrorAlert({ error }) {
return (
return error && (
<div className="alert alert-danger" role="alert">
{error}
</div>

@ -58,21 +58,19 @@ exports.globalToggle = async (req, res, next) => {
.getOneRuntimeMap('ddos_global')
.then(res => res.data.description.split('').reverse()[0])
if (globalAcl === '1') {
await res.locals.dataPlane
.deleteRuntimeMapEntry({
await res.locals
.dataPlaneAll('deleteRuntimeMapEntry', {
map: 'ddos_global',
id: 'true'
});
} else {
await res.locals.dataPlane
.addPayloadRuntimeMap({
await res.locals
.dataPlaneAll('addPayloadRuntimeMap', {
name: 'ddos_global'
}, [
{
key: 'true',
value: 'true'
}
]);
}, [{
key: 'true',
value: 'true'
}]);
}
} catch (e) {
return next(e);

@ -28,7 +28,7 @@ exports.domainsJson = async (req, res) => {
* POST /domain/add
* add domain
*/
exports.addDomain = async (req, res) => {
exports.addDomain = async (req, res, next) => {
if (!req.body.domain || typeof req.body.domain !== 'string' || req.body.domain.length === 0) {
return dynamicResponse(req, res, 400, { error: 'Invalid input' });
@ -40,35 +40,44 @@ exports.addDomain = async (req, res) => {
return dynamicResponse(req, res, 400, { error: 'Invalid input' });
}
const { csr, key, cert, haproxyCert, date } = await acme.generate(req.body.domain);
const fd = new FormData();
fd.append('file_upload', new Blob([haproxyCert], { type: 'text/plain' }), `${req.body.domain}.pem`);
const { description, file, storage_name: storageName } = await fetch(`${res.locals.dataPlane.defaults.baseURL}/services/haproxy/storage/ssl_certificates`, {
method: 'POST',
headers: { 'authorization': res.locals.dataPlane.defaults.headers.authorization },
body: fd,
})
.then(certRes => certRes.json());
const account = await db.db.collection('certs')
.replaceOne({
_id: req.body.domain,
}, {
try {
const { csr, key, cert, haproxyCert, date } = await acme.generate(req.body.domain);
const fd = new FormData();
fd.append('file_upload', new Blob([haproxyCert], { type: 'text/plain' }), `${req.body.domain}.pem`);
const { description, file, storage_name: storageName } = await res.locals.fetchAll('/v2/services/haproxy/storage/ssl_certificates?force_reload=true', {
method: 'POST',
headers: { 'authorization': res.locals.dataPlane.defaults.headers.authorization },
body: fd,
});
let update = {
_id: req.body.domain,
username: res.locals.user.username,
csr, key, cert, haproxyCert, // cert creation data
description, file, storageName, // dataplane api response
date,
}, {
upsert: true,
});
//TODO: make upload to all user clusters/servers
//TODO: add scheduled task to aggregate domains and upload certs to clusters of that username through dataplane
//TODO: make scheduled task also run this again for certs close to expiry and repeat ^
//TODO: 90 day expiry on cert documents with index on date
//TODO: on domain removal, keep cert to use for re-adding if we still have the cert in DB
}
if (description) {
//may be null due to "already exists", so we keep existing props
update = { ...update, description, file, storageName };
}
await db.db.collection('certs')
.updateOne({
_id: req.body.domain,
}, {
$set: update,
}, {
upsert: true,
});
await db.db.collection('accounts')
.updateOne({_id: res.locals.user.username}, {$addToSet: {domains: req.body.domain }});
//TODO: add scheduled task to aggregate domains and upload certs to clusters of that username through dataplane
//TODO: make scheduled task also run this again for certs close to expiry and repeat ^
//TODO: 90 day expiry on cert documents with index on date
//TODO: on domain removal, keep cert to use for re-adding if we still have the cert in DB
await db.db.collection('accounts')
.updateOne({_id: res.locals.user.username}, {$addToSet: {domains: req.body.domain }});
} catch (e) {
return next(e);
}
return dynamicResponse(req, res, 302, { redirect: '/domains' });
};

@ -104,12 +104,12 @@ exports.deleteMapForm = async (req, res, next) => {
.catch(() => {});
if (backendMapEntry) {
await res.locals
.dataPlane.deleteRuntimeServer({
.dataPlaneAll('deleteRuntimeServer', {
backend: 'servers',
name: backendMapEntry.value,
});
await res.locals
.dataPlane.deleteRuntimeMapEntry({
.dataPlaneAll('deleteRuntimeMapEntry', {
map: process.env.BACKENDS_MAP_NAME, //'backends'
id: req.body.key, //'example.com'
});
@ -120,7 +120,7 @@ exports.deleteMapForm = async (req, res, next) => {
}
await res.locals
.dataPlane.deleteRuntimeMapEntry({
.dataPlaneAll('deleteRuntimeMapEntry', {
map: req.params.name, //'ddos'
id: req.body.key, //'example.com'
});
@ -238,15 +238,8 @@ exports.patchMapForm = async (req, res, next) => {
return dynamicResponse(req, res, 400, { error: 'No server slots available' });
}
const [address, port] = value.split(':');
//delete, add because apparently "replace" cant update the fucking ADDRESS???
// await res.locals
// .dataPlane.deleteRuntimeServer({
// backend: 'servers',
// name: `websrv${freeSlotId}`,
// })
// .catch(e => console.error(e));
await res.locals
.dataPlane.addRuntimeServer({
.dataPlaneAll('addRuntimeServer', {
backend: 'servers',
}, {
address,
@ -254,8 +247,8 @@ exports.patchMapForm = async (req, res, next) => {
name: `websrv${freeSlotId}`,
id: `${freeSlotId}`,
});
await res.locals.dataPlane
.addPayloadRuntimeMap({
await res.locals
.dataPlaneAll('addPayloadRuntimeMap', {
name: process.env.BACKENDS_MAP_NAME,
}, [{
key: req.body.key,
@ -264,8 +257,8 @@ exports.patchMapForm = async (req, res, next) => {
}
}
await res.locals.dataPlane
.addPayloadRuntimeMap({
await res.locals
.dataPlaneAll('addPayloadRuntimeMap', {
name: req.params.name
}, [{
key: req.body.key,

@ -84,7 +84,11 @@ const testRouter = (server, app) => {
//TODO: handle cluster
res.locals.fMap = server.locals.fMap;
res.locals.mapValueNames = server.locals.mapValueNames;
const firstClusterURL = new URL(res.locals.user.clusters[res.locals.user.activeCluster].split(',')[0]);
const clusterUrls = res.locals.user.clusters[res.locals.user.activeCluster]
.split(',')
.map(u => new URL(u));
const firstClusterURL = clusterUrls[0];
//NOTE: all servers in cluster must have same credentials for now
const base64Auth = Buffer.from(`${firstClusterURL.username}:${firstClusterURL.password}`).toString("base64");
const api = new OpenAPIClientAxios({
definition: `${firstClusterURL.origin}/v2/specification_openapiv3`,
@ -94,8 +98,22 @@ const testRouter = (server, app) => {
}
}
});
res.locals.dataPlane = await api.init();
res.locals.dataPlane.defaults.baseURL = `${firstClusterURL.origin}/v2`;
const apiInstance = await api.init();
apiInstance.defaults.baseURL = `${firstClusterURL.origin}/v2`;
res.locals.dataPlane = apiInstance;
res.locals.dataPlaneAll = async (operationId, parameters, data, config) => {
const promiseResults = await Promise.all(clusterUrls.map(clusterUrl => {
return apiInstance[operationId](parameters, data, { ...config, baseUrl: `${clusterUrl.origin}/v2` })
}));
return promiseResults[0]; //TODO: better desync handling
}
res.locals.fetchAll = async (path, options) => {
//used for stuff that dataplaneapi with axios seems to struggle with e.g. multipart body
const promiseResults = await Promise.all(clusterUrls.map(clusterUrl => {
return fetch(`${clusterUrl.origin}${path}`, options).then(resp => resp.json());
}));
return promiseResults[0]; //TODO: better desync handling
}
next();
} catch (e) {
return dynamicResponse(req, res, 500, { error: e });

Loading…
Cancel
Save