mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 18:56:39 +00:00
Handle different routers based on target path
This commit is contained in:
@@ -115,7 +115,9 @@ export const targets = pgTable("targets", {
|
|||||||
method: varchar("method"),
|
method: varchar("method"),
|
||||||
port: integer("port").notNull(),
|
port: integer("port").notNull(),
|
||||||
internalPort: integer("internalPort"),
|
internalPort: integer("internalPort"),
|
||||||
enabled: boolean("enabled").notNull().default(true)
|
enabled: boolean("enabled").notNull().default(true),
|
||||||
|
path: text("path"),
|
||||||
|
pathMatchType: text("pathMatchType"), // exact, prefix, regex
|
||||||
});
|
});
|
||||||
|
|
||||||
export const exitNodes = pgTable("exitNodes", {
|
export const exitNodes = pgTable("exitNodes", {
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ export const targets = sqliteTable("targets", {
|
|||||||
method: text("method"),
|
method: text("method"),
|
||||||
port: integer("port").notNull(),
|
port: integer("port").notNull(),
|
||||||
internalPort: integer("internalPort"),
|
internalPort: integer("internalPort"),
|
||||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true)
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
|
path: text("path"),
|
||||||
|
pathMatchType: text("pathMatchType"), // exact, prefix, regex
|
||||||
});
|
});
|
||||||
|
|
||||||
export const exitNodes = sqliteTable("exitNodes", {
|
export const exitNodes = sqliteTable("exitNodes", {
|
||||||
|
|||||||
@@ -105,11 +105,9 @@ export async function getTraefikConfig(
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all resources with related data
|
|
||||||
const allResources = await db.transaction(async (tx) => {
|
|
||||||
// Get resources with their targets and sites in a single optimized query
|
// Get resources with their targets and sites in a single optimized query
|
||||||
// Start from sites on this exit node, then join to targets and resources
|
// Start from sites on this exit node, then join to targets and resources
|
||||||
const resourcesWithTargetsAndSites = await tx
|
const resourcesWithTargetsAndSites = await db
|
||||||
.select({
|
.select({
|
||||||
// Resource fields
|
// Resource fields
|
||||||
resourceId: resources.resourceId,
|
resourceId: resources.resourceId,
|
||||||
@@ -133,6 +131,9 @@ export async function getTraefikConfig(
|
|||||||
method: targets.method,
|
method: targets.method,
|
||||||
port: targets.port,
|
port: targets.port,
|
||||||
internalPort: targets.internalPort,
|
internalPort: targets.internalPort,
|
||||||
|
path: targets.path,
|
||||||
|
pathMatchType: targets.pathMatchType,
|
||||||
|
|
||||||
// Site fields
|
// Site fields
|
||||||
siteId: sites.siteId,
|
siteId: sites.siteId,
|
||||||
siteType: sites.type,
|
siteType: sites.type,
|
||||||
@@ -163,9 +164,15 @@ export async function getTraefikConfig(
|
|||||||
|
|
||||||
resourcesWithTargetsAndSites.forEach((row) => {
|
resourcesWithTargetsAndSites.forEach((row) => {
|
||||||
const resourceId = row.resourceId;
|
const resourceId = row.resourceId;
|
||||||
|
const targetPath = sanitizePath(row.path) || ""; // Handle null/undefined paths
|
||||||
|
const pathMatchType = row.pathMatchType || "";
|
||||||
|
|
||||||
if (!resourcesMap.has(resourceId)) {
|
// Create a unique key combining resourceId and path+pathMatchType
|
||||||
resourcesMap.set(resourceId, {
|
const pathKey = [targetPath, pathMatchType].filter(Boolean).join("-");
|
||||||
|
const mapKey = [resourceId, pathKey].filter(Boolean).join("-");
|
||||||
|
|
||||||
|
if (!resourcesMap.has(mapKey)) {
|
||||||
|
resourcesMap.set(mapKey, {
|
||||||
resourceId: row.resourceId,
|
resourceId: row.resourceId,
|
||||||
fullDomain: row.fullDomain,
|
fullDomain: row.fullDomain,
|
||||||
ssl: row.ssl,
|
ssl: row.ssl,
|
||||||
@@ -180,12 +187,14 @@ export async function getTraefikConfig(
|
|||||||
setHostHeader: row.setHostHeader,
|
setHostHeader: row.setHostHeader,
|
||||||
enableProxy: row.enableProxy,
|
enableProxy: row.enableProxy,
|
||||||
targets: [],
|
targets: [],
|
||||||
headers: row.headers
|
headers: row.headers,
|
||||||
|
path: row.path, // the targets will all have the same path
|
||||||
|
pathMatchType: row.pathMatchType // the targets will all have the same pathMatchType
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add target with its associated site data
|
// Add target with its associated site data
|
||||||
resourcesMap.get(resourceId).targets.push({
|
resourcesMap.get(mapKey).targets.push({
|
||||||
resourceId: row.resourceId,
|
resourceId: row.resourceId,
|
||||||
targetId: row.targetId,
|
targetId: row.targetId,
|
||||||
ip: row.ip,
|
ip: row.ip,
|
||||||
@@ -203,10 +212,13 @@ export async function getTraefikConfig(
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(resourcesMap.values());
|
|
||||||
});
|
// convert the map to an object for printing
|
||||||
|
|
||||||
if (!allResources.length) {
|
logger.debug(`Resources: ${JSON.stringify(Object.fromEntries(resourcesMap), null, 2)}`);
|
||||||
|
|
||||||
|
// make sure we have at least one resource
|
||||||
|
if (resourcesMap.size === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,14 +234,15 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const resource of allResources) {
|
// get the key and the resource
|
||||||
|
for (const [key, resource] of resourcesMap.entries()) {
|
||||||
const targets = resource.targets;
|
const targets = resource.targets;
|
||||||
|
|
||||||
const routerName = `${resource.resourceId}-router`;
|
const routerName = `${key}-router`;
|
||||||
const serviceName = `${resource.resourceId}-service`;
|
const serviceName = `${key}-service`;
|
||||||
const fullDomain = `${resource.fullDomain}`;
|
const fullDomain = `${resource.fullDomain}`;
|
||||||
const transportName = `${resource.resourceId}-transport`;
|
const transportName = `${key}-transport`;
|
||||||
const hostHeaderMiddlewareName = `${resource.resourceId}-host-header-middleware`;
|
const headersMiddlewareName = `${key}-headers-middleware`;
|
||||||
|
|
||||||
if (!resource.enabled) {
|
if (!resource.enabled) {
|
||||||
continue;
|
continue;
|
||||||
@@ -241,9 +254,6 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!resource.fullDomain) {
|
if (!resource.fullDomain) {
|
||||||
logger.error(
|
|
||||||
`Resource ${resource.resourceId} has no fullDomain`
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,7 +315,6 @@ export async function getTraefikConfig(
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (resource.headers && resource.headers.length > 0) {
|
if (resource.headers && resource.headers.length > 0) {
|
||||||
const headersMiddlewareName = `${resource.resourceId}-headers-middleware`;
|
|
||||||
// if there are headers, parse them into an object
|
// if there are headers, parse them into an object
|
||||||
let headersObj: { [key: string]: string } = {};
|
let headersObj: { [key: string]: string } = {};
|
||||||
const headersArr = resource.headers.split(",");
|
const headersArr = resource.headers.split(",");
|
||||||
@@ -338,6 +347,18 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let rule = `Host(\`${fullDomain}\`)`;
|
||||||
|
if (resource.path && resource.pathMatchType) {
|
||||||
|
// add path to rule based on match type
|
||||||
|
if (resource.pathMatchType === "exact") {
|
||||||
|
rule += ` && Path(\`${resource.path}\`)`;
|
||||||
|
} else if (resource.pathMatchType === "prefix") {
|
||||||
|
rule += ` && PathPrefix(\`${resource.path}\`)`;
|
||||||
|
} else if (resource.pathMatchType === "regex") {
|
||||||
|
rule += ` && PathRegexp(\`${resource.path}\`)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config_output.http.routers![routerName] = {
|
config_output.http.routers![routerName] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
resource.ssl
|
resource.ssl
|
||||||
@@ -346,7 +367,7 @@ export async function getTraefikConfig(
|
|||||||
],
|
],
|
||||||
middlewares: routerMiddlewares,
|
middlewares: routerMiddlewares,
|
||||||
service: serviceName,
|
service: serviceName,
|
||||||
rule: `Host(\`${fullDomain}\`)`,
|
rule: rule,
|
||||||
priority: 100,
|
priority: 100,
|
||||||
...(resource.ssl ? { tls } : {})
|
...(resource.ssl ? { tls } : {})
|
||||||
};
|
};
|
||||||
@@ -358,7 +379,7 @@ export async function getTraefikConfig(
|
|||||||
],
|
],
|
||||||
middlewares: [redirectHttpsMiddlewareName],
|
middlewares: [redirectHttpsMiddlewareName],
|
||||||
service: serviceName,
|
service: serviceName,
|
||||||
rule: `Host(\`${fullDomain}\`)`,
|
rule: rule,
|
||||||
priority: 100
|
priority: 100
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -550,3 +571,13 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
return config_output;
|
return config_output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizePath(path: string | null | undefined): string | undefined {
|
||||||
|
if (!path) return undefined;
|
||||||
|
// clean any non alphanumeric characters from the path and replace with dashes
|
||||||
|
// the path cant be too long either, so limit to 50 characters
|
||||||
|
if (path.length > 50) {
|
||||||
|
path = path.substring(0, 50);
|
||||||
|
}
|
||||||
|
return path.replace(/[^a-zA-Z0-9]/g, "");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user