diff --git a/api/build/approve.go b/api/build/approve.go index 86c491c04..61b1b6a4e 100644 --- a/api/build/approve.go +++ b/api/build/approve.go @@ -3,6 +3,7 @@ package build import ( + "context" "fmt" "net/http" "strings" @@ -110,7 +111,7 @@ func ApproveBuild(c *gin.Context) { // publish the build to the queue go Enqueue( - ctx, + context.WithoutCancel(ctx), queue.FromGinContext(c), database.FromContext(c), models.ToItem(b), diff --git a/api/build/create.go b/api/build/create.go index 8fef779c7..dd9da486b 100644 --- a/api/build/create.go +++ b/api/build/create.go @@ -3,6 +3,7 @@ package build import ( + "context" "fmt" "net/http" @@ -149,7 +150,7 @@ func CreateBuild(c *gin.Context) { // publish the build to the queue go Enqueue( - ctx, + context.WithoutCancel(ctx), queue.FromGinContext(c), database.FromContext(c), item, diff --git a/api/build/restart.go b/api/build/restart.go index aed5fccd4..24b30409d 100644 --- a/api/build/restart.go +++ b/api/build/restart.go @@ -3,6 +3,7 @@ package build import ( + "context" "fmt" "net/http" "strings" @@ -157,7 +158,7 @@ func RestartBuild(c *gin.Context) { // publish the build to the queue go Enqueue( - ctx, + context.WithoutCancel(ctx), queue.FromGinContext(c), database.FromContext(c), item, diff --git a/api/webhook/post.go b/api/webhook/post.go index c854c44d9..0f770021d 100644 --- a/api/webhook/post.go +++ b/api/webhook/post.go @@ -489,8 +489,6 @@ func PostWebhook(c *gin.Context) { } } - c.JSON(http.StatusCreated, b) - // regardless of whether the build is published to queue, we want to attempt to auto-cancel if no errors defer func() { if err == nil && build.ShouldAutoCancel(p.Metadata.AutoCancel, b, repo.GetBranch()) { @@ -531,6 +529,10 @@ func PostWebhook(c *gin.Context) { } }() + // track if we have already responded to the http request + // helps prevent multiple responses to the same request in the event of errors + responded := false + // if the webhook was from a Pull event from a forked repository, verify it is allowed to run if webhook.PullRequest.IsFromFork { l.Tracef("inside %s workflow for fork PR build %s/%d", repo.GetApproveBuild(), repo.GetFullName(), b.GetNumber()) @@ -540,6 +542,8 @@ func PostWebhook(c *gin.Context) { err = gatekeepBuild(c, b, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) + } else { + c.JSON(http.StatusCreated, b) } return @@ -550,6 +554,8 @@ func PostWebhook(c *gin.Context) { err = gatekeepBuild(c, b, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) + } else { + c.JSON(http.StatusCreated, b) } return @@ -564,12 +570,16 @@ func PostWebhook(c *gin.Context) { contributor, err := scm.FromContext(c).RepoContributor(ctx, repo.GetOwner(), b.GetSender(), repo.GetOrg(), repo.GetName()) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) + + responded = true } if !contributor { err = gatekeepBuild(c, b, repo) if err != nil { util.HandleError(c, http.StatusInternalServerError, err) + } else if !responded { + c.JSON(http.StatusCreated, b) } return @@ -591,12 +601,17 @@ func PostWebhook(c *gin.Context) { // publish the build to the queue go build.Enqueue( - ctx, + context.WithoutCancel(ctx), queue.FromGinContext(c), database.FromContext(c), item, b.GetHost(), ) + + // respond only when necessary + if !responded { + c.JSON(http.StatusCreated, b) + } } // handleRepositoryEvent is a helper function that processes repository events from the SCM and updates diff --git a/cmd/vela-server/main.go b/cmd/vela-server/main.go index d5d0c34aa..d1207328f 100644 --- a/cmd/vela-server/main.go +++ b/cmd/vela-server/main.go @@ -17,6 +17,7 @@ import ( "github.com/go-vela/server/queue" "github.com/go-vela/server/scm" "github.com/go-vela/server/secret" + "github.com/go-vela/server/tracing" "github.com/go-vela/server/version" "github.com/go-vela/types/constants" ) @@ -279,6 +280,9 @@ func main() { // Add Source Flags app.Flags = append(app.Flags, scm.Flags...) + // Add Tracing Flags + app.Flags = append(app.Flags, tracing.Flags...) + if err = app.Run(os.Args); err != nil { logrus.Fatal(err) } diff --git a/cmd/vela-server/schedule.go b/cmd/vela-server/schedule.go index 422dcd710..21729bed9 100644 --- a/cmd/vela-server/schedule.go +++ b/cmd/vela-server/schedule.go @@ -226,7 +226,7 @@ func processSchedule(ctx context.Context, s *api.Schedule, settings *settings.Pl // publish the build to the queue go build.Enqueue( - ctx, + context.WithoutCancel(ctx), queue, database, item, diff --git a/cmd/vela-server/scm.go b/cmd/vela-server/scm.go index c8c441442..7124761f0 100644 --- a/cmd/vela-server/scm.go +++ b/cmd/vela-server/scm.go @@ -7,10 +7,11 @@ import ( "github.com/urfave/cli/v2" "github.com/go-vela/server/scm" + "github.com/go-vela/server/tracing" ) // helper function to setup the scm from the CLI arguments. -func setupSCM(c *cli.Context) (scm.Service, error) { +func setupSCM(c *cli.Context, tc *tracing.Client) (scm.Service, error) { logrus.Debug("creating scm client from CLI configuration") // scm configuration @@ -24,6 +25,7 @@ func setupSCM(c *cli.Context) (scm.Service, error) { StatusContext: c.String("scm.context"), WebUIAddress: c.String("webui-addr"), Scopes: c.StringSlice("scm.scopes"), + Tracing: tc, } // setup the scm diff --git a/cmd/vela-server/server.go b/cmd/vela-server/server.go index 43e4cb859..7733b9c15 100644 --- a/cmd/vela-server/server.go +++ b/cmd/vela-server/server.go @@ -26,6 +26,7 @@ import ( "github.com/go-vela/server/queue" "github.com/go-vela/server/router" "github.com/go-vela/server/router/middleware" + "github.com/go-vela/server/tracing" ) //nolint:funlen,gocyclo // ignore function length and cyclomatic complexity @@ -78,7 +79,21 @@ func server(c *cli.Context) error { return err } - database, err := database.FromCLIContext(c) + tc, err := tracing.FromCLIContext(c) + if err != nil { + return err + } + + if tc.EnableTracing { + defer func() { + err := tc.TracerProvider.Shutdown(context.Background()) + if err != nil { + logrus.Errorf("unable to shutdown tracer provider: %v", err) + } + }() + } + + database, err := database.FromCLIContext(c, tc) if err != nil { return err } @@ -93,7 +108,7 @@ func server(c *cli.Context) error { return err } - scm, err := setupSCM(c) + scm, err := setupSCM(c, tc) if err != nil { return err } @@ -189,6 +204,8 @@ func server(c *cli.Context) error { middleware.DefaultRepoEventsMask(c.Int64("default-repo-events-mask")), middleware.DefaultRepoApproveBuild(c.String("default-repo-approve-build")), middleware.ScheduleFrequency(c.Duration("schedule-minimum-frequency")), + middleware.TracingClient(tc), + middleware.TracingInstrumentation(tc), ) addr, err := url.Parse(c.String("server-addr")) diff --git a/database/context.go b/database/context.go index a11863e7d..fe2fe753a 100644 --- a/database/context.go +++ b/database/context.go @@ -7,6 +7,8 @@ import ( "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" + + "github.com/go-vela/server/tracing" ) const key = "database" @@ -38,7 +40,7 @@ func ToContext(c Setter, d Interface) { } // FromCLIContext creates and returns a database engine from the urfave/cli context. -func FromCLIContext(c *cli.Context) (Interface, error) { +func FromCLIContext(c *cli.Context, tc *tracing.Client) (Interface, error) { logrus.Debug("creating database engine from CLI configuration") return New( @@ -54,5 +56,6 @@ func FromCLIContext(c *cli.Context) (Interface, error) { WithLogSlowThreshold(c.Duration("database.log.slow_threshold")), WithLogShowSQL(c.Bool("database.log.show_sql")), WithSkipCreation(c.Bool("database.skip_creation")), + WithTracing(tc), ) } diff --git a/database/context_test.go b/database/context_test.go index d5887f864..b6b12c98e 100644 --- a/database/context_test.go +++ b/database/context_test.go @@ -10,6 +10,8 @@ import ( "github.com/gin-gonic/gin" "github.com/urfave/cli/v2" + + "github.com/go-vela/server/tracing" ) func TestDatabase_FromContext(t *testing.T) { @@ -132,7 +134,7 @@ func TestDatabase_FromCLIContext(t *testing.T) { // run tests for _, test := range tests { t.Run(test.name, func(t *testing.T) { - _, err := FromCLIContext(test.context) + _, err := FromCLIContext(test.context, &tracing.Client{Config: tracing.Config{EnableTracing: false}}) if test.failure { if err == nil { diff --git a/database/database.go b/database/database.go index f9e2658ed..6f21ec40a 100644 --- a/database/database.go +++ b/database/database.go @@ -8,6 +8,7 @@ import ( "time" "github.com/sirupsen/logrus" + "github.com/uptrace/opentelemetry-go-extra/otelgorm" "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" @@ -28,6 +29,7 @@ import ( "github.com/go-vela/server/database/step" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" + "github.com/go-vela/server/tracing" "github.com/go-vela/types/constants" ) @@ -70,6 +72,8 @@ type ( ctx context.Context // sirupsen/logrus logger used in database functions logger *logrus.Entry + // configurations related to telemetry/tracing + tracing *tracing.Client settings.SettingsInterface build.BuildInterface @@ -105,7 +109,7 @@ func New(opts ...EngineOpt) (Interface, error) { e.client = new(gorm.DB) e.config = new(config) e.logger = new(logrus.Entry) - e.ctx = context.TODO() + e.ctx = context.Background() // apply all provided configuration options for _, opt := range opts { @@ -191,6 +195,19 @@ func New(opts ...EngineOpt) (Interface, error) { return nil, err } + // initialize otel tracing if enabled + if e.tracing.EnableTracing { + otelPlugin := otelgorm.NewPlugin( + otelgorm.WithTracerProvider(e.tracing.TracerProvider), + otelgorm.WithoutQueryVariables(), + ) + + err := e.client.Use(otelPlugin) + if err != nil { + return nil, err + } + } + // set the maximum amount of time a connection may be reused db.SetConnMaxLifetime(e.config.ConnectionLife) // set the maximum number of connections in the idle connection pool @@ -230,5 +247,6 @@ func NewTest() (Interface, error) { WithLogShowSQL(false), WithLogSkipNotFound(true), WithLogSlowThreshold(200*time.Millisecond), + WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}), ) } diff --git a/database/database_test.go b/database/database_test.go index a8b70111a..00866b7a1 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -11,6 +11,8 @@ import ( "gorm.io/driver/postgres" "gorm.io/driver/sqlite" "gorm.io/gorm" + + "github.com/go-vela/server/tracing" ) func TestDatabase_New(t *testing.T) { @@ -110,6 +112,7 @@ func TestDatabase_New(t *testing.T) { WithLogSlowThreshold(test.config.LogSlowThreshold), WithEncryptionKey(test.config.EncryptionKey), WithSkipCreation(test.config.SkipCreation), + WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}), ) if test.failure { diff --git a/database/integration_test.go b/database/integration_test.go index 973eb5495..465793800 100644 --- a/database/integration_test.go +++ b/database/integration_test.go @@ -33,6 +33,7 @@ import ( "github.com/go-vela/server/database/testutils" "github.com/go-vela/server/database/user" "github.com/go-vela/server/database/worker" + "github.com/go-vela/server/tracing" "github.com/go-vela/types/constants" "github.com/go-vela/types/library" "github.com/go-vela/types/raw" @@ -114,6 +115,7 @@ func TestDatabase_Integration(t *testing.T) { WithDriver(test.config.Driver), WithEncryptionKey(test.config.EncryptionKey), WithSkipCreation(test.config.SkipCreation), + WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}), ) if err != nil { t.Errorf("unable to create new database engine for %s: %v", test.name, err) diff --git a/database/opts.go b/database/opts.go index 3a35d751e..14607a856 100644 --- a/database/opts.go +++ b/database/opts.go @@ -5,6 +5,8 @@ package database import ( "context" "time" + + "github.com/go-vela/server/tracing" ) // EngineOpt represents a configuration option to initialize the database engine. @@ -130,6 +132,15 @@ func WithSkipCreation(skipCreation bool) EngineOpt { } } +// WithTracing sets the shared tracing config in the database engine. +func WithTracing(tracing *tracing.Client) EngineOpt { + return func(e *engine) error { + e.tracing = tracing + + return nil + } +} + // WithContext sets the context in the database engine. func WithContext(ctx context.Context) EngineOpt { return func(e *engine) error { diff --git a/docker-compose.yml b/docker-compose.yml index 6cd534285..a96b4b87f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,6 +44,9 @@ services: VELA_ENABLE_SECURE_COOKIE: 'false' VELA_REPO_ALLOWLIST: '*' VELA_SCHEDULE_ALLOWLIST: '*' + VELA_OTEL_TRACING_ENABLE: true + VELA_OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 + VELA_OTEL_TRACING_SAMPLER_RATELIMIT_PER_SECOND: 100 env_file: - .env restart: always @@ -152,18 +155,29 @@ services: # # https://www.vaultproject.io/ vault: - image: hashicorp/vault:latest - container_name: vault - command: server -dev + image: hashicorp/vault:latest + container_name: vault + command: server -dev + networks: + - vela + environment: + VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 + VAULT_DEV_ROOT_TOKEN_ID: vela + ports: + - '8200:8200' + cap_add: + - IPC_LOCK + + jaeger: + image: jaegertracing/all-in-one:latest + container_name: jaeger networks: - vela environment: - VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 - VAULT_DEV_ROOT_TOKEN_ID: vela + COLLECTOR_OTLP_ENABLED: true ports: - - '8200:8200' - cap_add: - - IPC_LOCK + - '16686:16686' + - '4318:4318' networks: vela: diff --git a/go.mod b/go.mod index 8dd81bf8e..080004dec 100644 --- a/go.mod +++ b/go.mod @@ -36,11 +36,21 @@ require ( github.com/redis/go-redis/v9 v9.6.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.11.0 + github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.1 github.com/urfave/cli/v2 v2.27.2 + go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.53.0 + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 + go.opentelemetry.io/otel v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 + go.opentelemetry.io/otel/sdk v1.28.0 + go.opentelemetry.io/otel/trace v1.28.0 go.starlark.net v0.0.0-20240314022150-ee8ed142361c golang.org/x/crypto v0.25.0 golang.org/x/oauth2 v0.21.0 golang.org/x/sync v0.7.0 + golang.org/x/time v0.5.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/postgres v1.5.9 gorm.io/driver/sqlite v1.5.6 @@ -55,28 +65,32 @@ require ( github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic v1.11.9 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/gorilla/css v1.0.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect @@ -93,8 +107,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect @@ -121,14 +134,19 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.1 // indirect github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect github.com/yuin/gopher-lua v1.1.1 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect - golang.org/x/time v0.5.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.120.1 // indirect k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect diff --git a/go.sum b/go.sum index 1f2a95401..6baf24652 100644 --- a/go.sum +++ b/go.sum @@ -35,14 +35,16 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= -github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= -github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= +github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -52,7 +54,6 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -67,8 +68,10 @@ github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -77,16 +80,19 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/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-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= -github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-vela/types v0.24.1-0.20240826141537-76a66e72d5dc h1:VyT2tBwPVO9fMmn+22TC4rY4dp+d71To/Qo5Vk4cOSo= @@ -118,6 +124,8 @@ github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/goware/urlx v0.3.2 h1:gdoo4kBHlkqZNaf6XlQ12LGtQOmpKJrR04Rc3RnpJEo= github.com/goware/urlx v0.3.2/go.mod h1:h8uwbJy68o+tQXCGZNa9D73WN8n0r9OBae5bUnLcgjw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 h1:CWyXh/jylQWp2dtiV33mY4iSSp6yf4lmn+c7/tN+ObI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0/go.mod h1:nCLIt0w3Ept2NwF8ThLmrppXsfT07oC8k0XNDxd8sVU= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -171,8 +179,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -242,8 +250,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/redis/go-redis/v9 v9.6.0 h1:NLck+Rab3AOTHw21CGRpvQpgTrAU4sgdCswqGtlhGRA= github.com/redis/go-redis/v9 v9.6.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 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/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -278,6 +286,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.1 h1:GFFXCsiOWqrAovcIzxqJOYBEy2A/0jd//JNz/jTy1CA= +github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.1/go.mod h1:ncqprpzpjuZHDkvsnl/baPLA0stLgZSLsYEvUhAVkbM= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.1 h1:i4f4ey/v5x0zXurkqV/zbOZlMLu8WNIvpDn1tJzdutY= +github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.1/go.mod h1:ZKgZNsGk5Y+uOxRHcYb4MKLVpmKYU4/u7BUtbStJm7w= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= @@ -289,6 +301,28 @@ github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBU github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.53.0 h1:ktt8061VV/UU5pdPF6AcEFyuPxMizf/vU6eD1l+13LI= +go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.53.0/go.mod h1:JSRiHPV7E3dbOAP0N6SRPg2nC/cugJnVXRqP018ejtY= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 h1:0vzgiFDsCh/jxRCR1xcRrtMoeCu2itXz/PsXst5P8rI= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0/go.mod h1:y0vOY2OKFMOTvwxKfurStPayUUKGHlNeVqNneHmFXr0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/contrib/propagators/b3 v1.28.0 h1:XR6CFQrQ/ttAYmTBX2loUEFGdk1h17pxYI8828dk/1Y= +go.opentelemetry.io/contrib/propagators/b3 v1.28.0/go.mod h1:DWRkzJONLquRz7OJPh2rRbZ7MugQj62rk7g6HRnEqh0= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.starlark.net v0.0.0-20240314022150-ee8ed142361c h1:roAjH18hZcwI4hHStHbkXjF5b7UUyZ/0SG3hXNN1SjA= go.starlark.net v0.0.0-20240314022150-ee8ed142361c/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -312,8 +346,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -357,8 +391,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE= +google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/router/middleware/tracing.go b/router/middleware/tracing.go new file mode 100644 index 000000000..1ba2db0ff --- /dev/null +++ b/router/middleware/tracing.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "github.com/gin-gonic/gin" + "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" + + tracingMiddleware "github.com/go-vela/server/router/middleware/tracing" + "github.com/go-vela/server/tracing" +) + +// TracingClient is a middleware function that attaches the tracing config +// to the context of every http.Request. +func TracingClient(tc *tracing.Client) gin.HandlerFunc { + return func(c *gin.Context) { + tracingMiddleware.ToContext(c, tc) + + c.Next() + } +} + +// TracingInstrumentation is a middleware function that attaches the tracing config +// to the context of every http.Request. +func TracingInstrumentation(tc *tracing.Client) gin.HandlerFunc { + if tc.EnableTracing { + return otelgin.Middleware(tc.ServiceName, otelgin.WithTracerProvider(tc.TracerProvider)) + } + + return func(_ *gin.Context) {} +} diff --git a/router/middleware/tracing/context.go b/router/middleware/tracing/context.go new file mode 100644 index 000000000..ea457bbf1 --- /dev/null +++ b/router/middleware/tracing/context.go @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + + "github.com/go-vela/server/tracing" +) + +const key = "tracing" + +// Setter defines a context that enables setting values. +type Setter interface { + Set(string, interface{}) +} + +// FromContext returns the associated value with this context. +func FromContext(c context.Context) *tracing.Client { + value := c.Value(key) + if value == nil { + return nil + } + + tc, ok := value.(*tracing.Client) + if !ok { + return nil + } + + return tc +} + +// ToContext adds the value to this context if it supports +// the Setter interface. +func ToContext(c Setter, tc *tracing.Client) { + c.Set(key, tc) +} diff --git a/router/middleware/tracing/context_test.go b/router/middleware/tracing/context_test.go new file mode 100644 index 000000000..fce8efeea --- /dev/null +++ b/router/middleware/tracing/context_test.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "testing" + + "github.com/gin-gonic/gin" + + "github.com/go-vela/server/tracing" +) + +func TestTracing_FromContext(t *testing.T) { + // setup types + serviceName := "vela-test" + want := &tracing.Client{ + Config: tracing.Config{ + ServiceName: serviceName, + }, + } + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, want) + + // run test + got := FromContext(context) + + if got != want { + t.Errorf("FromContext is %v, want %v", got, want) + } +} + +func TestTracing_FromContext_Bad(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestTracing_FromContext_WrongType(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + context.Set(key, 1) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestTracing_FromContext_Empty(t *testing.T) { + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + + // run test + got := FromContext(context) + + if got != nil { + t.Errorf("FromContext is %v, want nil", got) + } +} + +func TestTracing_ToContext(t *testing.T) { + // setup types + serviceName := "vela-test" + want := &tracing.Client{ + Config: tracing.Config{ + ServiceName: serviceName, + }, + } + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := context.Value(key) + + if got != want { + t.Errorf("ToContext is %v, want %v", got, want) + } +} diff --git a/router/middleware/tracing/doc.go b/router/middleware/tracing/doc.go new file mode 100644 index 000000000..7c3816b93 --- /dev/null +++ b/router/middleware/tracing/doc.go @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +// Package tracing provides the ability for inserting +// or extracting the Vela OTEL tracing client +// from the middleware chain for the API. +// +// Usage: +// +// import "github.com/go-vela/server/router/middleware/tracing" +package tracing diff --git a/router/middleware/tracing/tracing.go b/router/middleware/tracing/tracing.go new file mode 100644 index 000000000..d12115cea --- /dev/null +++ b/router/middleware/tracing/tracing.go @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/tracing" +) + +// Retrieve gets the value in the given context. +func Retrieve(c *gin.Context) *tracing.Client { + return FromContext(c) +} + +// Establish sets the value in the given context. +func Establish() gin.HandlerFunc { + return func(c *gin.Context) { + l := c.MustGet("logger").(*logrus.Entry) + tc := Retrieve(c) + + l.Debugf("reading tracing client from context") + + ToContext(c, tc) + c.Next() + } +} diff --git a/router/middleware/tracing/tracing_test.go b/router/middleware/tracing/tracing_test.go new file mode 100644 index 000000000..8c1d3cec1 --- /dev/null +++ b/router/middleware/tracing/tracing_test.go @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + + "github.com/go-vela/server/tracing" +) + +func TestTracing_Retrieve(t *testing.T) { + // setup types + serviceName := "vela-test" + want := &tracing.Client{ + Config: tracing.Config{ + ServiceName: serviceName, + }, + } + + // setup context + gin.SetMode(gin.TestMode) + context, _ := gin.CreateTestContext(nil) + ToContext(context, want) + + // run test + got := Retrieve(context) + + if got != want { + t.Errorf("Retrieve is %v, want %v", got, want) + } +} + +func TestTracing_Establish(t *testing.T) { + // setup types + serviceName := "vela-test" + want := &tracing.Client{ + Config: tracing.Config{ + ServiceName: serviceName, + }, + } + + got := new(tracing.Client) + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/hello", nil) + + // setup mock server + engine.Use(func(c *gin.Context) { c.Set("logger", logrus.NewEntry(logrus.StandardLogger())) }) + engine.Use(func(c *gin.Context) { ToContext(c, want) }) + engine.Use(Establish()) + engine.GET("/hello", func(c *gin.Context) { + got = Retrieve(c) + + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(resp, context.Request) + + if resp.Code != http.StatusOK { + t.Errorf("Establish returned %v, want %v", resp.Code, http.StatusOK) + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("Establish is %v, want %v", got, want) + } +} diff --git a/router/middleware/tracing_test.go b/router/middleware/tracing_test.go new file mode 100644 index 000000000..76313cc41 --- /dev/null +++ b/router/middleware/tracing_test.go @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 + +package middleware + +import ( + "errors" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/gin-gonic/gin" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + + "github.com/go-vela/server/tracing" +) + +func TestMiddleware_TracingClient(t *testing.T) { + // setup types + var got *tracing.Client + want := &tracing.Client{ + Config: tracing.Config{ + EnableTracing: true, + }, + } + + // setup context + gin.SetMode(gin.TestMode) + + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) + + // setup mock server + engine.Use(TracingClient(want)) + engine.GET("/health", func(c *gin.Context) { + got = c.Value("tracing").(*tracing.Client) + + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if !reflect.DeepEqual(got, want) { + t.Errorf("TracingClient is %v, want %v", got, want) + } +} + +func TestMiddleware_TracingInstrumentation(t *testing.T) { + // setup types + tt := []struct { + tc *tracing.Client + assert func(trace.SpanContext) error + }{ + { + tc: &tracing.Client{ + Config: tracing.Config{ + EnableTracing: false, + ServiceName: "vela-test", + }, + TracerProvider: sdktrace.NewTracerProvider(), + }, + assert: func(got trace.SpanContext) error { + if !reflect.DeepEqual(got, trace.SpanContext{}) { + return errors.New("span context is not empty") + } + return nil + }, + }, + { + tc: &tracing.Client{ + Config: tracing.Config{ + EnableTracing: true, + ServiceName: "vela-test", + }, + TracerProvider: sdktrace.NewTracerProvider(), + }, + assert: func(got trace.SpanContext) error { + if reflect.DeepEqual(got, trace.SpanContext{}) { + return errors.New("span context is empty") + } + return nil + }, + }, + } + + // setup context + gin.SetMode(gin.TestMode) + + for _, test := range tt { + got := trace.SpanContext{} + resp := httptest.NewRecorder() + context, engine := gin.CreateTestContext(resp) + context.Request, _ = http.NewRequest(http.MethodGet, "/health", nil) + + // setup mock server + engine.Use(TracingInstrumentation(test.tc)) + engine.GET("/health", func(c *gin.Context) { + got = trace.SpanContextFromContext(c.Request.Context()) + + c.Status(http.StatusOK) + }) + + // run test + engine.ServeHTTP(context.Writer, context.Request) + + if resp.Code != http.StatusOK { + t.Errorf("TracingInstrumentation returned %v, want %v", resp.Code, http.StatusOK) + } + + err := test.assert(got) + if err != nil { + t.Errorf("TracingInstrumentation test assertion failed: %s", err) + } + } +} diff --git a/scm/github/github.go b/scm/github/github.go index caf795723..5fa96dc16 100644 --- a/scm/github/github.go +++ b/scm/github/github.go @@ -5,11 +5,16 @@ package github import ( "context" "fmt" + "net/http/httptrace" "net/url" "github.com/google/go-github/v63/github" "github.com/sirupsen/logrus" + "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "golang.org/x/oauth2" + + "github.com/go-vela/server/tracing" ) const ( @@ -50,6 +55,7 @@ type client struct { config *config OAuth *oauth2.Config AuthReq *github.AuthorizationRequest + Tracing *tracing.Client // https://pkg.go.dev/github.com/sirupsen/logrus#Entry Logger *logrus.Entry } @@ -134,6 +140,7 @@ func NewTest(urls ...string) (*client, error) { WithServerWebhookAddress(""), WithStatusContext("continuous-integration/vela"), WithWebUIAddress(address), + WithTracing(&tracing.Client{Config: tracing.Config{EnableTracing: false}}), ) } @@ -145,7 +152,7 @@ func (c *client) newClientToken(ctx context.Context, token string) *github.Clien ) // create the OAuth client - tc := oauth2.NewClient(context.Background(), ts) + tc := oauth2.NewClient(ctx, ts) // if c.SkipVerify { // tc.Transport.(*oauth2.Transport).Base = &http.Transport{ // Proxy: http.ProxyFromEnvironment, @@ -155,6 +162,15 @@ func (c *client) newClientToken(ctx context.Context, token string) *github.Clien // } // } + if c.Tracing.Config.EnableTracing { + tc.Transport = otelhttp.NewTransport( + tc.Transport, + otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { + return otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans()) + }), + ) + } + // create the GitHub client from the OAuth client github := github.NewClient(tc) diff --git a/scm/github/opts.go b/scm/github/opts.go index df6d46506..bb7385827 100644 --- a/scm/github/opts.go +++ b/scm/github/opts.go @@ -5,6 +5,8 @@ package github import ( "fmt" "strings" + + "github.com/go-vela/server/tracing" ) // ClientOpt represents a configuration option to initialize the scm client for GitHub. @@ -149,3 +151,12 @@ func WithScopes(scopes []string) ClientOpt { return nil } } + +// WithTracing sets the shared tracing config in the scm client for GitHub. +func WithTracing(tracing *tracing.Client) ClientOpt { + return func(e *client) error { + e.Tracing = tracing + + return nil + } +} diff --git a/scm/github/opts_test.go b/scm/github/opts_test.go index 40ed4da87..8a6a2617e 100644 --- a/scm/github/opts_test.go +++ b/scm/github/opts_test.go @@ -5,6 +5,8 @@ package github import ( "reflect" "testing" + + "github.com/go-vela/server/tracing" ) func TestGithub_ClientOpt_WithAddress(t *testing.T) { @@ -348,3 +350,41 @@ func TestGithub_ClientOpt_WithScopes(t *testing.T) { } } } + +func TestGithub_ClientOpt_WithTracing(t *testing.T) { + // setup tests + tests := []struct { + failure bool + tracing *tracing.Client + want *tracing.Client + }{ + { + failure: false, + tracing: &tracing.Client{}, + want: &tracing.Client{}, + }, + } + + // run tests + for _, test := range tests { + _service, err := New( + WithTracing(test.tracing), + ) + + if test.failure { + if err == nil { + t.Errorf("WithTracing should have returned err") + } + + continue + } + + if err != nil { + t.Errorf("WithTracing returned err: %v", err) + } + + if !reflect.DeepEqual(_service.Tracing, test.want) { + t.Errorf("WithTracing is %v, want %v", _service.Tracing, test.want) + } + } +} diff --git a/scm/setup.go b/scm/setup.go index 3b4082f7f..c32a0cf66 100644 --- a/scm/setup.go +++ b/scm/setup.go @@ -9,6 +9,7 @@ import ( "github.com/sirupsen/logrus" "github.com/go-vela/server/scm/github" + "github.com/go-vela/server/tracing" "github.com/go-vela/types/constants" ) @@ -36,6 +37,8 @@ type Setup struct { WebUIAddress string // specifies the OAuth scopes to use for the scm client Scopes []string + // specifies OTel tracing configurations + Tracing *tracing.Client } // Github creates and returns a Vela service capable of @@ -55,6 +58,7 @@ func (s *Setup) Github() (Service, error) { github.WithStatusContext(s.StatusContext), github.WithWebUIAddress(s.WebUIAddress), github.WithScopes(s.Scopes), + github.WithTracing(s.Tracing), ) } diff --git a/tracing/config.go b/tracing/config.go new file mode 100644 index 000000000..7cbb7b431 --- /dev/null +++ b/tracing/config.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "maps" + "os" + "strings" + + "github.com/urfave/cli/v2" + sdktrace "go.opentelemetry.io/otel/sdk/trace" +) + +// Client represents the tracing client and the configurations that were used to initialize it. +type Client struct { + Config + TracerProvider *sdktrace.TracerProvider +} + +// Config represents the configurations for otel tracing. +type Config struct { + EnableTracing bool + ServiceName string + ExporterURL string + CertPath string + TLSMinVersion string + ResourceAttributes map[string]string + TraceStateAttributes map[string]string + SpanAttributes map[string]string + Sampler +} + +// Sampler represents the configurations for the otel sampler. +// Used to determine if a trace should be sampled. +type Sampler struct { + PerSecond float64 +} + +// FromCLIContext takes cli context and returns a tracing config to supply to traceable services. +func FromCLIContext(c *cli.Context) (*Client, error) { + cfg := Config{ + EnableTracing: c.Bool("tracing.enable"), + ServiceName: c.String("tracing.service.name"), + ExporterURL: c.String("tracing.exporter.endpoint"), + CertPath: c.String("tracing.exporter.cert_path"), + TLSMinVersion: c.String("tracing.exporter.tls-min-version"), + ResourceAttributes: map[string]string{}, + TraceStateAttributes: map[string]string{}, + SpanAttributes: map[string]string{}, + Sampler: Sampler{ + PerSecond: c.Float64("tracing.sampler.persecond"), + }, + } + + // identity func used to map a string back to itself + identityFn := func(s string) string { return s } + + // span attributes + cfg.SpanAttributes = keyValueSliceToMap(c.StringSlice("tracing.span.attributes"), identityFn) + + // tracestate attributes + cfg.TraceStateAttributes = keyValueSliceToMap(c.StringSlice("tracing.tracestate.attributes"), identityFn) + + // merge static resource attributes with those fetched from the environment using os.Getenv + cfg.ResourceAttributes = keyValueSliceToMap(c.StringSlice("tracing.resource.attributes"), identityFn) + m := keyValueSliceToMap(c.StringSlice("tracing.resource.env_attributes"), os.Getenv) + maps.Copy(cfg.ResourceAttributes, m) + + client := &Client{ + Config: cfg, + } + + if cfg.EnableTracing { + // initialize the tracer provider and assign it to the client + tracer, err := initTracer(c.Context, cfg) + if err != nil { + return nil, err + } + + client.TracerProvider = tracer + } + + return client, nil +} + +// keyValueSliceToMap converts a slice of key=value strings to a map of key to value using the supplied map function. +func keyValueSliceToMap(kv []string, fn func(string) string) map[string]string { + m := map[string]string{} + + for _, attr := range kv { + parts := strings.SplitN(attr, "=", 2) + + if len(parts) != 2 || len(parts[1]) == 0 { + continue + } + + k := parts[0] + v := fn(parts[1]) + + if len(v) == 0 { + continue + } + + m[k] = v + } + + return m +} diff --git a/tracing/flags.go b/tracing/flags.go new file mode 100644 index 000000000..5e08868b6 --- /dev/null +++ b/tracing/flags.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "github.com/urfave/cli/v2" +) + +var Flags = []cli.Flag{ + // Tracing Flags + + &cli.BoolFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_ENABLE"}, + Name: "tracing.enable", + Usage: "enable otel tracing. see: https://opentelemetry.io/docs/concepts/signals/traces/", + Value: false, + }, + &cli.StringFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_SERVICE_NAME"}, + Name: "tracing.service.name", + Usage: "set otel tracing service name. see: https://opentelemetry.io/docs/languages/sdk-configuration/general/", + Value: "vela-server", + }, + + // Exporter Flags + + &cli.StringFlag{ + EnvVars: []string{"VELA_OTEL_EXPORTER_OTLP_ENDPOINT"}, + Name: "tracing.exporter.endpoint", + Usage: "set the otel exporter endpoint (ex. scheme://host:port). see: https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/", + }, + &cli.StringFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_EXPORTER_SSL_CERT_PATH"}, + Name: "tracing.exporter.cert_path", + Usage: "set the path to certs used for communicating with the otel exporter. if not set, will use insecure communication. see: https://opentelemetry.io/docs/specs/otel/protocol/exporter/", + }, + &cli.StringFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_TLS_MIN_VERSION"}, + Name: "tracing.exporter.tls-min-version", + Usage: "optional TLS minimum version requirement to set when communicating with the otel exporter", + Value: "1.2", + }, + + // Attribute Flags + + &cli.StringSliceFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_RESOURCE_ATTRIBUTES"}, + Name: "tracing.resource.attributes", + Usage: "set otel resource (span) attributes as a list of key=value pairs. each one will be attached to each span as a 'process' attribute. see: https://opentelemetry.io/docs/languages/go/instrumentation/#span-attributes", + Value: cli.NewStringSlice("process.runtime.name=go"), + }, + &cli.StringSliceFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_RESOURCE_ENV_ATTRIBUTES"}, + Name: "tracing.resource.env_attributes", + Usage: "set otel resource (span) attributes as a list of key=env_variable_key pairs. each one will be attached to each span as a 'process' attribute where the value is retrieved from the environment using the pair value. see: https://opentelemetry.io/docs/languages/go/instrumentation/#span-attributes", + }, + &cli.StringSliceFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_SPAN_ATTRIBUTES"}, + Name: "tracing.span.attributes", + Usage: "set otel span attributes as a list of key=value pairs. each one will be attached to each span as a 'tag' attribute. see: https://opentelemetry.io/docs/languages/go/instrumentation/#span-attributes", + }, + &cli.StringSliceFlag{ + EnvVars: []string{"VELA_OTEL_TRACING_TRACESTATE_ATTRIBUTES"}, + Name: "tracing.tracestate.attributes", + Usage: "set otel tracestate (span) attributes as a list of key=value pairs. each one will be inserted into the tracestate for each sampled span. see: https://www.w3.org/TR/trace-context", + }, + + // Sampler Flags + + &cli.Float64Flag{ + EnvVars: []string{"VELA_OTEL_TRACING_SAMPLER_RATELIMIT_PER_SECOND"}, + Name: "tracing.sampler.persecond", + Usage: "set otel tracing head-sampler rate-limiting to N per second. see: https://opentelemetry.io/docs/concepts/sampling/", + Value: 100, + }, +} diff --git a/tracing/sampler.go b/tracing/sampler.go new file mode 100644 index 000000000..198a74700 --- /dev/null +++ b/tracing/sampler.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "fmt" + "time" + + "go.opentelemetry.io/otel/attribute" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "golang.org/x/time/rate" +) + +// RateLimitSampler is a sampler that uses time-based rate limiting. +type RateLimitSampler struct { + Config + limiter *rate.Limiter +} + +// NewRateLimitSampler returns a new rate limit sampler. +func NewRateLimitSampler(tc Config) *RateLimitSampler { + return &RateLimitSampler{ + Config: tc, + limiter: rate.NewLimiter(rate.Every(time.Duration(1.0/tc.PerSecond)*time.Second), 1), + } +} + +// ShouldSample determines if a trace should be sampled. +func (s *RateLimitSampler) ShouldSample(p sdktrace.SamplingParameters) sdktrace.SamplingResult { + psc := trace.SpanContextFromContext(p.ParentContext) + ts := psc.TraceState() + + for k, v := range s.Config.TraceStateAttributes { + ts, _ = ts.Insert(k, v) + } + + attributes := []attribute.KeyValue{ + attribute.String("sampler.algorithm", "rate-limiting"), + attribute.Float64("sampler.param", s.Config.PerSecond), + } + + for k, v := range s.Config.SpanAttributes { + attributes = append(attributes, attribute.String(k, v)) + } + + result := sdktrace.SamplingResult{ + Decision: sdktrace.Drop, + Attributes: attributes, + Tracestate: ts, + } + + if s.limiter.Allow() { + result.Decision = sdktrace.RecordAndSample + } + + return result +} + +// Description returns the description of the rate limit sampler. +func (s *RateLimitSampler) Description() string { + return fmt.Sprintf("rate-limit-sampler{%v}", s.Config.PerSecond) +} diff --git a/tracing/tracer.go b/tracing/tracer.go new file mode 100644 index 000000000..4cc05f389 --- /dev/null +++ b/tracing/tracer.go @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/url" + "os" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.17.0" +) + +// initTracer returns the tracer provider supplied to the tracing config. +func initTracer(ctx context.Context, cfg Config) (*sdktrace.TracerProvider, error) { + withTLS := otlptracehttp.WithInsecure() + + if len(cfg.CertPath) > 0 { + pem, err := os.ReadFile(cfg.CertPath) + if err != nil { + return nil, err + } + + certs := x509.NewCertPool() + certs.AppendCertsFromPEM(pem) + + tlsCfg := &tls.Config{ + RootCAs: certs, + MinVersion: tls.VersionTLS12, + } + + // if a TLS minimum version is supplied, set that in the config + if len(cfg.TLSMinVersion) > 0 { + var tlsVersion uint16 + + switch cfg.TLSMinVersion { + case "1.0": + tlsVersion = tls.VersionTLS10 + case "1.1": + tlsVersion = tls.VersionTLS11 + case "1.2": + tlsVersion = tls.VersionTLS12 + case "1.3": + tlsVersion = tls.VersionTLS13 + default: + return nil, fmt.Errorf("invalid TLS minimum version supplied: %s", cfg.TLSMinVersion) + } + + tlsCfg.MinVersion = tlsVersion + } + + withTLS = otlptracehttp.WithTLSClientConfig(tlsCfg) + } else { + logrus.Warn("no otel cert path set, exporting traces insecurely") + } + + if len(cfg.ExporterURL) == 0 { + return nil, errors.New("no otel exporter URL set (ex. scheme://host:port)") + } + + exporterURL, err := url.Parse(cfg.ExporterURL) + if err != nil { + return nil, errors.Wrap(err, fmt.Sprintf("unable to parse otel exporter URL (ex. scheme://host:port): %s", cfg.ExporterURL)) + } + + if len(exporterURL.Hostname()) == 0 { + return nil, fmt.Errorf("unable to parse host from otel exporter URL (ex. scheme://host:port): %s", cfg.ExporterURL) + } + + exporterEndpoint := exporterURL.Hostname() + if len(exporterURL.Port()) > 0 { + exporterEndpoint = fmt.Sprintf("%s:%s", exporterEndpoint, exporterURL.Port()) + } + + client := otlptracehttp.NewClient( + otlptracehttp.WithEndpoint(exporterEndpoint), + withTLS, + ) + + exporter, err := otlptrace.New(ctx, client) + if err != nil { + return nil, err + } + + attrs := []attribute.KeyValue{ + semconv.ServiceName(cfg.ServiceName), + } + + for k, v := range cfg.ResourceAttributes { + attrs = append(attrs, attribute.String(k, v)) + } + + res, err := resource.Merge( + resource.Default(), + resource.NewSchemaless(attrs...), + ) + if err != nil { + return nil, err + } + + rateLimitSampler := NewRateLimitSampler(cfg) + + tp := sdktrace.NewTracerProvider( + sdktrace.WithSampler(rateLimitSampler), + sdktrace.WithBatcher(exporter), + sdktrace.WithResource(res), + ) + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + + return tp, nil +}