Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#246: improve merging of same-service, same-hostname-path proxy confi… #249

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading