From 009002494a4297f3866017e51076da82fb93bc06 Mon Sep 17 00:00:00 2001 From: Yuval Kohavi Date: Thu, 4 Jun 2026 10:46:30 -0400 Subject: [PATCH 1/5] release workflow (#6) * wip - publish release * release yaml --- .github/workflows/release.yaml | 102 +++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 000000000..e87842781 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,102 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: release + +on: + workflow_dispatch: + inputs: + tag: + description: 'Image tag (e.g. v1.2.3-rc1). Leave blank to auto-generate from branch+SHA.' + required: false + create_release: + description: 'Create a GitHub release' + type: boolean + default: false + +permissions: + contents: write + packages: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Validate and resolve tag + id: tag + run: | + TAG="${{ inputs.tag }}" + if [[ -z "${TAG}" ]]; then + BRANCH="${GITHUB_REF_NAME//\//-}" + SHA="$(git rev-parse --short HEAD)" + TAG="${BRANCH}-${SHA}" + fi + if [[ "${{ inputs.create_release }}" == "true" ]]; then + if [[ ! "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?$ ]]; then + echo "::error::Tag '${TAG}' must match vMAJOR.MINOR.PATCH[-prerelease] when creating a release (e.g. v1.2.3 or v1.2.3-rc1)" + exit 1 + fi + fi + echo "value=${TAG}" >> "$GITHUB_OUTPUT" + if [[ "${{ inputs.create_release }}" == "true" ]]; then + echo "tags=${TAG},latest" >> "$GITHUB_OUTPUT" + else + echo "tags=${TAG}" >> "$GITHUB_OUTPUT" + fi + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Install ko + uses: ko-build/setup-ko@v0.7 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU (multi-arch) + uses: docker/setup-qemu-action@v3 + + - name: Build and push images + env: + # ghcr.io// — resolves correctly in forks + IMAGE_REPOSITORY: ghcr.io/${{ github.repository }} + IMAGE_TAGS: ${{ steps.tag.outputs.tags }} + run: | + set -o errexit -o nounset -o pipefail + + for component in ateapi atelet ateom-gvisor podcertcontroller atenet; do + KO_DOCKER_REPO="${IMAGE_REPOSITORY}/${component}" \ + ./hack/run-tool.sh ko build \ + --tags "${IMAGE_TAGS}" \ + --platform linux/amd64,linux/arm64 \ + --bare \ + "./cmd/${component}" + done + + - name: Create GitHub Release + if: inputs.create_release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.value }} + generate_release_notes: true From 200881896fd5db56de412628009bf44ea52409d2 Mon Sep 17 00:00:00 2001 From: Eitan Yarmush Date: Wed, 10 Jun 2026 10:18:17 -0400 Subject: [PATCH 2/5] router: add pluggable networking providers (#10) * router: add pluggable networking providers * make websockets work in agw, make agw default in install script Signed-off-by: Peter Jausovec --------- Signed-off-by: Peter Jausovec Co-authored-by: John Howard Co-authored-by: Peter Jausovec --- .../internal/app/router/agentgateway.go | 151 ++++++++++ cmd/atenet/internal/app/router/controller.go | 25 +- cmd/atenet/internal/app/router/dashboard.html | 16 +- cmd/atenet/internal/app/router/envoy.go | 118 ++++++++ cmd/atenet/internal/app/router/envoyrunner.go | 258 ------------------ cmd/atenet/internal/app/router/extproc.go | 53 +++- cmd/atenet/internal/app/router/health.go | 74 ++--- cmd/atenet/internal/app/router/provider.go | 102 +++++++ cmd/atenet/internal/app/router/proxyrunner.go | 183 +++++++++++++ cmd/atenet/internal/app/router/router.go | 82 +++--- cmd/atenet/internal/app/router/status.go | 2 + hack/install-ate.sh | 72 ++++- .../atenet-router-agentgateway.yaml | 197 +++++++++++++ manifests/ate-install/atenet-router.yaml | 4 +- .../base-agentgateway/kustomization.yaml | 25 ++ manifests/ate-install/base/kustomization.yaml | 25 ++ .../kind-agentgateway/kustomization.yaml | 43 +++ 17 files changed, 1064 insertions(+), 366 deletions(-) create mode 100644 cmd/atenet/internal/app/router/agentgateway.go create mode 100644 cmd/atenet/internal/app/router/envoy.go delete mode 100644 cmd/atenet/internal/app/router/envoyrunner.go create mode 100644 cmd/atenet/internal/app/router/provider.go create mode 100644 cmd/atenet/internal/app/router/proxyrunner.go create mode 100644 manifests/ate-install/atenet-router-agentgateway.yaml create mode 100644 manifests/ate-install/base-agentgateway/kustomization.yaml create mode 100644 manifests/ate-install/base/kustomization.yaml create mode 100644 manifests/ate-install/kind-agentgateway/kustomization.yaml diff --git a/cmd/atenet/internal/app/router/agentgateway.go b/cmd/atenet/internal/app/router/agentgateway.go new file mode 100644 index 000000000..af7fabe5a --- /dev/null +++ b/cmd/atenet/internal/app/router/agentgateway.go @@ -0,0 +1,151 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "context" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +type agentgatewayProvider struct { + cfg RouterConfig +} + +func (p agentgatewayProvider) Name() string { + return NetworkingModeAgentgateway +} + +func (p agentgatewayProvider) RequiresXDS() bool { + return false +} + +func (p agentgatewayProvider) ConfigMapData() map[string]string { + return map[string]string{"config.yaml": p.localConfig()} +} + +func (p agentgatewayProvider) Container() corev1.Container { + ports := []corev1.ContainerPort{ + {Name: "http", ContainerPort: int32(p.cfg.HttpPort)}, + {Name: "readiness", ContainerPort: 15021}, + {Name: "metrics", ContainerPort: 15020}, + } + if p.cfg.HttpsPort > 0 && tlsCertPath(p.cfg) != "" { + ports = append(ports, corev1.ContainerPort{Name: "https", ContainerPort: int32(p.cfg.HttpsPort)}) + } + + return corev1.Container{ + Name: "agentgateway", + Image: p.cfg.AgentgatewayImage, + Args: []string{"-f", "/etc/agentgateway/config.yaml"}, + Ports: ports, + VolumeMounts: []corev1.VolumeMount{ + {Name: "proxy-config", MountPath: "/etc/agentgateway"}, + }, + ReadinessProbe: &corev1.Probe{ + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/healthz/ready", + Port: intstr.FromInt32(15021), + }, + }, + PeriodSeconds: 10, + }, + } +} + +func (p agentgatewayProvider) ServicePorts() []corev1.ServicePort { + ports := []corev1.ServicePort{ + {Name: "http", Port: int32(p.cfg.HttpPort), TargetPort: intstr.FromString("http")}, + } + if p.cfg.HttpsPort > 0 && tlsCertPath(p.cfg) != "" { + ports = append(ports, corev1.ServicePort{Name: "https", Port: int32(p.cfg.HttpsPort), TargetPort: intstr.FromString("https")}) + } + return ports +} + +func (p agentgatewayProvider) CheckReady(ctx context.Context) (bool, string) { + return checkHTTPReady(ctx, "http://127.0.0.1:15021/healthz/ready", "") +} + +func (p agentgatewayProvider) localConfig() string { + httpRoute := p.routeBlock("substrate-http") + config := fmt.Sprintf(`# yaml-language-server: $schema=https://agentgateway.dev/schema/config +config: + adminAddr: "127.0.0.1:15000" + readinessAddr: "0.0.0.0:15021" + statsAddr: "0.0.0.0:15020" +binds: +- port: %d + listeners: + - name: http + protocol: HTTP + routes: +%s`, p.cfg.HttpPort, indent(httpRoute, 4)) + + if p.cfg.HttpsPort > 0 && tlsCertPath(p.cfg) != "" { + cert := tlsCertPath(p.cfg) + key := tlsKeyPath(p.cfg) + config += fmt.Sprintf(`- port: %d + listeners: + - name: https + protocol: HTTPS + tls: + cert: %q + key: %q + routes: +%s`, p.cfg.HttpsPort, cert, key, indent(p.routeBlock("substrate-https"), 4)) + } + + return config +} + +func (p agentgatewayProvider) routeBlock(name string) string { + extprocHost := fmt.Sprintf("%s:%d", p.cfg.ExtprocAddr, p.cfg.ExtprocPort) + // processingOptions limit ext_proc to request headers only; agentgateway defaults + // break WebSocket upgrades because this server only handles headers. + return fmt.Sprintf(`- name: %s + matches: + - path: + pathPrefix: / + policies: + extProc: + host: %q + failureMode: failClosed + processingOptions: + requestBodyMode: none + responseBodyMode: none + requestHeaderMode: send + responseHeaderMode: skip + requestTrailerMode: skip + responseTrailerMode: skip + backends: + - dynamic: {} +`, name, extprocHost) +} + +func indent(s string, spaces int) string { + prefix := strings.Repeat(" ", spaces) + lines := strings.Split(s, "\n") + for i, line := range lines { + if line != "" { + lines[i] = prefix + line + } + } + return strings.Join(lines, "\n") +} diff --git a/cmd/atenet/internal/app/router/controller.go b/cmd/atenet/internal/app/router/controller.go index 1b0a1ed18..d9a31d7eb 100644 --- a/cmd/atenet/internal/app/router/controller.go +++ b/cmd/atenet/internal/app/router/controller.go @@ -31,9 +31,10 @@ type Controller struct { cfg RouterConfig xdsSrv *XdsServer extprocSrv *ExtProcServer + provider proxyProvider atStore atStore - envoyRunner *envoyrunner + proxyRunner *proxyrunner } func NewController( @@ -42,8 +43,11 @@ func NewController( cfg RouterConfig, xdsSrv *XdsServer, extprocSrv *ExtProcServer, + provider proxyProvider, ) *Controller { - xdsSrv.SetConfig(cfg.HttpPort, cfg.ExtprocPort, cfg.ExtprocAddr) + if xdsSrv != nil { + xdsSrv.SetConfig(cfg.HttpPort, cfg.ExtprocPort, cfg.ExtprocAddr) + } var store atStore if cfg.TemplatesFile != "" { @@ -58,9 +62,10 @@ func NewController( cfg: cfg, xdsSrv: xdsSrv, extprocSrv: extprocSrv, + provider: provider, atStore: store, - envoyRunner: newEnvoyRunner(k8sClient, cfg), + proxyRunner: newProxyRunner(k8sClient, cfg, provider), } } @@ -92,16 +97,18 @@ func (c *Controller) reconcile(ctx context.Context) error { return err } - if err := c.xdsSrv.UpdateSnapshot(); err != nil { - slog.ErrorContext(ctx, "xDS Configuration generation problem", slog.String("err", err.Error())) - return err + if c.provider.RequiresXDS() { + if err := c.xdsSrv.UpdateSnapshot(); err != nil { + slog.ErrorContext(ctx, "xDS Configuration generation problem", slog.String("err", err.Error())) + return err + } } if !c.cfg.Standalone && c.cfg.TemplatesFile == "" { - // Reconcile Envoy router Deployment and Kubernetes cluster entities - err := c.envoyRunner.reconcile(ctx) + // Reconcile router proxy Deployment and Kubernetes cluster entities. + err := c.proxyRunner.reconcile(ctx) if err != nil { - slog.ErrorContext(ctx, "Error during Envoy router reconciliation", slog.String("err", err.Error())) + slog.ErrorContext(ctx, "Error during router proxy reconciliation", slog.String("err", err.Error())) return err } } diff --git a/cmd/atenet/internal/app/router/dashboard.html b/cmd/atenet/internal/app/router/dashboard.html index 41f3d93af..025ffa9dc 100644 --- a/cmd/atenet/internal/app/router/dashboard.html +++ b/cmd/atenet/internal/app/router/dashboard.html @@ -252,12 +252,16 @@

atenet Router Status

Namespace Context {{ .Namespace }} +
Component Network Allocation