add secondary domains
note that for updates to work, we keep the secondary domain optional, even though they are really not. part of #809
This commit is contained in:
99
src/apps.js
99
src/apps.js
@@ -127,6 +127,7 @@ exports = module.exports = {
|
||||
|
||||
// subdomain table types
|
||||
SUBDOMAIN_TYPE_PRIMARY: 'primary',
|
||||
SUBDOMAIN_TYPE_SECONDARY: 'secondary',
|
||||
SUBDOMAIN_TYPE_REDIRECT: 'redirect',
|
||||
SUBDOMAIN_TYPE_ALIAS: 'alias',
|
||||
|
||||
@@ -186,7 +187,6 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS
|
||||
|
||||
// const PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(',');
|
||||
|
||||
// validate the port bindings
|
||||
function validatePortBindings(portBindings, manifest) {
|
||||
assert.strictEqual(typeof portBindings, 'object');
|
||||
assert.strictEqual(typeof manifest, 'object');
|
||||
@@ -269,6 +269,29 @@ function translatePortBindings(portBindings, manifest) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function validateSecondaryDomains(secondaryDomains, manifest) {
|
||||
assert.strictEqual(typeof secondaryDomains, 'object');
|
||||
assert.strictEqual(typeof manifest, 'object');
|
||||
|
||||
const httpPorts = manifest.httpPorts || {};
|
||||
|
||||
for (const envName in secondaryDomains) {
|
||||
if (!(envName in httpPorts)) return new BoxError(BoxError.BAD_FIELD, `Invalid secondaryDomain in ${envName}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function translateSecondaryDomains(secondaryDomains) {
|
||||
assert.strictEqual(typeof secondaryDomains, 'object');
|
||||
|
||||
const result = [];
|
||||
for (const envName in secondaryDomains) {
|
||||
result.push([ { domain: secondaryDomains[envName].domain, subdomain: secondaryDomains[envName].subdomain, environmentVariable: envName }]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseCrontab(crontab) {
|
||||
assert(crontab === null || typeof crontab === 'string');
|
||||
|
||||
@@ -499,14 +522,14 @@ function removeInternalFields(app) {
|
||||
'location', 'domain', 'fqdn', 'crontab',
|
||||
'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'operators',
|
||||
'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags',
|
||||
'label', 'redirectDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts',
|
||||
'label', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts',
|
||||
'enableMailbox', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain');
|
||||
}
|
||||
|
||||
// non-admins can only see these
|
||||
function removeRestrictedFields(app) {
|
||||
return _.pick(app,
|
||||
'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'accessRestriction', 'redirectDomains', 'aliasDomains', 'sso',
|
||||
'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'accessRestriction', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'sso',
|
||||
'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup');
|
||||
}
|
||||
|
||||
@@ -602,17 +625,25 @@ function postProcess(result) {
|
||||
result.servicesConfig = safe.JSON.parse(result.servicesConfigJson) || {};
|
||||
delete result.servicesConfigJson;
|
||||
|
||||
let subdomains = JSON.parse(result.subdomains), domains = JSON.parse(result.domains), subdomainTypes = JSON.parse(result.subdomainTypes);
|
||||
const subdomains = JSON.parse(result.subdomains),
|
||||
domains = JSON.parse(result.domains),
|
||||
subdomainTypes = JSON.parse(result.subdomainTypes),
|
||||
subdomainEnvironmentVariables = JSON.parse(result.subdomainEnvironmentVariables);
|
||||
|
||||
delete result.subdomains;
|
||||
delete result.domains;
|
||||
delete result.subdomainTypes;
|
||||
delete result.subdomainEnvironmentVariables;
|
||||
|
||||
result.secondaryDomains = [];
|
||||
result.redirectDomains = [];
|
||||
result.aliasDomains = [];
|
||||
for (let i = 0; i < subdomainTypes.length; i++) {
|
||||
if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_PRIMARY) {
|
||||
result.location = subdomains[i];
|
||||
result.domain = domains[i];
|
||||
} else if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_SECONDARY) {
|
||||
result.secondaryDomains.push({ domain: domains[i], subdomain: subdomains[i], environmentVariable: subdomainEnvironmentVariables[i] });
|
||||
} else if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_REDIRECT) {
|
||||
result.redirectDomains.push({ domain: domains[i], subdomain: subdomains[i] });
|
||||
} else if (subdomainTypes[i] === exports.SUBDOMAIN_TYPE_ALIAS) {
|
||||
@@ -652,6 +683,7 @@ function attachProperties(app, domainObjectMap) {
|
||||
app.portBindings = result;
|
||||
app.iconUrl = app.hasIcon || app.hasAppStoreIcon ? `/api/v1/apps/${app.id}/icon` : null;
|
||||
app.fqdn = dns.fqdn(app.location, domainObjectMap[app.domain]);
|
||||
app.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
app.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
app.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
}
|
||||
@@ -755,6 +787,15 @@ async function add(id, appStoreId, manifest, location, domain, portBindings, dat
|
||||
});
|
||||
});
|
||||
|
||||
if (data.secondaryDomains) {
|
||||
data.secondaryDomains.forEach(function (d) {
|
||||
queries.push({
|
||||
query: 'INSERT INTO subdomains (appId, domain, subdomain, type, environmentVariable) VALUES (?, ?, ?, ?, ?)',
|
||||
args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_SECONDARY, d.environmentVariable ]
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (data.redirectDomains) {
|
||||
data.redirectDomains.forEach(function (d) {
|
||||
queries.push({
|
||||
@@ -793,6 +834,7 @@ async function updateWithConstraints(id, app, constraints) {
|
||||
assert.strictEqual(typeof constraints, 'string');
|
||||
assert(!('portBindings' in app) || typeof app.portBindings === 'object');
|
||||
assert(!('accessRestriction' in app) || typeof app.accessRestriction === 'object' || app.accessRestriction === '');
|
||||
assert(!('secondaryDomains' in app) || Array.isArray(app.secondaryDomains));
|
||||
assert(!('redirectDomains' in app) || Array.isArray(app.redirectDomains));
|
||||
assert(!('aliasDomains' in app) || Array.isArray(app.aliasDomains));
|
||||
assert(!('tags' in app) || Array.isArray(app.tags));
|
||||
@@ -825,6 +867,12 @@ async function updateWithConstraints(id, app, constraints) {
|
||||
queries.push({ query: 'DELETE FROM subdomains WHERE appId = ?', args: [ id ]}); // all locations of an app must be updated together
|
||||
queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, app.domain, app.location, exports.SUBDOMAIN_TYPE_PRIMARY ]});
|
||||
|
||||
if ('secondaryDomains' in app) {
|
||||
app.secondaryDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type, environmentVariable) VALUES (?, ?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_SECONDARY, d.environmentVariable ]});
|
||||
});
|
||||
}
|
||||
|
||||
if ('redirectDomains' in app) {
|
||||
app.redirectDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO subdomains (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, exports.SUBDOMAIN_TYPE_REDIRECT ]});
|
||||
@@ -850,7 +898,7 @@ async function updateWithConstraints(id, app, constraints) {
|
||||
if (p === 'manifest' || p === 'tags' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig' || p === 'operators') {
|
||||
fields.push(`${p}Json = ?`);
|
||||
values.push(JSON.stringify(app[p]));
|
||||
} else if (p !== 'portBindings' && p !== 'location' && p !== 'domain' && p !== 'redirectDomains' && p !== 'aliasDomains' && p !== 'env' && p !== 'mounts') {
|
||||
} else if (p !== 'portBindings' && p !== 'location' && p !== 'domain' && p !== 'secondaryDomains' && p !== 'redirectDomains' && p !== 'aliasDomains' && p !== 'env' && p !== 'mounts') {
|
||||
fields.push(p + ' = ?');
|
||||
values.push(app[p]);
|
||||
}
|
||||
@@ -940,9 +988,9 @@ async function getDomainObjectMap() {
|
||||
// each query simply join apps table with another table by id. we then join the full result together
|
||||
const PB_QUERY = 'SELECT id, GROUP_CONCAT(CAST(appPortBindings.hostPort AS CHAR(6))) AS hostPorts, GROUP_CONCAT(appPortBindings.environmentVariable) AS environmentVariables, GROUP_CONCAT(appPortBindings.type) AS portTypes FROM apps LEFT JOIN appPortBindings ON apps.id = appPortBindings.appId GROUP BY apps.id';
|
||||
const ENV_QUERY = 'SELECT id, JSON_ARRAYAGG(appEnvVars.name) AS envNames, JSON_ARRAYAGG(appEnvVars.value) AS envValues FROM apps LEFT JOIN appEnvVars ON apps.id = appEnvVars.appId GROUP BY apps.id';
|
||||
const SUBDOMAIN_QUERY = 'SELECT id, JSON_ARRAYAGG(subdomains.subdomain) AS subdomains, JSON_ARRAYAGG(subdomains.domain) AS domains, JSON_ARRAYAGG(subdomains.type) AS subdomainTypes FROM apps LEFT JOIN subdomains ON apps.id = subdomains.appId GROUP BY apps.id';
|
||||
const SUBDOMAIN_QUERY = 'SELECT id, JSON_ARRAYAGG(subdomains.subdomain) AS subdomains, JSON_ARRAYAGG(subdomains.domain) AS domains, JSON_ARRAYAGG(subdomains.type) AS subdomainTypes, JSON_ARRAYAGG(subdomains.environmentVariable) AS subdomainEnvironmentVariables FROM apps LEFT JOIN subdomains ON apps.id = subdomains.appId GROUP BY apps.id';
|
||||
const MOUNTS_QUERY = 'SELECT id, JSON_ARRAYAGG(appMounts.volumeId) AS volumeIds, JSON_ARRAYAGG(appMounts.readOnly) AS volumeReadOnlys FROM apps LEFT JOIN appMounts ON apps.id = appMounts.appId GROUP BY apps.id';
|
||||
const APPS_QUERY = `SELECT ${APPS_FIELDS_PREFIXED}, hostPorts, environmentVariables, portTypes, envNames, envValues, subdomains, domains, subdomainTypes, volumeIds, volumeReadOnlys FROM apps`
|
||||
const APPS_QUERY = `SELECT ${APPS_FIELDS_PREFIXED}, hostPorts, environmentVariables, portTypes, envNames, envValues, subdomains, domains, subdomainTypes, subdomainEnvironmentVariables, volumeIds, volumeReadOnlys FROM apps`
|
||||
+ ` LEFT JOIN (${PB_QUERY}) AS q1 on q1.id = apps.id`
|
||||
+ ` LEFT JOIN (${ENV_QUERY}) AS q2 on q2.id = apps.id`
|
||||
+ ` LEFT JOIN (${SUBDOMAIN_QUERY}) AS q3 on q3.id = apps.id`
|
||||
@@ -1222,6 +1270,14 @@ async function install(data, auditSource) {
|
||||
error = validateTags(tags);
|
||||
if (error) throw error;
|
||||
|
||||
let secondaryDomains = [];
|
||||
if ('secondaryDomains' in data) {
|
||||
error = validateSecondaryDomains(data.secondaryDomains, manifest);
|
||||
if (error) throw error;
|
||||
|
||||
secondaryDomains = translateSecondaryDomains(data.secondaryDomains);
|
||||
}
|
||||
|
||||
let sso = 'sso' in data ? data.sso : null;
|
||||
if ('sso' in data && !('optionalSso' in manifest)) throw new BoxError(BoxError.BAD_FIELD, 'sso can only be specified for apps with optionalSso');
|
||||
// if sso was unspecified, enable it by default if possible
|
||||
@@ -1243,9 +1299,10 @@ async function install(data, auditSource) {
|
||||
icon = Buffer.from(icon, 'base64');
|
||||
}
|
||||
|
||||
const locations = [{ subdomain: location, domain, type: 'primary' }]
|
||||
.concat(redirectDomains.map(ad => _.extend(ad, { type: 'redirect' })))
|
||||
.concat(aliasDomains.map(ad => _.extend(ad, { type: 'alias' })));
|
||||
const locations = [{ subdomain: location, domain, type: exports.SUBDOMAIN_TYPE_PRIMARY }]
|
||||
.concat(secondaryDomains.map(ad => _.extend(ad, { type: exports.SUBDOMAIN_TYPE_SECONDARY })))
|
||||
.concat(redirectDomains.map(ad => _.extend(ad, { type: exports.SUBDOMAIN_TYPE_REDIRECT })))
|
||||
.concat(aliasDomains.map(ad => _.extend(ad, { type: exports.SUBDOMAIN_TYPE_ALIAS })));
|
||||
|
||||
const domainObjectMap = await validateLocations(locations);
|
||||
|
||||
@@ -1263,6 +1320,7 @@ async function install(data, auditSource) {
|
||||
mailboxDomain,
|
||||
enableBackup,
|
||||
enableAutomaticUpdate,
|
||||
secondaryDomains,
|
||||
redirectDomains,
|
||||
aliasDomains,
|
||||
env,
|
||||
@@ -1290,6 +1348,7 @@ async function install(data, auditSource) {
|
||||
|
||||
const newApp = _.extend({}, _.omit(app, 'icon'), { appStoreId, manifest, location, domain, portBindings });
|
||||
newApp.fqdn = dns.fqdn(newApp.location, domainObjectMap[newApp.domain]);
|
||||
newApp.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
newApp.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
newApp.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
|
||||
@@ -1643,6 +1702,7 @@ async function setLocation(app, data, auditSource) {
|
||||
domain: data.domain.toLowerCase(),
|
||||
// these are intentionally reset, if not set
|
||||
portBindings: null,
|
||||
secondaryDomains: [],
|
||||
redirectDomains: [],
|
||||
aliasDomains: []
|
||||
};
|
||||
@@ -1660,6 +1720,13 @@ async function setLocation(app, data, auditSource) {
|
||||
values.mailboxDomain = values.domain;
|
||||
}
|
||||
|
||||
if ('secondaryDomains' in data) {
|
||||
error = validateSecondaryDomains(data.secondaryDomains, app.manifest);
|
||||
if (error) throw error;
|
||||
|
||||
values.secondaryDomains = translateSecondaryDomains(data.secondaryDomains);
|
||||
}
|
||||
|
||||
if ('redirectDomains' in data) {
|
||||
values.redirectDomains = data.redirectDomains;
|
||||
}
|
||||
@@ -1668,15 +1735,16 @@ async function setLocation(app, data, auditSource) {
|
||||
values.aliasDomains = data.aliasDomains;
|
||||
}
|
||||
|
||||
const locations = [{ subdomain: values.location, domain: values.domain, type: 'primary' }]
|
||||
.concat(values.redirectDomains.map(ad => _.extend(ad, { type: 'redirect' })))
|
||||
.concat(values.aliasDomains.map(ad => _.extend(ad, { type: 'alias' })));
|
||||
const locations = [{ subdomain: values.location, domain: values.domain, type: exports.SUBDOMAIN_TYPE_PRIMARY }]
|
||||
.concat(values.secondaryDomains.map(ad => _.extend(ad, { type: exports.SUBDOMAIN_TYPE_SECONDARY })))
|
||||
.concat(values.redirectDomains.map(ad => _.extend(ad, { type: exports.SUBDOMAIN_TYPE_REDIRECT })))
|
||||
.concat(values.aliasDomains.map(ad => _.extend(ad, { type: exports.SUBDOMAIN_TYPE_ALIAS })));
|
||||
|
||||
const domainObjectMap = await validateLocations(locations);
|
||||
|
||||
const task = {
|
||||
args: {
|
||||
oldConfig: _.pick(app, 'location', 'domain', 'fqdn', 'redirectDomains', 'aliasDomains', 'portBindings'),
|
||||
oldConfig: _.pick(app, 'location', 'domain', 'fqdn', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'portBindings'),
|
||||
skipDnsSetup: !!data.skipDnsSetup,
|
||||
overwriteDns: !!data.overwriteDns
|
||||
},
|
||||
@@ -1687,6 +1755,7 @@ async function setLocation(app, data, auditSource) {
|
||||
if (taskError) throw taskError;
|
||||
|
||||
values.fqdn = dns.fqdn(values.location, domainObjectMap[values.domain]);
|
||||
values.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
values.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
values.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
|
||||
@@ -2126,6 +2195,7 @@ async function clone(app, data, user, auditSource) {
|
||||
enableBackup: app.enableBackup,
|
||||
reverseProxyConfig: app.reverseProxyConfig,
|
||||
env: app.env,
|
||||
secondaryDomains: [],
|
||||
redirectDomains: [],
|
||||
aliasDomains: [],
|
||||
servicesConfig: app.servicesConfig,
|
||||
@@ -2152,6 +2222,7 @@ async function clone(app, data, user, auditSource) {
|
||||
|
||||
const newApp = _.extend({}, _.omit(obj, 'icon'), { appStoreId, manifest, location, domain, portBindings });
|
||||
newApp.fqdn = dns.fqdn(newApp.location, domainObjectMap[newApp.domain]);
|
||||
newApp.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
newApp.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
newApp.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user