feat: Production readiness improvements for WHOOSH council formation
Major security, observability, and configuration improvements:
## Security Hardening
- Implemented configurable CORS (no more wildcards)
- Added comprehensive auth middleware for admin endpoints
- Enhanced webhook HMAC validation
- Added input validation and rate limiting
- Security headers and CSP policies
## Configuration Management
- Made N8N webhook URL configurable (WHOOSH_N8N_BASE_URL)
- Replaced all hardcoded endpoints with environment variables
- Added feature flags for LLM vs heuristic composition
- Gitea fetch hardening with EAGER_FILTER and FULL_RESCAN options
## API Completeness
- Implemented GetCouncilComposition function
- Added GET /api/v1/councils/{id} endpoint
- Council artifacts API (POST/GET /api/v1/councils/{id}/artifacts)
- /admin/health/details endpoint with component status
- Database lookup for repository URLs (no hardcoded fallbacks)
## Observability & Performance
- Added OpenTelemetry distributed tracing with goal/pulse correlation
- Performance optimization database indexes
- Comprehensive health monitoring
- Enhanced logging and error handling
## Infrastructure
- Production-ready P2P discovery (replaces mock implementation)
- Removed unused Redis configuration
- Enhanced Docker Swarm integration
- Added migration files for performance indexes
## Code Quality
- Comprehensive input validation
- Graceful error handling and failsafe fallbacks
- Backwards compatibility maintained
- Following security best practices
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
339
vendor/go.opentelemetry.io/otel/exporters/jaeger/uploader.go
generated
vendored
Normal file
339
vendor/go.opentelemetry.io/otel/exporters/jaeger/uploader.go
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// 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 jaeger // import "go.opentelemetry.io/otel/exporters/jaeger"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/go-logr/stdr"
|
||||
|
||||
gen "go.opentelemetry.io/otel/exporters/jaeger/internal/gen-go/jaeger"
|
||||
"go.opentelemetry.io/otel/exporters/jaeger/internal/third_party/thrift/lib/go/thrift"
|
||||
)
|
||||
|
||||
// batchUploader send a batch of spans to Jaeger.
|
||||
type batchUploader interface {
|
||||
upload(context.Context, *gen.Batch) error
|
||||
shutdown(context.Context) error
|
||||
}
|
||||
|
||||
// EndpointOption configures a Jaeger endpoint.
|
||||
type EndpointOption interface {
|
||||
newBatchUploader() (batchUploader, error)
|
||||
}
|
||||
|
||||
type endpointOptionFunc func() (batchUploader, error)
|
||||
|
||||
func (fn endpointOptionFunc) newBatchUploader() (batchUploader, error) {
|
||||
return fn()
|
||||
}
|
||||
|
||||
// WithAgentEndpoint configures the Jaeger exporter to send spans to a Jaeger agent
|
||||
// over compact thrift protocol. This will use the following environment variables for
|
||||
// configuration if no explicit option is provided:
|
||||
//
|
||||
// - OTEL_EXPORTER_JAEGER_AGENT_HOST is used for the agent address host
|
||||
// - OTEL_EXPORTER_JAEGER_AGENT_PORT is used for the agent address port
|
||||
//
|
||||
// The passed options will take precedence over any environment variables and default values
|
||||
// will be used if neither are provided.
|
||||
func WithAgentEndpoint(options ...AgentEndpointOption) EndpointOption {
|
||||
return endpointOptionFunc(func() (batchUploader, error) {
|
||||
cfg := agentEndpointConfig{
|
||||
agentClientUDPParams{
|
||||
AttemptReconnecting: true,
|
||||
Host: envOr(envAgentHost, "localhost"),
|
||||
Port: envOr(envAgentPort, "6831"),
|
||||
},
|
||||
}
|
||||
for _, opt := range options {
|
||||
cfg = opt.apply(cfg)
|
||||
}
|
||||
|
||||
client, err := newAgentClientUDP(cfg.agentClientUDPParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &agentUploader{client: client}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// AgentEndpointOption configures a Jaeger agent endpoint.
|
||||
type AgentEndpointOption interface {
|
||||
apply(agentEndpointConfig) agentEndpointConfig
|
||||
}
|
||||
|
||||
type agentEndpointConfig struct {
|
||||
agentClientUDPParams
|
||||
}
|
||||
|
||||
type agentEndpointOptionFunc func(agentEndpointConfig) agentEndpointConfig
|
||||
|
||||
func (fn agentEndpointOptionFunc) apply(cfg agentEndpointConfig) agentEndpointConfig {
|
||||
return fn(cfg)
|
||||
}
|
||||
|
||||
// WithAgentHost sets a host to be used in the agent client endpoint.
|
||||
// This option overrides any value set for the
|
||||
// OTEL_EXPORTER_JAEGER_AGENT_HOST environment variable.
|
||||
// If this option is not passed and the env var is not set, "localhost" will be used by default.
|
||||
func WithAgentHost(host string) AgentEndpointOption {
|
||||
return agentEndpointOptionFunc(func(o agentEndpointConfig) agentEndpointConfig {
|
||||
o.Host = host
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithAgentPort sets a port to be used in the agent client endpoint.
|
||||
// This option overrides any value set for the
|
||||
// OTEL_EXPORTER_JAEGER_AGENT_PORT environment variable.
|
||||
// If this option is not passed and the env var is not set, "6831" will be used by default.
|
||||
func WithAgentPort(port string) AgentEndpointOption {
|
||||
return agentEndpointOptionFunc(func(o agentEndpointConfig) agentEndpointConfig {
|
||||
o.Port = port
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
var emptyLogger = logr.Logger{}
|
||||
|
||||
// WithLogger sets a logger to be used by agent client.
|
||||
// WithLogger and WithLogr will overwrite each other.
|
||||
func WithLogger(logger *log.Logger) AgentEndpointOption {
|
||||
return WithLogr(stdr.New(logger))
|
||||
}
|
||||
|
||||
// WithLogr sets a logr.Logger to be used by agent client.
|
||||
// WithLogr and WithLogger will overwrite each other.
|
||||
func WithLogr(logger logr.Logger) AgentEndpointOption {
|
||||
return agentEndpointOptionFunc(func(o agentEndpointConfig) agentEndpointConfig {
|
||||
o.Logger = logger
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithDisableAttemptReconnecting sets option to disable reconnecting udp client.
|
||||
func WithDisableAttemptReconnecting() AgentEndpointOption {
|
||||
return agentEndpointOptionFunc(func(o agentEndpointConfig) agentEndpointConfig {
|
||||
o.AttemptReconnecting = false
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithAttemptReconnectingInterval sets the interval between attempts to re resolve agent endpoint.
|
||||
func WithAttemptReconnectingInterval(interval time.Duration) AgentEndpointOption {
|
||||
return agentEndpointOptionFunc(func(o agentEndpointConfig) agentEndpointConfig {
|
||||
o.AttemptReconnectInterval = interval
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithMaxPacketSize sets the maximum UDP packet size for transport to the Jaeger agent.
|
||||
func WithMaxPacketSize(size int) AgentEndpointOption {
|
||||
return agentEndpointOptionFunc(func(o agentEndpointConfig) agentEndpointConfig {
|
||||
o.MaxPacketSize = size
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithCollectorEndpoint defines the full URL to the Jaeger HTTP Thrift collector. This will
|
||||
// use the following environment variables for configuration if no explicit option is provided:
|
||||
//
|
||||
// - OTEL_EXPORTER_JAEGER_ENDPOINT is the HTTP endpoint for sending spans directly to a collector.
|
||||
// - OTEL_EXPORTER_JAEGER_USER is the username to be sent as authentication to the collector endpoint.
|
||||
// - OTEL_EXPORTER_JAEGER_PASSWORD is the password to be sent as authentication to the collector endpoint.
|
||||
//
|
||||
// The passed options will take precedence over any environment variables.
|
||||
// If neither values are provided for the endpoint, the default value of "http://localhost:14268/api/traces" will be used.
|
||||
// If neither values are provided for the username or the password, they will not be set since there is no default.
|
||||
func WithCollectorEndpoint(options ...CollectorEndpointOption) EndpointOption {
|
||||
return endpointOptionFunc(func() (batchUploader, error) {
|
||||
cfg := collectorEndpointConfig{
|
||||
endpoint: envOr(envEndpoint, "http://localhost:14268/api/traces"),
|
||||
username: envOr(envUser, ""),
|
||||
password: envOr(envPassword, ""),
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
cfg = opt.apply(cfg)
|
||||
}
|
||||
|
||||
return &collectorUploader{
|
||||
endpoint: cfg.endpoint,
|
||||
username: cfg.username,
|
||||
password: cfg.password,
|
||||
httpClient: cfg.httpClient,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// CollectorEndpointOption configures a Jaeger collector endpoint.
|
||||
type CollectorEndpointOption interface {
|
||||
apply(collectorEndpointConfig) collectorEndpointConfig
|
||||
}
|
||||
|
||||
type collectorEndpointConfig struct {
|
||||
// endpoint for sending spans directly to a collector.
|
||||
endpoint string
|
||||
|
||||
// username to be used for authentication with the collector endpoint.
|
||||
username string
|
||||
|
||||
// password to be used for authentication with the collector endpoint.
|
||||
password string
|
||||
|
||||
// httpClient to be used to make requests to the collector endpoint.
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type collectorEndpointOptionFunc func(collectorEndpointConfig) collectorEndpointConfig
|
||||
|
||||
func (fn collectorEndpointOptionFunc) apply(cfg collectorEndpointConfig) collectorEndpointConfig {
|
||||
return fn(cfg)
|
||||
}
|
||||
|
||||
// WithEndpoint is the URL for the Jaeger collector that spans are sent to.
|
||||
// This option overrides any value set for the
|
||||
// OTEL_EXPORTER_JAEGER_ENDPOINT environment variable.
|
||||
// If this option is not passed and the environment variable is not set,
|
||||
// "http://localhost:14268/api/traces" will be used by default.
|
||||
func WithEndpoint(endpoint string) CollectorEndpointOption {
|
||||
return collectorEndpointOptionFunc(func(o collectorEndpointConfig) collectorEndpointConfig {
|
||||
o.endpoint = endpoint
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithUsername sets the username to be used in the authorization header sent for all requests to the collector.
|
||||
// This option overrides any value set for the
|
||||
// OTEL_EXPORTER_JAEGER_USER environment variable.
|
||||
// If this option is not passed and the environment variable is not set, no username will be set.
|
||||
func WithUsername(username string) CollectorEndpointOption {
|
||||
return collectorEndpointOptionFunc(func(o collectorEndpointConfig) collectorEndpointConfig {
|
||||
o.username = username
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithPassword sets the password to be used in the authorization header sent for all requests to the collector.
|
||||
// This option overrides any value set for the
|
||||
// OTEL_EXPORTER_JAEGER_PASSWORD environment variable.
|
||||
// If this option is not passed and the environment variable is not set, no password will be set.
|
||||
func WithPassword(password string) CollectorEndpointOption {
|
||||
return collectorEndpointOptionFunc(func(o collectorEndpointConfig) collectorEndpointConfig {
|
||||
o.password = password
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// WithHTTPClient sets the http client to be used to make request to the collector endpoint.
|
||||
func WithHTTPClient(client *http.Client) CollectorEndpointOption {
|
||||
return collectorEndpointOptionFunc(func(o collectorEndpointConfig) collectorEndpointConfig {
|
||||
o.httpClient = client
|
||||
return o
|
||||
})
|
||||
}
|
||||
|
||||
// agentUploader implements batchUploader interface sending batches to
|
||||
// Jaeger through the UDP agent.
|
||||
type agentUploader struct {
|
||||
client *agentClientUDP
|
||||
}
|
||||
|
||||
var _ batchUploader = (*agentUploader)(nil)
|
||||
|
||||
func (a *agentUploader) shutdown(ctx context.Context) error {
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- a.client.Close()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Prioritize not blocking the calling thread and just leak the
|
||||
// spawned goroutine to close the client.
|
||||
return ctx.Err()
|
||||
case err := <-done:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (a *agentUploader) upload(ctx context.Context, batch *gen.Batch) error {
|
||||
return a.client.EmitBatch(ctx, batch)
|
||||
}
|
||||
|
||||
// collectorUploader implements batchUploader interface sending batches to
|
||||
// Jaeger through the collector http endpoint.
|
||||
type collectorUploader struct {
|
||||
endpoint string
|
||||
username string
|
||||
password string
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
var _ batchUploader = (*collectorUploader)(nil)
|
||||
|
||||
func (c *collectorUploader) shutdown(ctx context.Context) error {
|
||||
// The Exporter will cancel any active exports and will prevent all
|
||||
// subsequent exports, so nothing to do here.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *collectorUploader) upload(ctx context.Context, batch *gen.Batch) error {
|
||||
body, err := serialize(batch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", c.endpoint, body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.username != "" && c.password != "" {
|
||||
req.SetBasicAuth(c.username, c.password)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-thrift")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
if err = resp.Body.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("failed to upload traces; HTTP status code: %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func serialize(obj thrift.TStruct) (*bytes.Buffer, error) {
|
||||
buf := thrift.NewTMemoryBuffer()
|
||||
if err := obj.Write(context.Background(), thrift.NewTBinaryProtocolConf(buf, &thrift.TConfiguration{})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Buffer, nil
|
||||
}
|
||||
Reference in New Issue
Block a user