From 716ad744b87b2dfc216c6cf5a76d5fab5507cde9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:53:32 +0000 Subject: [PATCH 01/36] chore(deps): bump soupsieve from 2.9 to 2.9.1 in /docs/_build Bumps [soupsieve](https://github.com/facelessuser/soupsieve) from 2.9 to 2.9.1. - [Release notes](https://github.com/facelessuser/soupsieve/releases) - [Commits](https://github.com/facelessuser/soupsieve/compare/2.9...2.9.1) --- updated-dependencies: - dependency-name: soupsieve dependency-version: 2.9.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/_build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_build/requirements.txt b/docs/_build/requirements.txt index c9c145b61..dfd4dafa9 100644 --- a/docs/_build/requirements.txt +++ b/docs/_build/requirements.txt @@ -18,6 +18,6 @@ python-dateutil==2.9.0.post0 PyYAML==6.0.3 pyyaml_env_tag==1.1 six==1.17.0 -soupsieve==2.9 +soupsieve==2.9.1 watchdog==6.0.0 zipp==4.1.0 From b925ad0be9e073a86c362fd78444d073b8b6fca7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 13:54:29 +0000 Subject: [PATCH 02/36] chore(deps): bump github.com/fluxcd/pkg/kustomize Bumps [github.com/fluxcd/pkg/kustomize](https://github.com/fluxcd/pkg) from 1.38.0 to 1.39.0. - [Commits](https://github.com/fluxcd/pkg/compare/kustomize/v1.38.0...kustomize/v1.39.0) --- updated-dependencies: - dependency-name: github.com/fluxcd/pkg/kustomize dependency-version: 1.39.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 2 +- plugins/scheduler-k3s/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index d76cde3f4..7405b2c96 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -16,7 +16,7 @@ require ( github.com/dokku/dokku/plugins/registry v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/storage v0.0.0-00010101000000-000000000000 github.com/fatih/color v1.19.0 - github.com/fluxcd/pkg/kustomize v1.38.0 + github.com/fluxcd/pkg/kustomize v1.39.0 github.com/go-openapi/jsonpointer v1.0.0 github.com/go-resty/resty/v2 v2.17.2 github.com/gofrs/flock v0.13.0 diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index db03ce852..46cb072a4 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -90,8 +90,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/pkg/kustomize v1.38.0 h1:95KIqpk3UBkxFNC1kCnqbawdiNAbgATVWE5THfbq7PY= -github.com/fluxcd/pkg/kustomize v1.38.0/go.mod h1:OCKAb5Tc0bFVpeclk/W0uWjjlB9dcLm/6XMvx69zu/4= +github.com/fluxcd/pkg/kustomize v1.39.0 h1:NIlAJtm1lyBP7i8gHf0zQ1crH8OLuLA2C8G/uWiWhEY= +github.com/fluxcd/pkg/kustomize v1.39.0/go.mod h1:OCKAb5Tc0bFVpeclk/W0uWjjlB9dcLm/6XMvx69zu/4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= From 64785ca0ee8d634a9fe8f3ce2891e094972c2440 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:54:09 +0000 Subject: [PATCH 03/36] chore(deps): bump sass from 1.101.3 to 1.101.6 in /tests/apps/multi Bumps [sass](https://github.com/sass/dart-sass) from 1.101.3 to 1.101.6. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.101.3...1.101.6) --- updated-dependencies: - dependency-name: sass dependency-version: 1.101.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tests/apps/multi/package-lock.json | 8 ++++---- tests/apps/multi/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/apps/multi/package-lock.json b/tests/apps/multi/package-lock.json index 871f70def..354c552f8 100644 --- a/tests/apps/multi/package-lock.json +++ b/tests/apps/multi/package-lock.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "bootstrap": "^5.3.8", - "sass": "^1.101.3" + "sass": "^1.101.6" }, "engines": { "node": "24.x.x", @@ -432,9 +432,9 @@ } }, "node_modules/sass": { - "version": "1.101.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.3.tgz", - "integrity": "sha512-Z1lLHhtAII+dyLNIQB6JQTZMy7sDxk3f5NzbINRc9ks1P0HCGvSuKev0wUhULFpLSaHBIMZrcTs9WDQUZerrgA==", + "version": "1.101.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.6.tgz", + "integrity": "sha512-j8qYug9WuX19eU5sxJWQlbR8RYhKgXiOYgGjkJRkcW35c3neWtxPdcUW0saN6Od2L0aqEp0AmH9R/QeAxrffMQ==", "license": "MIT", "dependencies": { "chokidar": "^5.0.0", diff --git a/tests/apps/multi/package.json b/tests/apps/multi/package.json index 0ba94e3f1..ef559f6e4 100644 --- a/tests/apps/multi/package.json +++ b/tests/apps/multi/package.json @@ -2,7 +2,7 @@ "author": "Al Johri", "dependencies": { "bootstrap": "^5.3.8", - "sass": "^1.101.3" + "sass": "^1.101.6" }, "description": "Test application for heroku multi buildpack.", "engines": { From ce88f439ddcabb64f89e048fc2c2a19acb04214a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:54:23 +0000 Subject: [PATCH 04/36] chore(deps): bump k8s.io/client-go in /plugins/scheduler-k3s Bumps [k8s.io/client-go](https://github.com/kubernetes/client-go) from 0.36.2 to 0.36.3. - [Changelog](https://github.com/kubernetes/client-go/blob/master/CHANGELOG.md) - [Commits](https://github.com/kubernetes/client-go/compare/v0.36.2...v0.36.3) --- updated-dependencies: - dependency-name: k8s.io/client-go dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 8 ++++---- plugins/scheduler-k3s/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 7405b2c96..7023a4c3a 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -32,9 +32,9 @@ require ( gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.21.3 - k8s.io/api v0.36.2 - k8s.io/apimachinery v0.36.2 - k8s.io/client-go v0.36.2 + k8s.io/api v0.36.3 + k8s.io/apimachinery v0.36.3 + k8s.io/client-go v0.36.3 k8s.io/kubectl v0.36.2 k8s.io/kubernetes v1.36.2 k8s.io/utils v0.0.0-20260626114624-be93311217bd @@ -169,7 +169,7 @@ require ( k8s.io/component-base v0.36.2 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 // indirect - k8s.io/streaming v0.36.2 // indirect + k8s.io/streaming v0.36.3 // indirect oras.land/oras-go/v2 v2.6.1 // indirect sigs.k8s.io/controller-runtime v0.24.1 // indirect sigs.k8s.io/gateway-api v1.6.0 // indirect diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 46cb072a4..8000c0b13 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -469,18 +469,18 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= helm.sh/helm/v3 v3.21.3 h1:wkamdwI3liEkW6wI1l9aGqQZGxcTKyt8kx0qJLPcmCg= helm.sh/helm/v3 v3.21.3/go.mod h1:iaJ0iNsPoTZl++7h6vzQFyT0VEVtLYJiyRBDkPOOBTs= -k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= -k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= +k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= +k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= -k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= -k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= +k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= k8s.io/cli-runtime v0.36.2 h1:CconTvEeV4DJs4ZX3HQKCFbFRGsm6OtuBM9yjmMP2VM= k8s.io/cli-runtime v0.36.2/go.mod h1:LddcjiMf4YlnHO7c1Y7rEtDqL84FyiYVLco7V679GUU= -k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= -k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= +k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= +k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= @@ -491,8 +491,8 @@ k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= k8s.io/kubernetes v1.36.2 h1:qsCug7E1dMVO+rNuCENKG64Z7SVS/fTqDHGI1/NCmTg= k8s.io/kubernetes v1.36.2/go.mod h1:MLdeJ3qw2CWH9BFml5GvptxQVQckz54fJOZ/WuixpFE= -k8s.io/streaming v0.36.2 h1:NSKthPPg9UFSKsRauVJUVGH2Dvn8fhKmY4qrMkw/p98= -k8s.io/streaming v0.36.2/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= +k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= +k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= mvdan.cc/sh/v3 v3.13.1 h1:DP3TfgZhDkT7lerUdnp6PTGKyxxzz6T+cOlY/xEvfWk= From 230a63e3c095e42700cfd45748dfa6e655aee5ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 02:23:01 +0000 Subject: [PATCH 05/36] chore(deps): bump k8s.io/kubectl in /plugins/scheduler-k3s Bumps [k8s.io/kubectl](https://github.com/kubernetes/kubectl) from 0.36.2 to 0.36.3. - [Commits](https://github.com/kubernetes/kubectl/compare/v0.36.2...v0.36.3) --- updated-dependencies: - dependency-name: k8s.io/kubectl dependency-version: 0.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 6 +++--- plugins/scheduler-k3s/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 7023a4c3a..3e6d210d1 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -35,7 +35,7 @@ require ( k8s.io/api v0.36.3 k8s.io/apimachinery v0.36.3 k8s.io/client-go v0.36.3 - k8s.io/kubectl v0.36.2 + k8s.io/kubectl v0.36.3 k8s.io/kubernetes v1.36.2 k8s.io/utils v0.0.0-20260626114624-be93311217bd mvdan.cc/sh/v3 v3.13.1 @@ -165,8 +165,8 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/apiextensions-apiserver v0.36.2 // indirect k8s.io/apiserver v0.36.2 // indirect - k8s.io/cli-runtime v0.36.2 // indirect - k8s.io/component-base v0.36.2 // indirect + k8s.io/cli-runtime v0.36.3 // indirect + k8s.io/component-base v0.36.3 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 // indirect k8s.io/streaming v0.36.3 // indirect diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 8000c0b13..087614ed7 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -477,18 +477,18 @@ k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= -k8s.io/cli-runtime v0.36.2 h1:CconTvEeV4DJs4ZX3HQKCFbFRGsm6OtuBM9yjmMP2VM= -k8s.io/cli-runtime v0.36.2/go.mod h1:LddcjiMf4YlnHO7c1Y7rEtDqL84FyiYVLco7V679GUU= +k8s.io/cli-runtime v0.36.3 h1:g+eJ+M1sYpnNYp/q5fzaw2KejIL0Q7DH+xFl6YVoL4U= +k8s.io/cli-runtime v0.36.3/go.mod h1:hZpAqK8nSFXvvLaVCbzUPVp8e9TRLSTCfpNzMt7s3tE= k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= -k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= -k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= +k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY= +k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 h1:mPMaPMpBij2V1Wv/fR+HW124vVGXXvOSS9ver/9yjWs= k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= -k8s.io/kubectl v0.36.2 h1:rpUGGpeL09XVOLep2yle5jrtk//JA1L6ZHfkQQtVEwk= -k8s.io/kubectl v0.36.2/go.mod h1:gVbQ3B/yb4bSR2ggQ7rd0W6icUSWs7sduH4e16Vii+0= +k8s.io/kubectl v0.36.3 h1:TesKp+XYQEjPYoFvuobcVnuvira2+/xAVlq//+kksaI= +k8s.io/kubectl v0.36.3/go.mod h1:W+NEb1CzBGmoaI1Nrpn2ETo9omNBl0AsyxnnMT40N6E= k8s.io/kubernetes v1.36.2 h1:qsCug7E1dMVO+rNuCENKG64Z7SVS/fTqDHGI1/NCmTg= k8s.io/kubernetes v1.36.2/go.mod h1:MLdeJ3qw2CWH9BFml5GvptxQVQckz54fJOZ/WuixpFE= k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= From 52a44467f949d201760ed60c777c44d63a6e0749 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:54:09 +0000 Subject: [PATCH 06/36] chore(deps): bump sass from 1.101.6 to 1.101.7 in /tests/apps/multi Bumps [sass](https://github.com/sass/dart-sass) from 1.101.6 to 1.101.7. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.101.6...1.101.7) --- updated-dependencies: - dependency-name: sass dependency-version: 1.101.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tests/apps/multi/package-lock.json | 8 ++++---- tests/apps/multi/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/apps/multi/package-lock.json b/tests/apps/multi/package-lock.json index 354c552f8..249ff0af7 100644 --- a/tests/apps/multi/package-lock.json +++ b/tests/apps/multi/package-lock.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "bootstrap": "^5.3.8", - "sass": "^1.101.6" + "sass": "^1.101.7" }, "engines": { "node": "24.x.x", @@ -432,9 +432,9 @@ } }, "node_modules/sass": { - "version": "1.101.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.6.tgz", - "integrity": "sha512-j8qYug9WuX19eU5sxJWQlbR8RYhKgXiOYgGjkJRkcW35c3neWtxPdcUW0saN6Od2L0aqEp0AmH9R/QeAxrffMQ==", + "version": "1.101.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.7.tgz", + "integrity": "sha512-cDeUYU0dhwKVbpYg/ppsjyuoddxYhWlJOkRoI7+/iZsaSp7iowWDfm+tL2HcUafedWBDvbf/+hx0QRgKG4JSHA==", "license": "MIT", "dependencies": { "chokidar": "^5.0.0", diff --git a/tests/apps/multi/package.json b/tests/apps/multi/package.json index ef559f6e4..7ca7a380c 100644 --- a/tests/apps/multi/package.json +++ b/tests/apps/multi/package.json @@ -2,7 +2,7 @@ "author": "Al Johri", "dependencies": { "bootstrap": "^5.3.8", - "sass": "^1.101.6" + "sass": "^1.101.7" }, "description": "Test application for heroku multi buildpack.", "engines": { From 867924b080e2fef7b7a2fd919c5f9f1b3e1fc36d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:54:46 +0000 Subject: [PATCH 07/36] chore(deps): bump k8s.io/kubernetes in /plugins/scheduler-k3s Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.36.2 to 1.36.3. - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.36.2...v1.36.3) --- updated-dependencies: - dependency-name: k8s.io/kubernetes dependency-version: 1.36.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 2 +- plugins/scheduler-k3s/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 3e6d210d1..03b410e6f 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -36,7 +36,7 @@ require ( k8s.io/apimachinery v0.36.3 k8s.io/client-go v0.36.3 k8s.io/kubectl v0.36.3 - k8s.io/kubernetes v1.36.2 + k8s.io/kubernetes v1.36.3 k8s.io/utils v0.0.0-20260626114624-be93311217bd mvdan.cc/sh/v3 v3.13.1 sigs.k8s.io/kustomize/api v0.21.1 diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 087614ed7..e216c74b4 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -489,8 +489,8 @@ k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 h1:mPMaPMpBij2V1Wv/fR+HW1 k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= k8s.io/kubectl v0.36.3 h1:TesKp+XYQEjPYoFvuobcVnuvira2+/xAVlq//+kksaI= k8s.io/kubectl v0.36.3/go.mod h1:W+NEb1CzBGmoaI1Nrpn2ETo9omNBl0AsyxnnMT40N6E= -k8s.io/kubernetes v1.36.2 h1:qsCug7E1dMVO+rNuCENKG64Z7SVS/fTqDHGI1/NCmTg= -k8s.io/kubernetes v1.36.2/go.mod h1:MLdeJ3qw2CWH9BFml5GvptxQVQckz54fJOZ/WuixpFE= +k8s.io/kubernetes v1.36.3 h1:qDQdoMiluAE2Eab6Fa52YV+WjiGz9mZFFoagEA6cI+o= +k8s.io/kubernetes v1.36.3/go.mod h1:6oChkQeI7Yf6lV9lFpSdRzODdbY/ECp/4zUeBk8ONaw= k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs= From e2214e281adba9b90aea77e87c7127ed88a57819 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:55:05 +0000 Subject: [PATCH 08/36] chore(deps): bump github.com/traefik/traefik/v2 Bumps [github.com/traefik/traefik/v2](https://github.com/traefik/traefik) from 2.11.52 to 2.11.53. - [Release notes](https://github.com/traefik/traefik/releases) - [Changelog](https://github.com/traefik/traefik/blob/v2.11.53/CHANGELOG.md) - [Commits](https://github.com/traefik/traefik/compare/v2.11.52...v2.11.53) --- updated-dependencies: - dependency-name: github.com/traefik/traefik/v2 dependency-version: 2.11.53 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 2 +- plugins/scheduler-k3s/go.sum | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 3e6d210d1..1006b7510 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -27,7 +27,7 @@ require ( github.com/onsi/gomega v1.42.1 github.com/ryanuber/columnize v2.1.2+incompatible github.com/spf13/pflag v1.0.10 - github.com/traefik/traefik/v2 v2.11.52 + github.com/traefik/traefik/v2 v2.11.53 golang.org/x/sync v0.22.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 087614ed7..567c16a72 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -359,8 +359,8 @@ github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYj github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ= github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= -github.com/traefik/traefik/v2 v2.11.52 h1:HS+tGn+I0+IcmPo0Zj2TPgj28TzuZq6pZdQRpYHwbfs= -github.com/traefik/traefik/v2 v2.11.52/go.mod h1:O+kwOI6YGcYDh3NKkbGtq3T8XNQIA2fl26ytPVwAqs0= +github.com/traefik/traefik/v2 v2.11.53 h1:IXMxvov4RZM2FGrOiholJUuPkZVB6nXlNowhGyghwWI= +github.com/traefik/traefik/v2 v2.11.53/go.mod h1:4mJRoD8Vp8PRFUCTF8R2nUTtY9bGmXTHpQgx/A1tD/o= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= @@ -447,12 +447,12 @@ golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec= +google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad h1:45WmJvIV6C2+O/jjLkPUH+F3aOj/1miDoU2DD0+NWbg= google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= -google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 5ed560eb3655ca47cb03b73cfde381664c7851b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:53:31 +0000 Subject: [PATCH 09/36] chore(deps): bump traefik in /plugins/traefik-vhosts Bumps traefik from v3.7.8 to v3.7.9. --- updated-dependencies: - dependency-name: traefik dependency-version: v3.7.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- plugins/traefik-vhosts/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/traefik-vhosts/Dockerfile b/plugins/traefik-vhosts/Dockerfile index f3f0808cb..486043edd 100644 --- a/plugins/traefik-vhosts/Dockerfile +++ b/plugins/traefik-vhosts/Dockerfile @@ -1 +1 @@ -FROM traefik:v3.7.8 +FROM traefik:v3.7.9 From 276f613be178231ac8fa29b4e452f1bb3876e71e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 13:54:13 +0000 Subject: [PATCH 10/36] chore(deps): bump sass from 1.101.7 to 1.102.0 in /tests/apps/multi Bumps [sass](https://github.com/sass/dart-sass) from 1.101.7 to 1.102.0. - [Release notes](https://github.com/sass/dart-sass/releases) - [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md) - [Commits](https://github.com/sass/dart-sass/compare/1.101.7...1.102.0) --- updated-dependencies: - dependency-name: sass dependency-version: 1.102.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/apps/multi/package-lock.json | 8 ++++---- tests/apps/multi/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/apps/multi/package-lock.json b/tests/apps/multi/package-lock.json index 249ff0af7..4304ae76e 100644 --- a/tests/apps/multi/package-lock.json +++ b/tests/apps/multi/package-lock.json @@ -11,7 +11,7 @@ "license": "ISC", "dependencies": { "bootstrap": "^5.3.8", - "sass": "^1.101.7" + "sass": "^1.102.0" }, "engines": { "node": "24.x.x", @@ -432,9 +432,9 @@ } }, "node_modules/sass": { - "version": "1.101.7", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.101.7.tgz", - "integrity": "sha512-cDeUYU0dhwKVbpYg/ppsjyuoddxYhWlJOkRoI7+/iZsaSp7iowWDfm+tL2HcUafedWBDvbf/+hx0QRgKG4JSHA==", + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", "license": "MIT", "dependencies": { "chokidar": "^5.0.0", diff --git a/tests/apps/multi/package.json b/tests/apps/multi/package.json index 7ca7a380c..895f99224 100644 --- a/tests/apps/multi/package.json +++ b/tests/apps/multi/package.json @@ -2,7 +2,7 @@ "author": "Al Johri", "dependencies": { "bootstrap": "^5.3.8", - "sass": "^1.101.7" + "sass": "^1.102.0" }, "description": "Test application for heroku multi buildpack.", "engines": { From fbcfbce94474c714599b0fd839eec2bee3dc27c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Jul 2026 13:54:14 +0000 Subject: [PATCH 11/36] chore(deps): bump github.com/cert-manager/cert-manager Bumps [github.com/cert-manager/cert-manager](https://github.com/cert-manager/cert-manager) from 1.21.0 to 1.21.1. - [Release notes](https://github.com/cert-manager/cert-manager/releases) - [Changelog](https://github.com/cert-manager/cert-manager/blob/master/RELEASE.md) - [Commits](https://github.com/cert-manager/cert-manager/compare/v1.21.0...v1.21.1) --- updated-dependencies: - dependency-name: github.com/cert-manager/cert-manager dependency-version: 1.21.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 2 +- plugins/scheduler-k3s/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index a4354aaeb..9dc86e657 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -5,7 +5,7 @@ go 1.26.2 require ( github.com/Masterminds/semver/v3 v3.5.0 github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b - github.com/cert-manager/cert-manager v1.21.0 + github.com/cert-manager/cert-manager v1.21.1 github.com/dokku/dokku/plugins/app-json v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000 diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 2f1b1a84d..167155ca3 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -39,8 +39,8 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= -github.com/cert-manager/cert-manager v1.21.0 h1:BCoB2BxA1lbqPusZCqPLPn8Q9MhraL7RbZGHbBcm3W8= -github.com/cert-manager/cert-manager v1.21.0/go.mod h1:V8iALETs2W428CjBZ4Ez3DftDeyu0f8PZnHDTcliS4I= +github.com/cert-manager/cert-manager v1.21.1 h1:0LttV37Q5c2CBNoHkjuI8sLKTXWZDC2SwQkxrBMKV9w= +github.com/cert-manager/cert-manager v1.21.1/go.mod h1:sVwmLBWoiB1BRd0rJElBGQuiu94z4k7p3Kd0FRQyfgw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80= From 0e4347cb3e60d599b207f1217d33bc456edebc32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:52:21 +0000 Subject: [PATCH 12/36] chore(deps): bump google.golang.org/grpc in /tests/apps/gogrpc Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.82.1 to 1.83.0. - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.82.1...v1.83.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-version: 1.83.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/apps/gogrpc/go.mod | 4 ++-- tests/apps/gogrpc/go.sum | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/apps/gogrpc/go.mod b/tests/apps/gogrpc/go.mod index 95065c7b7..2894518ae 100644 --- a/tests/apps/gogrpc/go.mod +++ b/tests/apps/gogrpc/go.mod @@ -6,7 +6,7 @@ require ( golang.org/x/net v0.55.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect - google.golang.org/grpc v1.82.1 + google.golang.org/grpc v1.83.0 ) require ( @@ -14,4 +14,4 @@ require ( google.golang.org/protobuf v1.36.11 ) -require google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect +require google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect diff --git a/tests/apps/gogrpc/go.sum b/tests/apps/gogrpc/go.sum index 5a7c3e8b9..594be5c85 100644 --- a/tests/apps/gogrpc/go.sum +++ b/tests/apps/gogrpc/go.sum @@ -12,16 +12,16 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= @@ -30,10 +30,10 @@ golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= +google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc/examples v0.0.0-20240118175532-987df1309236 h1:wTUkwrodi4IuWD3kYkMUwCU8BhwFaqDywrXmsthCvQA= google.golang.org/grpc/examples v0.0.0-20240118175532-987df1309236/go.mod h1:j5uROIAAgi3YmtiETMt1LW0d/lHqQ7wwrIY4uGRXLQ4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= From 87fe6e69252a41c4c656c78b91db786ca1990de3 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 31 Jul 2026 13:24:06 -0400 Subject: [PATCH 13/36] chore: ignore minor and patch updates for github actions Add ignore rules for minor and patch updates in Dependabot configuration. --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fcf196cae..fa2343c69 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -330,6 +330,11 @@ updates: open-pull-requests-limit: 10 labels: - "type: dependencies" + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-minor" + - "version-update:semver-patch" - package-ecosystem: "docker" directory: "/" schedule: From 9f1f19a58daf409cdf1fec5f15d9a98dbd7527db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:53:15 +0000 Subject: [PATCH 14/36] chore(deps): bump github.com/mattn/go-isatty in /plugins/app-json Bumps [github.com/mattn/go-isatty](https://github.com/mattn/go-isatty) from 0.0.23 to 0.0.24. - [Commits](https://github.com/mattn/go-isatty/compare/v0.0.23...v0.0.24) --- updated-dependencies: - dependency-name: github.com/mattn/go-isatty dependency-version: 0.0.24 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/app-json/go.mod | 2 +- plugins/app-json/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/app-json/go.mod b/plugins/app-json/go.mod index c4340ab81..f5292ead5 100644 --- a/plugins/app-json/go.mod +++ b/plugins/app-json/go.mod @@ -5,7 +5,7 @@ go 1.26.2 require ( github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/mattn/go-isatty v0.0.23 + github.com/mattn/go-isatty v0.0.24 github.com/spf13/pflag v1.0.10 github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a k8s.io/utils v0.0.0-20240102154912-e7106e64919e diff --git a/plugins/app-json/go.sum b/plugins/app-json/go.sum index f281d8009..9cd8f3203 100644 --- a/plugins/app-json/go.sum +++ b/plugins/app-json/go.sum @@ -16,8 +16,8 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/melbahja/goph v1.5.2 h1:2eoR45SLF3LyM6tnIhnpjakvXTjtQMJqOK/mp1PYojM= github.com/melbahja/goph v1.5.2/go.mod h1:T+5uoB1PDP6EeK2qXerf5gRh7b6IF8u37GK2ckEi9FU= github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= From 52f869d7b10dd039d7e6a7812b5528cba8c311b2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 1 Aug 2026 16:28:55 -0400 Subject: [PATCH 15/36] chore: bump go modules --- plugins/buildpacks/go.mod | 2 +- plugins/buildpacks/go.sum | 4 ++-- plugins/cron/go.mod | 2 +- plugins/cron/go.sum | 4 ++-- plugins/scheduler-docker-local/go.mod | 2 +- plugins/scheduler-docker-local/go.sum | 4 ++-- plugins/scheduler-k3s/go.mod | 2 +- plugins/scheduler-k3s/go.sum | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/buildpacks/go.mod b/plugins/buildpacks/go.mod index 52615e4ea..bf4916cba 100644 --- a/plugins/buildpacks/go.mod +++ b/plugins/buildpacks/go.mod @@ -18,7 +18,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/fs v0.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/melbahja/goph v1.5.2 // indirect github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/mint v1.6.3 // indirect diff --git a/plugins/buildpacks/go.sum b/plugins/buildpacks/go.sum index bc56d75c4..dc7b17c73 100644 --- a/plugins/buildpacks/go.sum +++ b/plugins/buildpacks/go.sum @@ -16,8 +16,8 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/melbahja/goph v1.5.2 h1:2eoR45SLF3LyM6tnIhnpjakvXTjtQMJqOK/mp1PYojM= github.com/melbahja/goph v1.5.2/go.mod h1:T+5uoB1PDP6EeK2qXerf5gRh7b6IF8u37GK2ckEi9FU= github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I= diff --git a/plugins/cron/go.mod b/plugins/cron/go.mod index 58e437477..0fbb0e03d 100644 --- a/plugins/cron/go.mod +++ b/plugins/cron/go.mod @@ -20,7 +20,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/fs v0.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/melbahja/goph v1.5.2 // indirect github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/mint v1.6.3 // indirect diff --git a/plugins/cron/go.sum b/plugins/cron/go.sum index 595d096de..31c3cd97d 100644 --- a/plugins/cron/go.sum +++ b/plugins/cron/go.sum @@ -22,8 +22,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/melbahja/goph v1.5.2 h1:2eoR45SLF3LyM6tnIhnpjakvXTjtQMJqOK/mp1PYojM= github.com/melbahja/goph v1.5.2/go.mod h1:T+5uoB1PDP6EeK2qXerf5gRh7b6IF8u37GK2ckEi9FU= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= diff --git a/plugins/scheduler-docker-local/go.mod b/plugins/scheduler-docker-local/go.mod index c9a05f71e..3a44ee756 100644 --- a/plugins/scheduler-docker-local/go.mod +++ b/plugins/scheduler-docker-local/go.mod @@ -21,7 +21,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kr/fs v0.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/melbahja/goph v1.5.2 // indirect github.com/otiai10/copy v1.14.1 // indirect github.com/otiai10/mint v1.6.3 // indirect diff --git a/plugins/scheduler-docker-local/go.sum b/plugins/scheduler-docker-local/go.sum index 595d096de..31c3cd97d 100644 --- a/plugins/scheduler-docker-local/go.sum +++ b/plugins/scheduler-docker-local/go.sum @@ -22,8 +22,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/melbahja/goph v1.5.2 h1:2eoR45SLF3LyM6tnIhnpjakvXTjtQMJqOK/mp1PYojM= github.com/melbahja/goph v1.5.2/go.mod h1:T+5uoB1PDP6EeK2qXerf5gRh7b6IF8u37GK2ckEi9FU= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 9dc86e657..9a123090e 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -109,7 +109,7 @@ require ( github.com/lib/pq v1.12.3 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.23 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-runewidth v0.0.20 // indirect github.com/melbahja/goph v1.5.2 // indirect github.com/miekg/dns v1.1.72 // indirect diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 167155ca3..5e801f92e 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -239,8 +239,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= -github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= From d17a538a2cdd5cd446a5cc01e60f65dc8d167a03 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Sun, 2 Aug 2026 06:53:52 +0000 Subject: [PATCH 16/36] chore: bump docker-image-labeler to 0.10.0 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..3ec4b1005 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -10,10 +10,10 @@ }, { "name": "docker-image-labeler", - "version": "0.9.0", + "version": "0.10.0", "urls": { - "amd64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.9.0/docker-image-labeler-linux-amd64", - "arm64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.9.0/docker-image-labeler-linux-arm64" + "amd64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.10.0/docker-image-labeler-linux-amd64", + "arm64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.10.0/docker-image-labeler-linux-arm64" } }, { From 49f9d63760babf2b762927c773c4bc45e6b69a7d Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Sun, 2 Aug 2026 06:53:54 +0000 Subject: [PATCH 17/36] chore: bump docker-container-healthchecker to 0.16.0 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..09238577b 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -2,10 +2,10 @@ "dependencies": [ { "name": "docker-container-healthchecker", - "version": "0.15.2", + "version": "0.16.0", "urls": { - "amd64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.15.2/docker-container-healthchecker-linux-amd64", - "arm64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.15.2/docker-container-healthchecker-linux-arm64" + "amd64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-amd64", + "arm64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-arm64" } }, { From d1aa41e4f1cde0bf9d0aa26dba7bb9713cdcba49 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Sun, 2 Aug 2026 06:53:58 +0000 Subject: [PATCH 18/36] chore: bump procfile-util to 0.20.8 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..c827c6dbf 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -42,10 +42,10 @@ }, { "name": "procfile-util", - "version": "0.20.7", + "version": "0.20.8", "urls": { - "amd64": "https://github.com/dokku/procfile-util/releases/download/v0.20.7/procfile-util-linux-amd64", - "arm64": "https://github.com/dokku/procfile-util/releases/download/v0.20.7/procfile-util-linux-arm64" + "amd64": "https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-amd64", + "arm64": "https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-arm64" } }, { From e186f7be143e75ebf4572c29c5320edb2b7a6a18 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Mon, 3 Aug 2026 07:08:32 +0000 Subject: [PATCH 19/36] chore: bump dokku-update to 0.10.0 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..380cb4db9 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -86,10 +86,10 @@ }, { "name": "dokku-update", - "version": "0.9.8", + "version": "0.10.0", "urls": { - "amd64": "https://github.com/dokku/dokku-update/releases/download/v0.9.8/dokku-update", - "arm64": "https://github.com/dokku/dokku-update/releases/download/v0.9.8/dokku-update" + "amd64": "https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update", + "arm64": "https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update" } }, { From a29532bd0da5c82dab2c19c371dcb873d3f7acdc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:53:48 +0000 Subject: [PATCH 20/36] chore(deps): update markdown requirement in /docs/_build Updates the requirements on [markdown](https://github.com/Python-Markdown/markdown) to permit the latest version. - [Release notes](https://github.com/Python-Markdown/markdown/releases) - [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md) - [Commits](https://github.com/Python-Markdown/markdown/compare/3.10.2...3.10.3) --- updated-dependencies: - dependency-name: markdown dependency-version: 3.10.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- docs/_build/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_build/requirements.txt b/docs/_build/requirements.txt index dfd4dafa9..c9cfed5fb 100644 --- a/docs/_build/requirements.txt +++ b/docs/_build/requirements.txt @@ -3,7 +3,7 @@ click==8.4.2 ghp-import==2.1.0 importlib-metadata==9.0.0 Jinja2==3.1.6 -Markdown>=3.10.2,<3.11 +Markdown>=3.10.3,<3.11 MarkupSafe==3.0.3 mergedeep==1.3.4 mkdocs==1.6.1 From 05c389b5867ccec4aaf3341c05ead20f62334dfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Aug 2026 13:54:51 +0000 Subject: [PATCH 21/36] chore(deps): bump github.com/traefik/traefik/v2 Bumps [github.com/traefik/traefik/v2](https://github.com/traefik/traefik) from 2.11.53 to 2.11.54. - [Release notes](https://github.com/traefik/traefik/releases) - [Changelog](https://github.com/traefik/traefik/blob/v2.11.54/CHANGELOG.md) - [Commits](https://github.com/traefik/traefik/compare/v2.11.53...v2.11.54) --- updated-dependencies: - dependency-name: github.com/traefik/traefik/v2 dependency-version: 2.11.54 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 4 ++-- plugins/scheduler-k3s/go.sum | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 9dc86e657..503e24b26 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -27,7 +27,7 @@ require ( github.com/onsi/gomega v1.42.1 github.com/ryanuber/columnize v2.1.2+incompatible github.com/spf13/pflag v1.0.10 - github.com/traefik/traefik/v2 v2.11.53 + github.com/traefik/traefik/v2 v2.11.54 golang.org/x/sync v0.22.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -152,7 +152,7 @@ require ( go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/mod v0.37.0 // indirect - golang.org/x/net v0.56.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/term v0.45.0 // indirect diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 167155ca3..2cce78db5 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -218,8 +218,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kedacore/keda/v2 v2.20.1 h1:0Bb2zX/s+RSPtU2W+/Jk24A/aFl8MO79UuU0C4lA8ts= github.com/kedacore/keda/v2 v2.20.1/go.mod h1:wrV1MP6buZQ1S5/J/R7UyBeJr857x92E43LADVoSajs= -github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= -github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= +github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -359,8 +359,8 @@ github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a h1:a6TNDN9CgG+cYj github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ= github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= -github.com/traefik/traefik/v2 v2.11.53 h1:IXMxvov4RZM2FGrOiholJUuPkZVB6nXlNowhGyghwWI= -github.com/traefik/traefik/v2 v2.11.53/go.mod h1:4mJRoD8Vp8PRFUCTF8R2nUTtY9bGmXTHpQgx/A1tD/o= +github.com/traefik/traefik/v2 v2.11.54 h1:r2X5S9reiJz30bPgARwiMc8MY79gmX3zkPh4xQNXWCQ= +github.com/traefik/traefik/v2 v2.11.54/go.mod h1:JB0/BW+uMyw2BKPUHAA4Z3ttt/h03MGt9KcNbBbPNKA= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= @@ -405,8 +405,8 @@ go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYr go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= @@ -427,8 +427,8 @@ golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= From 85eace7c756fd8aa743258c2e5caa99fb2017be5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:52:27 +0000 Subject: [PATCH 22/36] chore(deps-dev): bump heroku/heroku-buildpack-php in /tests/apps/php Bumps [heroku/heroku-buildpack-php](https://github.com/heroku/heroku-buildpack-php) from 293 to 294. - [Release notes](https://github.com/heroku/heroku-buildpack-php/releases) - [Changelog](https://github.com/heroku/heroku-buildpack-php/blob/main/CHANGELOG.md) - [Commits](https://github.com/heroku/heroku-buildpack-php/compare/v293...v294) --- updated-dependencies: - dependency-name: heroku/heroku-buildpack-php dependency-version: '294' dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- tests/apps/php/composer.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/apps/php/composer.lock b/tests/apps/php/composer.lock index 3adc71ca8..72f8e08f1 100644 --- a/tests/apps/php/composer.lock +++ b/tests/apps/php/composer.lock @@ -603,16 +603,16 @@ "packages-dev": [ { "name": "heroku/heroku-buildpack-php", - "version": "v293", + "version": "v294", "source": { "type": "git", "url": "https://github.com/heroku/heroku-buildpack-php.git", - "reference": "76afc414bf3f106b7e50f67a0631f28765288f12" + "reference": "2a8f9567a34acc79b6ebc1118196cfeda031ca2a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/76afc414bf3f106b7e50f67a0631f28765288f12", - "reference": "76afc414bf3f106b7e50f67a0631f28765288f12", + "url": "https://api.github.com/repos/heroku/heroku-buildpack-php/zipball/2a8f9567a34acc79b6ebc1118196cfeda031ca2a", + "reference": "2a8f9567a34acc79b6ebc1118196cfeda031ca2a", "shasum": "" }, "bin": [ @@ -642,9 +642,9 @@ ], "support": { "issues": "https://github.com/heroku/heroku-buildpack-php/issues", - "source": "https://github.com/heroku/heroku-buildpack-php/tree/v293" + "source": "https://github.com/heroku/heroku-buildpack-php/tree/v294" }, - "time": "2026-07-08T19:47:51+00:00" + "time": "2026-07-31T14:30:30+00:00" } ], "aliases": [], From be619eb999837e402cb3d9cd1c62020b1a3727cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:54:02 +0000 Subject: [PATCH 23/36] chore(deps): bump traefik in /plugins/traefik-vhosts Bumps traefik from v3.7.9 to v3.7.10. --- updated-dependencies: - dependency-name: traefik dependency-version: v3.7.10 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- plugins/traefik-vhosts/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/traefik-vhosts/Dockerfile b/plugins/traefik-vhosts/Dockerfile index 486043edd..10165a3f6 100644 --- a/plugins/traefik-vhosts/Dockerfile +++ b/plugins/traefik-vhosts/Dockerfile @@ -1 +1 @@ -FROM traefik:v3.7.9 +FROM traefik:v3.7.10 From c4c61db92b3e939c72b07090d59061f5d4174940 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 13:54:45 +0000 Subject: [PATCH 24/36] chore(deps): bump django in /tests/apps/dockerfile-release Bumps [django](https://github.com/django/django) from 5.2.16 to 5.2.17. - [Commits](https://github.com/django/django/compare/5.2.16...5.2.17) --- updated-dependencies: - dependency-name: django dependency-version: 5.2.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- tests/apps/dockerfile-release/Pipfile.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/apps/dockerfile-release/Pipfile.lock b/tests/apps/dockerfile-release/Pipfile.lock index ab96fdf0b..46da75903 100644 --- a/tests/apps/dockerfile-release/Pipfile.lock +++ b/tests/apps/dockerfile-release/Pipfile.lock @@ -18,11 +18,11 @@ "default": { "asgiref": { "hashes": [ - "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce", - "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133" + "sha256:59dcb51c272ad209d59bed5708a64a333083e86017d7fcdd67498eeab7784340", + "sha256:fe386d1c2bff7259ea95929266d12a8cf9a8b5a1c2598402967d8792e7a7c094" ], - "markers": "python_version >= '3.9'", - "version": "==3.11.1" + "markers": "python_version >= '3.10'", + "version": "==3.12.1" }, "dj-database-url": { "hashes": [ @@ -35,12 +35,12 @@ }, "django": { "hashes": [ - "sha256:04f354bf9d807a86ad1a8392fe3808d362358a8eafc322848e0e43e59b24371d", - "sha256:59ea02020c3136fce14bef0bbece21a10a4febef5eed1c51c22ae468efa22200" + "sha256:9d4d93be539a18ab80d058eb515900e10951e04c537c5a6b394fc49528d3251f", + "sha256:f04fb3b36ee119e1af4fa1d397d5fd6cf12700f49321e84d4f4c642c5b1973db" ], "index": "pypi", "markers": "python_version >= '3.10'", - "version": "==5.2.16" + "version": "==5.2.17" }, "djangorestframework": { "hashes": [ From 819f32ff86b4bfa29e223b9461bbffd572788358 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:07 +0000 Subject: [PATCH 25/36] chore: bump plugn to 0.17.1 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..36ac0e08f 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -68,10 +68,10 @@ }, { "name": "plugn", - "version": "0.17.0", + "version": "0.17.1", "urls": { - "amd64": "https://github.com/dokku/plugn/releases/download/v0.17.0/plugn-linux-amd64", - "arm64": "https://github.com/dokku/plugn/releases/download/v0.17.0/plugn-linux-arm64" + "amd64": "https://github.com/dokku/plugn/releases/download/v0.17.1/plugn-linux-amd64", + "arm64": "https://github.com/dokku/plugn/releases/download/v0.17.1/plugn-linux-arm64" } } ], From 43b69c57bc73ba36cbdaef6ea5f39868d06fc605 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:08 +0000 Subject: [PATCH 26/36] chore: bump lambda-builder to 0.9.4 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..b5f0ccf01 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -18,10 +18,10 @@ }, { "name": "lambda-builder", - "version": "0.9.3", + "version": "0.9.4", "urls": { - "amd64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.3/lambda-builder-linux-amd64", - "arm64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.3/lambda-builder-linux-arm64" + "amd64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.4/lambda-builder-linux-amd64", + "arm64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.4/lambda-builder-linux-arm64" } }, { From 4de89c28e965abe2ae08f866cbe91a76fb42af7e Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:09 +0000 Subject: [PATCH 27/36] chore: bump sshcommand to 0.20.2 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..ac283992c 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -50,10 +50,10 @@ }, { "name": "sshcommand", - "version": "0.20.1", + "version": "0.20.2", "urls": { - "amd64": "https://github.com/dokku/sshcommand/releases/download/v0.20.1/sshcommand", - "arm64": "https://github.com/dokku/sshcommand/releases/download/v0.20.1/sshcommand" + "amd64": "https://github.com/dokku/sshcommand/releases/download/v0.20.2/sshcommand", + "arm64": "https://github.com/dokku/sshcommand/releases/download/v0.20.2/sshcommand" } } ], From f182ed4ccb0bf65f68cbd6a40b555eb0c6f6f3dc Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:11 +0000 Subject: [PATCH 28/36] chore: bump dokku-event-listener to 0.20.1 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..7eb6de538 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -78,10 +78,10 @@ "recommendations": [ { "name": "dokku-event-listener", - "version": "0.20.0", + "version": "0.20.1", "urls": { - "amd64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.0/dokku-event-listener-linux-amd64", - "arm64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.0/dokku-event-listener-linux-arm64" + "amd64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.1/dokku-event-listener-linux-amd64", + "arm64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.1/dokku-event-listener-linux-arm64" } }, { From ea77396a2462ea8d3fc02d549e5dbdf634d8f54c Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:14 +0000 Subject: [PATCH 29/36] chore: bump netrc to 0.11.1 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..6a51f8ab9 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -26,10 +26,10 @@ }, { "name": "netrc", - "version": "0.11.0", + "version": "0.11.1", "urls": { - "amd64": "https://github.com/dokku/netrc/releases/download/v0.11.0/netrc-linux-amd64", - "arm64": "https://github.com/dokku/netrc/releases/download/v0.11.0/netrc-linux-arm64" + "amd64": "https://github.com/dokku/netrc/releases/download/v0.11.1/netrc-linux-amd64", + "arm64": "https://github.com/dokku/netrc/releases/download/v0.11.1/netrc-linux-arm64" } }, { From ac6d2723d98ebd7929bd9700e48a33a632e8a461 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:16 +0000 Subject: [PATCH 30/36] chore: bump herokuish to 0.11.14 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..89a875b69 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -94,10 +94,10 @@ }, { "name": "herokuish", - "version": "0.11.13", + "version": "0.11.14", "urls": { - "amd64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.13/herokuish_0.11.13_linux_x86_64.tgz", - "arm64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.13/herokuish_0.11.13_linux_x86_64.tgz" + "amd64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.14/herokuish_0.11.14_linux_x86_64.tgz", + "arm64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.14/herokuish_0.11.14_linux_x86_64.tgz" } } ] From bfbfdd3d9ed3ddbc69f76d220a3f7392786a7e14 Mon Sep 17 00:00:00 2001 From: Dokku Bot Date: Thu, 6 Aug 2026 06:55:18 +0000 Subject: [PATCH 31/36] chore: bump gliderlabs-sigil to 0.12.1 --- contrib/dependencies.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/dependencies.json b/contrib/dependencies.json index e78b51a46..579a9afcf 100644 --- a/contrib/dependencies.json +++ b/contrib/dependencies.json @@ -60,10 +60,10 @@ "predependencies": [ { "name": "gliderlabs-sigil", - "version": "0.12.0", + "version": "0.12.1", "urls": { - "amd64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.0/sigil-linux-amd64", - "arm64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.0/sigil-linux-arm64" + "amd64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.1/sigil-linux-amd64", + "arm64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.1/sigil-linux-arm64" } }, { From 8e17eee65334af8ba902e72fce82406b01ae476f Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 7 Aug 2026 02:31:12 -0400 Subject: [PATCH 32/36] feat: add --kubelet-args to scheduler-k3s:initialize The server node created by `scheduler-k3s:initialize` had no way to receive kubelet arguments, unlike nodes joined through `scheduler-k3s:cluster:add` or configured via `scheduler-k3s:profiles:add`. This meant settings such as `allowed-unsafe-sysctls` were unreachable on a single-node install. --- docs/deployment/schedulers/k3s.md | 14 +++ plugins/scheduler-k3s/functions.go | 59 +++++++++++++ plugins/scheduler-k3s/functions_test.go | 87 +++++++++++++++++++ .../scheduler-k3s/src/commands/commands.go | 2 +- .../src/subcommands/subcommands.go | 3 +- plugins/scheduler-k3s/subcommands.go | 43 ++------- tests/unit/test_helper.bash | 4 + 7 files changed, 176 insertions(+), 36 deletions(-) diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index fd55e2adb..8fdad03bc 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -78,6 +78,20 @@ Dokku can also use Traefik on cluster initialization via the [Traefik's CRDs](ht dokku scheduler-k3s:initialize --ingress-class traefik ``` +Kubelet flags for the initial server node can be supplied by passing `--kubelet-args` with a comma-separated `key=value` list. This is the only way to configure the kubelet on the node created by `scheduler-k3s:initialize`, as that node never passes through `scheduler-k3s:cluster:add`. + +```shell +dokku scheduler-k3s:initialize \ + --kubelet-args allowed-unsafe-sysctls=net.ipv6.conf.all.disable_ipv6 +``` + +Multiple kubelet arguments can be specified in the same call by separating them with commas. + +```shell +dokku scheduler-k3s:initialize \ + --kubelet-args allowed-unsafe-sysctls=net.ipv6.conf.all.disable_ipv6,max-pods=150 +``` + ### Adding nodes to the cluster > [!WARNING] diff --git a/plugins/scheduler-k3s/functions.go b/plugins/scheduler-k3s/functions.go index 08fba6bac..dcfb18acc 100644 --- a/plugins/scheduler-k3s/functions.go +++ b/plugins/scheduler-k3s/functions.go @@ -1286,6 +1286,65 @@ func resolveLetsencryptIssuer(appName string, clusterIssuerName string, appEmail } } +// InitializeInstallerArgsInput contains the inputs to initializeInstallerArgs +type InitializeInstallerArgsInput struct { + // IngressClass is the ingress class the cluster is initialized with + IngressClass string + // KubeletArgs is a list of key=value kubelet arguments for the server node + KubeletArgs []string + // NodeName is the generated name of the server node + NodeName string + // TaintScheduling is whether to taint the node against app workloads + TaintScheduling bool + // Token is the cluster join token + Token string +} + +// initializeInstallerArgs builds the argument list handed to the k3s installer +// when creating the initial server node +func initializeInstallerArgs(input InitializeInstallerArgsInput) []string { + args := []string{ + // initialize the cluster + "--cluster-init", + // disable local-storage + "--disable", "local-storage", + // disable traefik so it can be installed separately + "--disable", "traefik", + // expose etcd metrics + "--etcd-expose-metrics", + // use wireguard for flannel + "--flannel-backend=wireguard-native", + // bind controller-manager to all interfaces + "--kube-controller-manager-arg", "bind-address=0.0.0.0", + // bind proxy metrics to all interfaces + "--kube-proxy-arg", "metrics-bind-address=0.0.0.0", + // bind scheduler to all interfaces + "--kube-scheduler-arg", "bind-address=0.0.0.0", + // gc terminated pods + "--kube-controller-manager-arg", "terminated-pod-gc-threshold=10", + // specify the node name + "--node-name", input.NodeName, + // allow access for the dokku user + "--write-kubeconfig-mode", "0644", + // specify a token + "--token", input.Token, + } + + if input.TaintScheduling { + args = append(args, "--node-taint", "CriticalAddonsOnly=true:NoSchedule") + } + + for _, kubeletArg := range input.KubeletArgs { + args = append(args, "--kubelet-arg", kubeletArg) + } + + if input.IngressClass == "nginx" { + args = append(args, "--disable", "traefik") + } + + return args +} + func getKustomizeDirectory(appName string) string { directory := filepath.Join(common.MustGetEnv("DOKKU_LIB_ROOT"), "data", "scheduler-k3s", appName) return filepath.Join(directory, "kustomization") diff --git a/plugins/scheduler-k3s/functions_test.go b/plugins/scheduler-k3s/functions_test.go index 10bf58d6c..c2de4576f 100644 --- a/plugins/scheduler-k3s/functions_test.go +++ b/plugins/scheduler-k3s/functions_test.go @@ -91,3 +91,90 @@ func TestNeedsImagePullSecretsPrune(t *testing.T) { }) } } + +func TestInitializeInstallerArgs(t *testing.T) { + cases := []struct { + name string + input InitializeInstallerArgsInput + wantPairs [][2]string + wantAbsent []string + }{ + { + name: "no kubelet args", + input: InitializeInstallerArgsInput{ + IngressClass: "traefik", + NodeName: "ip-10-0-0-1-abc", + Token: "sometoken", + }, + wantAbsent: []string{"--kubelet-arg", "--node-taint"}, + }, + { + name: "single kubelet arg", + input: InitializeInstallerArgsInput{ + IngressClass: "traefik", + KubeletArgs: []string{"allowed-unsafe-sysctls=net.ipv4.tcp_rmem"}, + NodeName: "ip-10-0-0-1-abc", + Token: "sometoken", + }, + wantPairs: [][2]string{ + {"--kubelet-arg", "allowed-unsafe-sysctls=net.ipv4.tcp_rmem"}, + }, + }, + { + name: "multiple kubelet args each get their own flag", + input: InitializeInstallerArgsInput{ + IngressClass: "traefik", + KubeletArgs: []string{"allowed-unsafe-sysctls=net.ipv4.tcp_rmem", "max-pods=150"}, + NodeName: "ip-10-0-0-1-abc", + Token: "sometoken", + }, + wantPairs: [][2]string{ + {"--kubelet-arg", "allowed-unsafe-sysctls=net.ipv4.tcp_rmem"}, + {"--kubelet-arg", "max-pods=150"}, + }, + }, + { + name: "kubelet args coexist with taint scheduling", + input: InitializeInstallerArgsInput{ + IngressClass: "nginx", + KubeletArgs: []string{"max-pods=150"}, + NodeName: "ip-10-0-0-1-abc", + TaintScheduling: true, + Token: "sometoken", + }, + wantPairs: [][2]string{ + {"--node-taint", "CriticalAddonsOnly=true:NoSchedule"}, + {"--kubelet-arg", "max-pods=150"}, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := initializeInstallerArgs(tc.input) + + for _, pair := range tc.wantPairs { + if !containsArgPair(got, pair[0], pair[1]) { + t.Errorf("initializeInstallerArgs() missing %q %q, got %v", pair[0], pair[1], got) + } + } + + for _, absent := range tc.wantAbsent { + for _, arg := range got { + if arg == absent { + t.Errorf("initializeInstallerArgs() unexpectedly contains %q, got %v", absent, got) + } + } + } + }) + } +} + +func containsArgPair(args []string, flag string, value string) bool { + for i := 0; i < len(args)-1; i++ { + if args[i] == flag && args[i+1] == value { + return true + } + } + return false +} diff --git a/plugins/scheduler-k3s/src/commands/commands.go b/plugins/scheduler-k3s/src/commands/commands.go index 0e93ffc3d..b0c4be449 100644 --- a/plugins/scheduler-k3s/src/commands/commands.go +++ b/plugins/scheduler-k3s/src/commands/commands.go @@ -27,7 +27,7 @@ Additional commands:` scheduler-k3s:cluster:list [--format json|stdout], Lists all nodes in a Dokku-managed cluster scheduler-k3s:cluster:remove [node-id], Removes client node to a Dokku-managed cluster scheduler-k3s:ensure-charts, Ensures the k3s charts are installed - scheduler-k3s:initialize [--server-ip SERVER_IP] [--taint-scheduling], Initializes a cluster + scheduler-k3s:initialize [--server-ip SERVER_IP] [--taint-scheduling] [--kubelet-args KUBELET_ARGS], Initializes a cluster scheduler-k3s:labels:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear a label for a given app/process-type/resource-type combination scheduler-k3s:labels:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE], Displays a scheduler-k3s labels report for one or more apps scheduler-k3s:preview [--context N] [--show-secrets] [--show-secrets-decoded], Displays a diff between the current and next deployment for an app diff --git a/plugins/scheduler-k3s/src/subcommands/subcommands.go b/plugins/scheduler-k3s/src/subcommands/subcommands.go index 7cc2f1e5b..52a58ce96 100644 --- a/plugins/scheduler-k3s/src/subcommands/subcommands.go +++ b/plugins/scheduler-k3s/src/subcommands/subcommands.go @@ -128,8 +128,9 @@ func main() { taintScheduling := args.Bool("taint-scheduling", false, "taint-scheduling: add a taint against scheduling app workloads") serverIP := args.String("server-ip", "", "server-ip: IP address of the dokku server node") ingressClass := args.String("ingress-class", "nginx", "ingress-class: ingress-class to use for all outbound traffic") + kubeletArgs := args.StringSlice("kubelet-args", []string{}, "kubelet-args: repeatable key=value kubelet arguments (e.g., --kubelet-args key=value)") args.Parse(os.Args[2:]) - err = scheduler_k3s.CommandInitialize(*ingressClass, *serverIP, *taintScheduling) + err = scheduler_k3s.CommandInitialize(*ingressClass, *serverIP, *taintScheduling, *kubeletArgs) case "labels:set": args := flag.NewFlagSet("scheduler-k3s:labels:set", flag.ExitOnError) global := args.Bool("global", false, "--global: set a global property") diff --git a/plugins/scheduler-k3s/subcommands.go b/plugins/scheduler-k3s/subcommands.go index b65844c8f..09e5094d7 100644 --- a/plugins/scheduler-k3s/subcommands.go +++ b/plugins/scheduler-k3s/subcommands.go @@ -174,7 +174,7 @@ func CommandLabelsReport(appName string, format string, processType string, reso } // CommandInitialize initializes a k3s cluster on the local server -func CommandInitialize(ingressClass string, serverIP string, taintScheduling bool) error { +func CommandInitialize(ingressClass string, serverIP string, taintScheduling bool, kubeletArgs []string) error { if ingressClass != "nginx" && ingressClass != "traefik" { return fmt.Errorf("Invalid ingress-class: %s", ingressClass) } @@ -308,40 +308,15 @@ func CommandInitialize(ingressClass string, serverIP string, taintScheduling boo } nodeName = strings.ReplaceAll(strings.ToLower(fmt.Sprintf("ip-%s-%s", nodeName, fmt.Sprintf("%X", b))), ".", "-") - args := []string{ - // initialize the cluster - "--cluster-init", - // disable local-storage - "--disable", "local-storage", - // disable traefik so it can be installed separately - "--disable", "traefik", - // expose etcd metrics - "--etcd-expose-metrics", - // use wireguard for flannel - "--flannel-backend=wireguard-native", - // bind controller-manager to all interfaces - "--kube-controller-manager-arg", "bind-address=0.0.0.0", - // bind proxy metrics to all interfaces - "--kube-proxy-arg", "metrics-bind-address=0.0.0.0", - // bind scheduler to all interfaces - "--kube-scheduler-arg", "bind-address=0.0.0.0", - // gc terminated pods - "--kube-controller-manager-arg", "terminated-pod-gc-threshold=10", - // specify the node name - "--node-name", nodeName, - // allow access for the dokku user - "--write-kubeconfig-mode", "0644", - // specify a token - "--token", token, - } - if taintScheduling { - args = append(args, "--node-taint", "CriticalAddonsOnly=true:NoSchedule") - } - common.CommandPropertySet("scheduler-k3s", "--global", "ingress-class", ingressClass, DefaultProperties, GlobalProperties) - if ingressClass == "nginx" { - args = append(args, "--disable", "traefik") - } + + args := initializeInstallerArgs(InitializeInstallerArgsInput{ + IngressClass: ingressClass, + KubeletArgs: kubeletArgs, + NodeName: nodeName, + TaintScheduling: taintScheduling, + Token: token, + }) common.LogInfo2Quiet("Running k3s installer") installerCmd, err := common.CallExecCommand(common.ExecCommandInput{ diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index fb35b626a..d24ee09d2 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -740,6 +740,10 @@ install_k3s() { args="$args --server-ip $CI_SERVER_IP" fi + if [[ -n "$KUBELET_ARGS" ]]; then + args="$args --kubelet-args $KUBELET_ARGS" + fi + run /bin/bash -c "dokku scheduler-k3s:initialize ${args}" echo "output: $output" echo "status: $status" From f050726f1ac5cfe3aedb574be898e1129cc6ca87 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 7 Aug 2026 02:32:34 -0400 Subject: [PATCH 33/36] feat: label nodes with the node profile they joined with Node profiles controlled how a node joined the cluster but left no trace on the node afterwards, so a profile could not be selected against with `kubectl`, a `nodeSelector`, or a node affinity rule. Nodes joined without a profile are left unlabeled, and the server node created by `scheduler-k3s:initialize` never carries the label since it does not pass through `scheduler-k3s:cluster:add`. --- docs/deployment/schedulers/k3s.md | 15 +++++ plugins/scheduler-k3s/functions.go | 21 +++++++ plugins/scheduler-k3s/functions_test.go | 77 +++++++++++++++++++++++++ plugins/scheduler-k3s/scheduler_k3s.go | 3 + plugins/scheduler-k3s/subcommands.go | 5 +- 5 files changed, 117 insertions(+), 4 deletions(-) diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index 8fdad03bc..8572ca8d5 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -214,6 +214,21 @@ dokku scheduler-k3s:profiles:remove edge-workers Removal only deletes the stored definition; nodes that already joined the cluster keep their existing configuration. +#### The node profile label + +When a node joins via `scheduler-k3s:cluster:add --profile `, Dokku labels it with `dokku.com/node-profile=`. This makes a profile selectable after the fact, whether via `kubectl`, a `nodeSelector`, or a node affinity rule. + +```shell +kubectl get nodes -L dokku.com/node-profile +``` + +Nodes added without `--profile` are not labeled, as an empty label value would be indistinguishable from a profile literally named the empty string. + +Two limits are worth knowing before relying on this label: + +- The server node never carries it. That node is created by `scheduler-k3s:initialize` and never passes through `scheduler-k3s:cluster:add`, so no profile is ever associated with it. +- Nodes that joined before this label existed are not backfilled. Use `kubectl label node dokku.com/node-profile=` to set it on an existing node. + ### Changing deployment settings The k3s plugin provides a number of settings that can be used to managed deployments on a per-app basis. The following table outlines ones not covered elsewhere: diff --git a/plugins/scheduler-k3s/functions.go b/plugins/scheduler-k3s/functions.go index dcfb18acc..a53f16771 100644 --- a/plugins/scheduler-k3s/functions.go +++ b/plugins/scheduler-k3s/functions.go @@ -1286,6 +1286,27 @@ func resolveLetsencryptIssuer(appName string, clusterIssuerName string, appEmail } } +// nodeLabels returns the labels to apply to a node joining the cluster, including +// the node profile label when the node was added with a named profile. The returned +// map is always a fresh copy so callers cannot mutate ServerLabels or WorkerLabels. +func nodeLabels(role string, profileName string) map[string]string { + source := ServerLabels + if role == "worker" { + source = WorkerLabels + } + + labels := make(map[string]string, len(source)+1) + for key, value := range source { + labels[key] = value + } + + if profileName != "" { + labels[NodeProfileLabel] = profileName + } + + return labels +} + // InitializeInstallerArgsInput contains the inputs to initializeInstallerArgs type InitializeInstallerArgsInput struct { // IngressClass is the ingress class the cluster is initialized with diff --git a/plugins/scheduler-k3s/functions_test.go b/plugins/scheduler-k3s/functions_test.go index c2de4576f..b2df0995b 100644 --- a/plugins/scheduler-k3s/functions_test.go +++ b/plugins/scheduler-k3s/functions_test.go @@ -178,3 +178,80 @@ func containsArgPair(args []string, flag string, value string) bool { } return false } + +func TestNodeLabels(t *testing.T) { + cases := []struct { + name string + role string + profileName string + wantKey string + wantValue string + wantProfile bool + }{ + { + name: "server without a profile", + role: "server", + profileName: "", + wantKey: "svccontroller.k3s.cattle.io/enablelb", + wantValue: "true", + wantProfile: false, + }, + { + name: "worker without a profile", + role: "worker", + profileName: "", + wantKey: "node-role.kubernetes.io/worker", + wantValue: "worker", + wantProfile: false, + }, + { + name: "worker with a profile", + role: "worker", + profileName: "edge-workers", + wantKey: "node-role.kubernetes.io/worker", + wantValue: "worker", + wantProfile: true, + }, + { + name: "server with a profile", + role: "server", + profileName: "control-plane", + wantKey: "svccontroller.k3s.cattle.io/enablelb", + wantValue: "true", + wantProfile: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := nodeLabels(tc.role, tc.profileName) + + if got[tc.wantKey] != tc.wantValue { + t.Errorf("nodeLabels() role label = %q, want %q", got[tc.wantKey], tc.wantValue) + } + + profile, ok := got[NodeProfileLabel] + if ok != tc.wantProfile { + t.Errorf("nodeLabels() has %s = %v, want %v", NodeProfileLabel, ok, tc.wantProfile) + } + if tc.wantProfile && profile != tc.profileName { + t.Errorf("nodeLabels() %s = %q, want %q", NodeProfileLabel, profile, tc.profileName) + } + }) + } +} + +func TestNodeLabelsDoesNotMutatePackageLabels(t *testing.T) { + serverBefore := len(ServerLabels) + workerBefore := len(WorkerLabels) + + nodeLabels("server", "control-plane") + nodeLabels("worker", "edge-workers") + + if len(ServerLabels) != serverBefore { + t.Errorf("nodeLabels() mutated ServerLabels: len = %d, want %d", len(ServerLabels), serverBefore) + } + if len(WorkerLabels) != workerBefore { + t.Errorf("nodeLabels() mutated WorkerLabels: len = %d, want %d", len(WorkerLabels), workerBefore) + } +} diff --git a/plugins/scheduler-k3s/scheduler_k3s.go b/plugins/scheduler-k3s/scheduler_k3s.go index f11a67e5f..e9fdc3795 100644 --- a/plugins/scheduler-k3s/scheduler_k3s.go +++ b/plugins/scheduler-k3s/scheduler_k3s.go @@ -231,6 +231,9 @@ type NodeProfile struct { KubeletArgs []string `json:"kubelet_args,omitempty"` } +// NodeProfileLabel is the node label recording the node profile a node was added with +const NodeProfileLabel = "dokku.com/node-profile" + // ServerLabels are the labels for a server node var ServerLabels = map[string]string{ "svccontroller.k3s.cattle.io/enablelb": "true", diff --git a/plugins/scheduler-k3s/subcommands.go b/plugins/scheduler-k3s/subcommands.go index 09e5094d7..54b827d81 100644 --- a/plugins/scheduler-k3s/subcommands.go +++ b/plugins/scheduler-k3s/subcommands.go @@ -875,10 +875,7 @@ export INSTALL_K3S_VERSION=%s return fmt.Errorf("Unable to find node after joining cluster, node will not be annotated/labeled appropriately access registry secrets") } - labels := ServerLabels - if incomingProfile.Role == "worker" { - labels = WorkerLabels - } + labels := nodeLabels(incomingProfile.Role, profileName) for key, value := range labels { common.LogInfo2Quiet(fmt.Sprintf("Labeling node %s=%s", key, value)) From cd1089500b84d5f679eab2fd086d7f05f7f8bbd2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 7 Aug 2026 02:38:46 -0400 Subject: [PATCH 34/36] feat: translate docker-options --sysctl on the k3s scheduler The `docker-local` scheduler supports `--sysctl` for free because docker options are passed verbatim to `docker run`, but the k3s scheduler silently dropped it. Namespaced sysctls now render into the pod's `securityContext.sysctls` for deployments, cron jobs, and one-off runs. A sysctl the kernel does not namespace fails the deploy instead of being dropped, since it cannot take effect within a pod regardless of what was requested. --- docs/advanced-usage/docker-options.md | 10 + docs/deployment/schedulers/k3s.md | 39 ++++ plugins/scheduler-k3s/functions.go | 57 +++++ plugins/scheduler-k3s/functions_test.go | 102 +++++++++ plugins/scheduler-k3s/template.go | 27 +++ plugins/scheduler-k3s/template_test.go | 199 ++++++++++++++++++ .../templates/chart/cron-job.yaml | 8 + .../templates/chart/deployment.yaml | 8 + tests/unit/scheduler-k3s-5.bats | 80 +++++++ 9 files changed, 530 insertions(+) create mode 100644 plugins/scheduler-k3s/template_test.go create mode 100644 tests/unit/scheduler-k3s-5.bats diff --git a/docs/advanced-usage/docker-options.md b/docs/advanced-usage/docker-options.md index 054904e34..d7885167e 100644 --- a/docs/advanced-usage/docker-options.md +++ b/docs/advanced-usage/docker-options.md @@ -38,6 +38,16 @@ More information on supported Docker options can be found [here](https://docs.do Container options configured via the `docker-options` plugin are not used to modify the process a container runs. Container options are the `[OPTIONS]` portion of the following, where `[CONTAINER_COMMAND]` and `[ARG]` are the process and the arguments passed to it that are launched in the created container: `docker run [OPTIONS] [CONTAINER_COMMAND] [ARG...]`. Please see the documentation for [customizing the run command](/docs/deployment/builders/dockerfiles.md#customizing-the-run-command) or use a [Procfile](/docs/deployment/builders/dockerfiles.md#procfiles-and-multiple-processes) to modify the command used by a Dockerfile-based container. +#### Scheduler support + +Docker options are written in Docker's own vocabulary and are passed verbatim to `docker run` by the `docker-local` scheduler. Other schedulers translate only the subset that has an equivalent in their own runtime, and ignore the rest. + +The `k3s` scheduler translates `--cap-add`, `--cap-drop`, `--privileged`, and `--sysctl` into their Kubernetes equivalents. See the [k3s scheduler documentation](/docs/deployment/schedulers/k3s.md) for details, including the restriction that only namespaced sysctls can be set on a pod. + +```shell +dokku docker-options:add node-js-app deploy "--sysctl net.ipv4.ip_unprivileged_port_start=1024" +``` + #### Mounting volumes and host directories Docker supports volume and host directory mounting via the `-v` or `--volume` flags. In order to simplify usage, Dokku provides a `storage` plugin as an abstraction to interact with persistent storage. In most cases, the Dokku project recommends using the persistent storage plugin over directly manipulating docker options at different phases. See the [persistent storage documentation](/docs/advanced-usage/persistent-storage.md) for more information on how to attach persistent storage to your app. diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index 8572ca8d5..b0c9c59f2 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -740,6 +740,44 @@ A single configured metadata key can also be queried with a flag of the form `-- dokku scheduler-k3s:autoscaling-auth:report node-js-app --scheduler-k3s-autoscaling-auth.datadog.apiKey ``` +### Setting kernel sysctls + +Kernel sysctls fall into two categories, and which one a sysctl belongs to determines how it must be set. + +The kernel maintains a per-namespace copy of `net.*` (network namespace) as well as `kernel.shm*`, `kernel.msg*`, `kernel.sem`, and `fs.mqueue.*` (IPC namespace). These can be set on a single app's pods. Every other sysctl - including all of `vm.*`, and therefore `vm.max_map_count` - holds a single value shared by the entire machine, so it cannot be scoped to a pod and must be applied to the node itself. + +#### Namespaced sysctls + +Namespaced sysctls are set with the `docker-options` plugin, and are translated into the pod's `securityContext.sysctls`. A `ps:restart` is required to apply them. + +```shell +dokku docker-options:add node-js-app deploy "--sysctl net.ipv4.ip_unprivileged_port_start=1024" +``` + +Passing a non-namespaced sysctl this way fails the deploy rather than silently dropping the value, since it provably cannot take effect within a pod. Note this differs from the `docker-local` scheduler, where such an option is passed straight through to `docker run`. + +Kubernetes further splits namespaced sysctls into a *safe* list that any pod may set, and everything else. A sysctl outside the safe list - `net.core.somaxconn`, for example - is rejected at pod admission unless the node's kubelet was started with a matching `allowed-unsafe-sysctls` value, which can be supplied at cluster initialization or when joining a node. + +```shell +dokku scheduler-k3s:initialize --kubelet-args allowed-unsafe-sysctls=net.core.somaxconn +``` + +Dokku does not enforce the safe list itself, as its membership changes between Kubernetes releases. Only the namespaced/non-namespaced distinction, which is a property of the kernel, is validated. + +#### Non-namespaced sysctls + +Non-namespaced sysctls are a property of the node, not of any app. Set them directly on each node in the cluster. + +```shell +sudo sysctl -w vm.max_map_count=262144 +``` + +```shell +echo "vm.max_map_count = 262144" | sudo tee /etc/sysctl.d/99-max-map-count.conf +``` + +The second command is what makes the change survive a reboot; `sysctl -w` alone does not. Repeat both on every node, including any added later via `scheduler-k3s:cluster:add`. + ### Integrating Kustomize Dokku supports integration with [Kustomize](https://kustomize.io/) to further customize the generated helm charts for app deployments. For example, a `config/kustomize/kustomization.yaml` file with the following contents will override the scale for each process deployed to `3`: @@ -894,6 +932,7 @@ This plugin implements various functionality through `plugn` triggers to integra - `--cap-add` - `--cap-drop` - `--privileged` + - `--sysctl` (namespaced sysctls only, see [Setting kernel sysctls](#setting-kernel-sysctls)) - `cron` - `enter` - `deploy` diff --git a/plugins/scheduler-k3s/functions.go b/plugins/scheduler-k3s/functions.go index a53f16771..842a6021b 100644 --- a/plugins/scheduler-k3s/functions.go +++ b/plugins/scheduler-k3s/functions.go @@ -1754,12 +1754,62 @@ func getStartCommand(input StartCommandInput) (StartCommandOutput, error) { }, nil } +// namespacedSysctlPrefixes are the sysctl prefixes the kernel maintains per-namespace +var namespacedSysctlPrefixes = []string{ + "net.", + "kernel.shm", + "kernel.msg", + "fs.mqueue.", +} + +// isNamespacedSysctl reports whether a sysctl is maintained per-namespace by the +// kernel and can therefore be set on a pod spec. Sysctls outside these subtrees +// hold a single value shared by the entire machine, and kubelet rejects them. +func isNamespacedSysctl(name string) bool { + if name == "kernel.sem" { + return true + } + + for _, prefix := range namespacedSysctlPrefixes { + if strings.HasPrefix(name, prefix) { + return true + } + } + + return false +} + +// parseSysctls converts docker-option key=value pairs into sysctls sorted by name, +// rejecting any sysctl that cannot take effect within a pod's namespaces. +func parseSysctls(values []string) ([]Sysctl, error) { + sysctls := []Sysctl{} + for _, value := range values { + name, sysctlValue, found := strings.Cut(value, "=") + if !found || name == "" { + return nil, fmt.Errorf("Invalid --sysctl value, expected name=value: %s", value) + } + + if !isNamespacedSysctl(name) { + return nil, fmt.Errorf("Sysctl %s is not namespaced and cannot be set on a pod, it must be applied at the node level instead", name) + } + + sysctls = append(sysctls, Sysctl{Name: name, Value: sysctlValue}) + } + + sort.Slice(sysctls, func(i int, j int) bool { + return sysctls[i].Name < sysctls[j].Name + }) + + return sysctls, nil +} + func getSecurityContext(appName string, phase string) (SecurityContext, error) { securityContext := SecurityContext{} deployOptions, err := dockeroptions.GetSpecifiedDockerOptionsForPhase(appName, phase, []string{ "--cap-add", "--cap-drop", "--privileged", + "--sysctl", }) if err != nil { return SecurityContext{}, fmt.Errorf("Error getting deploy options: %w", err) @@ -1782,6 +1832,13 @@ func getSecurityContext(appName string, phase string) (SecurityContext, error) { } securityContext.Capabilities.Drop = capabilities } + if sysctlOptions, ok := deployOptions["--sysctl"]; ok { + sysctls, err := parseSysctls(sysctlOptions) + if err != nil { + return SecurityContext{}, err + } + securityContext.Sysctls = sysctls + } return securityContext, nil } diff --git a/plugins/scheduler-k3s/functions_test.go b/plugins/scheduler-k3s/functions_test.go index b2df0995b..6edcfbf6c 100644 --- a/plugins/scheduler-k3s/functions_test.go +++ b/plugins/scheduler-k3s/functions_test.go @@ -255,3 +255,105 @@ func TestNodeLabelsDoesNotMutatePackageLabels(t *testing.T) { t.Errorf("nodeLabels() mutated WorkerLabels: len = %d, want %d", len(WorkerLabels), workerBefore) } } + +func TestIsNamespacedSysctl(t *testing.T) { + cases := []struct { + name string + want bool + }{ + {name: "net.core.somaxconn", want: true}, + {name: "net.ipv4.tcp_rmem", want: true}, + {name: "kernel.shm_rmid_forced", want: true}, + {name: "kernel.shmmax", want: true}, + {name: "kernel.msgmax", want: true}, + {name: "kernel.sem", want: true}, + {name: "fs.mqueue.msg_max", want: true}, + {name: "vm.max_map_count", want: false}, + {name: "vm.swappiness", want: false}, + {name: "kernel.pid_max", want: false}, + {name: "kernel.semaphore", want: false}, + {name: "fs.file-max", want: false}, + {name: "", want: false}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := isNamespacedSysctl(tc.name); got != tc.want { + t.Errorf("isNamespacedSysctl(%q) = %v, want %v", tc.name, got, tc.want) + } + }) + } +} + +func TestParseSysctls(t *testing.T) { + cases := []struct { + name string + values []string + want []Sysctl + wantErr bool + }{ + { + name: "empty input", + values: []string{}, + want: []Sysctl{}, + }, + { + name: "single namespaced sysctl", + values: []string{"net.core.somaxconn=1024"}, + want: []Sysctl{{Name: "net.core.somaxconn", Value: "1024"}}, + }, + { + name: "sorted by name regardless of input order", + values: []string{"net.core.somaxconn=1024", "kernel.sem=250", "fs.mqueue.msg_max=20"}, + want: []Sysctl{ + {Name: "fs.mqueue.msg_max", Value: "20"}, + {Name: "kernel.sem", Value: "250"}, + {Name: "net.core.somaxconn", Value: "1024"}, + }, + }, + { + name: "value containing an equals sign is preserved", + values: []string{"net.ipv4.tcp_rmem=4096 87380 6291456"}, + want: []Sysctl{{Name: "net.ipv4.tcp_rmem", Value: "4096 87380 6291456"}}, + }, + { + name: "non-namespaced sysctl is rejected", + values: []string{"vm.max_map_count=262144"}, + wantErr: true, + }, + { + name: "missing equals sign is rejected", + values: []string{"net.core.somaxconn"}, + wantErr: true, + }, + { + name: "empty name is rejected", + values: []string{"=1024"}, + wantErr: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := parseSysctls(tc.values) + if tc.wantErr { + if err == nil { + t.Fatalf("parseSysctls(%v) expected an error, got %v", tc.values, got) + } + return + } + if err != nil { + t.Fatalf("parseSysctls(%v) unexpected error: %v", tc.values, err) + } + + if len(got) != len(tc.want) { + t.Fatalf("parseSysctls(%v) = %v, want %v", tc.values, got, tc.want) + } + for i := range got { + if got[i] != tc.want[i] { + t.Errorf("parseSysctls(%v)[%d] = %v, want %v", tc.values, i, got[i], tc.want[i]) + } + } + }) + } +} diff --git a/plugins/scheduler-k3s/template.go b/plugins/scheduler-k3s/template.go index c5fbc8194..b191f9272 100644 --- a/plugins/scheduler-k3s/template.go +++ b/plugins/scheduler-k3s/template.go @@ -394,6 +394,32 @@ type SecurityContext struct { Capabilities SecurityContextCapabilities `yaml:"capabilities,omitempty"` // Privileged contains the privileged flag for a process Privileged bool `yaml:"privileged,omitempty"` + // Sysctls contains the namespaced kernel sysctls for a process + Sysctls []Sysctl `yaml:"sysctls,omitempty"` +} + +// Sysctl contains a single kernel sysctl key/value pair +type Sysctl struct { + // Name is the name of the sysctl + Name string `yaml:"name"` + // Value is the value the sysctl is set to + Value string `yaml:"value"` +} + +// ToCoreV1PodSecurityContext converts the sysctls to a corev1.PodSecurityContext, +// returning nil when no sysctls are configured so an empty security context does +// not churn the pod template hash and trigger a spurious rollout. +func (s SecurityContext) ToCoreV1PodSecurityContext() *corev1.PodSecurityContext { + if len(s.Sysctls) == 0 { + return nil + } + + sysctls := make([]corev1.Sysctl, len(s.Sysctls)) + for i, sysctl := range s.Sysctls { + sysctls[i] = corev1.Sysctl{Name: sysctl.Name, Value: sysctl.Value} + } + + return &corev1.PodSecurityContext{Sysctls: sysctls} } // ToCoreV1SecurityContext converts the security context to a corev1.SecurityContext @@ -533,6 +559,7 @@ func templateKubernetesJob(input Job) (batchv1.Job, error) { }, }, RestartPolicy: corev1.RestartPolicyNever, + SecurityContext: input.SecurityContext.ToCoreV1PodSecurityContext(), ServiceAccountName: input.AppName, }, }, diff --git a/plugins/scheduler-k3s/template_test.go b/plugins/scheduler-k3s/template_test.go new file mode 100644 index 000000000..4fa94b047 --- /dev/null +++ b/plugins/scheduler-k3s/template_test.go @@ -0,0 +1,199 @@ +package scheduler_k3s + +import ( + "errors" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "gopkg.in/yaml.v3" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/engine" +) + +func TestToCoreV1PodSecurityContext(t *testing.T) { + t.Run("nil when no sysctls are configured", func(t *testing.T) { + securityContext := SecurityContext{Privileged: true} + if got := securityContext.ToCoreV1PodSecurityContext(); got != nil { + t.Errorf("ToCoreV1PodSecurityContext() = %v, want nil", got) + } + }) + + t.Run("nil for an empty sysctl slice", func(t *testing.T) { + securityContext := SecurityContext{Sysctls: []Sysctl{}} + if got := securityContext.ToCoreV1PodSecurityContext(); got != nil { + t.Errorf("ToCoreV1PodSecurityContext() = %v, want nil", got) + } + }) + + t.Run("preserves order and values", func(t *testing.T) { + securityContext := SecurityContext{ + Sysctls: []Sysctl{ + {Name: "kernel.sem", Value: "250"}, + {Name: "net.core.somaxconn", Value: "1024"}, + }, + } + + got := securityContext.ToCoreV1PodSecurityContext() + if got == nil { + t.Fatal("ToCoreV1PodSecurityContext() = nil, want a security context") + } + if len(got.Sysctls) != 2 { + t.Fatalf("ToCoreV1PodSecurityContext() has %d sysctls, want 2", len(got.Sysctls)) + } + if got.Sysctls[0].Name != "kernel.sem" || got.Sysctls[0].Value != "250" { + t.Errorf("ToCoreV1PodSecurityContext() sysctls[0] = %v, want kernel.sem=250", got.Sysctls[0]) + } + if got.Sysctls[1].Name != "net.core.somaxconn" || got.Sysctls[1].Value != "1024" { + t.Errorf("ToCoreV1PodSecurityContext() sysctls[1] = %v, want net.core.somaxconn=1024", got.Sysctls[1]) + } + }) +} + +func renderDeploymentTemplate(t *testing.T, globalValues map[string]interface{}) string { + t.Helper() + + chartDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(chartDir, "templates"), 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + chartYAML := []byte("apiVersion: v2\nname: test\nversion: 0.0.1\n") + if err := os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), chartYAML, 0o644); err != nil { + t.Fatalf("write Chart.yaml: %v", err) + } + + for _, name := range []string{"deployment.yaml", "_helpers.tpl"} { + contents, err := templates.ReadFile("templates/chart/" + name) + if err != nil { + t.Fatalf("read %s: %v", name, err) + } + if err := os.WriteFile(filepath.Join(chartDir, "templates", name), contents, 0o644); err != nil { + t.Fatalf("write %s: %v", name, err) + } + } + + loaded, err := loader.Load(chartDir) + if err != nil { + t.Fatalf("load chart: %v", err) + } + + global := map[string]interface{}{ + "app_name": "myapp", + "deployment_id": "1", + "namespace": "myapp", + "image": map[string]interface{}{ + "name": "myapp:latest", + "type": "dockerfile", + }, + } + for key, value := range globalValues { + global[key] = value + } + + values := map[string]interface{}{ + "global": global, + "processes": map[string]interface{}{ + "worker": map[string]interface{}{ + "args": []interface{}{"echo", "hello"}, + "replicas": 1, + }, + }, + } + + renderValues, err := chartutil.ToRenderValues(loaded, values, chartutil.ReleaseOptions{Name: "test", Namespace: "default"}, nil) + if err != nil { + t.Fatalf("ToRenderValues: %v", err) + } + + rendered, err := engine.Render(loaded, renderValues) + if err != nil { + t.Fatalf("render: %v", err) + } + + for name, content := range rendered { + if filepath.Base(name) == "deployment.yaml" { + return content + } + } + t.Fatalf("deployment.yaml not rendered; got: %v", rendered) + return "" +} + +// TestDeploymentSysctlsRendering asserts the pod-level securityContext.sysctls +// block only appears when sysctls are configured, and that numeric values are +// quoted. Kubernetes types sysctls[].value as a string, so an unquoted 1024 +// renders as a YAML integer and the API server rejects the manifest. +func TestDeploymentSysctlsRendering(t *testing.T) { + t.Run("absent when no security context is set", func(t *testing.T) { + manifest := renderDeploymentTemplate(t, map[string]interface{}{}) + if strings.Contains(manifest, "sysctls:") { + t.Errorf("rendered deployment unexpectedly contains sysctls:\n%s", manifest) + } + }) + + t.Run("absent when the security context has no sysctls", func(t *testing.T) { + manifest := renderDeploymentTemplate(t, map[string]interface{}{ + "security_context": map[string]interface{}{"privileged": true}, + }) + if strings.Contains(manifest, "sysctls:") { + t.Errorf("rendered deployment unexpectedly contains sysctls:\n%s", manifest) + } + }) + + t.Run("numeric values are quoted", func(t *testing.T) { + manifest := renderDeploymentTemplate(t, map[string]interface{}{ + "security_context": map[string]interface{}{ + "sysctls": []interface{}{ + map[string]interface{}{"name": "net.core.somaxconn", "value": "1024"}, + }, + }, + }) + + if !strings.Contains(manifest, "- name: net.core.somaxconn") { + t.Errorf("rendered deployment missing sysctl name:\n%s", manifest) + } + if !strings.Contains(manifest, `value: "1024"`) { + t.Errorf("rendered deployment did not quote the sysctl value:\n%s", manifest) + } + }) + + t.Run("sysctls land on the pod spec, not the container", func(t *testing.T) { + manifest := renderDeploymentTemplate(t, map[string]interface{}{ + "security_context": map[string]interface{}{ + "sysctls": []interface{}{ + map[string]interface{}{"name": "net.core.somaxconn", "value": "1024"}, + }, + }, + }) + + var doc map[string]interface{} + decoder := yaml.NewDecoder(strings.NewReader(manifest)) + for { + if err := decoder.Decode(&doc); err != nil { + if errors.Is(err, io.EOF) { + break + } + t.Fatalf("decode manifest: %v", err) + } + if doc == nil { + continue + } + + spec := doc["spec"].(map[string]interface{}) + template := spec["template"].(map[string]interface{}) + podSpec := template["spec"].(map[string]interface{}) + + securityContext, ok := podSpec["securityContext"].(map[string]interface{}) + if !ok { + t.Fatalf("pod spec has no securityContext:\n%s", manifest) + } + if _, ok := securityContext["sysctls"]; !ok { + t.Errorf("pod securityContext has no sysctls:\n%s", manifest) + } + } + }) +} diff --git a/plugins/scheduler-k3s/templates/chart/cron-job.yaml b/plugins/scheduler-k3s/templates/chart/cron-job.yaml index 371ddb34c..289f84f4a 100644 --- a/plugins/scheduler-k3s/templates/chart/cron-job.yaml +++ b/plugins/scheduler-k3s/templates/chart/cron-job.yaml @@ -73,6 +73,14 @@ spec: {{ include "print.labels" (dict "config" $.Values.global "key" "pod") | indent 12 }} {{ include "print.labels" (dict "config" $config "key" "pod") | indent 12 }} spec: + {{- if and (hasKey $.Values.global "security_context") $.Values.global.security_context.sysctls }} + securityContext: + sysctls: + {{- range $.Values.global.security_context.sysctls }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- end }} containers: - args: {{- range $config.args }} diff --git a/plugins/scheduler-k3s/templates/chart/deployment.yaml b/plugins/scheduler-k3s/templates/chart/deployment.yaml index 34312777e..c2fbb497d 100644 --- a/plugins/scheduler-k3s/templates/chart/deployment.yaml +++ b/plugins/scheduler-k3s/templates/chart/deployment.yaml @@ -58,6 +58,14 @@ spec: {{ include "print.labels" (dict "config" $.Values.global "key" "pod") | indent 8 }} {{ include "print.labels" (dict "config" $config "key" "pod") | indent 8 }} spec: + {{- if and (hasKey $.Values.global "security_context") $.Values.global.security_context.sysctls }} + securityContext: + sysctls: + {{- range $.Values.global.security_context.sysctls }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- end }} containers: - args: {{- range $config.args }} diff --git a/tests/unit/scheduler-k3s-5.bats b/tests/unit/scheduler-k3s-5.bats new file mode 100644 index 000000000..3f074e373 --- /dev/null +++ b/tests/unit/scheduler-k3s-5.bats @@ -0,0 +1,80 @@ +#!/usr/bin/env bats + +load test_helper + +TEST_APP="rdmtestapp" + +setup() { + uninstall_k3s || true + global_setup + dokku nginx:stop + export KUBECONFIG="/etc/rancher/k3s/k3s.yaml" +} + +teardown() { + global_teardown + dokku nginx:start + uninstall_k3s || true +} + +@test "(scheduler-k3s) docker-options sysctl" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "dokku apps:create $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.securityContext.sysctls'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "null" + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--sysctl net.ipv4.ip_unprivileged_port_start=1024'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:restart $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.securityContext.sysctls[0].name'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "net.ipv4.ip_unprivileged_port_start" + + run /bin/bash -c "kubectl get deployment $TEST_APP-web -o json | jq -r '.spec.template.spec.securityContext.sysctls[0].value'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "1024" + + run /bin/bash -c "dokku docker-options:remove $TEST_APP deploy '--sysctl net.ipv4.ip_unprivileged_port_start=1024'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--sysctl vm.max_map_count=262144'" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:restart $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "is not namespaced" -1 +} From 44cd5661780c1225c761493fe5218aa69ad28ca9 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 7 Aug 2026 02:46:30 -0400 Subject: [PATCH 35/36] feat: manage node-level kernel sysctls on the k3s scheduler Sysctls the kernel does not namespace, such as `vm.max_map_count`, cannot be set from a pod spec and previously had no answer beyond editing `/etc/sysctl.d` on each host by hand. `scheduler-k3s:node-sysctls:set` now applies them through a privileged daemonset, which reaches nodes joined later and reapplies after a reboot. Sysctls may be scoped to a node profile, with a profile scope inheriting the global values and overriding them on conflict so that every node is covered by exactly one daemonset. Clearing a sysctl stops dokku managing it but does not restore the previous value, which persists until the node reboots. --- docs/deployment/schedulers/k3s.md | 42 ++- plugins/scheduler-k3s/Makefile | 2 +- plugins/scheduler-k3s/functions.go | 20 +- plugins/scheduler-k3s/node_sysctls.go | 324 ++++++++++++++++++ plugins/scheduler-k3s/node_sysctls_test.go | 305 +++++++++++++++++ plugins/scheduler-k3s/report.go | 12 + plugins/scheduler-k3s/scheduler_k3s.go | 40 ++- .../scheduler-k3s/src/commands/commands.go | 2 + .../src/subcommands/subcommands.go | 17 + plugins/scheduler-k3s/subcommands.go | 98 ++++++ .../templates/daemonset.yaml | 55 +++ tests/unit/scheduler-k3s-5.bats | 67 ++++ 12 files changed, 964 insertions(+), 20 deletions(-) create mode 100644 plugins/scheduler-k3s/node_sysctls.go create mode 100644 plugins/scheduler-k3s/node_sysctls_test.go create mode 100644 plugins/scheduler-k3s/templates/node-sysctls-chart/templates/daemonset.yaml diff --git a/docs/deployment/schedulers/k3s.md b/docs/deployment/schedulers/k3s.md index b0c9c59f2..06bc6b0bb 100644 --- a/docs/deployment/schedulers/k3s.md +++ b/docs/deployment/schedulers/k3s.md @@ -17,6 +17,8 @@ scheduler-k3s:ensure-charts # Ensures the k3s charts are scheduler-k3s:initialize # Initializes a cluster scheduler-k3s:labels:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE> # Set or clear a label for a given app/process-type/resource-type combination scheduler-k3s:labels:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE] # Displays a scheduler-k3s labels report for one or more apps +scheduler-k3s:node-sysctls:set () [--global|--profile PROFILE] # Set or clear a node-level kernel sysctl for unprofiled nodes or a single node profile +scheduler-k3s:node-sysctls:report [--format stdout|json] # Displays the node-level kernel sysctls applied to each scope scheduler-k3s:preview [--context N] [--show-secrets] [--show-secrets-decoded] # Displays a diff between the current and next deployment for an app scheduler-k3s:profiles:add [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS] Adds a node profile to the k3s cluster scheduler-k3s:profiles:list [--format json|stdout] # Lists all node profiles in the k3s cluster @@ -766,17 +768,47 @@ Dokku does not enforce the safe list itself, as its membership changes between K #### Non-namespaced sysctls -Non-namespaced sysctls are a property of the node, not of any app. Set them directly on each node in the cluster. +Non-namespaced sysctls are a property of the node, not of any app, and are managed with the `node-sysctls:set` command. Dokku applies them via a privileged DaemonSet, so they reach every node without being told which nodes exist, cover nodes joined later, and are reapplied after a node reboots. ```shell -sudo sysctl -w vm.max_map_count=262144 +dokku scheduler-k3s:node-sysctls:set --global vm.max_map_count 262144 +``` + +Omitting the value clears it. + +```shell +dokku scheduler-k3s:node-sysctls:set --global vm.max_map_count +``` + +Clearing a sysctl stops Dokku managing it, but does not restore whatever the node had before. The last value written stays in place until that node reboots, which is how `sysctl -w` behaves everywhere else. + +Sysctls can also be scoped to a [node profile](#node-profiles) with `--profile`, which applies them only to nodes joined with that profile. + +```shell +dokku scheduler-k3s:node-sysctls:set --profile edge-workers vm.max_map_count 524288 +``` + +A profile scope inherits everything set globally and overrides it on conflict, so each node is covered by exactly one DaemonSet and no two ever write the same value. Note that the server node created by `scheduler-k3s:initialize` never carries a profile label, so only globally-scoped sysctls reach it. + +Use `node-sysctls:report` to see the resolved set for every scope. + +```shell +dokku scheduler-k3s:node-sysctls:report ``` ```shell -echo "vm.max_map_count = 262144" | sudo tee /etc/sysctl.d/99-max-map-count.conf +dokku scheduler-k3s:node-sysctls:report --format json ``` -The second command is what makes the change survive a reboot; `sysctl -w` alone does not. Repeat both on every node, including any added later via `scheduler-k3s:cluster:add`. +The DaemonSet pulls `busybox` and `registry.k8s.io/pause` by default. On an air-gapped cluster or one behind a registry mirror, point them elsewhere: + +```shell +dokku scheduler-k3s:set --global node-sysctls-image registry.internal/busybox:1.36 +``` + +```shell +dokku scheduler-k3s:set --global node-sysctls-pause-image registry.internal/pause:3.9 +``` ### Integrating Kustomize @@ -1011,6 +1043,8 @@ If unspecified for any task, the default reservation will be `.1` CPU and `128Mi | `letsencrypt-server` | app + global | `prod` | `--scheduler-k3s-letsencrypt-server`, `--scheduler-k3s-global-letsencrypt-server`, `--scheduler-k3s-computed-letsencrypt-server` | ACME directory (`prod` or `staging`) used for app certificates | | `namespace` | app + global | `default` | `--scheduler-k3s-namespace`, `--scheduler-k3s-global-namespace`, `--scheduler-k3s-computed-namespace` | Kubernetes namespace into which the app's resources are installed | | `network-interface` | global only | `eth0` | `--scheduler-k3s-global-network-interface`, `--scheduler-k3s-computed-network-interface` | Host network interface used by k3s | +| `node-sysctls-image` | global only | `busybox:1.36` | `--scheduler-k3s-global-node-sysctls-image` | Image used to apply node-level sysctls, override for air-gapped clusters | +| `node-sysctls-pause-image` | global only | `registry.k8s.io/pause:3.9` | `--scheduler-k3s-global-node-sysctls-pause-image` | Image keeping the node sysctls daemonset pods running | | `rollback-on-failure` | app + global | `false` | `--scheduler-k3s-rollback-on-failure`, `--scheduler-k3s-global-rollback-on-failure`, `--scheduler-k3s-computed-rollback-on-failure` | When `true`, helm rolls back the release if a deploy fails | | `shm-size` | app + global | none | `--scheduler-k3s-shm-size`, `--scheduler-k3s-global-shm-size`, `--scheduler-k3s-computed-shm-size` | `/dev/shm` size override applied to app containers | | `token` | global only | none | `--scheduler-k3s-global-token` (masked as `*******` in default stdout output; the raw value is returned when queried via `--format json` or when this flag is requested explicitly) | Cluster join token used by `scheduler-k3s:cluster-add` | diff --git a/plugins/scheduler-k3s/Makefile b/plugins/scheduler-k3s/Makefile index f0e959413..2320d6497 100644 --- a/plugins/scheduler-k3s/Makefile +++ b/plugins/scheduler-k3s/Makefile @@ -1,4 +1,4 @@ -SUBCOMMANDS = subcommands/annotations:set subcommands/annotations:report subcommands/autoscaling-auth:set subcommands/autoscaling-auth:report subcommands/charts:report subcommands/charts:set subcommands/cluster:add subcommands/cluster:list subcommands/cluster:remove subcommands/ensure-charts subcommands/initialize subcommands/labels:set subcommands/labels:report subcommands/preview subcommands/profiles:add subcommands/profiles:list subcommands/profiles:remove subcommands/report subcommands/set subcommands/show-kubeconfig subcommands/uninstall +SUBCOMMANDS = subcommands/annotations:set subcommands/annotations:report subcommands/autoscaling-auth:set subcommands/autoscaling-auth:report subcommands/charts:report subcommands/charts:set subcommands/cluster:add subcommands/cluster:list subcommands/cluster:remove subcommands/ensure-charts subcommands/initialize subcommands/labels:set subcommands/labels:report subcommands/node-sysctls:set subcommands/node-sysctls:report subcommands/preview subcommands/profiles:add subcommands/profiles:list subcommands/profiles:remove subcommands/report subcommands/set subcommands/show-kubeconfig subcommands/uninstall TRIGGERS = triggers/core-post-deploy triggers/core-post-extract triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-certs-update triggers/post-certs-remove triggers/post-create triggers/post-delete triggers/report triggers/scheduler-app-status triggers/scheduler-deploy triggers/scheduler-enter triggers/scheduler-is-deployed triggers/scheduler-logs triggers/scheduler-proxy-config triggers/scheduler-proxy-logs triggers/scheduler-post-delete triggers/scheduler-run triggers/scheduler-run-list triggers/scheduler-stop triggers/scheduler-cron-write triggers/storage-create triggers/storage-destroy triggers/storage-status triggers/scheduler-storage-exec BUILD = commands subcommands triggers PLUGIN_NAME = scheduler-k3s diff --git a/plugins/scheduler-k3s/functions.go b/plugins/scheduler-k3s/functions.go index 842a6021b..d024ab6ef 100644 --- a/plugins/scheduler-k3s/functions.go +++ b/plugins/scheduler-k3s/functions.go @@ -1391,6 +1391,24 @@ func getComputedKustomizeRootPath(appName string) string { return kustomizeRootPath } +func getComputedNodeSysctlsImage() string { + image := common.PropertyGet("scheduler-k3s", "--global", "node-sysctls-image") + if image == "" { + image = DefaultNodeSysctlsImage + } + + return image +} + +func getComputedNodeSysctlsPauseImage() string { + image := common.PropertyGet("scheduler-k3s", "--global", "node-sysctls-pause-image") + if image == "" { + image = DefaultNodeSysctlsPauseImage + } + + return image +} + func getNamespace(appName string) string { return common.PropertyGet("scheduler-k3s", appName, "namespace") } @@ -1790,7 +1808,7 @@ func parseSysctls(values []string) ([]Sysctl, error) { } if !isNamespacedSysctl(name) { - return nil, fmt.Errorf("Sysctl %s is not namespaced and cannot be set on a pod, it must be applied at the node level instead", name) + return nil, fmt.Errorf("Sysctl %s is not namespaced and cannot be set on a pod, apply it to the nodes with 'dokku scheduler-k3s:node-sysctls:set %s ' instead", name, name) } sysctls = append(sysctls, Sysctl{Name: name, Value: sysctlValue}) diff --git a/plugins/scheduler-k3s/node_sysctls.go b/plugins/scheduler-k3s/node_sysctls.go new file mode 100644 index 000000000..0250e266e --- /dev/null +++ b/plugins/scheduler-k3s/node_sysctls.go @@ -0,0 +1,324 @@ +package scheduler_k3s + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// NodeSysctlsValues contains the values for a dokku-managed node sysctls helm chart +type NodeSysctlsValues struct { + Global NodeSysctlsGlobalValues `yaml:"global"` +} + +// NodeSysctlsGlobalValues contains the global values for the node sysctls chart +type NodeSysctlsGlobalValues struct { + Image string `yaml:"image"` + PauseImage string `yaml:"pause_image"` + ProfileName string `yaml:"profile_name,omitempty"` + ReleaseName string `yaml:"release_name"` + Sysctls []Sysctl `yaml:"sysctls"` +} + +// nodeSysctlScope is a resolved set of sysctls destined for a single DaemonSet +type nodeSysctlScope struct { + // ProfileName is the node profile this scope targets, empty for the global scope + ProfileName string + // ReleaseName is the helm release backing this scope + ReleaseName string + // Sysctls are the fully resolved sysctls for this scope + Sysctls []Sysctl +} + +// getNodeSysctlsProperty returns the property name backing a node sysctls scope +func getNodeSysctlsProperty(profileName string) string { + if profileName == "" { + return "node-sysctls.global" + } + + return fmt.Sprintf("node-sysctls.profile.%s", profileName) +} + +// getNodeSysctlsReleaseName returns the helm release name for a node sysctls scope +func getNodeSysctlsReleaseName(profileName string) string { + if profileName == "" { + return "dokku-node-sysctls-global" + } + + return fmt.Sprintf("dokku-node-sysctls-profile-%s", profileName) +} + +// getNodeSysctls returns the sysctls stored against a single scope +func getNodeSysctls(profileName string) (map[string]string, error) { + sysctls, err := common.PropertyMapGet("scheduler-k3s", "--global", getNodeSysctlsProperty(profileName)) + if err != nil { + return nil, fmt.Errorf("Unable to read node sysctls: %w", err) + } + + return sysctls, nil +} + +// listNodeProfileNames returns the names of every stored node profile +func listNodeProfileNames() ([]string, error) { + properties, err := common.PropertyGetAllByPrefix("scheduler-k3s", "--global", "node-profile-") + if err != nil { + return nil, fmt.Errorf("Unable to get node profiles: %w", err) + } + + names := []string{} + for property, data := range properties { + if !strings.HasSuffix(property, ".json") { + continue + } + + var profile NodeProfile + if err := json.Unmarshal([]byte(data), &profile); err != nil { + return nil, fmt.Errorf("Unable to unmarshal node profile: %w", err) + } + + names = append(names, profile.Name) + } + + sort.Strings(names) + return names, nil +} + +// sortedSysctls converts a name/value map into sysctls sorted by name so the +// rendered manifest is stable across runs +func sortedSysctls(values map[string]string) []Sysctl { + sysctls := make([]Sysctl, 0, len(values)) + for name, value := range values { + sysctls = append(sysctls, Sysctl{Name: name, Value: value}) + } + + sort.Slice(sysctls, func(i int, j int) bool { + return sysctls[i].Name < sysctls[j].Name + }) + + return sysctls +} + +// mergeNodeSysctls layers a profile's sysctls over the global ones, with the profile +// winning on conflict, and returns the result sorted by name. Profile scopes must +// carry the global values too: profiled nodes are excluded from the global DaemonSet, +// so anything omitted here would never reach them. +func mergeNodeSysctls(global map[string]string, profile map[string]string) []Sysctl { + merged := map[string]string{} + for name, value := range global { + merged[name] = value + } + for name, value := range profile { + merged[name] = value + } + + return sortedSysctls(merged) +} + +// resolveNodeSysctlScopes returns one scope per DaemonSet that should exist, with +// profile scopes carrying the global sysctls merged underneath their own. Every +// node matches exactly one scope: profiled nodes match their profile's DaemonSet, +// and unprofiled nodes match the global one, so no two DaemonSets ever write the +// same sysctl on the same node. +func resolveNodeSysctlScopes() ([]nodeSysctlScope, error) { + globalSysctls, err := getNodeSysctls("") + if err != nil { + return nil, err + } + + scopes := []nodeSysctlScope{ + { + ReleaseName: getNodeSysctlsReleaseName(""), + Sysctls: sortedSysctls(globalSysctls), + }, + } + + profileNames, err := listNodeProfileNames() + if err != nil { + return nil, err + } + + for _, profileName := range profileNames { + profileSysctls, err := getNodeSysctls(profileName) + if err != nil { + return nil, err + } + + scopes = append(scopes, nodeSysctlScope{ + ProfileName: profileName, + ReleaseName: getNodeSysctlsReleaseName(profileName), + Sysctls: mergeNodeSysctls(globalSysctls, profileSysctls), + }) + } + + return scopes, nil +} + +// CreateOrUpdateNodeSysctls reconciles every node sysctls DaemonSet against the +// stored properties. All scopes are reconciled on every call rather than only the +// mutated one, since profile scopes inherit the global sysctls at render time and +// would otherwise serve a stale value after a global change. +func CreateOrUpdateNodeSysctls(ctx context.Context) error { + if err := isKubernetesAvailable(); err != nil { + common.LogDebug("kubernetes not available, skipping node sysctls sync") + return nil + } + + scopes, err := resolveNodeSysctlScopes() + if err != nil { + return err + } + + for _, scope := range scopes { + if len(scope.Sysctls) == 0 { + if err := deleteNodeSysctlsRelease(scope.ReleaseName); err != nil { + return err + } + continue + } + + if err := installNodeSysctlsChart(ctx, scope); err != nil { + return err + } + } + + return nil +} + +// installNodeSysctlsChart installs or upgrades the DaemonSet backing a single scope +func installNodeSysctlsChart(ctx context.Context, scope nodeSysctlScope) error { + chartDir, err := os.MkdirTemp("", "dokku-node-sysctls-chart-") + if err != nil { + return fmt.Errorf("error creating chart directory: %w", err) + } + defer os.RemoveAll(chartDir) + + if err := os.MkdirAll(filepath.Join(chartDir, "templates"), os.FileMode(0755)); err != nil { + return fmt.Errorf("error creating chart templates directory: %w", err) + } + + chart := &Chart{ + ApiVersion: "v2", + AppVersion: "1.0.0", + Name: scope.ReleaseName, + Icon: "https://dokku.com/assets/dokku-logo.svg", + Version: "0.0.1", + } + + err = writeYaml(WriteYamlInput{ + Object: chart, + Path: filepath.Join(chartDir, "Chart.yaml"), + }) + if err != nil { + return fmt.Errorf("error writing chart: %w", err) + } + + b, err := templates.ReadFile("templates/node-sysctls-chart/templates/daemonset.yaml") + if err != nil { + return fmt.Errorf("error reading node-sysctls template: %w", err) + } + + filename := filepath.Join(chartDir, "templates", "daemonset.yaml") + if err := os.WriteFile(filename, b, os.FileMode(0644)); err != nil { + return fmt.Errorf("error writing node-sysctls template: %w", err) + } + + if os.Getenv("DOKKU_TRACE") == "1" { + common.CatFile(filename) + } + + values := &NodeSysctlsValues{ + Global: NodeSysctlsGlobalValues{ + Image: getComputedNodeSysctlsImage(), + PauseImage: getComputedNodeSysctlsPauseImage(), + ProfileName: scope.ProfileName, + ReleaseName: scope.ReleaseName, + Sysctls: scope.Sysctls, + }, + } + + err = writeYaml(WriteYamlInput{ + Object: values, + Path: filepath.Join(chartDir, "values.yaml"), + }) + if err != nil { + return fmt.Errorf("error writing values: %w", err) + } + + helmAgent, err := NewHelmAgent(NodeSysctlsNamespace, DeployLogPrinter) + if err != nil { + return fmt.Errorf("error creating helm agent: %w", err) + } + + chartPath, err := filepath.Abs(chartDir) + if err != nil { + return fmt.Errorf("error getting chart path: %w", err) + } + + common.LogVerboseQuiet(fmt.Sprintf("Applying node sysctls for %s", nodeSysctlsScopeLabel(scope.ProfileName))) + err = helmAgent.InstallOrUpgradeChart(ctx, ChartInput{ + ChartPath: chartPath, + Namespace: NodeSysctlsNamespace, + ReleaseName: scope.ReleaseName, + Wait: false, + }) + if err != nil { + return fmt.Errorf("error installing node sysctls chart: %w", err) + } + + return nil +} + +// deleteNodeSysctlsRelease removes a node sysctls DaemonSet when its scope resolves +// to an empty set. The kernel values it wrote are not reverted; they persist on the +// affected nodes until those nodes reboot. +func deleteNodeSysctlsRelease(releaseName string) error { + helmAgent, err := NewHelmAgent(NodeSysctlsNamespace, DeployLogPrinter) + if err != nil { + return fmt.Errorf("error creating helm agent: %w", err) + } + + exists, err := helmAgent.ChartExists(releaseName) + if err != nil { + return fmt.Errorf("error checking if node sysctls chart exists: %w", err) + } + + if !exists { + return nil + } + + common.LogVerboseQuiet(fmt.Sprintf("Removing node sysctls release %s", releaseName)) + if err := helmAgent.UninstallChart(releaseName); err != nil { + return fmt.Errorf("error uninstalling node sysctls chart: %w", err) + } + + return nil +} + +// DeleteNodeSysctls removes the stored sysctls and DaemonSet for a single node profile +func DeleteNodeSysctls(ctx context.Context, profileName string) error { + if err := common.PropertyDelete("scheduler-k3s", "--global", getNodeSysctlsProperty(profileName)); err != nil { + return fmt.Errorf("Unable to delete node sysctls: %w", err) + } + + if err := isKubernetesAvailable(); err != nil { + common.LogDebug("kubernetes not available, skipping node sysctls deletion") + return nil + } + + return deleteNodeSysctlsRelease(getNodeSysctlsReleaseName(profileName)) +} + +// nodeSysctlsScopeLabel returns a human readable name for a node sysctls scope +func nodeSysctlsScopeLabel(profileName string) string { + if profileName == "" { + return "unprofiled nodes" + } + + return fmt.Sprintf("node profile %s", profileName) +} diff --git a/plugins/scheduler-k3s/node_sysctls_test.go b/plugins/scheduler-k3s/node_sysctls_test.go new file mode 100644 index 000000000..00f2e54da --- /dev/null +++ b/plugins/scheduler-k3s/node_sysctls_test.go @@ -0,0 +1,305 @@ +package scheduler_k3s + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/engine" +) + +func TestGetNodeSysctlsProperty(t *testing.T) { + if got := getNodeSysctlsProperty(""); got != "node-sysctls.global" { + t.Errorf("getNodeSysctlsProperty(\"\") = %q, want node-sysctls.global", got) + } + if got := getNodeSysctlsProperty("edge-workers"); got != "node-sysctls.profile.edge-workers" { + t.Errorf("getNodeSysctlsProperty(\"edge-workers\") = %q, want node-sysctls.profile.edge-workers", got) + } +} + +// TestGetNodeSysctlsPropertyIsReserved asserts the property prefix is excluded from +// the annotations scan. Without this a property like node-sysctls.profile.pod would +// be mistaken for a pod annotation. +func TestGetNodeSysctlsPropertyIsReserved(t *testing.T) { + reserved := false + for _, prefix := range reservedAnnotationPrefixes { + if prefix == "node-sysctls." { + reserved = true + } + } + + if !reserved { + t.Error("node-sysctls. is not in reservedAnnotationPrefixes") + } +} + +func TestGetNodeSysctlsReleaseName(t *testing.T) { + if got := getNodeSysctlsReleaseName(""); got != "dokku-node-sysctls-global" { + t.Errorf("getNodeSysctlsReleaseName(\"\") = %q, want dokku-node-sysctls-global", got) + } + if got := getNodeSysctlsReleaseName("edge-workers"); got != "dokku-node-sysctls-profile-edge-workers" { + t.Errorf("getNodeSysctlsReleaseName(\"edge-workers\") = %q, want dokku-node-sysctls-profile-edge-workers", got) + } +} + +func TestSortedSysctls(t *testing.T) { + got := sortedSysctls(map[string]string{ + "vm.swappiness": "10", + "vm.max_map_count": "262144", + "fs.file-max": "100000", + }) + + want := []Sysctl{ + {Name: "fs.file-max", Value: "100000"}, + {Name: "vm.max_map_count", Value: "262144"}, + {Name: "vm.swappiness", Value: "10"}, + } + + if len(got) != len(want) { + t.Fatalf("sortedSysctls() = %v, want %v", got, want) + } + for i := range got { + if got[i] != want[i] { + t.Errorf("sortedSysctls()[%d] = %v, want %v", i, got[i], want[i]) + } + } +} + +func TestSortedSysctlsEmpty(t *testing.T) { + if got := sortedSysctls(map[string]string{}); len(got) != 0 { + t.Errorf("sortedSysctls(empty) = %v, want empty", got) + } +} + +func TestNodeSysctlsScopeLabel(t *testing.T) { + if got := nodeSysctlsScopeLabel(""); got != "unprofiled nodes" { + t.Errorf("nodeSysctlsScopeLabel(\"\") = %q, want 'unprofiled nodes'", got) + } + if got := nodeSysctlsScopeLabel("edge"); got != "node profile edge" { + t.Errorf("nodeSysctlsScopeLabel(\"edge\") = %q, want 'node profile edge'", got) + } +} + +func renderNodeSysctlsTemplate(t *testing.T, values NodeSysctlsGlobalValues) string { + t.Helper() + + chartDir := t.TempDir() + if err := os.MkdirAll(filepath.Join(chartDir, "templates"), 0o755); err != nil { + t.Fatalf("mkdir: %v", err) + } + + chartYAML := []byte("apiVersion: v2\nname: test\nversion: 0.0.1\n") + if err := os.WriteFile(filepath.Join(chartDir, "Chart.yaml"), chartYAML, 0o644); err != nil { + t.Fatalf("write Chart.yaml: %v", err) + } + + tpl, err := templates.ReadFile("templates/node-sysctls-chart/templates/daemonset.yaml") + if err != nil { + t.Fatalf("read daemonset template: %v", err) + } + if err := os.WriteFile(filepath.Join(chartDir, "templates", "daemonset.yaml"), tpl, 0o644); err != nil { + t.Fatalf("write daemonset template: %v", err) + } + + loaded, err := loader.Load(chartDir) + if err != nil { + t.Fatalf("load chart: %v", err) + } + + sysctls := []interface{}{} + for _, sysctl := range values.Sysctls { + sysctls = append(sysctls, map[string]interface{}{"name": sysctl.Name, "value": sysctl.Value}) + } + + renderValues, err := chartutil.ToRenderValues(loaded, map[string]interface{}{ + "global": map[string]interface{}{ + "image": values.Image, + "pause_image": values.PauseImage, + "profile_name": values.ProfileName, + "release_name": values.ReleaseName, + "sysctls": sysctls, + }, + }, chartutil.ReleaseOptions{Name: "test", Namespace: "kube-system"}, nil) + if err != nil { + t.Fatalf("ToRenderValues: %v", err) + } + + rendered, err := engine.Render(loaded, renderValues) + if err != nil { + t.Fatalf("render: %v", err) + } + + for name, content := range rendered { + if filepath.Base(name) == "daemonset.yaml" { + return content + } + } + t.Fatalf("daemonset.yaml not rendered; got: %v", rendered) + return "" +} + +// TestNodeSysctlsDaemonSetRendering asserts each scope targets a disjoint set of +// nodes. If both the global and a profile DaemonSet landed on the same node they +// would race writing the same /proc/sys file, making the result depend on pod +// scheduling order. +func TestNodeSysctlsDaemonSetRendering(t *testing.T) { + base := NodeSysctlsGlobalValues{ + Image: DefaultNodeSysctlsImage, + PauseImage: DefaultNodeSysctlsPauseImage, + Sysctls: []Sysctl{{Name: "vm.max_map_count", Value: "262144"}}, + } + + t.Run("global scope excludes profiled nodes", func(t *testing.T) { + values := base + values.ReleaseName = "dokku-node-sysctls-global" + manifest := renderNodeSysctlsTemplate(t, values) + + if !strings.Contains(manifest, "operator: DoesNotExist") { + t.Errorf("global daemonset missing DoesNotExist affinity:\n%s", manifest) + } + if !strings.Contains(manifest, "key: dokku.com/node-profile") { + t.Errorf("global daemonset missing node profile affinity key:\n%s", manifest) + } + if strings.Contains(manifest, "nodeSelector:") { + t.Errorf("global daemonset should not use a nodeSelector:\n%s", manifest) + } + }) + + t.Run("profile scope targets only its own nodes", func(t *testing.T) { + values := base + values.ProfileName = "edge-workers" + values.ReleaseName = "dokku-node-sysctls-profile-edge-workers" + manifest := renderNodeSysctlsTemplate(t, values) + + if !strings.Contains(manifest, "dokku.com/node-profile: \"edge-workers\"") { + t.Errorf("profile daemonset missing nodeSelector:\n%s", manifest) + } + if strings.Contains(manifest, "DoesNotExist") { + t.Errorf("profile daemonset should not carry the global affinity:\n%s", manifest) + } + }) + + t.Run("tolerates every taint", func(t *testing.T) { + values := base + values.ReleaseName = "dokku-node-sysctls-global" + manifest := renderNodeSysctlsTemplate(t, values) + + if !strings.Contains(manifest, "- operator: Exists") { + t.Errorf("daemonset does not tolerate all taints:\n%s", manifest) + } + }) + + t.Run("applies each sysctl privileged", func(t *testing.T) { + values := base + values.ReleaseName = "dokku-node-sysctls-global" + values.Sysctls = []Sysctl{ + {Name: "vm.max_map_count", Value: "262144"}, + {Name: "vm.swappiness", Value: "10"}, + } + manifest := renderNodeSysctlsTemplate(t, values) + + if !strings.Contains(manifest, "privileged: true") { + t.Errorf("daemonset init container is not privileged:\n%s", manifest) + } + if !strings.Contains(manifest, `sysctl -w "vm.max_map_count=262144"`) { + t.Errorf("daemonset missing max_map_count write:\n%s", manifest) + } + if !strings.Contains(manifest, `sysctl -w "vm.swappiness=10"`) { + t.Errorf("daemonset missing swappiness write:\n%s", manifest) + } + }) + + t.Run("image overrides reach both containers", func(t *testing.T) { + values := base + values.ReleaseName = "dokku-node-sysctls-global" + values.Image = "registry.internal/busybox:1.36" + values.PauseImage = "registry.internal/pause:3.9" + manifest := renderNodeSysctlsTemplate(t, values) + + if !strings.Contains(manifest, `image: "registry.internal/busybox:1.36"`) { + t.Errorf("daemonset did not use the sysctl image override:\n%s", manifest) + } + if !strings.Contains(manifest, `image: "registry.internal/pause:3.9"`) { + t.Errorf("daemonset did not use the pause image override:\n%s", manifest) + } + }) +} + +// TestMergeNodeSysctls asserts a profile scope inherits the global sysctls and +// overrides them on conflict. Profiled nodes are excluded from the global +// DaemonSet, so a global value omitted here would never reach them. +func TestMergeNodeSysctls(t *testing.T) { + cases := []struct { + name string + global map[string]string + profile map[string]string + want []Sysctl + }{ + { + name: "no sysctls at all", + global: map[string]string{}, + profile: map[string]string{}, + want: []Sysctl{}, + }, + { + name: "profile inherits global values", + global: map[string]string{"vm.max_map_count": "262144"}, + profile: map[string]string{}, + want: []Sysctl{{Name: "vm.max_map_count", Value: "262144"}}, + }, + { + name: "profile wins on conflict", + global: map[string]string{"vm.max_map_count": "262144"}, + profile: map[string]string{"vm.max_map_count": "524288"}, + want: []Sysctl{{Name: "vm.max_map_count", Value: "524288"}}, + }, + { + name: "profile adds to global", + global: map[string]string{"vm.max_map_count": "262144"}, + profile: map[string]string{"vm.swappiness": "10"}, + want: []Sysctl{ + {Name: "vm.max_map_count", Value: "262144"}, + {Name: "vm.swappiness", Value: "10"}, + }, + }, + { + name: "profile only", + global: map[string]string{}, + profile: map[string]string{"vm.swappiness": "10"}, + want: []Sysctl{{Name: "vm.swappiness", Value: "10"}}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := mergeNodeSysctls(tc.global, tc.profile) + if len(got) != len(tc.want) { + t.Fatalf("mergeNodeSysctls() = %v, want %v", got, tc.want) + } + for i := range got { + if got[i] != tc.want[i] { + t.Errorf("mergeNodeSysctls()[%d] = %v, want %v", i, got[i], tc.want[i]) + } + } + }) + } +} + +// TestMergeNodeSysctlsDoesNotMutateInputs asserts the merge leaves the caller's maps +// alone, since resolveNodeSysctlScopes reuses the global map across every profile. +func TestMergeNodeSysctlsDoesNotMutateInputs(t *testing.T) { + global := map[string]string{"vm.max_map_count": "262144"} + profile := map[string]string{"vm.max_map_count": "524288", "vm.swappiness": "10"} + + mergeNodeSysctls(global, profile) + + if len(global) != 1 || global["vm.max_map_count"] != "262144" { + t.Errorf("mergeNodeSysctls() mutated the global map: %v", global) + } + if len(profile) != 2 { + t.Errorf("mergeNodeSysctls() mutated the profile map: %v", profile) + } +} diff --git a/plugins/scheduler-k3s/report.go b/plugins/scheduler-k3s/report.go index 7e7dd08fb..aee3c564c 100644 --- a/plugins/scheduler-k3s/report.go +++ b/plugins/scheduler-k3s/report.go @@ -62,6 +62,8 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { "--scheduler-k3s-global-namespace": reportGlobalNamespace, "--scheduler-k3s-computed-network-interface": reportComputedNetworkInterface, "--scheduler-k3s-global-network-interface": reportGlobalNetworkInterface, + "--scheduler-k3s-global-node-sysctls-image": reportGlobalNodeSysctlsImage, + "--scheduler-k3s-global-node-sysctls-pause-image": reportGlobalNodeSysctlsPauseImage, "--scheduler-k3s-computed-rollback-on-failure": reportComputedRollbackOnFailure, "--scheduler-k3s-global-rollback-on-failure": reportGlobalRollbackOnFailure, "--scheduler-k3s-computed-shm-size": reportComputedShmSize, @@ -99,6 +101,8 @@ func ReportSingleApp(appName string, format string, infoFlag string) error { "--scheduler-k3s-global-namespace": reportGlobalNamespace, "--scheduler-k3s-computed-network-interface": reportComputedNetworkInterface, "--scheduler-k3s-global-network-interface": reportGlobalNetworkInterface, + "--scheduler-k3s-global-node-sysctls-image": reportGlobalNodeSysctlsImage, + "--scheduler-k3s-global-node-sysctls-pause-image": reportGlobalNodeSysctlsPauseImage, "--scheduler-k3s-computed-rollback-on-failure": reportComputedRollbackOnFailure, "--scheduler-k3s-rollback-on-failure": reportRollbackOnFailure, "--scheduler-k3s-global-rollback-on-failure": reportGlobalRollbackOnFailure, @@ -715,6 +719,14 @@ func reportGlobalNetworkInterface(appName string) string { return getGlobalNetworkInterface() } +func reportGlobalNodeSysctlsImage(appName string) string { + return getComputedNodeSysctlsImage() +} + +func reportGlobalNodeSysctlsPauseImage(appName string) string { + return getComputedNodeSysctlsPauseImage() +} + func reportComputedRollbackOnFailure(appName string) string { return getComputedRollbackOnFailure(appName) } diff --git a/plugins/scheduler-k3s/scheduler_k3s.go b/plugins/scheduler-k3s/scheduler_k3s.go index e9fdc3795..49040dbb0 100644 --- a/plugins/scheduler-k3s/scheduler_k3s.go +++ b/plugins/scheduler-k3s/scheduler_k3s.go @@ -31,20 +31,22 @@ var ( // GlobalProperties is a map of all valid global k3s properties GlobalProperties = map[string]bool{ - "deploy-timeout": true, - "image-pull-secrets": true, - "ingress-class": true, - "kube-context": true, - "kubeconfig-path": true, - "kustomize-root-path": true, - "letsencrypt-server": true, - "letsencrypt-email-prod": true, - "letsencrypt-email-stag": true, - "namespace": true, - "network-interface": true, - "rollback-on-failure": true, - "shm-size": true, - "token": true, + "deploy-timeout": true, + "image-pull-secrets": true, + "ingress-class": true, + "kube-context": true, + "kubeconfig-path": true, + "kustomize-root-path": true, + "letsencrypt-server": true, + "letsencrypt-email-prod": true, + "letsencrypt-email-stag": true, + "namespace": true, + "network-interface": true, + "node-sysctls-image": true, + "node-sysctls-pause-image": true, + "rollback-on-failure": true, + "shm-size": true, + "token": true, } ) @@ -102,6 +104,7 @@ var reservedAnnotationPrefixes = []string{ "chart-overrides.", "labels.", "node-profile-", + "node-sysctls.", TriggerAuthPropertyPrefix, } @@ -234,6 +237,15 @@ type NodeProfile struct { // NodeProfileLabel is the node label recording the node profile a node was added with const NodeProfileLabel = "dokku.com/node-profile" +// NodeSysctlsNamespace is the namespace the node sysctls daemonsets are installed into +const NodeSysctlsNamespace = "kube-system" + +// DefaultNodeSysctlsImage is the image used to apply sysctls on each node +const DefaultNodeSysctlsImage = "busybox:1.36" + +// DefaultNodeSysctlsPauseImage is the image keeping the node sysctls daemonset pods running +const DefaultNodeSysctlsPauseImage = "registry.k8s.io/pause:3.9" + // ServerLabels are the labels for a server node var ServerLabels = map[string]string{ "svccontroller.k3s.cattle.io/enablelb": "true", diff --git a/plugins/scheduler-k3s/src/commands/commands.go b/plugins/scheduler-k3s/src/commands/commands.go index b0c4be449..f3cf658f2 100644 --- a/plugins/scheduler-k3s/src/commands/commands.go +++ b/plugins/scheduler-k3s/src/commands/commands.go @@ -30,6 +30,8 @@ Additional commands:` scheduler-k3s:initialize [--server-ip SERVER_IP] [--taint-scheduling] [--kubelet-args KUBELET_ARGS], Initializes a cluster scheduler-k3s:labels:set () [--process-type PROCESS_TYPE] <--resource-type RESOURCE_TYPE>, Set or clear a label for a given app/process-type/resource-type combination scheduler-k3s:labels:report [|--global] [--format stdout|json] [--process-type PROCESS_TYPE] [--resource-type RESOURCE_TYPE], Displays a scheduler-k3s labels report for one or more apps + scheduler-k3s:node-sysctls:set () [--global|--profile PROFILE], Set or clear a node-level kernel sysctl for unprofiled nodes or a single node profile + scheduler-k3s:node-sysctls:report [--format stdout|json], Displays the node-level kernel sysctls applied to each scope scheduler-k3s:preview [--context N] [--show-secrets] [--show-secrets-decoded], Displays a diff between the current and next deployment for an app scheduler-k3s:profiles:add [--role ROLE] [--insecure-allow-unknown-hosts] [--taint-scheduling] [--kubelet-args KUBELET_ARGS], Adds a node profile to the k3s cluster scheduler-k3s:profiles:list [--format json|stdout], Lists all node profiles in the k3s cluster diff --git a/plugins/scheduler-k3s/src/subcommands/subcommands.go b/plugins/scheduler-k3s/src/subcommands/subcommands.go index 52a58ce96..24631400c 100644 --- a/plugins/scheduler-k3s/src/subcommands/subcommands.go +++ b/plugins/scheduler-k3s/src/subcommands/subcommands.go @@ -164,6 +164,23 @@ func main() { appName = "--global" } err = scheduler_k3s.CommandLabelsReport(appName, *format, *processType, *resourceType, infoFlag) + case "node-sysctls:set": + args := flag.NewFlagSet("scheduler-k3s:node-sysctls:set", flag.ExitOnError) + global := args.Bool("global", false, "--global: scope to all nodes without a node profile") + profileName := args.String("profile", "", "--profile: scope to a node profile instead of all unprofiled nodes") + args.Parse(os.Args[2:]) + key := args.Arg(0) + value := args.Arg(1) + if *global && *profileName != "" { + err = fmt.Errorf("Only one of --global and --profile may be specified") + break + } + err = scheduler_k3s.CommandNodeSysctlsSet(*profileName, key, value) + case "node-sysctls:report": + args := flag.NewFlagSet("scheduler-k3s:node-sysctls:report", flag.ExitOnError) + format := args.String("format", "stdout", "format: [ stdout | json ]") + args.Parse(os.Args[2:]) + err = scheduler_k3s.CommandNodeSysctlsReport(*format) case "preview": args := flag.NewFlagSet("scheduler-k3s:preview", flag.ExitOnError) context := args.Int("context", 3, "--context: number of unchanged lines of context around each change (-1 for full output)") diff --git a/plugins/scheduler-k3s/subcommands.go b/plugins/scheduler-k3s/subcommands.go index 54b827d81..1ac72d72c 100644 --- a/plugins/scheduler-k3s/subcommands.go +++ b/plugins/scheduler-k3s/subcommands.go @@ -45,6 +45,95 @@ func CommandAnnotationsSet(appName string, processType string, resourceType stri return nil } +// CommandNodeSysctlsSet sets or clears a node-level kernel sysctl for a scope +func CommandNodeSysctlsSet(profileName string, key string, value string) error { + if key == "" { + return fmt.Errorf("Missing sysctl name") + } + + if profileName != "" { + if err := verifyNodeProfileExists(profileName); err != nil { + return err + } + } + + property := getNodeSysctlsProperty(profileName) + if value == "" { + if err := common.PropertyMapDelete("scheduler-k3s", "--global", property, key); err != nil { + return fmt.Errorf("Unable to delete property map entry: %w", err) + } + + common.LogWarn(fmt.Sprintf("Removing %s stops dokku managing it, but does not restore the previous value on affected nodes until they reboot", key)) + } else { + if err := common.PropertyMapSet("scheduler-k3s", "--global", property, key, value); err != nil { + return fmt.Errorf("Unable to set property map entry: %w", err) + } + } + + return CreateOrUpdateNodeSysctls(context.Background()) +} + +// CommandNodeSysctlsReport displays the configured node-level kernel sysctls +func CommandNodeSysctlsReport(format string) error { + if format != "stdout" && format != "json" { + return fmt.Errorf("Invalid format: %s", format) + } + + scopes, err := resolveNodeSysctlScopes() + if err != nil { + return err + } + + if format == "json" { + output := map[string]map[string]string{} + for _, scope := range scopes { + key := scope.ProfileName + if key == "" { + key = "--global" + } + + entries := map[string]string{} + for _, sysctl := range scope.Sysctls { + entries[sysctl.Name] = sysctl.Value + } + output[key] = entries + } + + b, err := json.Marshal(output) + if err != nil { + return fmt.Errorf("Unable to marshal json: %w", err) + } + + fmt.Println(string(b)) + return nil + } + + lines := []string{"scope|sysctl|value"} + for _, scope := range scopes { + scopeName := scope.ProfileName + if scopeName == "" { + scopeName = "--global" + } + + for _, sysctl := range scope.Sysctls { + lines = append(lines, fmt.Sprintf("%s|%s|%s", scopeName, sysctl.Name, sysctl.Value)) + } + } + + fmt.Println(columnize.SimpleFormat(lines)) + return nil +} + +// verifyNodeProfileExists returns an error when a node profile has not been created +func verifyNodeProfileExists(profileName string) error { + properties := common.PropertyGetDefault("scheduler-k3s", "--global", fmt.Sprintf("node-profile-%s.json", profileName), "") + if properties == "" { + return fmt.Errorf("Node profile %s not found", profileName) + } + + return nil +} + // CommandAutoscalingAuthSet set or clear a scheduler-k3s autoscaling keda trigger authentication object for an app func CommandAutoscalingAuthSet(appName string, trigger string, metadata map[string]string, global bool) error { if global { @@ -397,6 +486,11 @@ func CommandInitialize(ingressClass string, serverIP string, taintScheduling boo return fmt.Errorf("Unable to install helper commands: %w", err) } + common.LogInfo2Quiet("Applying node sysctls") + if err := CreateOrUpdateNodeSysctls(ctx); err != nil { + return fmt.Errorf("Unable to apply node sysctls: %w", err) + } + common.LogVerboseQuiet("Done") return nil @@ -1262,6 +1356,10 @@ func CommandProfilesRemove(profileName string) error { return fmt.Errorf("Unable to delete node profile: %w", err) } + if err := DeleteNodeSysctls(context.Background(), profileName); err != nil { + return err + } + common.LogInfo1(fmt.Sprintf("Node profile %s removed", profileName)) return nil } diff --git a/plugins/scheduler-k3s/templates/node-sysctls-chart/templates/daemonset.yaml b/plugins/scheduler-k3s/templates/node-sysctls-chart/templates/daemonset.yaml new file mode 100644 index 000000000..6eb2fcdd5 --- /dev/null +++ b/plugins/scheduler-k3s/templates/node-sysctls-chart/templates/daemonset.yaml @@ -0,0 +1,55 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + annotations: + dokku.com/managed: "true" + labels: + app.kubernetes.io/name: {{ .Values.global.release_name }} + app.kubernetes.io/part-of: dokku + name: {{ .Values.global.release_name }} + namespace: {{ .Release.Namespace }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ .Values.global.release_name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ .Values.global.release_name }} + app.kubernetes.io/part-of: dokku + spec: + {{- if .Values.global.profile_name }} + nodeSelector: + dokku.com/node-profile: {{ .Values.global.profile_name | quote }} + {{- else }} + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: dokku.com/node-profile + operator: DoesNotExist + {{- end }} + tolerations: + - operator: Exists + initContainers: + - name: apply-sysctls + image: {{ .Values.global.image | quote }} + securityContext: + privileged: true + command: + - sh + - -c + - | + set -e + {{- range .Values.global.sysctls }} + sysctl -w {{ printf "%s=%s" .name .value | quote }} + {{- end }} + containers: + - name: pause + image: {{ .Values.global.pause_image | quote }} + resources: + requests: + cpu: 1m + memory: 8Mi diff --git a/tests/unit/scheduler-k3s-5.bats b/tests/unit/scheduler-k3s-5.bats index 3f074e373..67fed3c71 100644 --- a/tests/unit/scheduler-k3s-5.bats +++ b/tests/unit/scheduler-k3s-5.bats @@ -12,6 +12,7 @@ setup() { } teardown() { + dokku scheduler-k3s:node-sysctls:set --global vm.max_map_count || true global_teardown dokku nginx:start uninstall_k3s || true @@ -78,3 +79,69 @@ teardown() { assert_failure assert_output_contains "is not namespaced" -1 } + +@test "(scheduler-k3s:node-sysctls) applies non-namespaced sysctls to nodes" { + if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_TOKEN" ]]; then + skip "skipping due to missing docker.io credentials DOCKERHUB_USERNAME:DOCKERHUB_TOKEN" + fi + + INGRESS_CLASS=nginx install_k3s + + run /bin/bash -c "kubectl get daemonset -n kube-system dokku-node-sysctls-global" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku scheduler-k3s:node-sysctls:set --global vm.max_map_count 262144" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku scheduler-k3s:node-sysctls:report --format json | jq -r '.\"--global\".\"vm.max_map_count\"'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "262144" + + run /bin/bash -c "kubectl rollout status daemonset -n kube-system dokku-node-sysctls-global --timeout=120s" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "kubectl get daemonset -n kube-system dokku-node-sysctls-global -o json | jq -r '.status.desiredNumberScheduled'" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "$(kubectl get nodes --no-headers | wc -l | tr -d ' ')" + + run /bin/bash -c "cat /proc/sys/vm/max_map_count" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "262144" + + run /bin/bash -c "dokku scheduler-k3s:node-sysctls:set --global vm.max_map_count" + echo "output: $output" + echo "status: $status" + assert_success + + run wait_for_daemonset_deletion dokku-node-sysctls-global + echo "output: $output" + echo "status: $status" + assert_success +} + +wait_for_daemonset_deletion() { + declare desc="waits for a daemonset to be removed from the api server" + declare NAME="$1" + + for _ in $(seq 1 30); do + if ! kubectl get daemonset -n kube-system "$NAME" >/dev/null 2>&1; then + return 0 + fi + sleep 2 + done + + echo "daemonset $NAME still exists after 60s" + return 1 +} From daee1e6c686e5a055f0052ff4a0a80b3afc9ad54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:25:18 +0000 Subject: [PATCH 36/36] chore(deps): bump github.com/kedacore/keda/v2 in /plugins/scheduler-k3s Bumps [github.com/kedacore/keda/v2](https://github.com/kedacore/keda) from 2.20.1 to 2.20.2. - [Release notes](https://github.com/kedacore/keda/releases) - [Changelog](https://github.com/kedacore/keda/blob/main/CHANGELOG.md) - [Commits](https://github.com/kedacore/keda/compare/v2.20.1...v2.20.2) --- updated-dependencies: - dependency-name: github.com/kedacore/keda/v2 dependency-version: 2.20.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- plugins/scheduler-k3s/go.mod | 2 +- plugins/scheduler-k3s/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scheduler-k3s/go.mod b/plugins/scheduler-k3s/go.mod index 503e24b26..73d6ee09f 100644 --- a/plugins/scheduler-k3s/go.mod +++ b/plugins/scheduler-k3s/go.mod @@ -22,7 +22,7 @@ require ( github.com/gofrs/flock v0.13.0 github.com/gosimple/slug v1.15.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/kedacore/keda/v2 v2.20.1 + github.com/kedacore/keda/v2 v2.20.2 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d github.com/onsi/gomega v1.42.1 github.com/ryanuber/columnize v2.1.2+incompatible diff --git a/plugins/scheduler-k3s/go.sum b/plugins/scheduler-k3s/go.sum index 2cce78db5..365d270e4 100644 --- a/plugins/scheduler-k3s/go.sum +++ b/plugins/scheduler-k3s/go.sum @@ -216,8 +216,8 @@ github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/ github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kedacore/keda/v2 v2.20.1 h1:0Bb2zX/s+RSPtU2W+/Jk24A/aFl8MO79UuU0C4lA8ts= -github.com/kedacore/keda/v2 v2.20.1/go.mod h1:wrV1MP6buZQ1S5/J/R7UyBeJr857x92E43LADVoSajs= +github.com/kedacore/keda/v2 v2.20.2 h1:SR60v8uIcelbveS8n/1jcl8tLdBBIbHMxmdOHw8tKqA= +github.com/kedacore/keda/v2 v2.20.2/go.mod h1:wrV1MP6buZQ1S5/J/R7UyBeJr857x92E43LADVoSajs= github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=