Skip to content

Commit c119835

Browse files
Merge pull request #204 from EcovadisCode/dotnet-core-chart-session-affinity
Update core chart to support sticky sessions
2 parents 6c01ad4 + c93efb1 commit c119835

5 files changed

Lines changed: 271 additions & 3 deletions

File tree

charts/core/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ apiVersion: v2
22
name: charts-core
33
description: A Helm chart for Kubernetes
44
type: application
5-
version: 2.7.0
5+
version: 2.8.0

charts/core/README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This chart supports integration with External Secrets Operator for syncing secre
77
### Prerequisites
88

99
ESO requires the following secrets to be defined in `secEnvVars`:
10+
1011
- `AZURE_CLIENT_ID` - Azure Service Principal Client ID
1112
- `AZURE_CLIENT_SECRET` - Azure Service Principal Client Secret
1213
- `AZURE_TENANT_ID` - Azure Tenant ID
@@ -34,17 +35,39 @@ global:
3435
### Generated Resources
3536
3637
When ESO is enabled, the chart creates:
38+
3739
- **ClusterSecretStore**: `<release-name>-cluster-secret-store` - connects to Azure Key Vault
3840
- **ExternalSecret**: `<release-name>-external-secret` - syncs secrets to `<release-name>-secure-kv`
3941

4042
The ESO will use the existing `<release-name>-secure` secret (created from `secEnvVars`) for authentication to Azure Key Vault.
4143

42-
# How to test locally
44+
## Sticky Sessions
45+
46+
Enables cookie-based session affinity for Traefik IngressRoutes. **Mutually exclusive with canary deployments** — when `global.canary.enabled` is `true`, sticky config is ignored. Flagger officially doesn't support sticky sessions, as well as it breaks user weighted routing when rollout is happening.
47+
48+
### Configuration
49+
50+
```yaml
51+
global:
52+
ingressRoutes:
53+
routes:
54+
- ruleName: private
55+
sticky:
56+
enabled: true
57+
name: my-cookie # default: <release-name>-sticky
58+
secure: false # default: true
59+
httpOnly: true # default: true
60+
sameSite: strict # default: strict
61+
```
62+
63+
## How to test locally
64+
4365
1. Install prerequisites as specified in tests requirements.txt
4466
2. in charts\charts\core type "helm template ." make sure the template renders correctly
4567
3. in charts\charts\core type "pytest" all tests should pass
4668

47-
# How to debug in VS code
69+
## How to debug in VS code
70+
4871
https://code.visualstudio.com/docs/python/testing
4972
test discovery in subfolders is based on existence of __init__.py file
5073
to run tests succesfully you need to set test working directory go to File->Preferences->settings, search Tests, select Python and find "Optional working directory for tests." Set it to charts\core

charts/core/templates/CRD/ingressroute.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ spec:
2424
name: {{ $rangeItem.serviceName | default ( include "charts-core.fullname" . ) }}
2525
namespace: {{ $rangeItem.serviceNamespace | default .Release.Namespace | quote}}
2626
port: {{ $rangeItem.servicePort | default 80 }}
27+
{{- if and (dig "sticky" "enabled" false $rangeItem) (not .Values.global.canary.enabled) }}
28+
sticky:
29+
cookie:
30+
name: {{ $rangeItem.sticky.name | default (printf "%s-sticky" (include "charts-core.fullname" .)) }}
31+
secure: {{ if kindIs "invalid" $rangeItem.sticky.secure }}true{{ else }}{{ $rangeItem.sticky.secure }} {{ end }}
32+
httpOnly: {{ if kindIs "invalid" $rangeItem.sticky.httpOnly }}true{{ else }}{{ $rangeItem.sticky.httpOnly }} {{ end }}
33+
sameSite: {{ $rangeItem.sticky.sameSite | default "strict" }}
34+
{{- end }}
2735
{{- if or $rangeItem.isStripprefixEnabled $rangeItem.stripPrefixes (ne "false" ($rangeItem.isRetryEnabled | toString)) $rangeItem.isCircuitBreakerEnabled }}
2836
middlewares:
2937
{{- end }}

charts/core/templates/tests/ingressroute_test.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,4 +286,235 @@ def test_host_rule_and_circuitbreaker_middleware_configured(self):
286286
"NetworkErrorRatio() > 0.10",
287287
jmespath.search(
288288
"spec.circuitBreaker.expression", docs[1])
289+
)
290+
291+
def test_sticky_session_rendered_when_enabled(self):
292+
docs = render_chart(
293+
values={
294+
"global": {
295+
"ingressRoutes": {
296+
"enabled": True,
297+
"routes": [
298+
{
299+
"ruleName": "http",
300+
"isRetryEnabled": False,
301+
"sticky": {
302+
"enabled": True
303+
}
304+
}
305+
]
306+
}
307+
}
308+
},
309+
name=".",
310+
show_only=["templates/CRD/ingressroute.yaml"]
311+
)
312+
self.assertIsNotNone(jmespath.search(
313+
"spec.routes[0].services[0].sticky.cookie", docs[0]))
314+
315+
def test_sticky_session_not_rendered_when_disabled(self):
316+
docs = render_chart(
317+
values={
318+
"global": {
319+
"ingressRoutes": {
320+
"enabled": True,
321+
"routes": [
322+
{
323+
"ruleName": "http",
324+
"isRetryEnabled": False,
325+
"sticky": {
326+
"enabled": False
327+
}
328+
}
329+
]
330+
}
331+
}
332+
},
333+
name=".",
334+
show_only=["templates/CRD/ingressroute.yaml"]
335+
)
336+
self.assertIsNone(jmespath.search(
337+
"spec.routes[0].services[0].sticky", docs[0]))
338+
339+
def test_sticky_session_not_rendered_when_canary_enabled(self):
340+
docs = render_chart(
341+
values={
342+
"global": {
343+
"canary": {
344+
"enabled": True
345+
},
346+
"ingressRoutes": {
347+
"enabled": True,
348+
"routes": [
349+
{
350+
"ruleName": "http",
351+
"isRetryEnabled": False,
352+
"sticky": {
353+
"enabled": True
354+
}
355+
}
356+
]
357+
}
358+
}
359+
},
360+
name=".",
361+
show_only=["templates/CRD/ingressroute.yaml"]
362+
)
363+
self.assertIsNone(jmespath.search(
364+
"spec.routes[0].services[0].sticky", docs[0]))
365+
366+
def test_sticky_session_custom_cookie_name(self):
367+
docs = render_chart(
368+
values={
369+
"global": {
370+
"ingressRoutes": {
371+
"enabled": True,
372+
"routes": [
373+
{
374+
"ruleName": "http",
375+
"isRetryEnabled": False,
376+
"sticky": {
377+
"enabled": True,
378+
"name": "my-cookie"
379+
}
380+
}
381+
]
382+
}
383+
}
384+
},
385+
name=".",
386+
show_only=["templates/CRD/ingressroute.yaml"]
387+
)
388+
self.assertEqual(
389+
"my-cookie",
390+
jmespath.search(
391+
"spec.routes[0].services[0].sticky.cookie.name", docs[0])
392+
)
393+
394+
def test_sticky_session_secure_defaults_to_true(self):
395+
docs = render_chart(
396+
values={
397+
"global": {
398+
"ingressRoutes": {
399+
"enabled": True,
400+
"routes": [
401+
{
402+
"ruleName": "http",
403+
"isRetryEnabled": False,
404+
"sticky": {
405+
"enabled": True
406+
}
407+
}
408+
]
409+
}
410+
}
411+
},
412+
name=".",
413+
show_only=["templates/CRD/ingressroute.yaml"]
414+
)
415+
self.assertTrue(jmespath.search(
416+
"spec.routes[0].services[0].sticky.cookie.secure", docs[0]))
417+
418+
def test_sticky_session_http_only_defaults_to_true(self):
419+
docs = render_chart(
420+
values={
421+
"global": {
422+
"ingressRoutes": {
423+
"enabled": True,
424+
"routes": [
425+
{
426+
"ruleName": "http",
427+
"isRetryEnabled": False,
428+
"sticky": {
429+
"enabled": True
430+
}
431+
}
432+
]
433+
}
434+
}
435+
},
436+
name=".",
437+
show_only=["templates/CRD/ingressroute.yaml"]
438+
)
439+
self.assertTrue(jmespath.search(
440+
"spec.routes[0].services[0].sticky.cookie.httpOnly", docs[0]))
441+
442+
def test_sticky_session_http_only_false_is_respected(self):
443+
docs = render_chart(
444+
values={
445+
"global": {
446+
"ingressRoutes": {
447+
"enabled": True,
448+
"routes": [
449+
{
450+
"ruleName": "http",
451+
"isRetryEnabled": False,
452+
"sticky": {
453+
"enabled": True,
454+
"httpOnly": False
455+
}
456+
}
457+
]
458+
}
459+
}
460+
},
461+
name=".",
462+
show_only=["templates/CRD/ingressroute.yaml"]
463+
)
464+
self.assertFalse(jmespath.search(
465+
"spec.routes[0].services[0].sticky.cookie.httpOnly", docs[0]))
466+
467+
def test_sticky_session_same_site_defaults_to_strict(self):
468+
docs = render_chart(
469+
values={
470+
"global": {
471+
"ingressRoutes": {
472+
"enabled": True,
473+
"routes": [
474+
{
475+
"ruleName": "http",
476+
"isRetryEnabled": False,
477+
"sticky": {
478+
"enabled": True
479+
}
480+
}
481+
]
482+
}
483+
}
484+
},
485+
name=".",
486+
show_only=["templates/CRD/ingressroute.yaml"]
487+
)
488+
self.assertEqual(
489+
"strict",
490+
jmespath.search(
491+
"spec.routes[0].services[0].sticky.cookie.sameSite", docs[0])
492+
)
493+
494+
def test_sticky_session_custom_same_site(self):
495+
docs = render_chart(
496+
values={
497+
"global": {
498+
"ingressRoutes": {
499+
"enabled": True,
500+
"routes": [
501+
{
502+
"ruleName": "http",
503+
"isRetryEnabled": False,
504+
"sticky": {
505+
"enabled": True,
506+
"sameSite": "lax"
507+
}
508+
}
509+
]
510+
}
511+
}
512+
},
513+
name=".",
514+
show_only=["templates/CRD/ingressroute.yaml"]
515+
)
516+
self.assertEqual(
517+
"lax",
518+
jmespath.search(
519+
"spec.routes[0].services[0].sticky.cookie.sameSite", docs[0])
289520
)

charts/core/values.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ global:
9898
# - /vadisservice
9999
#isRetryEnabled: true
100100
#isCircuitBreakerEnabled: true
101+
#sticky: # Preview feature. Currently works only with canary deployments disabled.
102+
#enabled: false
103+
#name: cookie-name
104+
#secure: true
105+
#httpOnly: true
106+
#sameSite: strict
101107

102108
envVarsEnabled: true
103109
envVars:

0 commit comments

Comments
 (0)