Files
zero-scale-platform/internal/temporal/client.go
Stephen Schuresko 7659c711ac feat: enhance zero-scale dashboard with resource display and update functionality
- Added RAM and CPU resource display in the zero-scale dashboard for active applications.
- Implemented dynamic updates for resource display based on application state.

feat: expand zero-scale control script with new commands and options

- Introduced `apply`, `upload-binary`, and `provision-db` commands to the zero-scale control script.
- Added options for specifying configuration files, binary uploads, and database provisioning parameters.

feat: implement temporal JWKS aggregator service

- Created a new service to aggregate JWKS from Gitea and Kubernetes API.
- Implemented caching and refresh logic for JWKS keys.

feat: deploy shared MySQL and Redis services

- Added Kubernetes manifests for shared MySQL and Redis services, including StatefulSet and ConfigMap definitions.
- Configured persistent storage and resource limits for MySQL and Redis deployments.

feat: add temporal OAuth secret example and values configuration

- Created example secret manifest for OAuth credentials required by Temporal.
- Added a values file for configuring Temporal server with JWKS integration and resource limits.

feat: implement Kubernetes token provider for Temporal client

- Developed a K8sTokenHeadersProvider to supply Kubernetes service account tokens to Temporal.
- Added tests for token retrieval and environment variable fallback.
2026-10-11 17:43:21 -04:00

85 lines
2.0 KiB
Go

package temporal
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
)
const (
defaultK8sTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
)
// K8sTokenHeadersProvider implements Temporal client.HeadersProvider to supply
// the cryptographically signed Kubernetes Projected ServiceAccount token
// to Temporal Server frontend gRPC calls.
type K8sTokenHeadersProvider struct {
tokenPath string
mu sync.RWMutex
cached string
lastRead time.Time
cacheTTL time.Duration
}
// NewK8sTokenHeadersProvider creates a new provider. If customPath is empty,
// it uses the standard in-cluster path.
func NewK8sTokenHeadersProvider(customPath string) *K8sTokenHeadersProvider {
path := defaultK8sTokenPath
if customPath != "" {
path = customPath
}
return &K8sTokenHeadersProvider{
tokenPath: path,
cacheTTL: 1 * time.Minute,
}
}
// GetHeaders returns the authorization header containing the Bearer token.
// Temporal SDK calls this method on gRPC requests.
func (p *K8sTokenHeadersProvider) GetHeaders(ctx context.Context) (map[string]string, error) {
token, err := p.getToken()
if err != nil {
// Fallback for local debugging outside of Kubernetes cluster
if envToken := os.Getenv("TEMPORAL_AUTH_TOKEN"); envToken != "" {
return map[string]string{
"authorization": "Bearer " + strings.TrimSpace(envToken),
}, nil
}
return nil, fmt.Errorf("temporal auth: unable to obtain token: %w", err)
}
return map[string]string{
"authorization": "Bearer " + token,
}, nil
}
func (p *K8sTokenHeadersProvider) getToken() (string, error) {
p.mu.RLock()
if p.cached != "" && time.Since(p.lastRead) < p.cacheTTL {
token := p.cached
p.mu.RUnlock()
return token, nil
}
p.mu.RUnlock()
p.mu.Lock()
defer p.mu.Unlock()
// Double-check after acquiring write lock
if p.cached != "" && time.Since(p.lastRead) < p.cacheTTL {
return p.cached, nil
}
data, err := os.ReadFile(p.tokenPath)
if err != nil {
return "", err
}
p.cached = strings.TrimSpace(string(data))
p.lastRead = time.Now()
return p.cached, nil
}