fix: replace client-go informer with REST loader, drop k8s deps
Informer cache sync timed out due to transient 429 from API server. REST loader does one GET + retry, polls every 30s. Zero new deps. go.mod back to 1.25.4, CI back to golang:1.25-bookworm.
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@
|
||||
# --platform=$BUILDPLATFORM pins the build stage to the machine doing the
|
||||
# building, then Go cross-compiles to $TARGETARCH. Without it, building an
|
||||
# amd64 image from an arm64 workstation runs the whole toolchain under QEMU.
|
||||
FROM --platform=$BUILDPLATFORM golang:1.26-bookworm AS build
|
||||
FROM --platform=$BUILDPLATFORM golang:1.25-bookworm AS build
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
|
||||
@@ -1,29 +1,13 @@
|
||||
/*
|
||||
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
|
||||
// Package v1 contains API Schema definitions for the gateway v1 API group.
|
||||
// These types document the CRD shape; the gateway itself uses the dynamic
|
||||
// REST loader (no typed client, no scheme registration).
|
||||
//
|
||||
// +kubebuilder:object:generate=true
|
||||
// +groupName=gateway.riotpiao.com
|
||||
package v1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is group version used to register these objects.
|
||||
GroupVersion = schema.GroupVersion{Group: "gateway.riotpiao.com", Version: "v1"}
|
||||
// GroupVersion constants for the CRD.
|
||||
const (
|
||||
Group = "gateway.riotpiao.com"
|
||||
Version = "v1"
|
||||
)
|
||||
|
||||
@@ -1,160 +1,58 @@
|
||||
/*
|
||||
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 documents the ServiceAdapter CRD shape.
|
||||
// These structs mirror the CRD schema in k8s/crd-serviceadapter.yaml.
|
||||
// The gateway reads CRs via plain REST+JSON — no typed client needed.
|
||||
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"`
|
||||
URL string `json:"url"`
|
||||
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
|
||||
Required bool `json:"required"`
|
||||
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"`
|
||||
Verb string `json:"verb"`
|
||||
UpstreamPath string `json:"upstreamPath"`
|
||||
RequestSchema string `json:"requestSchema,omitempty"`
|
||||
ResponseSchema string `json:"responseSchema,omitempty"`
|
||||
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
|
||||
Name string `json:"name"`
|
||||
Methods []ServiceAdapterMethod `json:"methods"`
|
||||
|
||||
// Auth applies to all methods in this resource unless overridden.
|
||||
// +kubebuilder:validation:Optional
|
||||
Auth *ServiceAdapterAuth `json:"auth,omitempty"`
|
||||
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"`
|
||||
ServiceName string `json:"serviceName"`
|
||||
Upstream ServiceAdapterUpstream `json:"upstream"`
|
||||
Auth ServiceAdapterAuth `json:"auth"`
|
||||
Retryable bool `json:"retryable,omitempty"`
|
||||
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
|
||||
Ready bool `json:"ready,omitempty"`
|
||||
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"`
|
||||
Items []ServiceAdapter `json:"items"`
|
||||
}
|
||||
|
||||
// Note: DeepCopyObject is not generated. The gateway uses the dynamic
|
||||
// (unstructured) k8s client, so typed scheme registration is unnecessary.
|
||||
// If a typed client is needed later, run controller-gen to generate
|
||||
// the DeepCopy methods and re-enable SchemeBuilder.Register here.
|
||||
|
||||
+14
-49
@@ -1,17 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
"time"
|
||||
|
||||
"forgejo.riotpiao.com/rock/homelab-frontend/internal/config"
|
||||
"forgejo.riotpiao.com/rock/homelab-frontend/internal/proxy"
|
||||
@@ -21,14 +17,12 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load configuration
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "failed to load config: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Determine if auth is enabled by checking if any route requires it
|
||||
authEnabled := false
|
||||
for _, route := range cfg.Routes {
|
||||
if route.Upstream.AuthRequired {
|
||||
@@ -37,10 +31,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Create the reverse proxy handler that routes requests based on configuration
|
||||
upstreamHandler := proxy.New(cfg)
|
||||
|
||||
// Create the Temporal workflow handler
|
||||
temporalHostPort := os.Getenv("TEMPORAL_HOST_PORT")
|
||||
if temporalHostPort == "" {
|
||||
temporalHostPort = "localhost:7233"
|
||||
@@ -48,77 +40,50 @@ func main() {
|
||||
log.Printf("Temporal server: %s", temporalHostPort)
|
||||
temporalHandler := temporal.NewHandler(temporalHostPort)
|
||||
|
||||
// Create server with health checker
|
||||
srv := server.New(cfg.ListenAddr, cfg.ShutdownTimeout, nil)
|
||||
|
||||
// Initialize health checker with config validity and auth status
|
||||
healthChecker := server.NewHealthChecker(true, authEnabled)
|
||||
srv.SetHealthChecker(healthChecker)
|
||||
|
||||
// Route klog (used by k8s client-go) to stderr so reflector errors are visible
|
||||
klog.InitFlags(nil)
|
||||
klog.SetOutput(os.Stderr)
|
||||
|
||||
// Create ServiceAdapter registry and dispatcher (phase 8)
|
||||
// ServiceAdapter: registry + loader + dispatcher
|
||||
registry := serviceadapter.NewRegistry(nil)
|
||||
dispatcher := serviceadapter.NewDispatcher(registry)
|
||||
|
||||
// Start ServiceAdapter informer to watch CRDs (8.1 integration)
|
||||
var informerMgr *serviceadapter.InformerManager
|
||||
kubeConfig, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
log.Printf("ServiceAdapter informer disabled: %v", err)
|
||||
var loader *serviceadapter.Loader
|
||||
if l, err := serviceadapter.NewLoader(registry, "api"); err != nil {
|
||||
log.Printf("ServiceAdapter loader disabled: %v", err)
|
||||
} else {
|
||||
dynamicClient, err := dynamic.NewForConfig(kubeConfig)
|
||||
if err != nil {
|
||||
log.Printf("ServiceAdapter informer: failed to create k8s client: %v", err)
|
||||
if err := l.Start(30 * time.Second); err != nil {
|
||||
log.Printf("ServiceAdapter loader failed: %v", err)
|
||||
} else {
|
||||
mgr, err := serviceadapter.NewInformerManager(dynamicClient, registry, "api")
|
||||
if err != nil {
|
||||
log.Printf("ServiceAdapter informer: failed to create manager: %v", err)
|
||||
} else {
|
||||
if err := mgr.Start(context.Background()); err != nil {
|
||||
log.Printf("ServiceAdapter informer: failed to start: %v", err)
|
||||
} else {
|
||||
informerMgr = mgr
|
||||
log.Printf("ServiceAdapter informer started")
|
||||
}
|
||||
}
|
||||
loader = l
|
||||
log.Printf("ServiceAdapter loader started")
|
||||
}
|
||||
}
|
||||
|
||||
// Create router that handles health endpoints, X-Service (ServiceAdapter) routing,
|
||||
// temporal endpoints, and passes others to upstream handler
|
||||
router := server.NewRouter(healthChecker, dispatcher, temporalHandler, upstreamHandler)
|
||||
srv.SetHandler(router)
|
||||
|
||||
// Set up signal handling
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
// Start server in a goroutine
|
||||
var serverErr error
|
||||
go func() {
|
||||
log.Printf("gateway listening on %s", srv.Addr())
|
||||
serverErr = srv.ListenAndServe()
|
||||
if serverErr != nil && serverErr != http.ErrServerClosed {
|
||||
log.Printf("server error: %v", serverErr)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Printf("server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for shutdown signal
|
||||
sig := <-sigChan
|
||||
log.Printf("received signal: %v", sig)
|
||||
|
||||
if informerMgr != nil {
|
||||
informerMgr.Stop()
|
||||
if loader != nil {
|
||||
loader.Stop()
|
||||
}
|
||||
|
||||
if err := srv.Shutdown(context.Background()); err != nil {
|
||||
if err := srv.Shutdown(nil); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "shutdown error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Printf("gateway shutdown complete")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module forgejo.riotpiao.com/rock/homelab-frontend
|
||||
|
||||
go 1.26.0
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
go.temporal.io/api v1.63.5
|
||||
@@ -8,66 +8,28 @@ require (
|
||||
google.golang.org/grpc v1.83.1
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.37.0
|
||||
k8s.io/client-go v0.37.0
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/swag v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/conv v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/pools v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.27.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/nexus-rpc/nexus-proto-annotations v0.1.0 // indirect
|
||||
github.com/nexus-rpc/sdk-go v0.7.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/robfig/cron v1.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/api v0.37.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,107 +1,47 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
|
||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/swag v0.27.1 h1:VotvOLWW8q/EAxB0YdsBBGC8XYyeL1YwBj2ungAGPNg=
|
||||
github.com/go-openapi/swag v0.27.1/go.mod h1:GTkJPwHfhJp6MWr4/rCh64HVI3Ofu+tcsbfjfHmTxpE=
|
||||
github.com/go-openapi/swag/cmdutils v0.27.1 h1:I7sYqaWVl5mq0NEmNQkAmFDyNin9ufvMX/p2zwtQaOE=
|
||||
github.com/go-openapi/swag/cmdutils v0.27.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
|
||||
github.com/go-openapi/swag/conv v0.27.1 h1:8wi9ZG+olmY1wXphl93EWniPtbSPkXM/feH7FgjsvrU=
|
||||
github.com/go-openapi/swag/conv v0.27.1/go.mod h1:QbqMivkpKhC3g1B1GGGOJ6ANewI3S62dbzYu3Duowqs=
|
||||
github.com/go-openapi/swag/fileutils v0.27.1 h1:QQqBSoi5mW4XpU85nS0mLcA+zAE6vLzrb0QkmLKf9oM=
|
||||
github.com/go-openapi/swag/fileutils v0.27.1/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1 h1:SVgK3i4USzCU5mibOOS/l4ea2h9UQXy7J7RNLTjuXjU=
|
||||
github.com/go-openapi/swag/jsonutils v0.27.1/go.mod h1:tdlEpZqdcQ17uj6J4YdK9vd8It5qWMwjWXOs0tjpRlk=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1 h1:mJu3COL9WEaZVp/Kf2PRMi7tPszPEJfSr/OO75ynCs8=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.1/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.27.1 h1:/DxUgDXKbBX4bcn7r9uEXfJyzN5XpiJmZplzQTjrRCY=
|
||||
github.com/go-openapi/swag/loading v0.27.1/go.mod h1:jvGh3iA2+zyUUycB5fgJWzeHnhrpvGnJJM0RVE9ZShE=
|
||||
github.com/go-openapi/swag/mangling v0.27.1 h1:yC9D0HyUE8gbP+BfmGx9+AA89ikwZTMjESK3OnnoaqA=
|
||||
github.com/go-openapi/swag/mangling v0.27.1/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
|
||||
github.com/go-openapi/swag/netutils v0.27.1 h1:mICMFoS82F5TZ4Zy3cqmcQk+BFeCp3Uyq3Np7GI0/qU=
|
||||
github.com/go-openapi/swag/netutils v0.27.1/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
|
||||
github.com/go-openapi/swag/pools v0.27.1 h1:9LeadcMyb2GJCbXX5hVQDbZ2Lq9TL4dCs/nx1j5DO0E=
|
||||
github.com/go-openapi/swag/pools v0.27.1/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.27.1 h1:ZXePZ0r2p1qSjo8tD3Un4vFj8+FqlCkczxDrJIhYUp8=
|
||||
github.com/go-openapi/swag/stringutils v0.27.1/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.27.1 h1:KSTdFlfnse4r6dP9IrEnwMldjE+zs71UeEB3//PtVXc=
|
||||
github.com/go-openapi/swag/typeutils v0.27.1/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1 h1:ftxv6xvXb1E3zohUc+okZ9nSqNb9StQX/FXnKZ98sQA=
|
||||
github.com/go-openapi/swag/yamlutils v0.27.1/go.mod h1:bnxFIB1qewGRiZHypXGZ3fNgf13/0HfRgnS/iZBDrOo=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nexus-rpc/nexus-proto-annotations v0.1.0 h1:2fELd+9sqUtNu6Fg//pw8YFsxOvp8vZ8hfP0nHhNI80=
|
||||
github.com/nexus-rpc/nexus-proto-annotations v0.1.0/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y=
|
||||
github.com/nexus-rpc/sdk-go v0.7.0 h1:38NrfY5rLnZAiMMs2ZfCKI/CSDzdfJG+27iAgfA8bUI=
|
||||
github.com/nexus-rpc/sdk-go v0.7.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
@@ -121,12 +61,6 @@ go.temporal.io/api v1.63.5 h1:c11+kPYHkXXL3UiShPdbMD+xtvqGsbTibUA9ypmiCa4=
|
||||
go.temporal.io/api v1.63.5/go.mod h1:SrlW2JMwVlDP4nRWSNznUFqnSHd+YeMDS1BkYo63HCQ=
|
||||
go.temporal.io/sdk v1.48.0 h1:WDctKDVuh0Z8Nf7euAyqs/EwcPg1JTIIq1Fut8Tq118=
|
||||
go.temporal.io/sdk v1.48.0/go.mod h1:SHv3+fLzD0GGZAwf0xNSvu8UmO1nFgG9WBSYoowApIk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -140,8 +74,6 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -157,8 +89,6 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
@@ -187,29 +117,5 @@ google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/api v0.37.0 h1:Z//Vj9N7RA/yS2sDmxyeo7h+RR4zbUrd2vrd3Z0TbB4=
|
||||
k8s.io/api v0.37.0/go.mod h1:LKXgcJWMc+f4OLbP5SFR8rulEg07zZhpi/zMULiBImk=
|
||||
k8s.io/apimachinery v0.37.0 h1:Np2AbDtf8x6RDHiD8T9LbKJ9gaegeVNa8yNm5FuGKm0=
|
||||
k8s.io/apimachinery v0.37.0/go.mod h1:RN3nhprFSCxOi5Selxd7oMTXOe/c+ZbcE7Im+TS2zkE=
|
||||
k8s.io/client-go v0.37.0 h1:nsN31fy8wBySuZ+QRnKmrjRSQLOG2rvoGN0tKd12zhQ=
|
||||
k8s.io/client-go v0.37.0/go.mod h1:FcGqw+Ll/gNQiq+nPGY1Oyt9y7SgDh1d3MW3RFDEbn0=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A=
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs=
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||
|
||||
+134
-121
@@ -1,28 +1,33 @@
|
||||
package serviceadapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// InformerManager watches ServiceAdapter CRs and populates the registry.
|
||||
type InformerManager struct {
|
||||
registry *Registry
|
||||
informer cache.SharedIndexInformer
|
||||
stopChan chan struct{}
|
||||
factory dynamicinformer.DynamicSharedInformerFactory
|
||||
// Loader watches ServiceAdapter CRs via the Kubernetes REST API and populates the registry.
|
||||
// No client-go dependency — uses the in-cluster service account token directly.
|
||||
type Loader struct {
|
||||
registry *Registry
|
||||
namespace string
|
||||
client *http.Client
|
||||
token string
|
||||
baseURL string
|
||||
stopChan chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewInformerManager creates a new informer that watches ServiceAdapters in the given namespace.
|
||||
func NewInformerManager(dynamicClient dynamic.Interface, registry *Registry, namespace string) (*InformerManager, error) {
|
||||
// NewLoader creates a loader that reads ServiceAdapters from the k8s API.
|
||||
// Returns an error if not running inside a cluster (missing SA token/ca).
|
||||
func NewLoader(registry *Registry, namespace string) (*Loader, error) {
|
||||
if registry == nil {
|
||||
return nil, fmt.Errorf("registry cannot be nil")
|
||||
}
|
||||
@@ -30,134 +35,142 @@ func NewInformerManager(dynamicClient dynamic.Interface, registry *Registry, nam
|
||||
namespace = "api"
|
||||
}
|
||||
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: "gateway.riotpiao.com",
|
||||
Version: "v1",
|
||||
Resource: "serviceadapters",
|
||||
}
|
||||
|
||||
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
|
||||
dynamicClient, 30*time.Second, namespace, nil,
|
||||
)
|
||||
|
||||
informer := factory.ForResource(gvr).Informer()
|
||||
|
||||
_, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
u := obj.(*unstructured.Unstructured)
|
||||
if a, err := parseServiceAdapter(u); err == nil {
|
||||
if registry.Add(a) == nil {
|
||||
log.Printf("serviceadapter added: %s", a.ServiceName)
|
||||
}
|
||||
} else {
|
||||
log.Printf("serviceadapter parse error on add: %v", err)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(_, newObj interface{}) {
|
||||
u := newObj.(*unstructured.Unstructured)
|
||||
if a, err := parseServiceAdapter(u); err == nil {
|
||||
registry.Update(a)
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
u, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
u, ok = tombstone.Obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
if name, ok := u.Object["metadata"].(map[string]interface{})["name"]; ok {
|
||||
registry.Delete(name.(string))
|
||||
log.Printf("serviceadapter deleted: %s", name)
|
||||
}
|
||||
},
|
||||
})
|
||||
tokenBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("adding event handler: %w", err)
|
||||
return nil, fmt.Errorf("not in cluster: %w", err)
|
||||
}
|
||||
|
||||
return &InformerManager{
|
||||
registry: registry,
|
||||
informer: informer,
|
||||
caBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing CA cert: %w", err)
|
||||
}
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
pool.AppendCertsFromPEM(caBytes)
|
||||
|
||||
return &Loader{
|
||||
registry: registry,
|
||||
namespace: namespace,
|
||||
token: string(tokenBytes),
|
||||
baseURL: "https://kubernetes.default.svc",
|
||||
client: &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{RootCAs: pool},
|
||||
},
|
||||
},
|
||||
stopChan: make(chan struct{}),
|
||||
factory: factory,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start begins watching ServiceAdapter resources. Blocks until cache syncs or timeout.
|
||||
func (im *InformerManager) Start(ctx context.Context) error {
|
||||
im.factory.Start(im.stopChan)
|
||||
|
||||
done := make(chan bool, 1)
|
||||
go func() { done <- cache.WaitForCacheSync(im.stopChan, im.informer.HasSynced) }()
|
||||
|
||||
select {
|
||||
case synced := <-done:
|
||||
if !synced {
|
||||
return fmt.Errorf("cache sync failed")
|
||||
// Start loads ServiceAdapters immediately, then polls every interval.
|
||||
func (l *Loader) Start(interval time.Duration) error {
|
||||
if err := l.load(); err != nil {
|
||||
// Retry once after 2s — handles transient "storage reinitializing" (429)
|
||||
log.Printf("serviceadapter loader: first attempt failed (%v), retrying in 2s", err)
|
||||
time.Sleep(2 * time.Second)
|
||||
if err := l.load(); err != nil {
|
||||
return fmt.Errorf("serviceadapter loader: %w", err)
|
||||
}
|
||||
case <-time.After(30 * time.Second):
|
||||
return fmt.Errorf("cache sync timed out")
|
||||
}
|
||||
|
||||
// Background poll for changes
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := l.load(); err != nil {
|
||||
log.Printf("serviceadapter loader poll error: %v", err)
|
||||
}
|
||||
case <-l.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the informer.
|
||||
func (im *InformerManager) Stop() {
|
||||
close(im.stopChan)
|
||||
// Stop stops the background poll.
|
||||
func (l *Loader) Stop() {
|
||||
l.once.Do(func() { close(l.stopChan) })
|
||||
}
|
||||
|
||||
// parseServiceAdapter converts an unstructured object to ServiceAdapter.
|
||||
func parseServiceAdapter(u *unstructured.Unstructured) (*ServiceAdapter, error) {
|
||||
metadata, ok := u.Object["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid metadata")
|
||||
// load fetches all ServiceAdapters from the k8s API and syncs the registry.
|
||||
func (l *Loader) load() error {
|
||||
url := fmt.Sprintf("%s/apis/gateway.riotpiao.com/v1/namespaces/%s/serviceadapters", l.baseURL, l.namespace)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, _ := metadata["name"].(string)
|
||||
namespace, _ := metadata["namespace"].(string)
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
req.Header.Set("Authorization", "Bearer "+l.token)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := l.client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("k8s API request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 429 {
|
||||
return fmt.Errorf("k8s API: storage reinitializing (429)")
|
||||
}
|
||||
if resp.StatusCode != 200 {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("k8s API: %d %s", resp.StatusCode, string(body[:min(len(body), 200)]))
|
||||
}
|
||||
|
||||
spec, ok := u.Object["spec"].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing spec")
|
||||
var list crList
|
||||
if err := json.NewDecoder(resp.Body).Decode(&list); err != nil {
|
||||
return fmt.Errorf("decoding response: %w", err)
|
||||
}
|
||||
|
||||
serviceName, ok := spec["serviceName"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing spec.serviceName")
|
||||
}
|
||||
|
||||
adapter := &ServiceAdapter{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
ServiceName: serviceName,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
if upstreamMap, ok := spec["upstream"].(map[string]interface{}); ok {
|
||||
if url, ok := upstreamMap["url"].(string); ok {
|
||||
adapter.Spec.Upstream.URL = url
|
||||
// Sync: add/update all found, registry handles dedup
|
||||
for i := range list.Items {
|
||||
item := &list.Items[i]
|
||||
adapter := &ServiceAdapter{
|
||||
Name: item.Metadata.Name,
|
||||
Namespace: item.Metadata.Namespace,
|
||||
ServiceName: item.Spec.ServiceName,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
if timeout, ok := upstreamMap["timeoutSeconds"].(float64); ok {
|
||||
adapter.Spec.Upstream.TimeoutSeconds = int32(timeout)
|
||||
adapter.Spec.Upstream.URL = item.Spec.Upstream.URL
|
||||
adapter.Spec.Upstream.TimeoutSeconds = item.Spec.Upstream.TimeoutSeconds
|
||||
adapter.Spec.Auth.Required = item.Spec.Auth.Required
|
||||
adapter.Spec.Auth.Capability = item.Spec.Auth.Capability
|
||||
|
||||
if existing := l.registry.Get(item.Spec.ServiceName); existing != nil {
|
||||
l.registry.Update(adapter)
|
||||
} else {
|
||||
l.registry.Add(adapter)
|
||||
log.Printf("serviceadapter loaded: %s → %s", item.Spec.ServiceName, item.Spec.Upstream.URL)
|
||||
}
|
||||
}
|
||||
|
||||
if authMap, ok := spec["auth"].(map[string]interface{}); ok {
|
||||
if required, ok := authMap["required"].(bool); ok {
|
||||
adapter.Spec.Auth.Required = required
|
||||
}
|
||||
if capability, ok := authMap["capability"].(string); ok {
|
||||
adapter.Spec.Auth.Capability = capability
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return adapter, nil
|
||||
// Minimal JSON structs for the k8s list response — no client-go needed.
|
||||
type crList struct {
|
||||
Items []crItem `json:"items"`
|
||||
}
|
||||
|
||||
type crItem struct {
|
||||
Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
} `json:"metadata"`
|
||||
Spec struct {
|
||||
ServiceName string `json:"serviceName"`
|
||||
Upstream struct {
|
||||
URL string `json:"url"`
|
||||
TimeoutSeconds int32 `json:"timeoutSeconds"`
|
||||
} `json:"upstream"`
|
||||
Auth struct {
|
||||
Required bool `json:"required"`
|
||||
Capability string `json:"capability"`
|
||||
} `json:"auth"`
|
||||
} `json:"spec"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user