feat: load service adapters from ConfigMap, remove k8s API dependency
Adapters defined in config.yaml alongside routes and models. Parsed by existing config loader, populated into registry at startup. Removed: client-go deps, REST loader, informer, nginx proxy, CiliumNetworkPolicy, apis/gateway/v1/ (duplicate types). Kept: merged CI pipeline, imagePullPolicy Always, CA certs in Dockerfile.
This commit is contained in:
@@ -1,58 +0,0 @@
|
|||||||
# Build and push on main branch — triggered automatically when commits land on main.
|
|
||||||
# Tag is commit short SHA: unique, immutable, maps to exactly one commit.
|
|
||||||
# Image: forgejo.riotpiao.com/rock/api-gateway:<commit-sha>
|
|
||||||
#
|
|
||||||
# ArgoCD auto-deploys to api namespace as revisions roll in.
|
|
||||||
name: Build and push
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: forgejo.riotpiao.com
|
|
||||||
IMAGE: forgejo.riotpiao.com/rock/api-gateway
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build and push image
|
|
||||||
runs-on: golang
|
|
||||||
container:
|
|
||||||
image: docker:27-cli
|
|
||||||
volumes:
|
|
||||||
- /docker-certs/client:/docker-certs/client:ro
|
|
||||||
env:
|
|
||||||
DOCKER_HOST: tcp://localhost:2376
|
|
||||||
DOCKER_TLS_VERIFY: "1"
|
|
||||||
DOCKER_CERT_PATH: /docker-certs/client
|
|
||||||
steps:
|
|
||||||
- name: install node (required by JS-based actions)
|
|
||||||
run: apk add --no-cache nodejs git
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get short SHA
|
|
||||||
id: sha
|
|
||||||
run: |
|
|
||||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
||||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Registry login
|
|
||||||
run: |
|
|
||||||
echo "${REGISTRY_PAT}" | docker login "${REGISTRY}" \
|
|
||||||
--username rock --password-stdin
|
|
||||||
env:
|
|
||||||
REGISTRY_PAT: ${{ secrets.REGISTRY_PAT }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
--build-arg "VERSION=${{ steps.sha.outputs.short_sha }}" \
|
|
||||||
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
|
||||||
-t "${IMAGE}:latest" \
|
|
||||||
.
|
|
||||||
|
|
||||||
- name: Push
|
|
||||||
run: |
|
|
||||||
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
|
||||||
docker push "${IMAGE}:latest"
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
# Forgejo Actions build — push image on main only.
|
|
||||||
# Tag is commit short SHA: unique, immutable, maps to exactly one commit.
|
|
||||||
# No write-back, no git push — ArgoCD Image Updater pulls new builds autonomously.
|
|
||||||
# Enabled by Stage 1 (B, C1).
|
|
||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: forgejo.riotpiao.com
|
|
||||||
IMAGE: forgejo.riotpiao.com/rock/api-gateway
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build and push image
|
|
||||||
# golang, not a retired generic "docker" runner -- this repo is Go, and
|
|
||||||
# every runner now carries its own dind sidecar to build/push that
|
|
||||||
# repo's images. `container.image` below overrides the runner's own
|
|
||||||
# default (golang:1.25-bookworm) with docker:27-cli for this job only.
|
|
||||||
runs-on: golang
|
|
||||||
container:
|
|
||||||
image: docker:27-cli
|
|
||||||
# No `options: --network host` here -- act_runner ignores that per-job
|
|
||||||
# override and always decides the job container's network from its own
|
|
||||||
# config.yaml (container.network), which defaults to an isolated
|
|
||||||
# per-job bridge. Confirmed live: with that default, DOCKER_HOST=
|
|
||||||
# tcp://localhost:2376 resolved to the job container itself, not dind,
|
|
||||||
# so every command past `docker login` (which never touches DOCKER_HOST
|
|
||||||
# -- it only talks to the registry) failed with "Cannot connect to the
|
|
||||||
# Docker daemon". host networking is set once, for every job, in the
|
|
||||||
# runner's own Helm chart.
|
|
||||||
#
|
|
||||||
# The mTLS certs dind generates at startup are a separate gap: they
|
|
||||||
# live in an emptyDir mounted into the runner/dind containers, not into
|
|
||||||
# containers a workflow spins up. Job containers get no bind mounts at
|
|
||||||
# all unless the path is in the runner's container.valid_volumes
|
|
||||||
# allowlist (empty by default -- this exact mount was rejected until
|
|
||||||
# the runner's Helm chart added a config.yaml scoping valid_volumes to
|
|
||||||
# exactly this path).
|
|
||||||
volumes:
|
|
||||||
- /docker-certs/client:/docker-certs/client:ro
|
|
||||||
env:
|
|
||||||
DOCKER_HOST: tcp://localhost:2376
|
|
||||||
DOCKER_TLS_VERIFY: "1"
|
|
||||||
DOCKER_CERT_PATH: /docker-certs/client
|
|
||||||
steps:
|
|
||||||
# actions/checkout@v4 is a JS action -- Forgejo Actions execs it with
|
|
||||||
# `node`, which docker:27-cli (Alpine) doesn't ship. Without this the
|
|
||||||
# checkout step fails with "exec: node: executable file not found in
|
|
||||||
# $PATH" before any of the job's own steps run. Verified locally:
|
|
||||||
# `apk add --no-cache nodejs git` in this exact image gets node v22 +
|
|
||||||
# git 2.47, and the checkout action's dist/index.js then actually
|
|
||||||
# executes (confirmed by running it directly) instead of failing on a
|
|
||||||
# missing binary.
|
|
||||||
- name: install node (required by JS-based actions)
|
|
||||||
run: apk add --no-cache nodejs git
|
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get short SHA
|
|
||||||
id: sha
|
|
||||||
run: |
|
|
||||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
||||||
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Registry login
|
|
||||||
run: |
|
|
||||||
echo "${REGISTRY_PAT}" | docker login "${REGISTRY}" \
|
|
||||||
--username rock --password-stdin
|
|
||||||
env:
|
|
||||||
REGISTRY_PAT: ${{ secrets.REGISTRY_PAT }}
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
docker build \
|
|
||||||
--build-arg "VERSION=${{ steps.sha.outputs.short_sha }}" \
|
|
||||||
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
|
||||||
.
|
|
||||||
|
|
||||||
- name: Push
|
|
||||||
run: docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
|
||||||
|
|
||||||
- name: Report digest
|
|
||||||
run: |
|
|
||||||
docker inspect --format='{{index .RepoDigests 0}}' "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
|
||||||
+56
-29
@@ -1,19 +1,5 @@
|
|||||||
# Forgejo Actions CI — verification only (vet, test, build).
|
# Single pipeline: verify → build → push.
|
||||||
# Build and push happens in build.yaml on main push.
|
# One workflow per push, one concurrency group per branch.
|
||||||
#
|
|
||||||
# Path is .gitea/workflows/, not .forgejo/workflows/ or .github/workflows/.
|
|
||||||
# Verified live against this instance (Forgejo 1.27.0, forgejo.riotpiao.com)
|
|
||||||
# on 2026-08-21: a .forgejo/workflows/*.yaml file never creates an action_run
|
|
||||||
# row on push, not once, for any repo -- confirmed both from application logs
|
|
||||||
# (silent, no error) and directly in the action_run table. A .gitea/workflows
|
|
||||||
# file with an identical job spec fires immediately. .github/workflows also
|
|
||||||
# gets scanned (that's how the old, dead ubuntu-latest CI on this repo and on
|
|
||||||
# kmsvc-manage both got action_run rows despite matching no runner) -- so
|
|
||||||
# .forgejo/workflows/ specifically appears unsupported on this instance/version,
|
|
||||||
# not workflow detection being off in general.
|
|
||||||
#
|
|
||||||
# runs-on: golang -- the generic "docker" runner was retired in favor of
|
|
||||||
# per-language runners (golang/node/rust), each with its own dind sidecar.
|
|
||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -22,18 +8,21 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ci-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: forgejo.riotpiao.com
|
||||||
|
IMAGE: forgejo.riotpiao.com/rock/api-gateway
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
name: Test, vet, build
|
name: Vet, test, build
|
||||||
runs-on: golang
|
runs-on: golang
|
||||||
container:
|
container:
|
||||||
image: golang:1.25-bookworm
|
image: golang:1.26-bookworm
|
||||||
steps:
|
steps:
|
||||||
# actions/checkout@v4 is a JS action -- Forgejo Actions execs it with
|
|
||||||
# `node`, which golang:1.25-bookworm doesn't ship. Without this the
|
|
||||||
# checkout step fails with "exec: node: executable file not found in
|
|
||||||
# $PATH" before any of the job's own steps run. Same fix already in use
|
|
||||||
# in kmsvc-manage's ci.yaml; carried over here.
|
|
||||||
- name: install node (required by JS-based actions)
|
- name: install node (required by JS-based actions)
|
||||||
run: apt-get update && apt-get install -y --no-install-recommends nodejs ca-certificates git
|
run: apt-get update && apt-get install -y --no-install-recommends nodejs ca-certificates git
|
||||||
|
|
||||||
@@ -42,15 +31,53 @@ jobs:
|
|||||||
- name: go vet
|
- name: go vet
|
||||||
run: go vet ./...
|
run: go vet ./...
|
||||||
|
|
||||||
# The race detector needs cgo, so this cannot run with CGO_ENABLED=0.
|
|
||||||
- name: go test -race
|
- name: go test -race
|
||||||
run: go test ./... -race
|
run: go test ./... -race
|
||||||
|
|
||||||
- name: Static build
|
- name: Static build (smoke)
|
||||||
run: CGO_ENABLED=0 go build -trimpath -o gateway ./cmd/gateway
|
run: CGO_ENABLED=0 go build -trimpath -o gateway ./cmd/gateway
|
||||||
|
|
||||||
- name: govulncheck
|
push:
|
||||||
|
name: Build and push image
|
||||||
|
needs: verify
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
runs-on: golang
|
||||||
|
container:
|
||||||
|
image: docker:27-cli
|
||||||
|
volumes:
|
||||||
|
- /docker-certs/client:/docker-certs/client:ro
|
||||||
|
env:
|
||||||
|
DOCKER_HOST: tcp://localhost:2376
|
||||||
|
DOCKER_TLS_VERIFY: "1"
|
||||||
|
DOCKER_CERT_PATH: /docker-certs/client
|
||||||
|
steps:
|
||||||
|
- name: install node (required by JS-based actions)
|
||||||
|
run: apk add --no-cache nodejs git
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Get short SHA
|
||||||
|
id: sha
|
||||||
run: |
|
run: |
|
||||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||||
govulncheck ./...
|
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||||
continue-on-error: true
|
|
||||||
|
- name: Registry login
|
||||||
|
run: |
|
||||||
|
echo "${REGISTRY_PAT}" | docker login "${REGISTRY}" \
|
||||||
|
--username rock --password-stdin
|
||||||
|
env:
|
||||||
|
REGISTRY_PAT: ${{ secrets.REGISTRY_PAT }}
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
--build-arg "VERSION=${{ steps.sha.outputs.short_sha }}" \
|
||||||
|
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
||||||
|
-t "${IMAGE}:latest" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Push image
|
||||||
|
run: |
|
||||||
|
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
||||||
|
docker push "${IMAGE}:latest"
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ FROM gcr.io/distroless/static-debian12:nonroot
|
|||||||
# securityContext; if one changes, both must.
|
# securityContext; if one changes, both must.
|
||||||
USER 65532:65532
|
USER 65532:65532
|
||||||
|
|
||||||
|
# distroless/static has no CA certs. Copy them from the build stage so Go's
|
||||||
|
# crypto/tls can verify the Kubernetes API server certificate.
|
||||||
|
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
COPY --from=build /out/gateway /gateway
|
COPY --from=build /out/gateway /gateway
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2026.
|
|
||||||
|
|
||||||
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 v1 contains API Schema definitions for the gateway v1 API group
|
|
||||||
// +kubebuilder:object:generate=true
|
|
||||||
// +groupName=gateway.riotpiao.com
|
|
||||||
package v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
"sigs.k8s.io/controller-runtime/pkg/scheme"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// GroupVersion is group version used to register these objects
|
|
||||||
GroupVersion = schema.GroupVersion{Group: "gateway.riotpiao.com", Version: "v1"}
|
|
||||||
|
|
||||||
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
|
|
||||||
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
|
|
||||||
|
|
||||||
// AddToScheme adds the types in this group-version to the given scheme.
|
|
||||||
AddToScheme = SchemeBuilder.AddToScheme
|
|
||||||
)
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2026.
|
|
||||||
|
|
||||||
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 v1
|
|
||||||
|
|
||||||
import (
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServiceAdapterUpstream defines the upstream target for this adapter.
|
|
||||||
type ServiceAdapterUpstream struct {
|
|
||||||
// URL is the upstream service endpoint.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:MinLength=1
|
|
||||||
URL string `json:"url"`
|
|
||||||
|
|
||||||
// TimeoutSeconds is the request timeout in seconds.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:Minimum=1
|
|
||||||
// +kubebuilder:validation:Maximum=3600
|
|
||||||
TimeoutSeconds int32 `json:"timeoutSeconds"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceAdapterAuth defines authentication requirements.
|
|
||||||
type ServiceAdapterAuth struct {
|
|
||||||
// Required indicates if authentication is needed.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
Required bool `json:"required"`
|
|
||||||
|
|
||||||
// Capability is the required capability name (e.g., "reasoning", "embedding").
|
|
||||||
// Empty if auth is not required.
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
Capability string `json:"capability,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceAdapterMethod defines a single method endpoint.
|
|
||||||
type ServiceAdapterMethod struct {
|
|
||||||
// Verb is the HTTP method (GET, POST, etc.).
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:Enum=GET;POST;PUT;DELETE;PATCH;HEAD;OPTIONS
|
|
||||||
Verb string `json:"verb"`
|
|
||||||
|
|
||||||
// UpstreamPath is the path to forward to on the upstream.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:MinLength=1
|
|
||||||
UpstreamPath string `json:"upstreamPath"`
|
|
||||||
|
|
||||||
// RequestSchema is the flat KV+type validation schema for requests (optional).
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
RequestSchema string `json:"requestSchema,omitempty"`
|
|
||||||
|
|
||||||
// ResponseSchema is the flat KV+type validation schema for responses (optional).
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
ResponseSchema string `json:"responseSchema,omitempty"`
|
|
||||||
|
|
||||||
// Auth overrides the resource-level auth for this method (optional).
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
Auth *ServiceAdapterAuth `json:"auth,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceAdapterResource defines a resource exposed by this adapter.
|
|
||||||
type ServiceAdapterResource struct {
|
|
||||||
// Name is the resource identifier.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:MinLength=1
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
// Methods are the HTTP methods available for this resource.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:MinItems=1
|
|
||||||
Methods []ServiceAdapterMethod `json:"methods"`
|
|
||||||
|
|
||||||
// Auth applies to all methods in this resource unless overridden.
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
Auth *ServiceAdapterAuth `json:"auth,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceAdapterSpec defines the desired state of ServiceAdapter.
|
|
||||||
type ServiceAdapterSpec struct {
|
|
||||||
// ServiceName is the unique identifier for this service.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
// +kubebuilder:validation:MinLength=1
|
|
||||||
// +kubebuilder:validation:MaxLength=63
|
|
||||||
ServiceName string `json:"serviceName"`
|
|
||||||
|
|
||||||
// Upstream defines where to forward requests.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
Upstream ServiceAdapterUpstream `json:"upstream"`
|
|
||||||
|
|
||||||
// Auth defines default authentication for this adapter.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
Auth ServiceAdapterAuth `json:"auth"`
|
|
||||||
|
|
||||||
// Retryable indicates if requests can be retried on 5xx.
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
// +kubebuilder:validation:Default=false
|
|
||||||
Retryable bool `json:"retryable,omitempty"`
|
|
||||||
|
|
||||||
// Resources are the endpoints exposed by this adapter.
|
|
||||||
// +kubebuilder:validation:Required
|
|
||||||
Resources []ServiceAdapterResource `json:"resources"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceAdapterStatus defines the observed state of ServiceAdapter.
|
|
||||||
type ServiceAdapterStatus struct {
|
|
||||||
// Ready indicates if the adapter is loaded and healthy.
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
Ready bool `json:"ready,omitempty"`
|
|
||||||
|
|
||||||
// Error message if the adapter failed to load.
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
|
|
||||||
// LastSyncTime is when the adapter was last synced.
|
|
||||||
// +kubebuilder:validation:Optional
|
|
||||||
LastSyncTime *metav1.Time `json:"lastSyncTime,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
|
||||||
// +kubebuilder:resource:scope=Namespaced
|
|
||||||
// +kubebuilder:subresource:status
|
|
||||||
// +kubebuilder:printcolumn:name="Service",type=string,JSONPath=`.spec.serviceName`
|
|
||||||
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.ready`
|
|
||||||
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
|
||||||
|
|
||||||
// ServiceAdapter describes a service exposed through the gateway.
|
|
||||||
type ServiceAdapter struct {
|
|
||||||
metav1.TypeMeta `json:",inline"`
|
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
|
||||||
|
|
||||||
Spec ServiceAdapterSpec `json:"spec,omitempty"`
|
|
||||||
Status ServiceAdapterStatus `json:"status,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
|
||||||
|
|
||||||
// ServiceAdapterList contains a list of ServiceAdapter.
|
|
||||||
type ServiceAdapterList struct {
|
|
||||||
metav1.TypeMeta `json:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata,omitempty"`
|
|
||||||
Items []ServiceAdapter `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
SchemeBuilder.Register(&ServiceAdapter{}, &ServiceAdapterList{})
|
|
||||||
}
|
|
||||||
+5
-1
@@ -53,7 +53,11 @@ func main() {
|
|||||||
srv.SetHealthChecker(healthChecker)
|
srv.SetHealthChecker(healthChecker)
|
||||||
|
|
||||||
// Create ServiceAdapter registry and dispatcher (phase 8)
|
// Create ServiceAdapter registry and dispatcher (phase 8)
|
||||||
registry := serviceadapter.NewRegistry(nil) // nil uses default logger
|
registry := serviceadapter.NewRegistry(nil)
|
||||||
|
for _, a := range cfg.Adapters {
|
||||||
|
_ = registry.Add(a)
|
||||||
|
}
|
||||||
|
log.Printf("%d service adapters loaded", registry.Count())
|
||||||
dispatcher := serviceadapter.NewDispatcher(registry)
|
dispatcher := serviceadapter.NewDispatcher(registry)
|
||||||
|
|
||||||
// Create router that handles health endpoints, X-Service (ServiceAdapter) routing,
|
// Create router that handles health endpoints, X-Service (ServiceAdapter) routing,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forgejo.riotpiao.com/rock/homelab-frontend/internal/serviceadapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds the gateway configuration.
|
// Config holds the gateway configuration.
|
||||||
@@ -18,6 +20,8 @@ type Config struct {
|
|||||||
// Models maps model names to their upstream configuration.
|
// Models maps model names to their upstream configuration.
|
||||||
// Multiple models can point to the same upstream address.
|
// Multiple models can point to the same upstream address.
|
||||||
Models map[string]*ModelUpstream
|
Models map[string]*ModelUpstream
|
||||||
|
// Adapters holds service adapter definitions for X-Service routing.
|
||||||
|
Adapters []*serviceadapter.ServiceAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ModelUpstream holds upstream configuration for a specific model.
|
// ModelUpstream holds upstream configuration for a specific model.
|
||||||
@@ -94,16 +98,18 @@ func Load() (*Config, error) {
|
|||||||
shutdownTimeout = d
|
shutdownTimeout = d
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load routes and models from config file
|
// Load routes, models, and adapters from config file
|
||||||
routes := make(map[string]*Route)
|
routes := make(map[string]*Route)
|
||||||
models := make(map[string]*ModelUpstream)
|
models := make(map[string]*ModelUpstream)
|
||||||
|
var adapters []*serviceadapter.ServiceAdapter
|
||||||
if configPath, ok := os.LookupEnv("CONFIG_PATH"); ok {
|
if configPath, ok := os.LookupEnv("CONFIG_PATH"); ok {
|
||||||
loadedRoutes, loadedModels, err := LoadRoutesAndModelsFromFile(configPath)
|
loadedRoutes, loadedModels, loadedAdapters, err := LoadRoutesAndModelsFromFile(configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
routes = loadedRoutes
|
routes = loadedRoutes
|
||||||
models = loadedModels
|
models = loadedModels
|
||||||
|
adapters = loadedAdapters
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Config{
|
return &Config{
|
||||||
@@ -111,5 +117,6 @@ func Load() (*Config, error) {
|
|||||||
ShutdownTimeout: shutdownTimeout,
|
ShutdownTimeout: shutdownTimeout,
|
||||||
Routes: routes,
|
Routes: routes,
|
||||||
Models: models,
|
Models: models,
|
||||||
|
Adapters: adapters,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-25
@@ -6,13 +6,15 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"forgejo.riotpiao.com/rock/homelab-frontend/internal/serviceadapter"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// rawConfig represents the structure of the YAML configuration file.
|
// rawConfig represents the structure of the YAML configuration file.
|
||||||
type rawConfig struct {
|
type rawConfig struct {
|
||||||
Routes []rawRoute `yaml:"routes"`
|
Routes []rawRoute `yaml:"routes"`
|
||||||
Models []rawModel `yaml:"models"`
|
Models []rawModel `yaml:"models"`
|
||||||
|
Adapters []rawAdapter `yaml:"adapters"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// rawRoute represents a single route in the YAML configuration.
|
// rawRoute represents a single route in the YAML configuration.
|
||||||
@@ -28,6 +30,29 @@ type rawModel struct {
|
|||||||
Path string `yaml:"path"`
|
Path string `yaml:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rawAdapter represents a service adapter in the YAML configuration.
|
||||||
|
type rawAdapter struct {
|
||||||
|
ServiceName string `yaml:"serviceName"`
|
||||||
|
Upstream struct {
|
||||||
|
URL string `yaml:"url"`
|
||||||
|
TimeoutSeconds int32 `yaml:"timeoutSeconds"`
|
||||||
|
} `yaml:"upstream"`
|
||||||
|
Auth struct {
|
||||||
|
Required bool `yaml:"required"`
|
||||||
|
Capability string `yaml:"capability"`
|
||||||
|
} `yaml:"auth"`
|
||||||
|
Retryable bool `yaml:"retryable"`
|
||||||
|
Resources []struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Methods []struct {
|
||||||
|
Verb string `yaml:"verb"`
|
||||||
|
UpstreamPath string `yaml:"upstreamPath"`
|
||||||
|
RequestSchema string `yaml:"requestSchema"`
|
||||||
|
ResponseSchema string `yaml:"responseSchema"`
|
||||||
|
} `yaml:"methods"`
|
||||||
|
} `yaml:"resources"`
|
||||||
|
}
|
||||||
|
|
||||||
// rawUpstream represents upstream configuration in YAML.
|
// rawUpstream represents upstream configuration in YAML.
|
||||||
type rawUpstream struct {
|
type rawUpstream struct {
|
||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
@@ -39,32 +64,32 @@ type rawUpstream struct {
|
|||||||
AuthRequired *bool `yaml:"authRequired"`
|
AuthRequired *bool `yaml:"authRequired"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRoutesAndModelsFromFile loads both route and model configuration from a YAML file.
|
// LoadRoutesAndModelsFromFile loads route, model, and adapter configuration from a YAML file.
|
||||||
func LoadRoutesAndModelsFromFile(path string) (map[string]*Route, map[string]*ModelUpstream, error) {
|
func LoadRoutesAndModelsFromFile(path string) (map[string]*Route, map[string]*ModelUpstream, []*serviceadapter.ServiceAdapter, error) {
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to read config file %q: %w", path, err)
|
return nil, nil, nil, fmt.Errorf("failed to read config file %q: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var raw rawConfig
|
var raw rawConfig
|
||||||
if err := yaml.Unmarshal(data, &raw); err != nil {
|
if err := yaml.Unmarshal(data, &raw); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to parse config file %q: %w", path, err)
|
return nil, nil, nil, fmt.Errorf("failed to parse config file %q: %w", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load routes
|
// Load routes
|
||||||
routes := make(map[string]*Route)
|
routes := make(map[string]*Route)
|
||||||
for _, rawRoute := range raw.Routes {
|
for _, rawRoute := range raw.Routes {
|
||||||
if rawRoute.Name == "" {
|
if rawRoute.Name == "" {
|
||||||
return nil, nil, fmt.Errorf("route has empty name")
|
return nil, nil, nil, fmt.Errorf("route has empty name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, exists := routes[rawRoute.Name]; exists {
|
if _, exists := routes[rawRoute.Name]; exists {
|
||||||
return nil, nil, fmt.Errorf("duplicate route: \"%s\"", rawRoute.Name)
|
return nil, nil, nil, fmt.Errorf("duplicate route: \"%s\"", rawRoute.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
upstream, err := parseUpstream(rawRoute.Name, rawRoute.Upstream)
|
upstream, err := parseUpstream(rawRoute.Name, rawRoute.Upstream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
routes[rawRoute.Name] = &Route{
|
routes[rawRoute.Name] = &Route{
|
||||||
@@ -76,26 +101,18 @@ func LoadRoutesAndModelsFromFile(path string) (map[string]*Route, map[string]*Mo
|
|||||||
// Load models
|
// Load models
|
||||||
models := make(map[string]*ModelUpstream)
|
models := make(map[string]*ModelUpstream)
|
||||||
for _, rawModel := range raw.Models {
|
for _, rawModel := range raw.Models {
|
||||||
// Validate model name is not empty
|
|
||||||
if rawModel.Name == "" {
|
if rawModel.Name == "" {
|
||||||
return nil, nil, fmt.Errorf("model has empty name")
|
return nil, nil, nil, fmt.Errorf("model has empty name")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate model names
|
|
||||||
if _, exists := models[rawModel.Name]; exists {
|
if _, exists := models[rawModel.Name]; exists {
|
||||||
return nil, nil, fmt.Errorf("duplicate model: \"%s\"", rawModel.Name)
|
return nil, nil, nil, fmt.Errorf("duplicate model: \"%s\"", rawModel.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate address is not empty
|
|
||||||
if rawModel.Address == "" {
|
if rawModel.Address == "" {
|
||||||
return nil, nil, fmt.Errorf("model \"%s\": field 'address' is required", rawModel.Name)
|
return nil, nil, nil, fmt.Errorf("model \"%s\": field 'address' is required", rawModel.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate address format (host:port)
|
|
||||||
if _, _, err := net.SplitHostPort(rawModel.Address); err != nil {
|
if _, _, err := net.SplitHostPort(rawModel.Address); err != nil {
|
||||||
return nil, nil, fmt.Errorf("model \"%s\": invalid address \"%s\": %w", rawModel.Name, rawModel.Address, err)
|
return nil, nil, nil, fmt.Errorf("model \"%s\": invalid address \"%s\": %w", rawModel.Name, rawModel.Address, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
models[rawModel.Name] = &ModelUpstream{
|
models[rawModel.Name] = &ModelUpstream{
|
||||||
Name: rawModel.Name,
|
Name: rawModel.Name,
|
||||||
Address: rawModel.Address,
|
Address: rawModel.Address,
|
||||||
@@ -103,15 +120,45 @@ func LoadRoutesAndModelsFromFile(path string) (map[string]*Route, map[string]*Mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return routes, models, nil
|
// Load adapters
|
||||||
|
adapters := make([]*serviceadapter.ServiceAdapter, 0, len(raw.Adapters))
|
||||||
|
for _, ra := range raw.Adapters {
|
||||||
|
if ra.ServiceName == "" {
|
||||||
|
return nil, nil, nil, fmt.Errorf("adapter has empty serviceName")
|
||||||
|
}
|
||||||
|
a := &serviceadapter.ServiceAdapter{
|
||||||
|
Name: ra.ServiceName,
|
||||||
|
ServiceName: ra.ServiceName,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
a.Spec.ServiceName = ra.ServiceName
|
||||||
|
a.Spec.Upstream.URL = ra.Upstream.URL
|
||||||
|
a.Spec.Upstream.TimeoutSeconds = ra.Upstream.TimeoutSeconds
|
||||||
|
a.Spec.Auth.Required = ra.Auth.Required
|
||||||
|
a.Spec.Auth.Capability = ra.Auth.Capability
|
||||||
|
a.Spec.Retryable = ra.Retryable
|
||||||
|
for _, rr := range ra.Resources {
|
||||||
|
res := serviceadapter.Resource{Name: rr.Name}
|
||||||
|
for _, rm := range rr.Methods {
|
||||||
|
res.Methods = append(res.Methods, serviceadapter.Method{
|
||||||
|
Verb: rm.Verb,
|
||||||
|
UpstreamPath: rm.UpstreamPath,
|
||||||
|
RequestSchema: rm.RequestSchema,
|
||||||
|
ResponseSchema: rm.ResponseSchema,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
a.Spec.Resources = append(a.Spec.Resources, res)
|
||||||
|
}
|
||||||
|
adapters = append(adapters, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return routes, models, adapters, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadRoutesFromFile loads route configuration from a YAML file.
|
// LoadRoutesFromFile loads route configuration from a YAML file.
|
||||||
// It validates that all required fields are present and have valid values.
|
|
||||||
// Returns an error if the configuration is invalid.
|
|
||||||
// Deprecated: Use LoadRoutesAndModelsFromFile instead.
|
// Deprecated: Use LoadRoutesAndModelsFromFile instead.
|
||||||
func LoadRoutesFromFile(path string) (map[string]*Route, error) {
|
func LoadRoutesFromFile(path string) (map[string]*Route, error) {
|
||||||
routes, _, err := LoadRoutesAndModelsFromFile(path)
|
routes, _, _, err := LoadRoutesAndModelsFromFile(path)
|
||||||
return routes, err
|
return routes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
_, models, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
_, models, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to load config: %v", err)
|
t.Fatalf("failed to load config: %v", err)
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
_, _, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error for duplicate model name, got nil")
|
t.Errorf("expected error for duplicate model name, got nil")
|
||||||
}
|
}
|
||||||
@@ -127,7 +127,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
_, _, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error for empty model name, got nil")
|
t.Errorf("expected error for empty model name, got nil")
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
_, _, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error for missing address, got nil")
|
t.Errorf("expected error for missing address, got nil")
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
_, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
_, _, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error for invalid address, got nil")
|
t.Errorf("expected error for invalid address, got nil")
|
||||||
}
|
}
|
||||||
@@ -219,7 +219,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
_, models, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
_, models, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to load config: %v", err)
|
t.Fatalf("failed to load config: %v", err)
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ models:
|
|||||||
tmpFile.WriteString(data)
|
tmpFile.WriteString(data)
|
||||||
tmpFile.Close()
|
tmpFile.Close()
|
||||||
|
|
||||||
routes, models, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
routes, models, _, err := LoadRoutesAndModelsFromFile(tmpFile.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to load config: %v", err)
|
t.Fatalf("failed to load config: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,39 +6,39 @@ import (
|
|||||||
|
|
||||||
// Upstream defines an upstream target.
|
// Upstream defines an upstream target.
|
||||||
type Upstream struct {
|
type Upstream struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url" yaml:"url"`
|
||||||
TimeoutSeconds int32 `json:"timeoutSeconds"`
|
TimeoutSeconds int32 `json:"timeoutSeconds" yaml:"timeoutSeconds"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth defines authentication requirements.
|
// Auth defines authentication requirements.
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
Required bool `json:"required"`
|
Required bool `json:"required" yaml:"required"`
|
||||||
Capability string `json:"capability,omitempty"`
|
Capability string `json:"capability,omitempty" yaml:"capability,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method defines an HTTP method endpoint.
|
// Method defines an HTTP method endpoint.
|
||||||
type Method struct {
|
type Method struct {
|
||||||
Verb string `json:"verb"`
|
Verb string `json:"verb" yaml:"verb"`
|
||||||
UpstreamPath string `json:"upstreamPath"`
|
UpstreamPath string `json:"upstreamPath" yaml:"upstreamPath"`
|
||||||
RequestSchema string `json:"requestSchema,omitempty"`
|
RequestSchema string `json:"requestSchema,omitempty" yaml:"requestSchema,omitempty"`
|
||||||
ResponseSchema string `json:"responseSchema,omitempty"`
|
ResponseSchema string `json:"responseSchema,omitempty" yaml:"responseSchema,omitempty"`
|
||||||
Auth *Auth `json:"auth,omitempty"`
|
Auth *Auth `json:"auth,omitempty" yaml:"auth,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resource defines a resource with multiple methods.
|
// Resource defines a resource with multiple methods.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name" yaml:"name"`
|
||||||
Methods []Method `json:"methods"`
|
Methods []Method `json:"methods" yaml:"methods"`
|
||||||
Auth *Auth `json:"auth,omitempty"`
|
Auth *Auth `json:"auth,omitempty" yaml:"auth,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spec is the ServiceAdapter spec.
|
// Spec is the ServiceAdapter spec.
|
||||||
type Spec struct {
|
type Spec struct {
|
||||||
ServiceName string `json:"serviceName"`
|
ServiceName string `json:"serviceName" yaml:"serviceName"`
|
||||||
Upstream Upstream `json:"upstream"`
|
Upstream Upstream `json:"upstream" yaml:"upstream"`
|
||||||
Auth Auth `json:"auth"`
|
Auth Auth `json:"auth" yaml:"auth"`
|
||||||
Retryable bool `json:"retryable,omitempty"`
|
Retryable bool `json:"retryable,omitempty" yaml:"retryable,omitempty"`
|
||||||
Resources []Resource `json:"resources"`
|
Resources []Resource `json:"resources" yaml:"resources"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status is the ServiceAdapter status.
|
// Status is the ServiceAdapter status.
|
||||||
|
|||||||
@@ -36,3 +36,107 @@ data:
|
|||||||
- name: "BAAI/bge-reranker-base"
|
- name: "BAAI/bge-reranker-base"
|
||||||
address: "reranker-predictor.llm-serving:80"
|
address: "reranker-predictor.llm-serving:80"
|
||||||
path: "/v1/rerank"
|
path: "/v1/rerank"
|
||||||
|
|
||||||
|
# Service adapters for X-Service header routing
|
||||||
|
adapters:
|
||||||
|
- serviceName: sqs
|
||||||
|
upstream:
|
||||||
|
url: http://kmsvc-management-service.kmsvc.svc.cluster.local:8080
|
||||||
|
timeoutSeconds: 30
|
||||||
|
auth:
|
||||||
|
required: false
|
||||||
|
resources:
|
||||||
|
- name: send-message
|
||||||
|
methods:
|
||||||
|
- verb: POST
|
||||||
|
upstreamPath: /sqs/send
|
||||||
|
- name: receive-message
|
||||||
|
methods:
|
||||||
|
- verb: POST
|
||||||
|
upstreamPath: /sqs/receive
|
||||||
|
- name: list-queues
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /sqs/queues
|
||||||
|
|
||||||
|
- serviceName: workflow
|
||||||
|
upstream:
|
||||||
|
url: http://temporal-frontend.temporal.svc.cluster.local:7233
|
||||||
|
timeoutSeconds: 60
|
||||||
|
auth:
|
||||||
|
required: false
|
||||||
|
resources:
|
||||||
|
- name: execute
|
||||||
|
methods:
|
||||||
|
- verb: POST
|
||||||
|
upstreamPath: /workflow/execute
|
||||||
|
- name: describe
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /workflow/describe
|
||||||
|
- name: list
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /workflow/list
|
||||||
|
|
||||||
|
- serviceName: memory
|
||||||
|
upstream:
|
||||||
|
url: http://poimen-memory.poimen.svc.cluster.local:8080
|
||||||
|
timeoutSeconds: 30
|
||||||
|
auth:
|
||||||
|
required: true
|
||||||
|
capability: "memory:read"
|
||||||
|
resources:
|
||||||
|
- name: query
|
||||||
|
methods:
|
||||||
|
- verb: POST
|
||||||
|
upstreamPath: /memory/query
|
||||||
|
- name: ingest
|
||||||
|
methods:
|
||||||
|
- verb: POST
|
||||||
|
upstreamPath: /memory/ingest
|
||||||
|
- name: skills
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /memory/skills
|
||||||
|
|
||||||
|
- serviceName: s3
|
||||||
|
upstream:
|
||||||
|
url: http://minio-operator.minio-operator.svc.cluster.local:9000
|
||||||
|
timeoutSeconds: 30
|
||||||
|
auth:
|
||||||
|
required: false
|
||||||
|
resources:
|
||||||
|
- name: list-objects
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /
|
||||||
|
- name: get-object
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /
|
||||||
|
- name: put-object
|
||||||
|
methods:
|
||||||
|
- verb: PUT
|
||||||
|
upstreamPath: /
|
||||||
|
|
||||||
|
- serviceName: iam
|
||||||
|
upstream:
|
||||||
|
url: http://authentik-outpost.authentik.svc.cluster.local:9000
|
||||||
|
timeoutSeconds: 30
|
||||||
|
auth:
|
||||||
|
required: true
|
||||||
|
capability: "iam:admin"
|
||||||
|
resources:
|
||||||
|
- name: list-roles
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /api/v3/roles
|
||||||
|
- name: list-users
|
||||||
|
methods:
|
||||||
|
- verb: GET
|
||||||
|
upstreamPath: /api/v3/users
|
||||||
|
- name: create-role
|
||||||
|
methods:
|
||||||
|
- verb: POST
|
||||||
|
upstreamPath: /api/v3/roles
|
||||||
|
|||||||
+2
-2
@@ -45,8 +45,8 @@ spec:
|
|||||||
# Tag is pinned in kustomization.yaml so there is exactly one place to
|
# Tag is pinned in kustomization.yaml so there is exactly one place to
|
||||||
# bump it. Never :latest — Argo cannot make a deterministic rollout
|
# bump it. Never :latest — Argo cannot make a deterministic rollout
|
||||||
# decision from a mutable tag, and 6.1 requires SHA tags.
|
# decision from a mutable tag, and 6.1 requires SHA tags.
|
||||||
image: forgejo.riotpiao.com/rock/api-gateway
|
image: forgejo.riotpiao.com/rock/api-gateway:latest
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 8080
|
containerPort: 8080
|
||||||
|
|||||||
Reference in New Issue
Block a user