diff --git a/.gitignore b/.gitignore index a8ed097..778802e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ tclp - -.idea/ \ No newline at end of file +config.yaml +.idea/ diff --git a/cmd/main.go b/cmd/main.go index 22e5fcd..629d473 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,13 +5,21 @@ import ( "fmt" "log" "net" + "net/http" "os" + "os/signal" "strconv" + "syscall" "temporal-sa/temporal-cloud-proxy/auth" + "temporal-sa/temporal-cloud-proxy/crypto" + "temporal-sa/temporal-cloud-proxy/metrics" "temporal-sa/temporal-cloud-proxy/proxy" "temporal-sa/temporal-cloud-proxy/utils" + "time" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/cli/v2" + "go.opentelemetry.io/otel/attribute" "go.temporal.io/api/workflowservice/v1" "go.temporal.io/sdk/client" "google.golang.org/grpc" @@ -61,12 +69,32 @@ func main() { grpcServer := grpc.NewServer() workflowservice.RegisterWorkflowServiceServer(grpcServer, handler) + // Initialize metrics + metrics.InitPrometheus() + metricsServer := &http.Server{Addr: ":" + strconv.Itoa(cfg.Metrics.Port)} + http.Handle(metrics.DefaultPrometheusPath, promhttp.Handler()) + go func() { + fmt.Printf("Metrics is exposed at %s:%d%s\n", cfg.Server.Host, cfg.Metrics.Port, metrics.DefaultPrometheusPath) + if err := metricsServer.ListenAndServe(); err != http.ErrServerClosed { + log.Printf("metrics server error: %v", err) + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + fmt.Println("\nShutting down gracefully...") + grpcServer.GracefulStop() + os.Exit(0) + }() + lis, err := net.Listen("tcp", cfg.Server.Host+":"+strconv.Itoa(cfg.Server.Port)) if err != nil { return err } - fmt.Printf("listening on %s:%d\n", cfg.Server.Host, cfg.Server.Port) + fmt.Printf("Proxy is listening on %s:%d\n", cfg.Server.Host, cfg.Server.Port) err = grpcServer.Serve(lis) @@ -118,15 +146,42 @@ func configureProxy(proxyConns *proxy.Conn, cfg *utils.Config) error { } } + metricsHandler := metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{ + // Todo: do we need these many attributes? + InitialAttributes: attribute.NewSet( + attribute.String("source", t.Source), + attribute.String("target", t.Target), + attribute.String("namespace", t.Namespace), + attribute.String("auth_type", authType), + attribute.String("encryption_key", t.EncryptionKey), + ), + }) + + // Parse global caching config + var cachingConfig *crypto.CachingConfig + if cfg.Encryption.Caching.MaxCache > 0 || cfg.Encryption.Caching.MaxAge != "" || cfg.Encryption.Caching.MaxUsage > 0 { + cachingConfig = &crypto.CachingConfig{ + MaxCache: cfg.Encryption.Caching.MaxCache, + MaxMessagesUsed: cfg.Encryption.Caching.MaxUsage, + } + if cfg.Encryption.Caching.MaxAge != "" { + if duration, err := time.ParseDuration(cfg.Encryption.Caching.MaxAge); err == nil { + cachingConfig.MaxAge = duration + } + } + } + err := proxyConns.AddConn(proxy.AddConnInput{ - Source: t.Source, - Target: t.Target, - TLSCertPath: t.TLS.CertFile, - TLSKeyPath: t.TLS.KeyFile, - EncryptionKeyID: t.EncryptionKey, - Namespace: t.Namespace, - AuthManager: authManager, - AuthType: authType, + Source: t.Source, + Target: t.Target, + TLSCertPath: t.TLS.CertFile, + TLSKeyPath: t.TLS.KeyFile, + EncryptionKeyID: t.EncryptionKey, + Namespace: t.Namespace, + AuthManager: authManager, + AuthType: authType, + MetricsHandler: metricsHandler, + CryptoCachingConfig: cachingConfig, }) if err != nil { diff --git a/codec/encryption_codec.go b/codec/encryption_codec.go index a45a761..b21ec28 100644 --- a/codec/encryption_codec.go +++ b/codec/encryption_codec.go @@ -6,10 +6,12 @@ import ( "time" "temporal-sa/temporal-cloud-proxy/crypto" + "temporal-sa/temporal-cloud-proxy/metrics" "github.com/aws/aws-sdk-go/service/kms" commonpb "go.temporal.io/api/common/v1" + "go.temporal.io/sdk/client" "go.temporal.io/sdk/converter" ) @@ -29,13 +31,29 @@ const ( // Codec implements PayloadCodec using the crypto package's cached material manager. type Codec struct { - KeyID string - Cipher *crypto.Cipher - CodecContext map[string]string + KeyID string + Cipher *crypto.Cipher + CodecContext map[string]string + MetricsHandler client.MetricsHandler } -// NewEncryptionCodec creates a new encryption codec with the specified key ID, AWS KMS client, and codec context. -func NewEncryptionCodec(kmsClient *kms.KMS, codecContext map[string]string, encryptionKeyID string) converter.PayloadCodec { +// NewEncryptionCodecWithCaching creates a new encryption codec with configurable caching. +func NewEncryptionCodecWithCaching( + kmsClient *kms.KMS, + codecContext map[string]string, + encryptionKeyID string, + metricsHandler client.MetricsHandler, + cachingConfig *crypto.CachingConfig, +) converter.PayloadCodec { + // Set default caching config if not provided + if cachingConfig == nil { + cachingConfig = &crypto.CachingConfig{ + MaxCache: 100, + MaxAge: 5 * time.Minute, + MaxMessagesUsed: 100, + } + } + // Create AWS KMS provider awsProvider := crypto.NewAWSKMSProvider(kmsClient, crypto.KMSOptions{ KeyID: encryptionKeyID, @@ -45,18 +63,18 @@ func NewEncryptionCodec(kmsClient *kms.KMS, codecContext map[string]string, encr // Create caching materials manager cachingMM, _ := crypto.NewCachingMaterialsManager( awsProvider, - 100, // maxCache - 5*time.Minute, // maxAge - 1000, // maxMessagesUsed + *cachingConfig, + metricsHandler, ) // Create cipher with caching materials manager cipher := crypto.NewCipher(cachingMM) return &Codec{ - KeyID: encryptionKeyID, - Cipher: cipher, - CodecContext: codecContext, + KeyID: encryptionKeyID, + Cipher: cipher, + CodecContext: codecContext, + MetricsHandler: metricsHandler, } } @@ -77,10 +95,14 @@ func (e *Codec) createCryptoContext(purpose, encryptionKeyID string, codecContex // Encode implements converter.PayloadCodec.Encode. func (e *Codec) Encode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error) { + start := time.Now() + e.MetricsHandler.Counter(metrics.EncryptRequests).Inc(1) + result := make([]*commonpb.Payload, len(payloads)) for i, p := range payloads { origBytes, err := p.Marshal() if err != nil { + e.MetricsHandler.Counter(metrics.EncryptErrors).Inc(1) return payloads, err } @@ -98,6 +120,7 @@ func (e *Codec) Encode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error ciphertext, encryptedKey, err := e.Cipher.Encrypt(context.Background(), input) if err != nil { + e.MetricsHandler.Counter(metrics.EncryptErrors).Inc(1) return payloads, err } @@ -111,11 +134,16 @@ func (e *Codec) Encode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error } } + e.MetricsHandler.Counter(metrics.EncryptSuccess).Inc(1) + e.MetricsHandler.Timer(metrics.EncryptLatency).Record(time.Since(start)) return result, nil } // Decode implements converter.PayloadCodec.Decode. func (e *Codec) Decode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error) { + start := time.Now() + e.MetricsHandler.Counter(metrics.DecryptRequests).Inc(1) + result := make([]*commonpb.Payload, len(payloads)) for i, p := range payloads { // Only if it's encrypted @@ -126,6 +154,7 @@ func (e *Codec) Decode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error keyID, ok := p.Metadata[MetadataEncryptionKeyID] if !ok { + e.MetricsHandler.Counter(metrics.DecryptErrors).Inc(1) return payloads, fmt.Errorf("no encryption key id") } @@ -135,6 +164,7 @@ func (e *Codec) Decode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error // Get the encrypted key from metadata encryptedKey, ok := p.Metadata[MetadataEncryptedDataKey] if !ok { + e.MetricsHandler.Counter(metrics.DecryptErrors).Inc(1) return payloads, fmt.Errorf("no encrypted key in payload") } @@ -147,15 +177,19 @@ func (e *Codec) Decode(payloads []*commonpb.Payload) ([]*commonpb.Payload, error decrypted, err := e.Cipher.Decrypt(context.Background(), input) if err != nil { + e.MetricsHandler.Counter(metrics.DecryptErrors).Inc(1) return payloads, err } result[i] = &commonpb.Payload{} err = result[i].Unmarshal(decrypted) if err != nil { + e.MetricsHandler.Counter(metrics.DecryptErrors).Inc(1) return payloads, err } } + e.MetricsHandler.Counter(metrics.DecryptSuccess).Inc(1) + e.MetricsHandler.Timer(metrics.DecryptLatency).Record(time.Since(start)) return result, nil } diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 89eeef7..0000000 --- a/config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -server: - port: 7233 - host: "0.0.0.0" - -targets: - - source: "..internal" - target: "..tmprl.cloud:7233" - tls: - cert_file: "/path/to/./tls.crt" - key_file: "/path/to/./tls.key" - encryption_key: "" - namespace: "." - authentication: - type: "spiffe" - config: - trust_domain: "spiffe://example.org/" - endpoint: "unix:///tmp/spire-agent/public/api.sock" - audiences: - - "temporal_cloud_proxy" diff --git a/config.yaml.sample b/config.yaml.sample new file mode 100644 index 0000000..542a687 --- /dev/null +++ b/config.yaml.sample @@ -0,0 +1,40 @@ +server: + port: 7233 + host: "0.0.0.0" +metrics: + port: 9090 +encryption: + caching: + max_cache: 100 + max_age: "10m" + max_usage: 100 +targets: + - source: "..internal" + target: "..tmprl.cloud:7233" + tls: + cert_file: "/path/to/./tls.crt" + key_file: "/path/to/./tls.key" + encryption_key: "" + namespace: "." + authentication: + type: "spiffe" + config: + trust_domain: "spiffe://example.org/" + endpoint: "unix:///tmp/spire-agent/public/api.sock" + audiences: + - "temporal_cloud_proxy" + + - source: "..internal" + target: "..tmprl.cloud:7233" + tls: + cert_file: "/path/to/./tls.crt" + key_file: "/path/to/./tls.key" + encryption_key: "" + namespace: "." + authentication: + type: "spiffe" + config: + trust_domain: "spiffe://example.org/" + endpoint: "unix:///tmp/spire-agent/public/api.sock" + audiences: + - "temporal_cloud_proxy" diff --git a/crypto/benchmark_test.go b/crypto/benchmark_test.go index 2375c02..fdfe4b1 100644 --- a/crypto/benchmark_test.go +++ b/crypto/benchmark_test.go @@ -36,18 +36,24 @@ func setupManagers(b testing.TB) (*CachingMaterialsManager, *CachingMaterialsMan // Create the caching materials manager cachingMM, err := NewCachingMaterialsManager( awsProvider, - MaxCacheSize, - KeyTTL, - MaxKeyUsage, + CachingConfig{ + MaxCache: MaxCacheSize, + MaxAge: KeyTTL, + MaxMessagesUsed: MaxKeyUsage, + }, + nil, // MetricsHandler ) require.NoError(b, err, "Failed to create caching materials manager") // Create a non-caching materials manager noCacheMM, err := NewCachingMaterialsManager( awsProvider, - 1, // minimal cache size - 0, // zero TTL forces refresh - 1, // single use forces refresh + CachingConfig{ + MaxCache: 1, // minimal cache size + MaxAge: 0, // zero TTL forces refresh + MaxMessagesUsed: 1, // single use forces refresh + }, + nil, // MetricsHandler ) require.NoError(b, err, "Failed to create no-cache materials manager") @@ -216,9 +222,12 @@ func TestCachingBehavior(t *testing.T) { // Create the caching materials manager with short TTL for testing cachingMM, err := NewCachingMaterialsManager( awsProvider, - MaxCacheSize, - 100*time.Millisecond, // Very short TTL for testing - 5, // Low usage count for testing + CachingConfig{ + MaxCache: MaxCacheSize, + MaxAge: 100 * time.Millisecond, // Very short TTL for testing + MaxMessagesUsed: 5, // Low usage count for testing + }, + nil, // MetricsHandler ) require.NoError(t, err, "Failed to create caching materials manager") diff --git a/crypto/caching_materials_manager.go b/crypto/caching_materials_manager.go index 58fcfe0..58f778a 100644 --- a/crypto/caching_materials_manager.go +++ b/crypto/caching_materials_manager.go @@ -6,11 +6,20 @@ import ( "fmt" "sort" "sync" + "temporal-sa/temporal-cloud-proxy/metrics" "time" lru "github.com/hashicorp/golang-lru" + "go.temporal.io/sdk/client" ) +// CachingConfig holds configuration for caching materials manager +type CachingConfig struct { + MaxCache int + MaxAge time.Duration + MaxMessagesUsed int +} + // CachingMaterialsManager manages cryptographic materials with caching type CachingMaterialsManager struct { cache *lru.Cache @@ -18,25 +27,30 @@ type CachingMaterialsManager struct { maxAge time.Duration maxMessagesUsed int underlyingMM MaterialsManager + metricsHandler client.MetricsHandler } // NewCachingMaterialsManager creates a new caching materials manager func NewCachingMaterialsManager( underlyingMM MaterialsManager, - maxCache int, - maxAge time.Duration, - maxMessagesUsed int, + config CachingConfig, + metricsHandler client.MetricsHandler, ) (*CachingMaterialsManager, error) { - cache, err := lru.New(maxCache) + cache, err := lru.New(config.MaxCache) if err != nil { return nil, fmt.Errorf("failed to create cache: %v", err) } + if metricsHandler == nil { + metricsHandler = client.MetricsNopHandler + } + return &CachingMaterialsManager{ cache: cache, - maxAge: maxAge, - maxMessagesUsed: maxMessagesUsed, + maxAge: config.MaxAge, + maxMessagesUsed: config.MaxMessagesUsed, underlyingMM: underlyingMM, + metricsHandler: metricsHandler, }, nil } @@ -66,11 +80,16 @@ func (c *CachingMaterialsManager) GetMaterial(ctx context.Context, cryptoCtx Cry c.mutex.Unlock() } - // Get new material from underlying MM + // Get new material from underlying MM with metrics + start := time.Now() + c.metricsHandler.Counter(metrics.MaterialsManagerGetRequests).Inc(1) material, err := c.underlyingMM.GetMaterial(ctx, cryptoCtx) + c.metricsHandler.Timer(metrics.MaterialsManagerGetLatency).Record(time.Since(start)) if err != nil { + c.metricsHandler.Counter(metrics.MaterialsManagerGetErrors).Inc(1) return nil, err } + c.metricsHandler.Counter(metrics.MaterialsManagerGetSuccess).Inc(1) // Initialize usage metadata material.CreatedAt = time.Now() @@ -111,11 +130,16 @@ func (c *CachingMaterialsManager) DecryptMaterial(ctx context.Context, cryptoCtx c.mutex.Unlock() } - // Get new material from underlying MM + // Get new material from underlying MM with metrics + start := time.Now() + c.metricsHandler.Counter(metrics.MaterialsManagerDecryptRequests).Inc(1) decryptedMaterial, err := c.underlyingMM.DecryptMaterial(ctx, cryptoCtx, material) + c.metricsHandler.Timer(metrics.MaterialsManagerDecryptLatency).Record(time.Since(start)) if err != nil { + c.metricsHandler.Counter(metrics.MaterialsManagerDecryptErrors).Inc(1) return nil, err } + c.metricsHandler.Counter(metrics.MaterialsManagerDecryptSuccess).Inc(1) // Initialize usage metadata decryptedMaterial.CreatedAt = time.Now() diff --git a/crypto/materials_manager_test.go b/crypto/materials_manager_test.go index 7b1f199..4361ebe 100644 --- a/crypto/materials_manager_test.go +++ b/crypto/materials_manager_test.go @@ -42,9 +42,12 @@ func TestCachingMaterialsManager_GetMaterial(t *testing.T) { mockMM := NewMockMaterialsManager() cachingMM, err := NewCachingMaterialsManager( mockMM, - 10, // maxCache - 5*time.Minute, // maxAge - 5, // maxMessagesUsed + CachingConfig{ + MaxCache: 10, + MaxAge: 5 * time.Minute, + MaxMessagesUsed: 5, + }, + nil, // MetricsHandler ) require.NoError(t, err, "Failed to create caching materials manager") @@ -72,9 +75,12 @@ func TestCachingMaterialsManager_MaterialExpiration(t *testing.T) { mockMM := NewMockMaterialsManager() cachingMM, err := NewCachingMaterialsManager( mockMM, - 10, // maxCache - 50*time.Millisecond, // very short maxAge for testing - 100, // high maxMessagesUsed to focus on age + CachingConfig{ + MaxCache: 10, + MaxAge: 50 * time.Millisecond, // very short maxAge for testing + MaxMessagesUsed: 100, // high maxMessagesUsed to focus on age + }, + nil, // MetricsHandler ) require.NoError(t, err, "Failed to create caching materials manager") @@ -108,9 +114,12 @@ func TestCachingMaterialsManager_UsageLimit(t *testing.T) { maxUsage := 3 cachingMM, err := NewCachingMaterialsManager( mockMM, - 10, // maxCache - 5*time.Minute, // long maxAge to focus on usage count - maxUsage, // low maxMessagesUsed for testing + CachingConfig{ + MaxCache: 10, + MaxAge: 5 * time.Minute, // long maxAge to focus on usage count + MaxMessagesUsed: maxUsage, // low maxMessagesUsed for testing + }, + nil, // MetricsHandler ) require.NoError(t, err, "Failed to create caching materials manager") diff --git a/crypto/usage_count_test.go b/crypto/usage_count_test.go index 1517f26..e5a269d 100644 --- a/crypto/usage_count_test.go +++ b/crypto/usage_count_test.go @@ -32,9 +32,12 @@ func TestUsageCount(t *testing.T) { maxUsage := 3 cachingMM, err := NewCachingMaterialsManager( awsProvider, - MaxCacheSize, - 1*time.Hour, // Long TTL to focus on usage count - maxUsage, // Low usage count for testing + CachingConfig{ + MaxCache: MaxCacheSize, + MaxAge: 1 * time.Hour, // Long TTL to focus on usage count + MaxMessagesUsed: maxUsage, // Low usage count for testing + }, + nil, // MetricsHandler ) require.NoError(t, err, "Failed to create caching materials manager") @@ -95,9 +98,12 @@ func TestDecryptionWithDecryptMaterial(t *testing.T) { maxUsage := 3 // This should not affect decryption cachingMM, err := NewCachingMaterialsManager( awsProvider, - MaxCacheSize, - 1*time.Hour, // Long TTL to focus on usage count - maxUsage, // Low usage count for testing + CachingConfig{ + MaxCache: MaxCacheSize, + MaxAge: 1 * time.Hour, // Long TTL to focus on usage count + MaxMessagesUsed: maxUsage, // Low usage count for testing + }, + nil, // MetricsHandler ) require.NoError(t, err, "Failed to create caching materials manager") diff --git a/dashboards/grafana-dashboard.json b/dashboards/grafana-dashboard.json new file mode 100644 index 0000000..d985443 --- /dev/null +++ b/dashboards/grafana-dashboard.json @@ -0,0 +1,901 @@ +{ + "uid": "d3bbab29-8cd6-4eb9-906e-bc753a26d195", + "version": 2, + "title": "Temporal Cloud Proxy", + "tags": ["temporal", "proxy", "encryption"], + "timezone": "browser", + "templating": { + "list": [ + { + "name": "namespace", + "type": "query", + "query": "label_values(temporal_cloud_proxy_encrypt_requests, namespace)", + "multi": true, + "includeAll": true, + "allValue": ".*" + }, + { + "name": "source", + "type": "query", + "query": "label_values(temporal_cloud_proxy_encrypt_requests, source)", + "multi": true, + "includeAll": true, + "allValue": ".*" + }, + { + "name": "target", + "type": "query", + "query": "label_values(temporal_cloud_proxy_encrypt_requests, target)", + "multi": true, + "includeAll": true, + "allValue": ".*" + } + ] + }, + "panels": [ + { + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "expr": "rate(temporal_cloud_proxy_encrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m])", + "legendFormat": "Encrypt Requests/sec", + "refId": "A" + }, + { + "expr": "rate(temporal_cloud_proxy_decrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m])", + "legendFormat": "Decrypt Requests/sec", + "refId": "B" + } + ], + "title": "Encryption/Decryption Request Rate", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "expr": "temporal_cloud_proxy_encrypt_success{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} / temporal_cloud_proxy_encrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} * 100", + "legendFormat": "Encrypt Success %", + "refId": "A" + }, + { + "expr": "temporal_cloud_proxy_decrypt_success{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} / temporal_cloud_proxy_decrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} * 100", + "legendFormat": "Decrypt Success %", + "refId": "B" + } + ], + "title": "Success Rate", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 3, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "histogram_quantile(0.50, rate(temporal_cloud_proxy_encrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, rate(temporal_cloud_proxy_encrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p95", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, rate(temporal_cloud_proxy_encrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p99", + "refId": "C" + } + ], + "title": "Encryption Latency", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "histogram_quantile(0.50, rate(temporal_cloud_proxy_decrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, rate(temporal_cloud_proxy_decrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p95", + "refId": "B" + }, + { + "expr": "histogram_quantile(0.99, rate(temporal_cloud_proxy_decrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p99", + "refId": "C" + } + ], + "title": "Decryption Latency", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "rate(temporal_cloud_proxy_encrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m])", + "legendFormat": "{{namespace}} - Encrypt", + "refId": "A" + }, + { + "expr": "rate(temporal_cloud_proxy_decrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m])", + "legendFormat": "{{namespace}} - Decrypt", + "refId": "B" + } + ], + "title": "Request Volume by Namespace", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "expr": "rate(temporal_cloud_proxy_materials_manager_get_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m])", + "legendFormat": "Get Requests/sec", + "refId": "A" + }, + { + "expr": "rate(temporal_cloud_proxy_materials_manager_decrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m])", + "legendFormat": "Decrypt Requests/sec", + "refId": "B" + } + ], + "title": "Materials Manager Request Rate", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 32 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.0.3", + "targets": [ + { + "expr": "temporal_cloud_proxy_materials_manager_get_success{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} / temporal_cloud_proxy_materials_manager_get_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} * 100", + "legendFormat": "Get Success %", + "refId": "A" + }, + { + "expr": "temporal_cloud_proxy_materials_manager_decrypt_success{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} / temporal_cloud_proxy_materials_manager_decrypt_requests{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"} * 100", + "legendFormat": "Decrypt Success %", + "refId": "B" + } + ], + "title": "Materials Manager Success Rate", + "type": "stat" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 40 + }, + "id": 5, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "histogram_quantile(0.50, rate(temporal_cloud_proxy_materials_manager_get_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, rate(temporal_cloud_proxy_materials_manager_get_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p95", + "refId": "B" + } + ], + "title": "Materials Manager Get Latency", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 40 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "histogram_quantile(0.50, rate(temporal_cloud_proxy_materials_manager_decrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p50", + "refId": "A" + }, + { + "expr": "histogram_quantile(0.95, rate(temporal_cloud_proxy_materials_manager_decrypt_latency_seconds_bucket{namespace=~\"$namespace\",source=~\"$source\",target=~\"$target\"}[5m]))", + "legendFormat": "p95", + "refId": "B" + } + ], + "title": "Materials Manager Decrypt Latency", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 48 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "rate(process_cpu_seconds_total[5m]) * 100", + "legendFormat": "CPU %", + "refId": "A" + } + ], + "title": "CPU Usage", + "type": "timeseries" + }, + { + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 48 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "expr": "process_resident_memory_bytes", + "legendFormat": "RSS Memory", + "refId": "A" + }, + { + "expr": "go_memstats_heap_alloc_bytes", + "legendFormat": "Heap Allocated", + "refId": "B" + } + ], + "title": "Memory Usage", + "type": "timeseries" + } + ], + "time": { + "from": "now-1h", + "to": "now" + }, + "refresh": "30s", + "schemaVersion": 30, + "version": 1 +} diff --git a/go.mod b/go.mod index 80f3d41..969f06c 100644 --- a/go.mod +++ b/go.mod @@ -9,43 +9,61 @@ require ( github.com/hashicorp/golang-lru v1.0.2 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.27.6 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/exporters/prometheus v0.59.0 + go.opentelemetry.io/otel/metric v1.37.0 + go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/sdk/metric v1.37.0 go.temporal.io/api v1.47.0 go.temporal.io/sdk v1.34.0 - google.golang.org/grpc v1.72.0 + go.temporal.io/sdk/contrib/opentelemetry v0.6.0 + google.golang.org/grpc v1.73.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/go-jose/go-jose/v4 v4.0.4 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect github.com/zeebo/errs v1.4.0 // indirect - golang.org/x/crypto v0.37.0 // indirect + golang.org/x/crypto v0.39.0 // indirect ) require ( cloud.google.com/go/longrunning v0.6.7 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/nexus-rpc/sdk-go v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 github.com/stretchr/objx v0.5.2 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/text v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.11.0 // indirect google.golang.org/api v0.232.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/go.sum b/go.sum index e16c44a..830ffaa 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,16 @@ cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= @@ -21,12 +27,13 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 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/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -50,8 +57,8 @@ github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3 github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -60,6 +67,8 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -68,17 +77,29 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +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/sdk-go v0.3.0 h1:Y3B0kLYbMhd4C2u00kcYajvmOrfozEtTV/nHSnV57jA= github.com/nexus-rpc/sdk-go v0.3.0/go.mod h1:TpfkM2Cw0Rlk9drGkoiSMpFqflKTiQLWUNyKJjF8mKQ= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 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.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -105,20 +126,24 @@ github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo= +go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.temporal.io/api v1.47.0 h1:0pg8wZC9Jv79iMpe6jXMPQzADQJ5OiPuklYfC51bXGM= go.temporal.io/api v1.47.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= go.temporal.io/sdk v1.34.0 h1:VLg/h6ny7GvLFVoQPqz2NcC93V9yXboQwblkRvZ1cZE= go.temporal.io/sdk v1.34.0/go.mod h1:iE4U5vFrH3asOhqpBBphpj9zNtw8btp8+MSaf5A0D3w= +go.temporal.io/sdk/contrib/opentelemetry v0.6.0 h1:rNBArDj5iTUkcMwKocUShoAW59o6HdS7Nq4CTp4ldj8= +go.temporal.io/sdk/contrib/opentelemetry v0.6.0/go.mod h1:Lem8VrE2ks8P+FYcRM3UphPoBr+tfM3v/Kaf0qStzSg= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= @@ -126,8 +151,8 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= 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= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -145,8 +170,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -154,8 +179,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -165,13 +190,13 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -197,17 +222,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= -google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= -google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -215,8 +240,9 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/metrics/constants.go b/metrics/constants.go new file mode 100644 index 0000000..9203bed --- /dev/null +++ b/metrics/constants.go @@ -0,0 +1,31 @@ +package metrics + +const ( + DefaultPrometheusPath = "/metrics" + + TemporalProxyPrefix = "temporal_cloud_proxy_" + + // Encryption metrics + EncryptLatency = TemporalProxyPrefix + "encrypt_latency" + EncryptRequests = TemporalProxyPrefix + "encrypt_requests" + EncryptErrors = TemporalProxyPrefix + "encrypt_errors" + EncryptSuccess = TemporalProxyPrefix + "encrypt_success" + + // Decryption metrics + DecryptLatency = TemporalProxyPrefix + "decrypt_latency" + DecryptRequests = TemporalProxyPrefix + "decrypt_requests" + DecryptErrors = TemporalProxyPrefix + "decrypt_errors" + DecryptSuccess = TemporalProxyPrefix + "decrypt_success" + + // Materials manager get metrics + MaterialsManagerGetLatency = TemporalProxyPrefix + "materials_manager_get_latency" + MaterialsManagerGetRequests = TemporalProxyPrefix + "materials_manager_get_requests" + MaterialsManagerGetErrors = TemporalProxyPrefix + "materials_manager_get_errors" + MaterialsManagerGetSuccess = TemporalProxyPrefix + "materials_manager_get_success" + + // Materials manager decrypt metrics + MaterialsManagerDecryptLatency = TemporalProxyPrefix + "materials_manager_decrypt_latency" + MaterialsManagerDecryptRequests = TemporalProxyPrefix + "materials_manager_decrypt_requests" + MaterialsManagerDecryptErrors = TemporalProxyPrefix + "materials_manager_decrypt_errors" + MaterialsManagerDecryptSuccess = TemporalProxyPrefix + "materials_manager_decrypt_success" +) diff --git a/metrics/metrics_handler.go b/metrics/metrics_handler.go new file mode 100644 index 0000000..8daf3a7 --- /dev/null +++ b/metrics/metrics_handler.go @@ -0,0 +1,148 @@ +package metrics + +import ( + "context" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.temporal.io/sdk/client" +) + +const TemporalCloudMeterName = "temporal-cloud-proxy" + +var _ client.MetricsHandler = MetricsHandler{} + +// MetricsHandler is an implementation of client.MetricsHandler +// for open telemetry. +type MetricsHandler struct { + meter metric.Meter + attributes attribute.Set + onError func(error) +} + +// MetricsHandlerOptions are options provided to NewMetricsHandler. +type MetricsHandlerOptions struct { + // Meter is the Meter to use. If not set, one is obtained from the global + // meter provider using the name "temporal-sdk-go". + Meter metric.Meter + // InitialAttributes to set on the handler + // + // Optional: Defaults to the empty set. + InitialAttributes attribute.Set + // OnError Callback to invoke if the provided meter returns an error. + // + // Optional: Defaults to panicking on any error. + OnError func(error) +} + +// CounterFunc implements Counter with a single function. +type CounterFunc func(int64) + +// Inc implements Counter.Inc. +func (c CounterFunc) Inc(d int64) { c(d) } + +// GaugeFunc implements Gauge with a single function. +type GaugeFunc func(float64) + +// Update implements Gauge.Update. +func (g GaugeFunc) Update(d float64) { g(d) } + +// TimerFunc implements Timer with a single function. +type TimerFunc func(time.Duration) + +// Record implements Timer.Record. +func (t TimerFunc) Record(d time.Duration) { t(d) } + +// NewMetricsHandler returns a client.MetricsHandler that is backed by the given Meter +func NewMetricsHandler(options MetricsHandlerOptions) MetricsHandler { + if options.Meter == nil { + options.Meter = otel.GetMeterProvider().Meter(TemporalCloudMeterName) + } + if options.OnError == nil { + options.OnError = func(err error) { panic(err) } + } + return MetricsHandler{ + meter: options.Meter, + attributes: options.InitialAttributes, + onError: options.OnError, + } +} + +// ExtractMetricsHandler gets the underlying Open Telemetry MetricsHandler from a MetricsHandler +// if any is present. +// +// Raw use of the MetricHandler is discouraged but may be used for Histograms or other +// advanced features. This scope does not skip metrics during replay like the +// metrics handler does. Therefore the caller should check replay state. +func ExtractMetricsHandler(handler client.MetricsHandler) *MetricsHandler { + // Continually unwrap until we find an instance of our own handler + for { + otelHandler, ok := handler.(MetricsHandler) + if ok { + return &otelHandler + } + // If unwrappable, do so, otherwise return noop + unwrappable, _ := handler.(interface{ Unwrap() client.MetricsHandler }) + if unwrappable == nil { + return nil + } + handler = unwrappable.Unwrap() + } +} + +// GetMeter returns the meter used by this handler. +func (m MetricsHandler) GetMeter() metric.Meter { + return m.meter +} + +// GetAttributes returns the attributes set on this handler. +func (m MetricsHandler) GetAttributes() attribute.Set { + return m.attributes +} + +func (m MetricsHandler) WithTags(tags map[string]string) client.MetricsHandler { + attributes := m.attributes.ToSlice() + for k, v := range tags { + attributes = append(attributes, attribute.String(k, v)) + } + return MetricsHandler{ + meter: m.meter, + attributes: attribute.NewSet(attributes...), + onError: m.onError, + } +} + +func (m MetricsHandler) Counter(name string) client.MetricsCounter { + c, err := m.meter.Int64UpDownCounter(name) + if err != nil { + m.onError(err) + return client.MetricsNopHandler.Counter(name) + } + return CounterFunc(func(d int64) { + c.Add(context.Background(), d, metric.WithAttributeSet(m.attributes)) + }) +} + +func (m MetricsHandler) Gauge(name string) client.MetricsGauge { + g, err := m.meter.Float64Gauge(name) + if err != nil { + m.onError(err) + return client.MetricsNopHandler.Gauge(name) + } + return GaugeFunc(func(f float64) { + g.Record(context.Background(), f, metric.WithAttributeSet(m.attributes)) + }) +} + +func (m MetricsHandler) Timer(name string) client.MetricsTimer { + h, err := m.meter.Float64Histogram(name, metric.WithUnit("s")) + if err != nil { + m.onError(err) + return client.MetricsNopHandler.Timer(name) + } + return TimerFunc(func(t time.Duration) { + h.Record(context.Background(), t.Seconds(), metric.WithAttributeSet(m.attributes)) + }) +} diff --git a/metrics/metrics_handler_test.go b/metrics/metrics_handler_test.go new file mode 100644 index 0000000..a2ebc54 --- /dev/null +++ b/metrics/metrics_handler_test.go @@ -0,0 +1,192 @@ +package metrics_test + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + + "go.temporal.io/sdk/contrib/opentelemetry" +) + +func TestTags(t *testing.T) { + ctx := context.Background() + metricReader := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader)) + handler := opentelemetry.NewMetricsHandler( + opentelemetry.MetricsHandlerOptions{ + Meter: meterProvider.Meter("test"), + }, + ) + handlerWithTag := handler.WithTags(map[string]string{"tag1": "value1"}) + // Emit some values with multiple tags + handlerWithTag.WithTags(map[string]string{"tag2": "value2"}).Counter("testCounter").Inc(1) + handlerWithTag.Counter("testCounter").Inc(1) + // Assert result + var rm metricdata.ResourceMetrics + metricReader.Collect(ctx, &rm) + assert.Len(t, rm.ScopeMetrics, 1) + metrics := rm.ScopeMetrics[0].Metrics + assert.Len(t, metrics, 1) + want := metricdata.Metrics{ + Name: "testCounter", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{ + { + Attributes: attribute.NewSet(attribute.String("tag1", "value1")), + }, + { + Attributes: attribute.NewSet(attribute.String("tag1", "value1"), attribute.String("tag2", "value2")), + }, + }, + }, + } + metricdatatest.AssertEqual(t, want, metrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) +} + +func TestCounterHandler(t *testing.T) { + ctx := context.Background() + metricReader := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader)) + handler := opentelemetry.NewMetricsHandler( + opentelemetry.MetricsHandlerOptions{ + Meter: meterProvider.Meter("test"), + }, + ) + // Emit some values + testCounter := handler.WithTags(map[string]string{"tag1": "value1"}).Counter("testCounter") + testCounter.Inc(1) + testCounter.Inc(1) + testCounter.Inc(-1) + // Emit some values with different tags + testCounter2 := handler.WithTags(map[string]string{"tag1": "value2"}).Counter("testCounter") + testCounter2.Inc(5) + // Assert result + var rm metricdata.ResourceMetrics + metricReader.Collect(ctx, &rm) + assert.Len(t, rm.ScopeMetrics, 1) + metrics := rm.ScopeMetrics[0].Metrics + assert.Len(t, metrics, 1) + want := metricdata.Metrics{ + Name: "testCounter", + Data: metricdata.Sum[int64]{ + Temporality: metricdata.CumulativeTemporality, + IsMonotonic: false, + DataPoints: []metricdata.DataPoint[int64]{ + { + Value: 1, + Attributes: attribute.NewSet(attribute.String("tag1", "value1")), + }, + { + Value: 5, + Attributes: attribute.NewSet(attribute.String("tag1", "value2")), + }, + }, + }, + } + metricdatatest.AssertEqual(t, want, metrics[0], metricdatatest.IgnoreTimestamp()) +} + +func TestGaugeHandler(t *testing.T) { + ctx := context.Background() + metricReader := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader)) + handler := opentelemetry.NewMetricsHandler( + opentelemetry.MetricsHandlerOptions{ + Meter: meterProvider.Meter("test"), + }, + ) + + // Emit some values + testGauge := handler.WithTags(map[string]string{"tag1": "value1"}).Gauge("testGauge") + testGauge.Update(1) + testGauge.Update(5) + testGauge.Update(100) + // Emit some values with different tags + testGauge2 := handler.WithTags(map[string]string{"tag1": "value2"}).Gauge("testGauge") + testGauge2.Update(1000) + // Create a gauge, but don't set a value + _ = handler.Gauge("testGaugeNoValue") + + // Assert result + var rm metricdata.ResourceMetrics + metricReader.Collect(ctx, &rm) + assert.Len(t, rm.ScopeMetrics, 1) + metrics := rm.ScopeMetrics[0].Metrics + assert.Len(t, metrics, 1) + want := metricdata.Metrics{ + Name: "testGauge", + Data: metricdata.Gauge[float64]{ + DataPoints: []metricdata.DataPoint[float64]{ + { + Value: 100, + Attributes: attribute.NewSet(attribute.String("tag1", "value1")), + }, + { + Value: 1000, + Attributes: attribute.NewSet(attribute.String("tag1", "value2")), + }, + }, + }, + } + metricdatatest.AssertEqual(t, want, metrics[0], metricdatatest.IgnoreTimestamp()) +} + +func TestTimerHandler(t *testing.T) { + ctx := context.Background() + metricReader := metric.NewManualReader() + meterProvider := metric.NewMeterProvider(metric.WithReader(metricReader)) + handler := opentelemetry.NewMetricsHandler( + opentelemetry.MetricsHandlerOptions{ + Meter: meterProvider.Meter("test"), + }, + ) + testTimer := handler.WithTags(map[string]string{"tag1": "value1"}).Timer("testTimer") + testTimer.Record(time.Millisecond) + testTimer.Record(time.Second) + testTimer.Record(time.Hour) + // Emit some values with different tags + testTimer2 := handler.WithTags(map[string]string{"tag1": "value2"}).Timer("testTimer") + testTimer2.Record(time.Millisecond) + + var rm metricdata.ResourceMetrics + metricReader.Collect(ctx, &rm) + assert.Len(t, rm.ScopeMetrics, 1) + metrics := rm.ScopeMetrics[0].Metrics + assert.Len(t, metrics, 1) + want := metricdata.Metrics{ + Name: "testTimer", + Unit: "s", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Count: 3, + Sum: 3601.001, + Min: metricdata.NewExtrema(time.Millisecond.Seconds()), + Max: metricdata.NewExtrema(time.Hour.Seconds()), + Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, + BucketCounts: []uint64{0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + Attributes: attribute.NewSet(attribute.String("tag1", "value1")), + }, + { + Count: 1, + Sum: 0.001, + Min: metricdata.NewExtrema(time.Millisecond.Seconds()), + Max: metricdata.NewExtrema(time.Millisecond.Seconds()), + Bounds: []float64{0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000}, + BucketCounts: []uint64{0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + Attributes: attribute.NewSet(attribute.String("tag1", "value2")), + }, + }, + }, + } + metricdatatest.AssertEqual(t, want, metrics[0], metricdatatest.IgnoreTimestamp()) +} diff --git a/metrics/prometheus.go b/metrics/prometheus.go new file mode 100644 index 0000000..8ca417f --- /dev/null +++ b/metrics/prometheus.go @@ -0,0 +1,51 @@ +package metrics + +import ( + "fmt" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/sdk/metric" +) + +func InitPrometheus() (*metric.MeterProvider, error) { + exporter, err := prometheus.New() + if err != nil { + return nil, fmt.Errorf("failed to initialize prometheus exporter: %w", err) + } + + // Custom histogram buckets with millisecond precision + histogramView := metric.NewView( + metric.Instrument{Kind: metric.InstrumentKindHistogram}, + metric.Stream{ + Aggregation: metric.AggregationExplicitBucketHistogram{ + Boundaries: []float64{ + 0.00001, // 10 microseconds + 0.00005, // 50 microseconds + 0.0001, // 100 microseconds + 0.0005, // 500 microseconds + 0.001, // 1 millisecond + 0.005, // 5 milliseconds + 0.01, // 10 milliseconds + 0.025, // 25 milliseconds + 0.05, // 50 milliseconds + 0.1, // 100 milliseconds + 0.25, // 250 milliseconds + 0.5, // 500 milliseconds + 1.0, // 1 second + 2.5, // 2.5 seconds + 5.0, // 5 seconds + 10.0, // 10 seconds + }, + }, + }, + ) + + provider := metric.NewMeterProvider( + metric.WithReader(exporter), + metric.WithView(histogramView), + ) + otel.SetMeterProvider(provider) + + return provider, nil +} diff --git a/proxy/proxy.go b/proxy/proxy.go index 60ebf2c..3ca4fd9 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -5,17 +5,20 @@ import ( "crypto/tls" "errors" "fmt" - "go.temporal.io/sdk/converter" "net" "os" "sync" "temporal-sa/temporal-cloud-proxy/codec" + "temporal-sa/temporal-cloud-proxy/crypto" + + "go.temporal.io/sdk/converter" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "temporal-sa/temporal-cloud-proxy/auth" + "temporal-sa/temporal-cloud-proxy/metrics" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -58,14 +61,16 @@ func createKMSClient() *kms.KMS { // AddConnInput contains parameters for adding a new connection type AddConnInput struct { - Source string - Target string - TLSCertPath string - TLSKeyPath string - EncryptionKeyID string - Namespace string - AuthManager *auth.AuthManager - AuthType string + Source string + Target string + TLSCertPath string + TLSKeyPath string + EncryptionKeyID string + Namespace string + AuthManager *auth.AuthManager + AuthType string + MetricsHandler metrics.MetricsHandler + CryptoCachingConfig *crypto.CachingConfig } // AddConn adds a new connection to the proxy @@ -86,7 +91,13 @@ func (mc *Conn) AddConn(input AddConnInput) error { clientInterceptor, err := converter.NewPayloadCodecGRPCClientInterceptor( converter.PayloadCodecGRPCClientInterceptorOptions{ - Codecs: []converter.PayloadCodec{codec.NewEncryptionCodec(kmsClient, codecContext, input.EncryptionKeyID)}, + Codecs: []converter.PayloadCodec{codec.NewEncryptionCodecWithCaching( + kmsClient, + codecContext, + input.EncryptionKeyID, + input.MetricsHandler, + input.CryptoCachingConfig, + )}, }, ) if err != nil { diff --git a/utils/config.go b/utils/config.go index decb44b..90ba547 100644 --- a/utils/config.go +++ b/utils/config.go @@ -1,8 +1,10 @@ package utils type Config struct { - Server ServerConfig `yaml:"server"` - Targets []TargetConfig `yaml:"targets"` + Server ServerConfig `yaml:"server"` + Metrics MetricsConfig `yaml:"metrics"` + Encryption EncryptionConfig `yaml:"encryption"` + Targets []TargetConfig `yaml:"targets"` } type ServerConfig struct { @@ -10,6 +12,20 @@ type ServerConfig struct { Host string `yaml:"host"` } +type MetricsConfig struct { + Port int `yaml:"port"` +} + +type EncryptionConfig struct { + Caching CachingConfig `yaml:"caching"` +} + +type CachingConfig struct { + MaxCache int `yaml:"max_cache,omitempty"` + MaxAge string `yaml:"max_age,omitempty"` + MaxUsage int `yaml:"max_usage,omitempty"` +} + type TargetConfig struct { Source string `yaml:"source"` Target string `yaml:"target"`