Skip to content

Commit

Permalink
#246: improve merging of same-service, same-hostname-path proxy confi…
Browse files Browse the repository at this point in the history
…guration
  • Loading branch information
pirog committed Oct 23, 2024
1 parent 605e7df commit ff25d8d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 7 deletions.
14 changes: 14 additions & 0 deletions examples/proxy/.lando.local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
proxy:
web:
- hostname: lando-proxy.lndo.site
middlewares:
- name: test
key: headers.customresponseheaders.X-Lando-Merge
value: picard
- name: test
key: headers.customresponseheaders.X-Lando-Merge-XO
value: riker

plugins:
"@lando/core": "../.."
"@lando/proxy": "../../plugins/proxy"
11 changes: 11 additions & 0 deletions examples/proxy/.lando.upstream.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
proxy:
web:
- hostname: lando-proxy.lndo.site
middlewares:
- name: test
key: headers.customresponseheaders.X-Lando-Merge
value: kirk

plugins:
"@lando/core": "../.."
"@lando/proxy": "../../plugins/proxy"
2 changes: 1 addition & 1 deletion examples/proxy/.lando.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ proxy:
port: 8080
l337:
- "give.me.*.lndo.site:8888/more/subs"
- "*.wild.l337.lndo.site:8888"
- hostname: "*.wild.l337.lndo.site:8888"
- www.l337.lndo.site:8888
- hostname: l337.lndo.site
port: 8888
Expand Down
6 changes: 6 additions & 0 deletions plugins/proxy/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ module.exports = (app, lando) => {

// Parse the proxy config to get traefix labels
.then(() => {
// normalize and merge proxy routes
app.config.proxy = utils.normalizeRoutes(app.config.proxy);

// log error for any duplicates across services
// @NOTE: this actually just calculates ALL occurences of a given hostname and not within each service
// but with the deduping in normalizeRoutes it probably works well enough for right now
const urlCounts = utils.getUrlsCounts(app.config.proxy);
if (_.max(_.values(urlCounts)) > 1) {
app.log.error('You cannot assign url %s to more than one service!', _.findKey(urlCounts, c => c > 1));
Expand Down
56 changes: 50 additions & 6 deletions plugins/proxy/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const _ = require('lodash');
const hasher = require('object-hash');
const url = require('url');

const merge = require('../../../utils/merge');

/*
* Helper to get URLs for app info and scanning purposes
*/
Expand Down Expand Up @@ -37,6 +39,50 @@ exports.needsProtocolScan = (current, last, status = {http: true, https: true})
return status;
};

/*
* Helper to parse a url
*/
exports.normalizeRoutes = (services = {}) => Object.fromEntries(_.map(services, (routes, key) => {
const defaults = {port: '80', pathname: '/', middlewares: []};

// normalize routes
routes = routes.map(route => {
// if route is a string then
if (typeof route === 'string') route = {hostname: route};

// url parse hostname with wildcard stuff if needed
route.hostname = route.hostname.replace(/\*/g, '__wildcard__');

// if hostname does not start with http:// or https:// then prepend http
// @TODO: does this allow for protocol selection down the road?
if (!route.hostname.startsWith('http://') || !route.hostname.startsWith('https://')) {
route.hostname = `http://${route.hostname}`;
}

// at this point we should be able to parse the hostname
// @TODO: do we need to try/catch this?
const {hostname, port, pathname} = URL.parse(route.hostname);

// and rebase the whole thing
route = merge({}, [defaults, {port: port === '' ? '80' : port, pathname}, route, {hostname}], ['merge:key', 'replace']);

// wildcard replacement back
route.hostname = route.hostname.replace(/__wildcard__/g, '*');

// generate an id based on protocol/hostname/path so we can groupby and dedupe
route.id = hasher(`${route.hostname}-${route.pathname}`);

// and return
return route;
});

// merge together all routes with the same id
routes = _(_.groupBy(routes, 'id')).map((routes, id) => merge({}, routes, ['merge:key', 'replace'])).value();

// return
return [key, routes];
}));

/*
* Helper to get proxy runner
*/
Expand Down Expand Up @@ -105,20 +151,18 @@ exports.parseConfig = (config, sslReady = []) => _(config)
*/
exports.parseRoutes = (service, urls = [], sslReady, labels = {}) => {
// Prepare our URLs for traefik
const parsedUrls = _(urls)
.map(url => exports.parseUrl(url))
.map(parsedUrl => _.merge({}, parsedUrl, {id: hasher(parsedUrl)}))
.uniqBy('id')
.value();
const rules = _(urls).uniqBy('id').value();

// Add things into the labels
_.forEach(parsedUrls, rule => {
_.forEach(rules, rule => {
// Add some default middleware
rule.middlewares.push({name: 'lando', key: 'headers.customrequestheaders.X-Lando', value: 'on'});

// Add in any path stripping middleware we need it
if (rule.pathname.length > 1) {
rule.middlewares.push({name: 'stripprefix', key: 'stripprefix.prefixes', value: rule.pathname});
};

// Ensure we prefix all middleware with the ruleid
rule.middlewares = _(rule.middlewares)
.map(middleware => _.merge({}, middleware, {name: `${rule.id}-${middleware.name}`}))
Expand Down

0 comments on commit ff25d8d

Please sign in to comment.