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
#249)

* #246: improve merging of same-service, same-hostname-path proxy configuration

* #246: improve merging of same-service, same-hostname-path proxy configuration part 2

* #246: improve merging of same-service, same-hostname-path proxy configuration part 3

* #246: improve merging of same-service, same-hostname-path proxy configuration part 4

* #246: improve merging of same-service, same-hostname-path proxy configuration part 5
  • Loading branch information
pirog authored Oct 24, 2024
1 parent 605e7df commit b903e1c
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pr-docs-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:
os:
- ubuntu-24.04
node-version:
- "18"
- "20"
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
## {{ UNRELEASED_VERSION }} - [{{ UNRELEASED_DATE }}]({{ UNRELEASED_LINK }})


### Fixes

* Fixed bug causing https proxy routes to be assigned when they shouldnt be
* Fixed bug causing cache from repopulating old proxy addresses that have been removed [#209](https://github.com/lando/core/issues/209)
* Fixed bug in `v4` auto `entrypoint` and `command` population
* Fixed regression causing empty tooling `options` to throw an error [#240](https://github.com/lando/core/issues/240)
* Improved merging of same-service, same-hostname-pathname `proxy` routes, fixes [#246](https://github.com/lando/core/issues/246)

## v3.23.0-beta.4 - [October 22, 2024](https://github.com/lando/core/releases/tag/v3.23.0-beta.4)

Expand Down
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
4 changes: 4 additions & 0 deletions examples/proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ lando exec web4 -- env | grep LANDO_SERVICE_CERT | grep /certs/cert.crt
lando exec web4 -- cat \$LANDO_SERVICE_KEY
lando exec web4 -- env | grep LANDO_SERVICE_KEY | grep /certs/cert.key

# Should succcesfully merge same-service same-hostname-pathname routes together correctly
lando exec php -- curl -sI http://lando-proxy.lndo.site | grep -i "X-Lando-Merge" | grep picard
lando exec php -- curl -sI http://lando-proxy.lndo.site | grep -i "X-Lando-Merge-Xo" | grep riker

# Should remove proxy entries when removed from the landofile and rebuild
cp -rf .lando.yml .lando.old.yml
cp -rf .lando.stripped.yml .lando.yml
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
64 changes: 57 additions & 7 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 All @@ -53,11 +99,17 @@ exports.getProxyRunner = (project, files) => ({
* Helper to get the trafix rule
*/
exports.getRule = rule => {
// we do this so getRule is backwards campatible with the older rule.host
const host = rule.hostname ?? rule.host;

// Start with the rule we can assume
const hostRegex = rule.host.replace(new RegExp('\\*', 'g'), '{wildcard:[a-z0-9-]+}');
const hostRegex = host.replace(new RegExp('\\*', 'g'), '{wildcard:[a-z0-9-]+}');
const rules = [`HostRegexp(\`${hostRegex}\`)`];

// Add in the path prefix if we can
if (rule.pathname.length > 1) rules.push(`PathPrefix(\`${rule.pathname}\`)`);

// return
return rules.join(' && ');
};

Expand Down Expand Up @@ -105,20 +157,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 b903e1c

Please sign in to comment.