feat: add Helm chart for standalone AI Gateway (#27256)

Adds the `coder-ai-gateway` Helm chart for deploying the Coder AI
Gateway as a standalone Kubernetes workload.

Adds the coder-ai-gateway Helm chart for deploying the Coder AI Gateway as a standalone Kubernetes workload.

The chart supports AI Gateway keys from an existing Secret or environment configuration, Coder connectivity through CODER_URL, listener and Coder-facing TLS, and optional Service, Ingress, and Gateway API HTTPRoute resources.

Integrates the chart with existing Helm build, lint, golden generation, release artifact, Helm repository, and OCI publishing workflows.
This commit is contained in:
Paweł Banaszewski
2026-07-22 17:42:42 +02:00
committed by GitHub
parent 99e740bdb7
commit 8a3fb04510
40 changed files with 2299 additions and 7 deletions
+9 -1
View File
@@ -13,7 +13,7 @@ all: lint
lint: lint/helm
.PHONY: lint
lint/helm: lint/helm/coder lint/helm/provisioner
lint/helm: lint/helm/coder lint/helm/provisioner lint/helm/ai-gateway
.PHONY: lint/helm
lint/helm/coder:
@@ -25,3 +25,11 @@ lint/helm/provisioner:
helm dependency update --skip-refresh provisioner/
helm lint --strict --set coder.image.tag=v0.0.1 provisioner/
.PHONY: lint/helm/provisioner
lint/helm/ai-gateway:
helm dependency update --skip-refresh ai-gateway/
helm lint --strict \
--set coder.image.tag=v0.0.1 \
--set aigateway.keySecret.name=test \
ai-gateway/
.PHONY: lint/helm/ai-gateway
+1
View File
@@ -0,0 +1 @@
tests
+6
View File
@@ -0,0 +1,6 @@
dependencies:
- name: libcoder
repository: file://../libcoder
version: 0.1.0
digest: sha256:5c9a99109258073b590a9f98268490ef387fde24c0c7c7ade9c1a8c7ef5e6e10
generated: "2026-07-14T17:15:50.670279008Z"
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v2
name: coder-ai-gateway
description: Standalone AI Gateway for Coder
home: https://github.com/coder/coder
# These versions are replaced when Coder releases the chart.
type: application
version: "0.1.0"
appVersion: "0.1.0"
kubeVersion: ">= 1.23.0-0"
keywords:
- coder
- ai
sources:
- https://github.com/coder/coder/tree/main/helm/ai-gateway
icon: https://helm.coder.com/coder_logo_black.png
maintainers:
- name: Coder Technologies, Inc.
email: support@coder.com
url: https://coder.com/contact
dependencies:
- name: libcoder
version: 0.1.0
repository: file://../libcoder
+189
View File
@@ -0,0 +1,189 @@
# Coder AI Gateway Helm chart
This chart deploys the Coder AI Gateway as a standalone Kubernetes Deployment.
The Gateway connects to Coder using `CODER_URL` and an AI Gateway key. To forward
proxied AI traffic to the standalone Gateway, configure the Coder AI Gateway
Proxy (`aibridgeproxyd`) after installing the chart.
The chart does not create credentials or TLS Secrets.
## Install
### Prerequisites
- An AI Gateway key created in Coder.
- A Coder image that includes the `coder ai-gateway start` command. The official
Coder v2.36.0 image is the first version to include this command.
### Configure the chart
Create a `values.yaml` file with the Coder URL and an AI Gateway key source. The
following example uses a Kubernetes Secret in the Helm release namespace:
```console
kubectl create secret generic coder-ai-gateway-key \
--namespace <release-namespace> \
--from-literal=key='<AI gateway key>'
```
```yaml
coder:
image:
# Required when installing the chart directly from Git.
tag: "<coder version>"
env:
- name: CODER_URL
value: https://coder.example.com
aigateway:
keySecret:
name: coder-ai-gateway-key
```
The Gateway can also connect to Coder through an in-cluster Service, for
example:
```yaml
coder:
env:
- name: CODER_URL
value: http://coder.coder.svc.cluster.local:80
```
For HTTPS, the Coder certificate must cover the internal Service hostname and
the Gateway must trust its issuing CA.
Instead of `aigateway.keySecret`, set `CODER_AI_GATEWAY_KEY` or
`CODER_AI_GATEWAY_KEY_FILE` through `coder.env`. Environment variables can also
be supplied through `coder.envFrom`. The chart does not check for variable
conflicts, regardless of whether values come from Helm options, `coder.env`, or
`coder.envFrom`.
When installing a released chart package, the chart automatically uses the
matching Coder image version. Set `coder.image.tag` only when installing
directly from Git or overriding the image version. Custom images must provide
the `coder ai-gateway start` command.
### Install the chart
```console
helm install ai-gateway ./helm/ai-gateway \
--namespace <release-namespace> \
--values values.yaml
```
## Connect Coder to the standalone Gateway
To route proxied AI requests through the standalone Gateway, configure the Coder
AI Gateway Proxy with a target URL. When `service.enable` is true, the chart
notes show the direct in-cluster Service URL, including the scheme selected by
`aigateway.listenerTLS`. Retrieve it with:
```console
helm get notes ai-gateway --namespace <release-namespace>
```
The chart notes do not show an Ingress or `HTTPRoute` URL. To route through one
of these entry points, set `CODER_AI_GATEWAY_PROXY_TARGET` to its URL instead.
When `service.enable` is false, set the target to the URL of your user-managed
route to the Deployment.
When listener TLS uses a private CA, the AI Gateway Proxy must trust that CA to
connect directly to the Service over HTTPS.
## TLS
For Gateway-to-Coder HTTPS with a private CA, set
`aigateway.coderTLS.caSecret`. If Coder requires client mTLS, also set
`aigateway.coderTLS.clientSecret`.
Prefer terminating client-facing TLS at a Kubernetes Ingress or a `Gateway`
resource from the Kubernetes Gateway API. To terminate TLS in the AI Gateway
process, set `aigateway.listenerTLS.name` to an existing TLS Secret.
Client-facing TLS and backend TLS are independent. The `ingress.tls` settings
configure TLS between clients and the Ingress. For `HTTPRoute`, the Gateway
listener that accepts client connections is configured outside this chart.
These settings do not configure whether the Ingress or Gateway connects to the
AI Gateway Service using HTTP or HTTPS.
When `aigateway.listenerTLS` is enabled behind an Ingress or `HTTPRoute`,
configure the entry point to connect to the Service using HTTPS and trust the AI
Gateway certificate. Ingress backend TLS is controller-specific and can usually
be configured with `ingress.annotations`. Gateway API backend TLS uses a
separate `BackendTLSPolicy`, which can be managed outside this chart or rendered
with `extraTemplates`. The chart does not infer or validate this
controller-specific configuration. Without backend TLS, the entry point sends
plaintext HTTP to the HTTPS listener, which typically results in a TLS handshake
error reported as HTTP 502.
All referenced TLS Secrets must exist in the Helm release namespace.
## Networking
The data-plane Service, which carries LLM traffic, is a `ClusterIP` by default.
`NodePort` and `LoadBalancer` are explicit alternatives. Ingress and `HTTPRoute`
are optional and both route to the data-plane Service. If you enable Ingress or
`HTTPRoute`, use a `ClusterIP` Service unless you intentionally need a second
external entry point through a `LoadBalancer` Service.
## Scaling and resources
Set `coder.replicaCount` to run multiple AI Gateway replicas. The default
resource requests are 1 CPU and 1 GiB of memory per replica. These requests are
a starting point, not a capacity guarantee. CPU and memory usage depend heavily
on concurrent requests and payload size.
Adjust `coder.resources` after observing production traffic. Consider setting
`CODER_AI_GATEWAY_MAX_CONCURRENCY` through `coder.env` to bound concurrent
requests per replica. The application default is unlimited. The chart does not
set resource limits by default, which avoids CPU throttling and fixed memory
limits for bursty workloads. Manage resources such as a Horizontal Pod
Autoscaler or PodDisruptionBudget through your platform configuration or
`extraTemplates`.
## Metrics
Every pod runs an unauthenticated metrics listener on the named `metrics` port,
which maps to port `2112`. The chart does not create monitoring discovery
resources. Configure pod-based discovery with `coder.podAnnotations`, for
example:
```yaml
coder:
podAnnotations:
prometheus.io/scrape: "true"
prometheus.io/port: "2112"
```
Alternatively, create discovery resources such as a `ServiceMonitor` through
your monitoring stack or `extraTemplates`.
## Key rotation
1. Create a new AI Gateway key in Coder.
2. Update the configured key source:
- For `aigateway.keySecret`, update the referenced Secret or set `name` to a
new Secret.
- For a key supplied through `coder.env`, update the environment variable or
the file it references.
3. If the update did not trigger a rollout, restart the Deployment, for example:
```console
kubectl rollout restart deployment/coder-ai-gateway \
--namespace <release-namespace>
```
4. Verify every replica is ready and serving with the new key.
5. Revoke the old key.
Secret updates do not change the Deployment pod template automatically. A
reloader controller can be configured through `coder.annotations` or
`coder.podAnnotations`.
## Extra manifests
`extraTemplates` renders additional Kubernetes manifests as part of the Helm
release. Entries can be YAML strings or Kubernetes objects, and can use Helm
release values and chart helpers. Use them for small companion resources, such
as a `NetworkPolicy`.
+13
View File
@@ -0,0 +1,13 @@
Coder AI Gateway is installed as {{ include "coder.name" . }}.
{{- if .Values.service.enable }}
Configure the Coder deployment with:
CODER_AI_GATEWAY_PROXY_TARGET={{ ternary "https" "http" (not (empty .Values.aigateway.listenerTLS.name)) }}://{{ include "coder.name" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}
{{- else }}
The chart-managed Service is disabled. Configure CODER_AI_GATEWAY_PROXY_TARGET
with the URL of the user-managed route to this deployment.
{{- end }}
The metrics listener is active on port 2112. Configure discovery with
coder.podAnnotations or resources managed by your monitoring stack.
+150
View File
@@ -0,0 +1,150 @@
{{/*
Service account to merge into the libcoder template. The Gateway never needs
the Kubernetes API, so the token is not mounted.
*/}}
{{- define "coder-ai-gateway.serviceaccount" -}}
automountServiceAccountToken: false
{{- end }}
{{/*
HTTP probe shared by startup, liveness, and readiness configuration.
*/}}
{{- define "coder-ai-gateway.probe" -}}
httpGet:
path: {{ .path }}
port: http
scheme: {{ .scheme }}
initialDelaySeconds: {{ .probe.initialDelaySeconds }}
{{- range $field := list "periodSeconds" "timeoutSeconds" "successThreshold" "failureThreshold" }}
{{- if hasKey $.probe $field }}
{{ $field }}: {{ index $.probe $field }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Component annotation for pod metadata.
*/}}
{{- define "coder.componentAnnotation" -}}
app.kubernetes.io/component: ai-gateway
{{- end }}
{{/*
Deployment to merge into the libcoder template.
*/}}
{{- define "coder-ai-gateway.deployment" -}}
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: {{ .Values.aigateway.terminationGracePeriodSeconds }}
containers:
-
{{ include "libcoder.containerspec" (list . "coder-ai-gateway.containerspec") | indent 8 }}
volumes:
{{- if .Values.aigateway.keySecret.name }}
- name: ai-gateway-auth
secret:
secretName: {{ .Values.aigateway.keySecret.name }}
items:
- key: {{ .Values.aigateway.keySecret.key }}
path: key
{{- end }}
{{- if .Values.aigateway.listenerTLS.name }}
- name: ai-gateway-listener
secret:
secretName: {{ .Values.aigateway.listenerTLS.name }}
items:
- key: {{ .Values.aigateway.listenerTLS.certKey }}
path: tls.crt
- key: {{ .Values.aigateway.listenerTLS.keyKey }}
path: tls.key
{{- end }}
{{- if .Values.aigateway.coderTLS.caSecret.name }}
- name: coder-client-ca
secret:
secretName: {{ .Values.aigateway.coderTLS.caSecret.name }}
items:
- key: {{ .Values.aigateway.coderTLS.caSecret.key }}
path: ca.crt
{{- end }}
{{- if .Values.aigateway.coderTLS.clientSecret.name }}
- name: coder-client-tls
secret:
secretName: {{ .Values.aigateway.coderTLS.clientSecret.name }}
items:
- key: {{ .Values.aigateway.coderTLS.clientSecret.certKey }}
path: tls.crt
- key: {{ .Values.aigateway.coderTLS.clientSecret.keyKey }}
path: tls.key
{{- end }}
{{- include "coder.volumeList" . | nindent 8 }}
{{- end }}
{{/*
ContainerSpec for the AI Gateway container of the deployment.
*/}}
{{- define "coder-ai-gateway.containerspec" -}}
args:
- ai-gateway
- start
{{- with .Values.coder.envFrom }}
envFrom:
{{ toYaml . }}
{{- end }}
env:
{{ include "coder-ai-gateway.defaultEnv" . }}
{{/*
User additions follow chart defaults so they may reference or override them.
*/}}
{{- with .Values.coder.env }}
{{ toYaml . }}
{{- end }}
ports:
- name: http
containerPort: 4001
protocol: TCP
- name: metrics
containerPort: 2112
protocol: TCP
{{- $scheme := ternary "HTTPS" "HTTP" (not (empty .Values.aigateway.listenerTLS.name)) }}
{{- if .Values.coder.startupProbe.enabled }}
startupProbe:
{{ include "coder-ai-gateway.probe" (dict "probe" .Values.coder.startupProbe "path" "/healthz" "scheme" $scheme) | indent 2 }}
{{- end }}
{{- if .Values.coder.livenessProbe.enabled }}
livenessProbe:
{{ include "coder-ai-gateway.probe" (dict "probe" .Values.coder.livenessProbe "path" "/healthz" "scheme" $scheme) | indent 2 }}
{{- end }}
{{- if .Values.coder.readinessProbe.enabled }}
readinessProbe:
{{ include "coder-ai-gateway.probe" (dict "probe" .Values.coder.readinessProbe "path" "/readyz" "scheme" $scheme) | indent 2 }}
{{- end }}
volumeMounts:
{{- if .Values.aigateway.keySecret.name }}
- name: ai-gateway-auth
mountPath: /etc/coder/ai-gateway-auth
readOnly: true
{{- end }}
{{- if .Values.aigateway.listenerTLS.name }}
- name: ai-gateway-listener
mountPath: /etc/coder/ai-gateway-listener
readOnly: true
{{- end }}
{{- if .Values.aigateway.coderTLS.caSecret.name }}
- name: coder-client-ca
mountPath: /etc/coder/coder-client-ca
readOnly: true
{{- end }}
{{- if .Values.aigateway.coderTLS.clientSecret.name }}
- name: coder-client-tls
mountPath: /etc/coder/coder-client-tls
readOnly: true
{{- end }}
{{- include "coder.volumeMountList" . | nindent 0 }}
{{- end }}
+73
View File
@@ -0,0 +1,73 @@
{{/*
Environment variables configured by the chart. User-supplied variables follow
these entries, including when a name is duplicated.
*/}}
{{- define "coder-ai-gateway.defaultEnv" -}}
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 0.0.0.0:4001
{{- if .Values.aigateway.keySecret.name }}
- name: CODER_AI_GATEWAY_KEY_FILE
value: /etc/coder/ai-gateway-auth/key
{{- end }}
- name: CODER_PROMETHEUS_ENABLE
value: "true"
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
{{- if .Values.aigateway.listenerTLS.name }}
- name: CODER_AI_GATEWAY_TLS_CERT_FILE
value: /etc/coder/ai-gateway-listener/tls.crt
- name: CODER_AI_GATEWAY_TLS_KEY_FILE
value: /etc/coder/ai-gateway-listener/tls.key
{{- end }}
{{- if .Values.aigateway.coderTLS.caSecret.name }}
- name: CODER_CLIENT_TLS_CA_FILE
value: /etc/coder/coder-client-ca/ca.crt
{{- end }}
{{- if .Values.aigateway.coderTLS.clientSecret.name }}
- name: CODER_CLIENT_TLS_CERT_FILE
value: /etc/coder/coder-client-tls/tls.crt
- name: CODER_CLIENT_TLS_KEY_FILE
value: /etc/coder/coder-client-tls/tls.key
{{- end }}
{{- end -}}
{{/*
Cross-field validation, invoked once from aigateway.yaml. Emits nothing and
aborts rendering with a specific message on inconsistent values. Each failure
message is asserted verbatim in tests/chart_test.go.
*/}}
{{- define "coder-ai-gateway.validate" -}}
{{- if and .Values.aigateway.keySecret.name (not .Values.aigateway.keySecret.key) }}
{{- fail "aigateway.keySecret.key is required when name is set." }}
{{- end }}
{{- if and .Values.ingress.enable (not .Values.service.enable) }}
{{- fail "service.enable must be true when ingress.enable is true." }}
{{- end }}
{{- if and .Values.ingress.enable (not .Values.ingress.host) }}
{{- fail "ingress.host is required when ingress.enable is true." }}
{{- end }}
{{- if and .Values.httproute.enable (not .Values.service.enable) }}
{{- fail "service.enable must be true when httproute.enable is true." }}
{{- end }}
{{- if and .Values.httproute.enable (empty .Values.httproute.parentRefs) }}
{{- fail "httproute.parentRefs is required when httproute.enable is true." }}
{{- end }}
{{- $listener := .Values.aigateway.listenerTLS }}
{{- if and $listener.name (or (not $listener.certKey) (not $listener.keyKey)) }}
{{- fail "aigateway.listenerTLS.certKey and keyKey are required when name is set." }}
{{- end }}
{{- if and .Values.httproute.enable (not (.Capabilities.APIVersions.Has "gateway.networking.k8s.io/v1/HTTPRoute")) }}
{{- fail "httproute.enable requires the gateway.networking.k8s.io/v1 HTTPRoute CRD." }}
{{- end }}
{{- $client := .Values.aigateway.coderTLS.clientSecret }}
{{- if and $client.name (or (not $client.certKey) (not $client.keyKey)) }}
{{- fail "aigateway.coderTLS.clientSecret.certKey and keyKey are required when name is set." }}
{{- end }}
{{- $ca := .Values.aigateway.coderTLS.caSecret }}
{{- if and $ca.name (not $ca.key) }}
{{- fail "aigateway.coderTLS.caSecret.key is required when name is set." }}
{{- end }}
{{- if and .Values.service.nodePort (not (has .Values.service.type (list "NodePort" "LoadBalancer"))) }}
{{- fail "service.nodePort requires service.type to be NodePort or LoadBalancer." }}
{{- end }}
{{- end -}}
+8
View File
@@ -0,0 +1,8 @@
{{- include "coder-ai-gateway.validate" . -}}
---
{{- if not .Values.coder.serviceAccount.disableCreate }}
{{ include "libcoder.serviceaccount" (list . "coder-ai-gateway.serviceaccount") }}
{{- end }}
---
{{ include "libcoder.deployment" (list . "coder-ai-gateway.deployment") }}
@@ -0,0 +1,4 @@
{{- range .Values.extraTemplates }}
---
{{ include "coder.renderTemplate" (dict "value" . "context" $) }}
{{- end }}
+30
View File
@@ -0,0 +1,30 @@
{{- if .Values.httproute.enable }}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: {{ include "coder.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "coder.labels" . | nindent 4 }}
{{- with .Values.httproute.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.httproute.parentRefs }}
parentRefs:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.httproute.hostnames }}
hostnames:
{{- toYaml . | nindent 4 }}
{{- end }}
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: {{ include "coder.name" . }}
port: {{ .Values.service.port }}
{{- end }}
+36
View File
@@ -0,0 +1,36 @@
{{- if .Values.ingress.enable }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "coder.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "coder.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- with .Values.ingress.className }}
ingressClassName: {{ . }}
{{- end }}
{{- if .Values.ingress.tls.enable }}
tls:
- hosts:
- {{ .Values.ingress.host | quote }}
{{- with .Values.ingress.tls.secretName }}
secretName: {{ . }}
{{- end }}
{{- end }}
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: {{ .Values.ingress.path }}
pathType: {{ .Values.ingress.pathType }}
backend:
service:
name: {{ include "coder.name" . }}
port:
number: {{ .Values.service.port }}
{{- end }}
+35
View File
@@ -0,0 +1,35 @@
{{- if .Values.service.enable }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "coder.name" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "coder.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
sessionAffinity: {{ .Values.service.sessionAffinity }}
{{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }}
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
{{- end }}
{{- with .Values.service.loadBalancerClass }}
loadBalancerClass: {{ . }}
{{- end }}
{{- with .Values.service.loadBalancerIP }}
loadBalancerIP: {{ . }}
{{- end }}
selector:
{{- include "coder.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
{{- with .Values.service.nodePort }}
nodePort: {{ . }}
{{- end }}
{{- end }}
+229
View File
@@ -0,0 +1,229 @@
package tests // nolint: testpackage
import (
"bytes"
"flag"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/testutil"
)
var updateGoldenFiles = flag.Bool("update", false, "Update golden files")
var testCases = []testCase{
{
name: "default_values",
fixture: "default_values",
},
{
name: "networking",
fixture: "networking",
namespace: "ai-gateway-test",
apiVersions: []string{"gateway.networking.k8s.io/v1/HTTPRoute"},
},
{
name: "custom",
fixture: "custom",
namespace: "ai-gateway-test",
},
{
name: "nodeport",
fixture: "nodeport",
},
{
name: "missing_key_field",
fixture: "fails_missing_key_field",
expectedError: "aigateway.keySecret.key is required when name is set.",
},
{
name: "partial_listener_tls",
fixture: "fails_partial_listener_tls",
expectedError: "aigateway.listenerTLS.certKey and keyKey are required when name is set.",
},
// This verifies that listener TLS and Ingress can be rendered together.
// Production use requires controller-specific backend TLS and certificate
// trust configuration outside this chart.
{
name: "listener_tls_with_ingress",
fixture: "listener_tls_with_ingress",
},
{
name: "partial_client_tls",
fixture: "fails_partial_client_tls",
expectedError: "aigateway.coderTLS.clientSecret.certKey and keyKey are required when name is set.",
},
{
name: "partial_ca_tls",
fixture: "fails_partial_ca_tls",
expectedError: "aigateway.coderTLS.caSecret.key is required when name is set.",
},
{
name: "ingress_without_service",
fixture: "fails_ingress_without_service",
expectedError: "service.enable must be true when ingress.enable is true.",
},
{
name: "ingress_without_host",
fixture: "fails_ingress_without_host",
expectedError: "ingress.host is required when ingress.enable is true.",
},
{
name: "httproute_without_service",
fixture: "fails_httproute_without_service",
expectedError: "service.enable must be true when httproute.enable is true.",
},
{
name: "httproute_without_parent_refs",
fixture: "fails_httproute_without_parent_refs",
expectedError: "httproute.parentRefs is required when httproute.enable is true.",
apiVersions: []string{"gateway.networking.k8s.io/v1/HTTPRoute"},
},
{
name: "httproute_without_crd",
fixture: "networking",
expectedError: "httproute.enable requires the gateway.networking.k8s.io/v1 HTTPRoute CRD.",
},
{
name: "nodeport_with_clusterip",
fixture: "fails_nodeport_with_clusterip",
expectedError: "service.nodePort requires service.type to be NodePort or LoadBalancer.",
},
}
type testCase struct {
name string
fixture string
namespace string
expectedError string
apiVersions []string
}
func (tc testCase) valuesFilePath() string {
return filepath.Join("testdata", tc.fixture+".yaml")
}
func (tc testCase) goldenFilePath() string {
if tc.namespace == "default" {
return filepath.Join("testdata", tc.name+".golden")
}
return filepath.Join("testdata", tc.name+"_"+tc.namespace+".golden")
}
func TestRenderChart(t *testing.T) {
t.Parallel()
if *updateGoldenFiles {
t.Skip("Golden files are being updated")
}
if testutil.InCI() && (runtime.GOOS == "windows" || runtime.GOOS == "darwin") {
t.Skip("Skipping Helm tests on Windows and macOS in CI")
}
helmPath := lookupHelm(t)
err := updateHelmDependencies(t, helmPath, "..")
require.NoError(t, err, "failed to build Helm dependencies")
for _, tc := range testCases {
tc := tc
if tc.namespace == "" {
tc.namespace = "default"
}
t.Run(tc.namespace+"/"+tc.name, func(t *testing.T) {
t.Parallel()
output, err := runHelmTemplate(t, helmPath, tc.valuesFilePath(), tc.namespace, tc.apiVersions)
if tc.expectedError != "" {
require.Error(t, err)
require.Contains(t, output, tc.expectedError)
return
}
require.NoError(t, err, output)
golden, err := os.ReadFile(tc.goldenFilePath())
require.NoError(t, err)
golden = bytes.ReplaceAll(golden, []byte("\r"), nil)
require.Equal(t, string(golden), output)
})
}
}
func TestUpdateGoldenFiles(t *testing.T) {
t.Parallel()
if !*updateGoldenFiles {
t.Skip("Run with -update to update golden files")
}
helmPath := lookupHelm(t)
err := updateHelmDependencies(t, helmPath, "..")
require.NoError(t, err, "failed to build Helm dependencies")
for _, tc := range testCases {
if tc.expectedError != "" {
continue
}
if tc.namespace == "" {
tc.namespace = "default"
}
output, err := runHelmTemplate(t, helmPath, tc.valuesFilePath(), tc.namespace, tc.apiVersions)
require.NoError(t, err, output)
require.NoError(t, os.WriteFile(tc.goldenFilePath(), []byte(output), 0o644)) // nolint:gosec
}
}
func runHelmTemplate(t testing.TB, helmPath, valuesFile, namespace string, apiVersions []string) (string, error) {
t.Helper()
args := []string{"template", "ai-gateway", "..", "-f", valuesFile, "--namespace", namespace}
for _, apiVersion := range apiVersions {
args = append(args, "--api-versions", apiVersion)
}
cmd := exec.Command(helmPath, args...)
cmd.Dir = "."
output, err := cmd.CombinedOutput()
return string(output), err
}
// updateDepsOnce guards updateHelmDependencies: parallel top-level tests
// share the charts/ directory, and rebuilding it while another test
// templates the chart races.
var (
updateDepsOnce sync.Once
errUpdateDeps error
)
// updateHelmDependencies runs `helm dependency update .` on the given chartDir.
func updateHelmDependencies(t testing.TB, helmPath, chartDir string) error {
t.Helper()
updateDepsOnce.Do(func() {
// Remove charts/ from chartDir if it exists.
err := os.RemoveAll(filepath.Join(chartDir, "charts"))
if err != nil {
errUpdateDeps = xerrors.Errorf("failed to remove charts/ directory: %w", err)
return
}
cmd := exec.Command(helmPath, "dependency", "update", "--skip-refresh", ".")
cmd.Dir = chartDir
t.Logf("exec command: %v", cmd.Args)
out, err := cmd.CombinedOutput()
if err != nil {
errUpdateDeps = xerrors.Errorf("failed to run `helm dependency update`: %w\noutput: %s", err, out)
return
}
})
return errUpdateDeps
}
func lookupHelm(t testing.TB) string {
t.Helper()
helmPath, err := exec.LookPath("helm")
require.NoError(t, err, "helm not found in PATH")
return helmPath
}
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}
+69
View File
@@ -0,0 +1,69 @@
coder:
image:
tag: v2.36.0
startupProbe:
enabled: true
initialDelaySeconds: 2
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 7
livenessProbe:
enabled: true
initialDelaySeconds: 12
periodSeconds: 15
timeoutSeconds: 13
successThreshold: 1
failureThreshold: 17
readinessProbe:
enabled: true
initialDelaySeconds: 22
periodSeconds: 25
timeoutSeconds: 23
successThreshold: 21
failureThreshold: 27
serviceAccount:
name: workload-identity
disableCreate: true
env:
- name: CODER_URL
value: https://coder-primary.coder-system.svc.cluster.local:8443
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 127.0.0.1:4444
- name: CODER_VERBOSE
value: "true"
envFrom:
- configMapRef:
name: gateway-config
aigateway:
keySecret:
name: ai-gateway-key
key: token
coderTLS:
caSecret:
name: coder-ca
key: root.pem
clientSecret:
name: coder-client
certKey: client.crt
keyKey: client.key
listenerTLS:
name: gateway-tls
certKey: cert.pem
keyKey: key.pem
extraTemplates:
- |
apiVersion: v1
kind: ConfigMap
metadata:
name: '{{ include "coder.name" . }}-extra'
namespace: '{{ .Release.Namespace }}'
data:
release: '{{ .Release.Name }}'
- apiVersion: v1
kind: ConfigMap
metadata:
name: '{{ include "coder.name" . }}-object'
namespace: '{{ .Release.Namespace }}'
data:
namespace: '{{ .Release.Namespace }}'
@@ -0,0 +1,214 @@
---
# Source: coder-ai-gateway/templates/extra-templates.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: 'coder-ai-gateway-extra'
namespace: 'ai-gateway-test'
data:
release: 'ai-gateway'
---
# Source: coder-ai-gateway/templates/extra-templates.yaml
apiVersion: v1
data:
namespace: 'ai-gateway-test'
kind: ConfigMap
metadata:
name: 'coder-ai-gateway-object'
namespace: 'ai-gateway-test'
---
# Source: coder-ai-gateway/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder-ai-gateway
namespace: ai-gateway-test
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
sessionAffinity: None
selector:
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
---
# Source: coder-ai-gateway/templates/aigateway.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: ai-gateway-test
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/name: coder-ai-gateway
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
app.kubernetes.io/component: ai-gateway
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
spec:
automountServiceAccountToken: false
containers:
- args:
- ai-gateway
- start
command:
- /opt/coder
env:
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 0.0.0.0:4001
- name: CODER_AI_GATEWAY_KEY_FILE
value: /etc/coder/ai-gateway-auth/key
- name: CODER_PROMETHEUS_ENABLE
value: "true"
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_AI_GATEWAY_TLS_CERT_FILE
value: /etc/coder/ai-gateway-listener/tls.crt
- name: CODER_AI_GATEWAY_TLS_KEY_FILE
value: /etc/coder/ai-gateway-listener/tls.key
- name: CODER_CLIENT_TLS_CA_FILE
value: /etc/coder/coder-client-ca/ca.crt
- name: CODER_CLIENT_TLS_CERT_FILE
value: /etc/coder/coder-client-tls/tls.crt
- name: CODER_CLIENT_TLS_KEY_FILE
value: /etc/coder/coder-client-tls/tls.key
- name: CODER_URL
value: https://coder-primary.coder-system.svc.cluster.local:8443
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 127.0.0.1:4444
- name: CODER_VERBOSE
value: "true"
envFrom:
- configMapRef:
name: gateway-config
image: ghcr.io/coder/coder:v2.36.0
imagePullPolicy: IfNotPresent
lifecycle: {}
livenessProbe:
failureThreshold: 17
httpGet:
path: /healthz
port: http
scheme: HTTPS
initialDelaySeconds: 12
periodSeconds: 15
successThreshold: 1
timeoutSeconds: 13
name: coder
ports:
- containerPort: 4001
name: http
protocol: TCP
- containerPort: 2112
name: metrics
protocol: TCP
readinessProbe:
failureThreshold: 27
httpGet:
path: /readyz
port: http
scheme: HTTPS
initialDelaySeconds: 22
periodSeconds: 25
successThreshold: 21
timeoutSeconds: 23
resources:
requests:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
startupProbe:
failureThreshold: 7
httpGet:
path: /healthz
port: http
scheme: HTTPS
initialDelaySeconds: 2
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3
volumeMounts:
- mountPath: /etc/coder/ai-gateway-auth
name: ai-gateway-auth
readOnly: true
- mountPath: /etc/coder/ai-gateway-listener
name: ai-gateway-listener
readOnly: true
- mountPath: /etc/coder/coder-client-ca
name: coder-client-ca
readOnly: true
- mountPath: /etc/coder/coder-client-tls
name: coder-client-tls
readOnly: true
restartPolicy: Always
serviceAccountName: workload-identity
terminationGracePeriodSeconds: 330
volumes:
- name: ai-gateway-auth
secret:
items:
- key: token
path: key
secretName: ai-gateway-key
- name: ai-gateway-listener
secret:
items:
- key: cert.pem
path: tls.crt
- key: key.pem
path: tls.key
secretName: gateway-tls
- name: coder-client-ca
secret:
items:
- key: root.pem
path: ca.crt
secretName: coder-ca
- name: coder-client-tls
secret:
items:
- key: client.crt
path: tls.crt
- key: client.key
path: tls.key
secretName: coder-client
+133
View File
@@ -0,0 +1,133 @@
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: v1
automountServiceAccountToken: false
kind: ServiceAccount
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: default
---
# Source: coder-ai-gateway/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder-ai-gateway
namespace: default
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
sessionAffinity: None
selector:
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/name: coder-ai-gateway
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
app.kubernetes.io/component: ai-gateway
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
spec:
automountServiceAccountToken: false
containers:
- args:
- ai-gateway
- start
command:
- /opt/coder
env:
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 0.0.0.0:4001
- name: CODER_PROMETHEUS_ENABLE
value: "true"
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_URL
value: https://coder.example.com
- name: CODER_AI_GATEWAY_KEY
valueFrom:
secretKeyRef:
key: key
name: ai-gateway-key
image: ghcr.io/coder/coder:v2.36.0
imagePullPolicy: IfNotPresent
lifecycle: {}
name: coder
ports:
- containerPort: 4001
name: http
protocol: TCP
- containerPort: 2112
name: metrics
protocol: TCP
readinessProbe:
httpGet:
path: /readyz
port: http
scheme: HTTP
initialDelaySeconds: 0
resources:
requests:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts: []
restartPolicy: Always
serviceAccountName: coder-ai-gateway
terminationGracePeriodSeconds: 330
volumes: []
+13
View File
@@ -0,0 +1,13 @@
coder:
replicaCount: 3
image:
tag: v2.36.0
env:
- name: CODER_URL
value: https://coder.example.com
- name: CODER_AI_GATEWAY_KEY
valueFrom:
secretKeyRef:
name: ai-gateway-key
key: key
aigateway: {}
@@ -0,0 +1,8 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: ai-gateway-key
httproute:
enable: true
@@ -0,0 +1,10 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: ai-gateway-key
service:
enable: false
httproute:
enable: true
@@ -0,0 +1,8 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: key
ingress:
enable: true
@@ -0,0 +1,11 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: key
service:
enable: false
ingress:
enable: true
host: ai.example.com
@@ -0,0 +1,7 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: ai-gateway-key
key: ""
@@ -0,0 +1,8 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: ai-gateway-key
service:
nodePort: 30080
@@ -0,0 +1,10 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: ai-gateway-key
coderTLS:
caSecret:
name: coder-ca
key: ""
@@ -0,0 +1,10 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: key
coderTLS:
clientSecret:
name: client
certKey: ""
@@ -0,0 +1,9 @@
coder:
image:
tag: v2.36.0
aigateway:
keySecret:
name: key
listenerTLS:
name: listener
keyKey: ""
@@ -0,0 +1,180 @@
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: v1
automountServiceAccountToken: false
kind: ServiceAccount
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: default
---
# Source: coder-ai-gateway/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder-ai-gateway
namespace: default
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: ClusterIP
sessionAffinity: None
selector:
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/name: coder-ai-gateway
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
app.kubernetes.io/component: ai-gateway
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
spec:
automountServiceAccountToken: false
containers:
- args:
- ai-gateway
- start
command:
- /opt/coder
env:
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 0.0.0.0:4001
- name: CODER_AI_GATEWAY_KEY_FILE
value: /etc/coder/ai-gateway-auth/key
- name: CODER_PROMETHEUS_ENABLE
value: "true"
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_AI_GATEWAY_TLS_CERT_FILE
value: /etc/coder/ai-gateway-listener/tls.crt
- name: CODER_AI_GATEWAY_TLS_KEY_FILE
value: /etc/coder/ai-gateway-listener/tls.key
- name: CODER_URL
value: http://coder.default.svc.cluster.local:80
image: ghcr.io/coder/coder:v2.36.0
imagePullPolicy: IfNotPresent
lifecycle: {}
name: coder
ports:
- containerPort: 4001
name: http
protocol: TCP
- containerPort: 2112
name: metrics
protocol: TCP
readinessProbe:
httpGet:
path: /readyz
port: http
scheme: HTTPS
initialDelaySeconds: 0
resources:
requests:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /etc/coder/ai-gateway-auth
name: ai-gateway-auth
readOnly: true
- mountPath: /etc/coder/ai-gateway-listener
name: ai-gateway-listener
readOnly: true
restartPolicy: Always
serviceAccountName: coder-ai-gateway
terminationGracePeriodSeconds: 330
volumes:
- name: ai-gateway-auth
secret:
items:
- key: key
path: key
secretName: ai-gateway-key
- name: ai-gateway-listener
secret:
items:
- key: tls.crt
path: tls.crt
- key: tls.key
path: tls.key
secretName: listener-tls
---
# Source: coder-ai-gateway/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: coder-ai-gateway
namespace: default
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
rules:
- host: "ai.example.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: coder-ai-gateway
port:
number: 80
@@ -0,0 +1,14 @@
coder:
image:
tag: v2.36.0
env:
- name: CODER_URL
value: http://coder.default.svc.cluster.local:80
aigateway:
keySecret:
name: ai-gateway-key
listenerTLS:
name: listener-tls
ingress:
enable: true
host: ai.example.com
+33
View File
@@ -0,0 +1,33 @@
coder:
image:
tag: v2.36.0
env:
- name: CODER_URL
value: http://coder.ai-gateway-test.svc.cluster.local:80
aigateway:
keySecret:
name: ai-gateway-key
service:
type: LoadBalancer
port: 443
annotations:
service.example.com/internal: "true"
loadBalancerClass: example.com/lb
ingress:
enable: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
host: ai.example.com
tls:
enable: true
secretName: ai-example-tls
httproute:
enable: true
parentRefs:
- name: shared-gateway
namespace: gateway-system
hostnames:
- ai.example.com
annotations:
example.com/route: ai
@@ -0,0 +1,206 @@
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: v1
automountServiceAccountToken: false
kind: ServiceAccount
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: ai-gateway-test
---
# Source: coder-ai-gateway/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder-ai-gateway
namespace: ai-gateway-test
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
annotations:
service.example.com/internal: "true"
spec:
type: LoadBalancer
sessionAffinity: None
externalTrafficPolicy: Cluster
loadBalancerClass: example.com/lb
selector:
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
ports:
- name: http
port: 443
targetPort: http
protocol: TCP
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: ai-gateway-test
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/name: coder-ai-gateway
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
app.kubernetes.io/component: ai-gateway
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
spec:
automountServiceAccountToken: false
containers:
- args:
- ai-gateway
- start
command:
- /opt/coder
env:
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 0.0.0.0:4001
- name: CODER_AI_GATEWAY_KEY_FILE
value: /etc/coder/ai-gateway-auth/key
- name: CODER_PROMETHEUS_ENABLE
value: "true"
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_URL
value: http://coder.ai-gateway-test.svc.cluster.local:80
image: ghcr.io/coder/coder:v2.36.0
imagePullPolicy: IfNotPresent
lifecycle: {}
name: coder
ports:
- containerPort: 4001
name: http
protocol: TCP
- containerPort: 2112
name: metrics
protocol: TCP
readinessProbe:
httpGet:
path: /readyz
port: http
scheme: HTTP
initialDelaySeconds: 0
resources:
requests:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /etc/coder/ai-gateway-auth
name: ai-gateway-auth
readOnly: true
restartPolicy: Always
serviceAccountName: coder-ai-gateway
terminationGracePeriodSeconds: 330
volumes:
- name: ai-gateway-auth
secret:
items:
- key: key
path: key
secretName: ai-gateway-key
---
# Source: coder-ai-gateway/templates/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: coder-ai-gateway
namespace: ai-gateway-test
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
spec:
ingressClassName: nginx
tls:
- hosts:
- "ai.example.com"
secretName: ai-example-tls
rules:
- host: "ai.example.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: coder-ai-gateway
port:
number: 443
---
# Source: coder-ai-gateway/templates/httproute.yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: coder-ai-gateway
namespace: ai-gateway-test
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
annotations:
example.com/route: ai
spec:
parentRefs:
- name: shared-gateway
namespace: gateway-system
hostnames:
- ai.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: coder-ai-gateway
port: 443
+141
View File
@@ -0,0 +1,141 @@
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: v1
automountServiceAccountToken: false
kind: ServiceAccount
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: default
---
# Source: coder-ai-gateway/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: coder-ai-gateway
namespace: default
labels:
helm.sh/chart: coder-ai-gateway-0.1.0
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: "0.1.0"
app.kubernetes.io/managed-by: Helm
spec:
type: NodePort
sessionAffinity: ClientIP
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/instance: ai-gateway
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
nodePort: 30401
---
# Source: coder-ai-gateway/templates/aigateway.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
name: coder-ai-gateway
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/name: coder-ai-gateway
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
annotations:
app.kubernetes.io/component: ai-gateway
labels:
app.kubernetes.io/instance: ai-gateway
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: coder-ai-gateway
app.kubernetes.io/part-of: coder-ai-gateway
app.kubernetes.io/version: 0.1.0
helm.sh/chart: coder-ai-gateway-0.1.0
spec:
automountServiceAccountToken: false
containers:
- args:
- ai-gateway
- start
command:
- /opt/coder
env:
- name: CODER_AI_GATEWAY_HTTP_ADDRESS
value: 0.0.0.0:4001
- name: CODER_AI_GATEWAY_KEY_FILE
value: /etc/coder/ai-gateway-auth/key
- name: CODER_PROMETHEUS_ENABLE
value: "true"
- name: CODER_PROMETHEUS_ADDRESS
value: 0.0.0.0:2112
- name: CODER_URL
value: http://coder.default.svc.cluster.local:80
image: ghcr.io/coder/coder:v2.36.0
imagePullPolicy: IfNotPresent
lifecycle: {}
name: coder
ports:
- containerPort: 4001
name: http
protocol: TCP
- containerPort: 2112
name: metrics
protocol: TCP
readinessProbe:
httpGet:
path: /readyz
port: http
scheme: HTTP
initialDelaySeconds: 0
resources:
requests:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsNonRoot: true
runAsUser: 1000
seccompProfile:
type: RuntimeDefault
volumeMounts:
- mountPath: /etc/coder/ai-gateway-auth
name: ai-gateway-auth
readOnly: true
restartPolicy: Always
serviceAccountName: coder-ai-gateway
terminationGracePeriodSeconds: 330
volumes:
- name: ai-gateway-auth
secret:
items:
- key: key
path: key
secretName: ai-gateway-key
+14
View File
@@ -0,0 +1,14 @@
coder:
image:
tag: v2.36.0
env:
- name: CODER_URL
value: http://coder.default.svc.cluster.local:80
aigateway:
keySecret:
name: ai-gateway-key
service:
type: NodePort
nodePort: 30401
externalTrafficPolicy: Local
sessionAffinity: ClientIP
+143
View File
@@ -0,0 +1,143 @@
{
"$schema": "https://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"properties": {
"libcoder": {"type": "object"},
"nameOverride": {"type": "string"},
"coder": {
"type": "object", "additionalProperties": false,
"required": ["env", "envFrom", "image", "initContainers", "annotations", "labels", "podAnnotations", "podLabels", "serviceAccount", "securityContext", "podSecurityContext", "volumes", "volumeMounts", "replicaCount", "lifecycle", "resources", "startupProbe", "livenessProbe", "readinessProbe", "certs", "affinity", "tolerations", "nodeSelector", "topologySpreadConstraints", "priorityClassName", "command"],
"properties": {
"env": {"type": "array", "items": {"type": "object", "required": ["name"]}},
"envFrom": {"type": "array", "items": {"type": "object"}},
"image": {
"type": "object", "additionalProperties": false,
"required": ["repo", "tag", "pullPolicy", "pullSecrets"],
"properties": {
"repo": {"type": "string", "minLength": 1},
"tag": {"type": "string"},
"pullPolicy": {"enum": ["Always", "IfNotPresent", "Never"]},
"pullSecrets": {"type": "array", "items": {"type": "object"}}
}
},
"initContainers": {"type": "array"},
"annotations": {"type": "object"},
"labels": {"type": "object"},
"podAnnotations": {"type": "object"},
"podLabels": {"type": "object"},
"serviceAccount": {
"type": "object", "additionalProperties": false,
"required": ["annotations", "name", "disableCreate"],
"properties": {
"annotations": {"type": "object"},
"name": {"type": "string", "minLength": 1},
"disableCreate": {"type": "boolean"},
"labels": {"type": "object"}
}
},
"securityContext": {"type": "object"},
"podSecurityContext": {"type": "object"},
"volumes": {"type": "array"},
"volumeMounts": {"type": "array"},
"replicaCount": {"type": "integer", "minimum": 0},
"lifecycle": {"type": "object"},
"resources": {"type": "object"},
"startupProbe": {"$ref": "#/definitions/healthProbe"},
"livenessProbe": {"$ref": "#/definitions/healthProbe"},
"readinessProbe": {"$ref": "#/definitions/probe"},
"certs": {
"type": "object", "additionalProperties": false, "required": ["secrets"],
"properties": {
"secrets": {"type": "array", "items": {"type": "object", "required": ["name", "key"]}}
}
},
"affinity": {"type": "object"},
"tolerations": {"type": "array"},
"nodeSelector": {"type": "object"},
"topologySpreadConstraints": {"type": "array"},
"priorityClassName": {"type": "string"},
"command": {"type": "array", "minItems": 1, "items": {"type": "string"}}
}
},
"aigateway": {
"type": "object", "additionalProperties": false,
"required": ["keySecret", "coderTLS", "listenerTLS", "terminationGracePeriodSeconds"],
"properties": {
"keySecret": {
"type": "object", "additionalProperties": false,
"required": ["name", "key"],
"properties": {"name": {"type": "string"}, "key": {"type": "string"}}
},
"coderTLS": {
"type": "object", "additionalProperties": false,
"required": ["caSecret", "clientSecret"],
"properties": {
"caSecret": {
"type": "object", "additionalProperties": false, "required": ["name", "key"],
"properties": {"name": {"type": "string"}, "key": {"type": "string"}}
},
"clientSecret": {
"type": "object", "additionalProperties": false, "required": ["name", "certKey", "keyKey"],
"properties": {"name": {"type": "string"}, "certKey": {"type": "string"}, "keyKey": {"type": "string"}}
}
}
},
"listenerTLS": {
"type": "object", "additionalProperties": false, "required": ["name", "certKey", "keyKey"],
"properties": {"name": {"type": "string"}, "certKey": {"type": "string"}, "keyKey": {"type": "string"}}
},
"terminationGracePeriodSeconds": {"type": "integer", "minimum": 0}
}
},
"service": {
"type": "object", "additionalProperties": false,
"required": ["enable", "type", "port", "annotations", "sessionAffinity", "externalTrafficPolicy", "loadBalancerClass", "loadBalancerIP", "nodePort"],
"properties": {
"enable": {"type": "boolean"},
"type": {"enum": ["ClusterIP", "NodePort", "LoadBalancer"]},
"port": {"type": "integer", "minimum": 1, "maximum": 65535},
"annotations": {"type": "object"},
"sessionAffinity": {"enum": ["None", "ClientIP"]},
"externalTrafficPolicy": {"enum": ["Cluster", "Local"]},
"loadBalancerClass": {"type": "string"},
"loadBalancerIP": {"type": "string"},
"nodePort": {"type": ["integer", "null"], "minimum": 30000, "maximum": 32767}
}
},
"ingress": {
"type": "object", "additionalProperties": false,
"required": ["enable", "className", "annotations", "host", "path", "pathType", "tls"],
"properties": {
"enable": {"type": "boolean"}, "className": {"type": "string"}, "annotations": {"type": "object"},
"host": {"type": "string"}, "path": {"type": "string"}, "pathType": {"enum": ["Exact", "Prefix", "ImplementationSpecific"]},
"tls": {"type": "object", "additionalProperties": false, "required": ["enable", "secretName"], "properties": {"enable": {"type": "boolean"}, "secretName": {"type": "string"}}}
}
},
"httproute": {
"type": "object", "additionalProperties": false, "required": ["enable", "parentRefs", "hostnames", "annotations"],
"properties": {"enable": {"type": "boolean"}, "parentRefs": {"type": "array"}, "hostnames": {"type": "array", "items": {"type": "string"}}, "annotations": {"type": "object"}}
},
"extraTemplates": {"type": "array", "items": {"oneOf": [{"type": "string"}, {"type": "object"}]}}
},
"required": ["coder", "aigateway", "service", "ingress", "httproute", "extraTemplates"],
"definitions": {
"healthProbe": {
"allOf": [
{"$ref": "#/definitions/probe"},
{"properties": {"successThreshold": {"type": "integer", "const": 1}}}
]
},
"probe": {
"type": "object", "additionalProperties": false, "required": ["enabled", "initialDelaySeconds"],
"properties": {
"enabled": {"type": "boolean"},
"initialDelaySeconds": {"type": "integer", "minimum": 0},
"periodSeconds": {"type": "integer", "minimum": 1},
"timeoutSeconds": {"type": "integer", "minimum": 1},
"successThreshold": {"type": "integer", "minimum": 1},
"failureThreshold": {"type": "integer", "minimum": 1}
}
}
}
}
+228
View File
@@ -0,0 +1,228 @@
# coder -- Common workload configuration options.
coder:
# coder.env -- Additional environment variables for the AI Gateway
# container. These are rendered after chart-configured variables, including
# when a variable name is duplicated.
env: []
# - name: "CODER_VERBOSE"
# value: "true"
# coder.envFrom -- Additional envFrom sources for the AI Gateway container.
envFrom: []
# coder.image -- The image to use for the AI Gateway.
image:
# coder.image.repo -- The repository of the image.
repo: "ghcr.io/coder/coder"
# coder.image.tag -- The tag of the image, defaults to {{.Chart.AppVersion}}
# if not set. If you're using the chart directly from git, the default
# app version will not work and you'll need to set this value. The helm
# chart helpfully fails quickly in this case.
tag: ""
# coder.image.pullPolicy -- The pull policy to use for the image. See:
# https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy
pullPolicy: IfNotPresent
# coder.image.pullSecrets -- The secrets used for pulling the Coder image
# from a private registry.
pullSecrets: []
# - name: "pull-secret"
# coder.initContainers -- Init containers for the deployment. See:
# https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
initContainers:
[]
# - name: init-container
# image: busybox:1.28
# command: ['sh', '-c', "sleep 2"]
# coder.annotations -- The Deployment annotations. See:
# https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
annotations: {}
# coder.labels -- The Deployment labels. See:
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
labels: {}
# coder.podAnnotations -- The pod annotations. See:
# https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/
# The annotation `app.kubernetes.io/component: ai-gateway` is added
# automatically.
podAnnotations: {}
# coder.podLabels -- The pod labels. See:
# https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
podLabels: {}
# coder.serviceAccount -- Configuration for the automatically created
# service account.
serviceAccount:
# coder.serviceAccount.annotations -- The service account annotations.
annotations: {}
# coder.serviceAccount.name -- The service account name.
name: coder-ai-gateway
# coder.serviceAccount.disableCreate -- Whether to create the service
# account or use an existing service account.
disableCreate: false
# coder.securityContext -- Fields related to the container's security
# context (as opposed to the pod). Some fields are also present in the pod
# security context, in which case these values will take precedence.
securityContext:
# coder.securityContext.runAsNonRoot -- Requires that the container
# runs as an unprivileged user.
runAsNonRoot: true
# coder.securityContext.runAsUser -- Sets the user id of the container.
runAsUser: 1000
# coder.securityContext.runAsGroup -- Sets the group id of the container.
runAsGroup: 1000
# coder.securityContext.seccompProfile -- Sets the seccomp profile for
# the container.
seccompProfile:
type: RuntimeDefault
# coder.securityContext.allowPrivilegeEscalation -- Controls whether
# the container can gain additional privileges.
allowPrivilegeEscalation: false
# coder.podSecurityContext -- Pod-level security context settings. See:
# https://kubernetes.io/docs/tasks/configure-pod-container/security-context/
podSecurityContext: {}
# coder.volumes -- A list of extra volumes to add to the AI Gateway pod.
volumes: []
# - name: "my-volume"
# emptyDir: {}
# coder.volumeMounts -- A list of extra volume mounts to add to the AI
# Gateway container.
volumeMounts: []
# - name: "my-volume"
# mountPath: "/mnt/my-volume"
# coder.replicaCount -- The number of AI Gateway pods.
replicaCount: 1
# coder.lifecycle -- Container lifecycle handlers, allowing for lifecycle
# events such as postStart and preStop. See:
# https://kubernetes.io/docs/tasks/configure-pod-container/attach-handler-lifecycle-event/
lifecycle:
{}
# postStart:
# exec:
# command: ["/bin/sh", "-c", "echo postStart"]
# coder.resources -- The resources to request for the AI Gateway container.
resources:
requests:
cpu: "1"
memory: 1Gi
# coder.startupProbe -- Startup probe configuration for the AI Gateway.
# Enable this with a failure threshold long enough for initial provider load
# before enabling the liveness probe.
startupProbe:
enabled: false
initialDelaySeconds: 0
# coder.livenessProbe -- Liveness probe configuration for the AI Gateway.
# Without this probe, Kubernetes does not restart a running but unresponsive
# Gateway. Enable and tune the startup probe before enabling this probe.
livenessProbe:
enabled: false
initialDelaySeconds: 0
# coder.readinessProbe -- Readiness probe configuration for the AI Gateway.
readinessProbe:
enabled: true
initialDelaySeconds: 0
# coder.certs -- CA bundles to mount inside the AI Gateway pod.
certs:
# coder.certs.secrets -- A list of CA bundle secrets to mount into the
# pod. The secrets should exist in the same namespace as the Helm
# deployment. The given key in each secret is mounted at
# `/etc/ssl/certs/{secret_name}.crt`.
secrets:
[]
# - name: "my-ca-bundle"
# key: "ca-bundle.crt"
# coder.affinity -- Allows specifying an affinity rule for the deployment.
affinity: {}
# coder.tolerations -- Tolerations for tainted nodes. See:
# https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/
tolerations: []
# coder.nodeSelector -- Node labels for constraining pods to nodes. See:
# https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#nodeselector
nodeSelector: {}
# coder.topologySpreadConstraints -- Topology spread constraints for the
# pods. See:
# https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/
topologySpreadConstraints: []
# coder.priorityClassName -- The priority class name for the pods.
priorityClassName: ""
# coder.command -- The command to use when running the container. Used
# for customizing the location of the `coder` binary in your image.
command:
- /opt/coder
# Authentication and listener settings for the standalone gateway.
aigateway:
# Optional existing Secret containing an AI Gateway key. When unset, provide
# CODER_AI_GATEWAY_KEY or CODER_AI_GATEWAY_KEY_FILE through coder.env.
keySecret:
name: ""
key: key
coderTLS:
caSecret:
name: ""
key: ca.crt
clientSecret:
name: ""
certKey: tls.crt
keyKey: tls.key
# The certificate must cover every hostname clients use.
listenerTLS:
name: ""
certKey: tls.crt
keyKey: tls.key
# This must exceed the application's 300-second shutdown timeout.
terminationGracePeriodSeconds: 330
# Stable data-plane Service fronting the container's port 4001 listener.
service:
enable: true
type: ClusterIP
port: 80
annotations: {}
sessionAffinity: None
externalTrafficPolicy: Cluster
loadBalancerClass: ""
loadBalancerIP: ""
nodePort: null
# Optional networking.k8s.io/v1 Ingress.
ingress:
enable: false
className: ""
annotations: {}
host: ""
path: /
pathType: Prefix
tls:
enable: false
secretName: ""
# Optional Gateway API HTTPRoute. Requires the v1 HTTPRoute CRD.
httproute:
enable: false
parentRefs: []
hostnames: []
annotations: {}
# Additional templated Kubernetes manifests.
extraTemplates: []